New HTTP types: ContentType, Status. Responder/Handler/ErrorHandler changed.

This is a complete rework of `Responder`s and of the http backend in
general. This gets Rocket one step closer to HTTP library independence,
enabling many future features such as transparent async I/O, automatic
HEAD request parsing, pre/post hooks, and more.

Summary of changes:

  * `Responder::response` no longer takes in `FreshHyperResponse`.
    Instead, it returns a new `Response` type.
  * The new `Response` type now encapsulates a full HTTP response. As a
    result, `Responder`s now return it.
  * The `Handler` type now returns an `Outcome` directly.
  * The `ErrorHandler` returns a `Result`. It can no longer forward,
    which made no sense previously.
  * `Stream` accepts a chunked size parameter.
  * `StatusCode` removed in favor of new `Status` type.
  * `ContentType` significantly modified.
  * New, lightweight `Header` type that plays nicely with `Response`.
This commit is contained in:
Sergio Benitez 2016-12-15 00:47:31 -08:00
parent 5fca86c84f
commit 44f5f1998d
44 changed files with 1258 additions and 830 deletions

View File

@ -59,9 +59,12 @@ pub fn error_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
emit_item(push, quote_item!(ecx, emit_item(push, quote_item!(ecx,
fn $catch_fn_name<'_b>($err_ident: ::rocket::Error, fn $catch_fn_name<'_b>($err_ident: ::rocket::Error,
$req_ident: &'_b ::rocket::Request) $req_ident: &'_b ::rocket::Request)
-> ::rocket::Response<'_b> { -> ::rocket::response::Result<'_b> {
let result = $user_fn_name($fn_arguments); let response = $user_fn_name($fn_arguments);
rocket::Response::with_raw_status($code, result) let status = ::rocket::http::Status::raw($code);
::rocket::response::Responder::respond(
::rocket::response::status::Custom(status, response)
)
} }
).expect("catch function")); ).expect("catch function"));
@ -69,9 +72,9 @@ pub fn error_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
emit_item(push, quote_item!(ecx, emit_item(push, quote_item!(ecx,
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
pub static $struct_name: ::rocket::StaticCatchInfo = pub static $struct_name: ::rocket::StaticCatchInfo =
::rocket::StaticCatchInfo { ::rocket::StaticCatchInfo {
code: $code, code: $code,
handler: $catch_fn_name handler: $catch_fn_name
}; };
).expect("catch info struct")); ).expect("catch info struct"));
} }

View File

@ -15,7 +15,6 @@ use syntax::parse::token;
use syntax::ptr::P; use syntax::ptr::P;
use rocket::http::{Method, ContentType}; use rocket::http::{Method, ContentType};
use rocket::http::mime::{TopLevel, SubLevel};
fn method_to_path(ecx: &ExtCtxt, method: Method) -> Path { fn method_to_path(ecx: &ExtCtxt, method: Method) -> Path {
quote_enum!(ecx, method => ::rocket::http::Method { quote_enum!(ecx, method => ::rocket::http::Method {
@ -23,28 +22,14 @@ fn method_to_path(ecx: &ExtCtxt, method: Method) -> Path {
}) })
} }
// FIXME: This should return an Expr! (Ext is not a path.)
fn top_level_to_expr(ecx: &ExtCtxt, level: &TopLevel) -> Path {
quote_enum!(ecx, *level => ::rocket::http::mime::TopLevel {
Star, Text, Image, Audio, Video, Application, Multipart, Model, Message;
Ext(ref s) => quote_path!(ecx, ::rocket::http::mime::TopLevel::Ext($s))
})
}
// FIXME: This should return an Expr! (Ext is not a path.)
fn sub_level_to_expr(ecx: &ExtCtxt, level: &SubLevel) -> Path {
quote_enum!(ecx, *level => ::rocket::http::mime::SubLevel {
Star, Plain, Html, Xml, Javascript, Css, EventStream, Json,
WwwFormUrlEncoded, Msgpack, OctetStream, FormData, Png, Gif, Bmp, Jpeg;
Ext(ref s) => quote_path!(ecx, ::rocket::http::mime::SubLevel::Ext($s))
})
}
fn content_type_to_expr(ecx: &ExtCtxt, ct: Option<ContentType>) -> Option<P<Expr>> { fn content_type_to_expr(ecx: &ExtCtxt, ct: Option<ContentType>) -> Option<P<Expr>> {
ct.map(|ct| { ct.map(|ct| {
let top_level = top_level_to_expr(ecx, &ct.0); let (ttype, subtype) = (ct.ttype, ct.subtype);
let sub_level = sub_level_to_expr(ecx, &ct.1); quote_expr!(ecx, ::rocket::http::ContentType {
quote_expr!(ecx, ::rocket::http::ContentType($top_level, $sub_level, None)) ttype: ::std::borrow::Cow::Borrowed($ttype),
subtype: ::std::borrow::Cow::Borrowed($subtype),
params: None
})
}) })
} }
@ -84,7 +69,7 @@ impl RouteGenerateExt for RouteParams {
let $name: $ty = let $name: $ty =
match ::rocket::request::FromForm::from_form_string($form_string) { match ::rocket::request::FromForm::from_form_string($form_string) {
Ok(v) => v, Ok(v) => v,
Err(_) => return ::rocket::Response::forward(_data) Err(_) => return ::rocket::Outcome::Forward(_data)
}; };
).expect("form statement")) ).expect("form statement"))
} }
@ -105,11 +90,11 @@ impl RouteGenerateExt for RouteParams {
Some(quote_stmt!(ecx, Some(quote_stmt!(ecx,
let $name: $ty = let $name: $ty =
match ::rocket::data::FromData::from_data(&_req, _data) { match ::rocket::data::FromData::from_data(&_req, _data) {
::rocket::outcome::Outcome::Success(d) => d, ::rocket::Outcome::Success(d) => d,
::rocket::outcome::Outcome::Forward(d) => ::rocket::Outcome::Forward(d) =>
return ::rocket::Response::forward(d), return ::rocket::Outcome::Forward(d),
::rocket::outcome::Outcome::Failure((code, _)) => { ::rocket::Outcome::Failure((code, _)) => {
return ::rocket::Response::failure(code); return ::rocket::Outcome::Failure(code);
} }
}; };
).expect("data statement")) ).expect("data statement"))
@ -120,7 +105,7 @@ impl RouteGenerateExt for RouteParams {
let expr = quote_expr!(ecx, let expr = quote_expr!(ecx,
match _req.uri().query() { match _req.uri().query() {
Some(query) => query, Some(query) => query,
None => return ::rocket::Response::forward(_data) None => return ::rocket::Outcome::Forward(_data)
} }
); );
@ -149,11 +134,11 @@ impl RouteGenerateExt for RouteParams {
let expr = match param { let expr = match param {
Param::Single(_) => quote_expr!(ecx, match _req.get_param_str($i) { Param::Single(_) => quote_expr!(ecx, match _req.get_param_str($i) {
Some(s) => <$ty as ::rocket::request::FromParam>::from_param(s), Some(s) => <$ty as ::rocket::request::FromParam>::from_param(s),
None => return ::rocket::Response::forward(_data) None => return ::rocket::Outcome::Forward(_data)
}), }),
Param::Many(_) => quote_expr!(ecx, match _req.get_raw_segments($i) { Param::Many(_) => quote_expr!(ecx, match _req.get_raw_segments($i) {
Some(s) => <$ty as ::rocket::request::FromSegments>::from_segments(s), Some(s) => <$ty as ::rocket::request::FromSegments>::from_segments(s),
None => return ::rocket::Response::forward(_data) None => return ::rocket::Outcome::forward(_data)
}), }),
}; };
@ -164,7 +149,7 @@ impl RouteGenerateExt for RouteParams {
Err(e) => { Err(e) => {
println!(" => Failed to parse '{}': {:?}", println!(" => Failed to parse '{}': {:?}",
stringify!($original_ident), e); stringify!($original_ident), e);
return ::rocket::Response::forward(_data) return ::rocket::Outcome::Forward(_data)
} }
}; };
).expect("declared param parsing statement")); ).expect("declared param parsing statement"));
@ -195,9 +180,9 @@ impl RouteGenerateExt for RouteParams {
::rocket::request::FromRequest::from_request(&_req) { ::rocket::request::FromRequest::from_request(&_req) {
::rocket::outcome::Outcome::Success(v) => v, ::rocket::outcome::Outcome::Success(v) => v,
::rocket::outcome::Outcome::Forward(_) => ::rocket::outcome::Outcome::Forward(_) =>
return ::rocket::Response::forward(_data), return ::rocket::Outcome::forward(_data),
::rocket::outcome::Outcome::Failure((code, _)) => { ::rocket::outcome::Outcome::Failure((code, _)) => {
return ::rocket::Response::failure(code) return ::rocket::Outcome::Failure(code)
}, },
}; };
).expect("undeclared param parsing statement")); ).expect("undeclared param parsing statement"));
@ -250,12 +235,12 @@ fn generic_route_decorator(known_method: Option<Spanned<Method>>,
let route_fn_name = user_fn_name.prepend(ROUTE_FN_PREFIX); let route_fn_name = user_fn_name.prepend(ROUTE_FN_PREFIX);
emit_item(push, quote_item!(ecx, emit_item(push, quote_item!(ecx,
fn $route_fn_name<'_b>(_req: &'_b ::rocket::Request, _data: ::rocket::Data) fn $route_fn_name<'_b>(_req: &'_b ::rocket::Request, _data: ::rocket::Data)
-> ::rocket::Response<'_b> { -> ::rocket::handler::Outcome<'_b> {
$param_statements $param_statements
$query_statement $query_statement
$data_statement $data_statement
let result = $user_fn_name($fn_arguments); let responder = $user_fn_name($fn_arguments);
::rocket::Response::success(result) ::rocket::handler::Outcome::of(responder)
} }
).unwrap()); ).unwrap());

View File

@ -2,6 +2,8 @@ use syntax::ast::*;
use syntax::ext::base::{ExtCtxt, Annotatable}; use syntax::ext::base::{ExtCtxt, Annotatable};
use syntax::codemap::{Span, Spanned, dummy_spanned}; use syntax::codemap::{Span, Spanned, dummy_spanned};
use rocket::http::Status;
use utils::{span, MetaItemExt}; use utils::{span, MetaItemExt};
use super::Function; use super::Function;
@ -50,6 +52,9 @@ fn parse_code(ecx: &ExtCtxt, meta_item: &NestedMetaItem) -> Spanned<u16> {
if n.node < 400 || n.node > 599 { if n.node < 400 || n.node > 599 {
ecx.span_err(n.span, "code must be >= 400 and <= 599."); ecx.span_err(n.span, "code must be >= 400 and <= 599.");
span(0, n.span) span(0, n.span)
} else if Status::from_code(n.node as u16).is_none() {
ecx.span_warn(n.span, "status code is unknown.");
span(n.node as u16, n.span)
} else { } else {
span(n.node as u16, n.span) span(n.node as u16, n.span)
} }

View File

@ -269,7 +269,7 @@ fn parse_rank(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> isize {
fn parse_format(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> ContentType { fn parse_format(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> ContentType {
if let LitKind::Str(ref s, _) = *kv.value() { if let LitKind::Str(ref s, _) = *kv.value() {
if let Ok(ct) = ContentType::from_str(&s.as_str()) { if let Ok(ct) = ContentType::from_str(&s.as_str()) {
if ct.is_ext() { if !ct.is_known() {
let msg = format!("'{}' is not a known content-type", s); let msg = format!("'{}' is not a known content-type", s);
ecx.span_warn(kv.value.span, &msg); ecx.span_warn(kv.value.span, &msg);
} else { } else {
@ -286,5 +286,5 @@ fn parse_format(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> ContentType {
content-type accepted. e.g: format = "application/json""#) content-type accepted. e.g: format = "application/json""#)
.emit(); .emit();
ContentType::any() ContentType::Any
} }

View File

@ -8,8 +8,7 @@ use rocket::outcome::{Outcome, IntoOutcome};
use rocket::request::Request; use rocket::request::Request;
use rocket::data::{self, Data, FromData}; use rocket::data::{self, Data, FromData};
use rocket::response::{self, Responder, content}; use rocket::response::{self, Responder, content};
use rocket::http::StatusCode; use rocket::http::Status;
use rocket::http::hyper::FreshHyperResponse;
use self::serde::{Serialize, Deserialize}; use self::serde::{Serialize, Deserialize};
use self::serde_json::error::Error as SerdeError; use self::serde_json::error::Error as SerdeError;
@ -83,15 +82,14 @@ impl<T: Deserialize> FromData for JSON<T> {
} }
} }
impl<T: Serialize> Responder for JSON<T> { impl<T: Serialize> Responder<'static> for JSON<T> {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> response::Outcome<'a> { fn respond(self) -> response::Result<'static> {
match serde_json::to_string(&self.0) { serde_json::to_string(&self.0).map(|string| {
Ok(json_string) => content::JSON(json_string).respond(res), content::JSON(string).respond().unwrap()
Err(e) => { }).map_err(|e| {
error_!("JSON failed to serialize: {:?}", e); error_!("JSON failed to serialize: {:?}", e);
Outcome::Forward((StatusCode::BadRequest, res)) Status::BadRequest
} })
}
} }
} }

View File

@ -18,9 +18,7 @@ use std::collections::HashMap;
use rocket::config; use rocket::config;
use rocket::response::{self, Content, Responder}; use rocket::response::{self, Content, Responder};
use rocket::http::hyper::FreshHyperResponse; use rocket::http::{ContentType, Status};
use rocket::http::{ContentType, StatusCode};
use rocket::Outcome;
/// The Template type implements generic support for template rendering in /// The Template type implements generic support for template rendering in
/// Rocket. /// Rocket.
@ -159,16 +157,16 @@ impl Template {
} }
} }
impl Responder for Template { impl Responder<'static> for Template {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> response::Outcome<'a> { fn respond(self) -> response::Result<'static> {
let content_type = match self.1 { let content_type = match self.1 {
Some(ref ext) => ContentType::from_extension(ext), Some(ref ext) => ContentType::from_extension(ext),
None => ContentType::html() None => ContentType::HTML
}; };
match self.0 { match self.0 {
Some(ref render) => Content(content_type, render.as_str()).respond(res), Some(render) => Content(content_type, render).respond(),
None => Outcome::Forward((StatusCode::InternalServerError, res)), None => Err(Status::InternalServerError)
} }
} }
} }

View File

@ -4,11 +4,11 @@ use rocket::http::Method::*;
fn test_login<F: Fn(String) -> bool>(username: &str, password: &str, age: isize, test: F) { fn test_login<F: Fn(String) -> bool>(username: &str, password: &str, age: isize, test: F) {
let rocket = rocket::ignite().mount("/", routes![super::user_page, super::login]); let rocket = rocket::ignite().mount("/", routes![super::user_page, super::login]);
let req = MockRequest::new(Post, "/login") let result = MockRequest::new(Post, "/login")
.headers(&[("Content-Type", "application/x-www-form-urlencoded")]) .headers(&[("Content-Type", "application/x-www-form-urlencoded")])
.body(&format!("username={}&password={}&age={}", username, password, age)); .body(&format!("username={}&password={}&age={}", username, password, age))
let result = req.dispatch_with(&rocket); .dispatch_with(&rocket)
let result = result.unwrap(); .unwrap_or("".to_string());
assert!(test(result)); assert!(test(result));
} }
@ -29,5 +29,7 @@ fn test_bad_login() {
#[test] #[test]
fn test_bad_form() { fn test_bad_form() {
test_login("Sergio&other=blah&", "password", 30, |s| s.contains("400 Bad Request")); // FIXME: Need to be able to examine the status.
// test_login("Sergio&other=blah&", "password", 30, |s| s.contains("400 Bad Request"));
test_login("Sergio&other=blah&", "password", 30, |s| s.is_empty());
} }

View File

@ -39,8 +39,9 @@ mod test {
use rocket::http::Method::*; use rocket::http::Method::*;
fn test_header_count<'h>(headers: &[(&'h str, &'h str)]) { fn test_header_count<'h>(headers: &[(&'h str, &'h str)]) {
// FIXME: Should be able to count headers directly!
let rocket = rocket::ignite().mount("/", routes![super::header_count]); let rocket = rocket::ignite().mount("/", routes![super::header_count]);
let req = MockRequest::new(Get, "/").headers(headers); let mut req = MockRequest::new(Get, "/").headers(headers);
let result = req.dispatch_with(&rocket); let result = req.dispatch_with(&rocket);
assert_eq!(result.unwrap(), assert_eq!(result.unwrap(),
format!("Your request contained {} headers!", headers.len())); format!("Your request contained {} headers!", headers.len()));

View File

@ -4,17 +4,16 @@ use rocket::http::Method::*;
fn test(uri: &str, expected: String) { fn test(uri: &str, expected: String) {
let rocket = rocket::ignite().mount("/", routes![super::hello, super::hi]); let rocket = rocket::ignite().mount("/", routes![super::hello, super::hi]);
let req = MockRequest::new(Get, uri); let result = MockRequest::new(Get, uri).dispatch_with(&rocket);
let result = req.dispatch_with(&rocket);
assert_eq!(result.unwrap(), expected); assert_eq!(result.unwrap(), expected);
} }
fn test_404(uri: &str) { fn test_404(uri: &str) {
let rocket = rocket::ignite().mount("/", routes![super::hello, super::hi]); let rocket = rocket::ignite().mount("/", routes![super::hello, super::hi]);
let req = MockRequest::new(Get, uri); let result = MockRequest::new(Get, uri).dispatch_with(&rocket);
let result = req.dispatch_with(&rocket); // FIXME: Be able to check that actual HTTP response status code.
// TODO: Be able to check that actual HTTP response status code. // assert!(result.unwrap().contains("404"));
assert!(result.unwrap().contains("404 Not Found")); assert!(result.is_none());
} }
#[test] #[test]

View File

@ -4,8 +4,7 @@ use rocket::http::Method::*;
fn test(uri: &str, expected: String) { fn test(uri: &str, expected: String) {
let rocket = rocket::ignite().mount("/", routes![super::hello, super::hi]); let rocket = rocket::ignite().mount("/", routes![super::hello, super::hi]);
let req = MockRequest::new(Get, uri); let result = MockRequest::new(Get, uri).dispatch_with(&rocket);
let result = req.dispatch_with(&rocket);
assert_eq!(result.unwrap(), expected); assert_eq!(result.unwrap(), expected);
} }

View File

@ -5,7 +5,6 @@ use rocket::http::Method::*;
#[test] #[test]
fn hello_world() { fn hello_world() {
let rocket = rocket::ignite().mount("/", routes![super::hello]); let rocket = rocket::ignite().mount("/", routes![super::hello]);
let req = MockRequest::new(Get, "/"); let result = MockRequest::new(Get, "/").dispatch_with(&rocket);
let result = req.dispatch_with(&rocket);
assert_eq!(result.unwrap().as_str(), "Hello, world!"); assert_eq!(result.unwrap().as_str(), "Hello, world!");
} }

View File

@ -0,0 +1,3 @@
[global]
port = 8000

View File

@ -3,67 +3,74 @@ extern crate rocket;
use std::io; use std::io;
use std::fs::File; use std::fs::File;
use rocket::{Request, Response, Route, Data, Catcher, Error}; use rocket::{Request, Route, Data, Catcher, Error};
use rocket::http::StatusCode; use rocket::http::Status;
use rocket::request::FromParam; use rocket::request::FromParam;
use rocket::response::{self, Responder};
use rocket::handler::Outcome;
use rocket::http::Method::*; use rocket::http::Method::*;
fn forward(_req: &Request, data: Data) -> Response<'static> { fn forward(_req: &Request, data: Data) -> Outcome {
Response::forward(data) Outcome::forward(data)
} }
fn hi(_req: &Request, _: Data) -> Response<'static> { fn hi(_req: &Request, _: Data) -> Outcome {
Response::success("Hello!") Outcome::of("Hello!")
} }
fn name<'a>(req: &'a Request, _: Data) -> Response<'a> { fn name<'a>(req: &'a Request, _: Data) -> Outcome {
Response::success(req.get_param(0).unwrap_or("unnamed")) Outcome::of(req.get_param(0).unwrap_or("unnamed"))
} }
fn echo_url<'a>(req: &'a Request, _: Data) -> Response<'a> { fn echo_url(req: &Request, _: Data) -> Outcome<'static> {
let param = req.uri().as_str().split_at(6).1; let param = req.uri().as_str().split_at(6).1;
Response::success(String::from_param(param)) Outcome::of(String::from_param(param).unwrap())
} }
fn upload(req: &Request, data: Data) -> Response { fn upload(req: &Request, data: Data) -> Outcome {
if !req.content_type().is_text() { if !req.content_type().is_plain() {
println!(" => Content-Type of upload must be data. Ignoring."); println!(" => Content-Type of upload must be text/plain. Ignoring.");
return Response::failure(StatusCode::BadRequest); return Outcome::failure(Status::BadRequest);
} }
let file = File::create("/tmp/upload.txt"); let file = File::create("/tmp/upload.txt");
if let Ok(mut file) = file { if let Ok(mut file) = file {
if let Ok(n) = io::copy(&mut data.open(), &mut file) { if let Ok(n) = io::copy(&mut data.open(), &mut file) {
return Response::success(format!("OK: {} bytes uploaded.", n)); return Outcome::of(format!("OK: {} bytes uploaded.", n));
} }
println!(" => Failed copying."); println!(" => Failed copying.");
Response::failure(StatusCode::InternalServerError) Outcome::failure(Status::InternalServerError)
} else { } else {
println!(" => Couldn't open file: {:?}", file.unwrap_err()); println!(" => Couldn't open file: {:?}", file.unwrap_err());
Response::failure(StatusCode::InternalServerError) Outcome::failure(Status::InternalServerError)
} }
} }
fn not_found_handler(_: Error, req: &Request) -> Response { fn get_upload(_: &Request, _: Data) -> Outcome {
Response::success(format!("Couldn't find: {}", req.uri())) Outcome::of(File::open("/tmp/upload.txt").ok())
}
fn not_found_handler(_: Error, req: &Request) -> response::Result {
format!("Couldn't find: {}", req.uri()).respond()
} }
fn main() { fn main() {
let always_forward = Route::ranked(1, Get, "/", forward); let always_forward = Route::ranked(1, Get, "/", forward);
let hello = Route::ranked(2, Get, "/", hi); let hello = Route::ranked(2, Get, "/", hi);
let echo = Route::new(Get, "/", echo_url); let echo = Route::new(Get, "/echo:<str>", echo_url);
let name = Route::new(Get, "/<name>", name); let name = Route::new(Get, "/<name>", name);
let upload_route = Route::new(Post, "/upload", upload); let post_upload = Route::new(Post, "/", upload);
let get_upload = Route::new(Get, "/", get_upload);
let not_found_catcher = Catcher::new(404, not_found_handler); let not_found_catcher = Catcher::new(404, not_found_handler);
rocket::ignite() rocket::ignite()
.mount("/", vec![always_forward, hello, upload_route]) .mount("/", vec![always_forward, hello, echo])
.mount("/upload", vec![get_upload, post_upload])
.mount("/hello", vec![name.clone()]) .mount("/hello", vec![name.clone()])
.mount("/hi", vec![name]) .mount("/hi", vec![name])
.mount("/echo:<str>", vec![echo])
.catch(vec![not_found_catcher]) .catch(vec![not_found_catcher])
.launch(); .launch();
} }

View File

@ -6,8 +6,7 @@ extern crate rocket;
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use rocket::response::{NamedFile, Failure}; use rocket::response::NamedFile;
use rocket::http::StatusCode::NotFound;
#[get("/")] #[get("/")]
fn index() -> io::Result<NamedFile> { fn index() -> io::Result<NamedFile> {
@ -15,8 +14,8 @@ fn index() -> io::Result<NamedFile> {
} }
#[get("/<file..>")] #[get("/<file..>")]
fn files(file: PathBuf) -> Result<NamedFile, Failure> { fn files(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(Path::new("static/").join(file)).map_err(|_| Failure(NotFound)) NamedFile::open(Path::new("static/").join(file)).ok()
} }
fn main() { fn main() {

View File

@ -21,8 +21,7 @@ mod test {
#[test] #[test]
fn test_hello() { fn test_hello() {
let rocket = rocket::ignite().mount("/", routes![super::hello]); let rocket = rocket::ignite().mount("/", routes![super::hello]);
let req = MockRequest::new(Get, "/"); let result = MockRequest::new(Get, "/").dispatch_with(&rocket);
let result = req.dispatch_with(&rocket);
assert_eq!(result.unwrap().as_str(), "Hello, world!"); assert_eq!(result.unwrap().as_str(), "Hello, world!");
} }
} }

View File

@ -1,5 +1,5 @@
use response;
use handler::ErrorHandler; use handler::ErrorHandler;
use response::Response;
use codegen::StaticCatchInfo; use codegen::StaticCatchInfo;
use error::Error; use error::Error;
use request::Request; use request::Request;
@ -76,14 +76,15 @@ impl Catcher {
/// # Examples /// # Examples
/// ///
/// ```rust /// ```rust
/// use rocket::{Catcher, Request, Error, Response}; /// use rocket::{Catcher, Request, Error};
/// use rocket::response::{Result, Responder};
/// ///
/// fn handle_404(_: Error, req: &Request) -> Response { /// fn handle_404(_: Error, req: &Request) -> Result {
/// Response::success(format!("Couldn't find: {}", req.uri())) /// format!("Couldn't find: {}", req.uri()).respond()
/// } /// }
/// ///
/// fn handle_500(_: Error, _: &Request) -> Response { /// fn handle_500(_: Error, _: &Request) -> Result {
/// Response::success("Whoops, we messed up!") /// "Whoops, we messed up!".respond()
/// } /// }
/// ///
/// let not_found_catcher = Catcher::new(404, handle_404); /// let not_found_catcher = Catcher::new(404, handle_404);
@ -96,8 +97,8 @@ impl Catcher {
#[doc(hidden)] #[doc(hidden)]
#[inline(always)] #[inline(always)]
pub fn handle<'r>(&self, err: Error, request: &'r Request) -> Response<'r> { pub fn handle<'r>(&self, err: Error, req: &'r Request) -> response::Result<'r> {
(self.handler)(err, request) (self.handler)(err, req)
} }
#[inline(always)] #[inline(always)]
@ -153,10 +154,10 @@ macro_rules! default_errors {
let mut map = HashMap::new(); let mut map = HashMap::new();
$( $(
fn $fn_name<'r>(_: Error, _r: &'r Request) -> Response<'r> { fn $fn_name<'r>(_: Error, _r: &'r Request) -> response::Result<'r> {
Response::with_raw_status($code, status::Custom(Status::from_code($code).unwrap(),
content::HTML(error_page_template!($code, $name, $description)) content::HTML(error_page_template!($code, $name, $description))
) ).respond()
} }
map.insert($code, Catcher::new_default($code, $fn_name)); map.insert($code, Catcher::new_default($code, $fn_name));
@ -172,7 +173,8 @@ pub mod defaults {
use std::collections::HashMap; use std::collections::HashMap;
use request::Request; use request::Request;
use response::{Response, content}; use response::{self, content, status, Responder};
use http::Status;
use error::Error; use error::Error;
pub fn get() -> HashMap<u16, Catcher> { pub fn get() -> HashMap<u16, Catcher> {

View File

@ -5,6 +5,8 @@ use std::time::Duration;
use std::mem::transmute; use std::mem::transmute;
use super::data_stream::{DataStream, StreamReader, kill_stream}; use super::data_stream::{DataStream, StreamReader, kill_stream};
use ext::ReadExt;
use http::hyper::{HyperBodyReader, HyperHttpStream}; use http::hyper::{HyperBodyReader, HyperHttpStream};
use http::hyper::HyperNetworkStream; use http::hyper::HyperNetworkStream;
use http::hyper::HyperHttpReader::*; use http::hyper::HyperHttpReader::*;
@ -114,7 +116,8 @@ impl Data {
} }
/// Returns true if the `peek` buffer contains all of the data in the body /// Returns true if the `peek` buffer contains all of the data in the body
/// of the request. /// of the request. Returns `false` if it does not or if it is not known if
/// it does.
#[inline(always)] #[inline(always)]
pub fn peek_complete(&self) -> bool { pub fn peek_complete(&self) -> bool {
self.is_done self.is_done
@ -150,45 +153,27 @@ impl Data {
// Make sure the buffer is large enough for the bytes we want to peek. // Make sure the buffer is large enough for the bytes we want to peek.
const PEEK_BYTES: usize = 4096; const PEEK_BYTES: usize = 4096;
if buf.len() < PEEK_BYTES { if buf.len() < PEEK_BYTES {
trace!("Resizing peek buffer from {} to {}.", buf.len(), PEEK_BYTES); trace_!("Resizing peek buffer from {} to {}.", buf.len(), PEEK_BYTES);
buf.resize(PEEK_BYTES, 0); buf.resize(PEEK_BYTES, 0);
} }
// We want to fill the buffer with as many bytes as possible. We also // Fill the buffer with as many bytes as possible. If we read less than
// want to record if we reach the EOF while filling the buffer. The // that buffer's length, we know we reached the EOF. Otherwise, it's
// buffer already has `cap` bytes. We read up to buf.len() - 1 bytes, // unclear, so we just say we didn't reach EOF.
// and then we try reading 1 more to see if we've reached the EOF.
trace!("Init buffer cap: {}", cap); trace!("Init buffer cap: {}", cap);
let buf_len = buf.len(); let eof = match stream.read_max(&mut buf[cap..]) {
let eof = if cap < buf_len { Ok(n) => {
// We have room to read into the buffer. Let's do it. trace_!("Filled peek buf with {} bytes.", n);
match stream.read(&mut buf[cap..(buf_len - 1)]) { cap += n;
Ok(0) => true, cap < buf.len()
Ok(n) => {
trace!("Filled peek buf with {} bytes.", n);
cap += n;
match stream.read(&mut buf[cap..(cap + 1)]) {
Ok(n) => {
cap += n;
n == 0
}
Err(e) => {
error_!("Failed to check stream EOF status: {:?}", e);
false
}
}
}
Err(e) => {
error_!("Failed to read into peek buffer: {:?}", e);
false
}
} }
} else { Err(e) => {
// There's no more room in the buffer. Assume there are still bytes. error_!("Failed to read into peek buffer: {:?}.", e);
false false
},
}; };
trace!("Peek buffer size: {}, remaining: {}", buf_len, buf_len - cap); trace_!("Peek buffer size: {}, remaining: {}", buf.len(), buf.len() - cap);
Data { Data {
buffer: buf, buffer: buf,
stream: stream, stream: stream,

View File

@ -1,17 +1,17 @@
use outcome::{self, IntoOutcome}; use outcome::{self, IntoOutcome};
use outcome::Outcome::*; use outcome::Outcome::*;
use http::StatusCode; use http::Status;
use request::Request; use request::Request;
use data::Data; use data::Data;
/// Type alias for the `Outcome` of a `FromData` conversion. /// Type alias for the `Outcome` of a `FromData` conversion.
pub type Outcome<S, E> = outcome::Outcome<S, (StatusCode, E), Data>; pub type Outcome<S, E> = outcome::Outcome<S, (Status, E), Data>;
impl<'a, S, E> IntoOutcome<S, (StatusCode, E), Data> for Result<S, E> { impl<'a, S, E> IntoOutcome<S, (Status, E), Data> for Result<S, E> {
fn into_outcome(self) -> Outcome<S, E> { fn into_outcome(self) -> Outcome<S, E> {
match self { match self {
Ok(val) => Success(val), Ok(val) => Success(val),
Err(err) => Failure((StatusCode::InternalServerError, err)) Err(err) => Failure((Status::InternalServerError, err))
} }
} }
} }
@ -39,7 +39,7 @@ impl<'a, S, E> IntoOutcome<S, (StatusCode, E), Data> for Result<S, E> {
/// the value for the data parameter. As long as all other parsed types /// the value for the data parameter. As long as all other parsed types
/// succeed, the request will be handled by the requesting handler. /// succeed, the request will be handled by the requesting handler.
/// ///
/// * **Failure**(StatusCode, E) /// * **Failure**(Status, E)
/// ///
/// If the `Outcome` is `Failure`, the request will fail with the given status /// If the `Outcome` is `Failure`, the request will fail with the given status
/// code and error. The designated error /// code and error. The designated error

20
lib/src/ext.rs Normal file
View File

@ -0,0 +1,20 @@
use std::io;
pub trait ReadExt: io::Read {
fn read_max(&mut self, mut buf: &mut [u8]) -> io::Result<usize> {
let start_len = buf.len();
while !buf.is_empty() {
match self.read(buf) {
Ok(0) => break,
Ok(n) => { let tmp = buf; buf = &mut tmp[n..]; }
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
return Ok(start_len - buf.len())
}
}
impl<T: io::Read> ReadExt for T { }

35
lib/src/handler.rs Normal file
View File

@ -0,0 +1,35 @@
use data::Data;
use request::Request;
use response::{self, Response, Responder};
use error::Error;
use http::Status;
use outcome;
/// Type alias for the `Outcome` of a `Handler`.
pub type Outcome<'r> = outcome::Outcome<Response<'r>, Status, Data>;
impl<'r> Outcome<'r> {
#[inline]
pub fn of<T: Responder<'r>>(responder: T) -> Outcome<'r> {
match responder.respond() {
Ok(response) => outcome::Outcome::Success(response),
Err(status) => outcome::Outcome::Failure(status)
}
}
#[inline(always)]
pub fn failure(code: Status) -> Outcome<'static> {
outcome::Outcome::Failure(code)
}
#[inline(always)]
pub fn forward(data: Data) -> Outcome<'static> {
outcome::Outcome::Forward(data)
}
}
/// The type of a request handler.
pub type Handler = for<'r> fn(&'r Request, Data) -> Outcome<'r>;
/// The type of an error handler.
pub type ErrorHandler = for<'r> fn(Error, &'r Request) -> response::Result<'r>;

View File

@ -1,10 +1,9 @@
use std::default::Default; use std::borrow::{Borrow, Cow};
use std::str::FromStr; use std::str::FromStr;
use std::borrow::Borrow;
use std::fmt; use std::fmt;
use http::mime::{Mime, Param, Attr, Value, TopLevel, SubLevel}; use http::Header;
use http::mime::Mime;
use router::Collider; use router::Collider;
/// Typed representation of HTTP Content-Types. /// Typed representation of HTTP Content-Types.
@ -14,69 +13,90 @@ use router::Collider;
/// provides methods to parse HTTP Content-Type values /// provides methods to parse HTTP Content-Type values
/// ([from_str](#method.from_str)) and to return the ContentType associated with /// ([from_str](#method.from_str)) and to return the ContentType associated with
/// a file extension ([from_ext](#method.from_extension)). /// a file extension ([from_ext](#method.from_extension)).
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Hash)]
pub struct ContentType(pub TopLevel, pub SubLevel, pub Option<Vec<Param>>); pub struct ContentType {
pub ttype: Cow<'static, str>,
macro_rules! ctrs { pub subtype: Cow<'static, str>,
($($(#[$attr:meta])* | $name:ident: $top:ident/$sub:ident),+) => { pub params: Option<Cow<'static, str>>
$
($(#[$attr])*
#[inline(always)]
pub fn $name() -> ContentType {
ContentType::of(TopLevel::$top, SubLevel::$sub)
})+
};
} }
macro_rules! checkers { macro_rules! ctr_params {
($($(#[$attr:meta])* | $name:ident: $top:ident/$sub:ident),+) => { () => (None);
($param:expr) => (Some(Cow::Borrowed($param)));
}
macro_rules! ctrs {
($($str:expr, $name:ident, $check_name:ident =>
$top:expr, $sub:expr $(; $param:expr),*),+) => {
$( $(
$(#[$attr])* #[doc="[ContentType](struct.ContentType.html) for <b>"]
#[inline(always)] #[doc=$str]
pub fn $name(&self) -> bool { #[doc="</b>: <i>"]
self.0 == TopLevel::$top && self.1 == SubLevel::$sub #[doc=$top]
})+ #[doc="/"]
#[doc=$sub]
$(#[doc="; "] #[doc=$param])*
#[doc="</i>"]
#[allow(non_upper_case_globals)]
pub const $name: ContentType = ContentType {
ttype: Cow::Borrowed($top),
subtype: Cow::Borrowed($sub),
params: ctr_params!($($param)*)
};
)+
/// Returns `true` if this ContentType is known to Rocket.
pub fn is_known(&self) -> bool {
match (&*self.ttype, &*self.subtype) {
$(
($top, $sub) => true,
)+
_ => false
}
}
$(
#[doc="Returns `true` if `self` is a <b>"]
#[doc=$str]
#[doc="</b> ContentType: <i>"]
#[doc=$top]
#[doc="</i>/<i>"]
#[doc=$sub]
#[doc="</i>."]
/// Paramaters are not taken into account when doing that check.
pub fn $check_name(&self) -> bool {
self.ttype == $top && self.subtype == $sub
}
)+
}; };
} }
impl ContentType { impl ContentType {
#[doc(hidden)]
#[inline(always)] #[inline(always)]
pub fn new(t: TopLevel, s: SubLevel, params: Option<Vec<Param>>) -> ContentType { pub fn new<T, S>(ttype: T, subtype: S) -> ContentType
ContentType(t, s, params) where T: Into<Cow<'static, str>>, S: Into<Cow<'static, str>>
} {
ContentType {
/// Constructs a new content type of the given top level and sub level ttype: ttype.into(),
/// types. If the top-level type is `Text`, a charset of UTF-8 is set. subtype: subtype.into(),
/// params: None
/// # Examples
///
/// ```rust
/// use rocket::http::ContentType;
/// use rocket::http::mime::{TopLevel, SubLevel};
///
/// let ct = ContentType::of(TopLevel::Text, SubLevel::Html);
/// assert_eq!(ct.to_string(), "text/html; charset=utf-8".to_string());
/// assert!(ct.is_html());
/// ```
///
/// ```rust
/// use rocket::http::ContentType;
/// use rocket::http::mime::{TopLevel, SubLevel};
///
/// let ct = ContentType::of(TopLevel::Application, SubLevel::Json);
/// assert_eq!(ct.to_string(), "application/json".to_string());
/// assert!(ct.is_json());
/// ```
#[inline(always)]
pub fn of(t: TopLevel, s: SubLevel) -> ContentType {
if t == TopLevel::Text {
ContentType(t, s, Some(vec![(Attr::Charset, Value::Utf8)]))
} else {
ContentType(t, s, None)
} }
} }
#[inline(always)]
pub fn with_params<T, S, P>(ttype: T, subtype: S, params: Option<P>) -> ContentType
where T: Into<Cow<'static, str>>,
S: Into<Cow<'static, str>>,
P: Into<Cow<'static, str>>
{
ContentType {
ttype: ttype.into(),
subtype: subtype.into(),
params: params.map(|p| p.into())
}
}
/// Returns the Content-Type associated with the extension `ext`. Not all /// Returns the Content-Type associated with the extension `ext`. Not all
/// extensions are recognized. If an extensions is not recognized, then this /// extensions are recognized. If an extensions is not recognized, then this
/// method returns a ContentType of `any`. The currently recognized /// method returns a ContentType of `any`. The currently recognized
@ -103,77 +123,37 @@ impl ContentType {
/// assert!(foo.is_any()); /// assert!(foo.is_any());
/// ``` /// ```
pub fn from_extension(ext: &str) -> ContentType { pub fn from_extension(ext: &str) -> ContentType {
let (top_level, sub_level) = match ext { match ext {
"txt" => (TopLevel::Text, SubLevel::Plain), "txt" => ContentType::Plain,
"html" | "htm" => (TopLevel::Text, SubLevel::Html), "html" | "htm" => ContentType::HTML,
"xml" => (TopLevel::Text, SubLevel::Xml), "xml" => ContentType::XML,
"js" => (TopLevel::Application, SubLevel::Javascript), "js" => ContentType::JavaScript,
"css" => (TopLevel::Text, SubLevel::Css), "css" => ContentType::CSS,
"json" => (TopLevel::Application, SubLevel::Json), "json" => ContentType::JSON,
"png" => (TopLevel::Image, SubLevel::Png), "png" => ContentType::PNG,
"gif" => (TopLevel::Image, SubLevel::Gif), "gif" => ContentType::GIF,
"bmp" => (TopLevel::Image, SubLevel::Bmp), "bmp" => ContentType::BMP,
"jpeg" => (TopLevel::Image, SubLevel::Jpeg), "jpeg" | "jpg" => ContentType::JPEG,
"jpg" => (TopLevel::Image, SubLevel::Jpeg), "pdf" => ContentType::PDF,
"pdf" => (TopLevel::Application, SubLevel::Ext("pdf".into())), _ => ContentType::Any
_ => (TopLevel::Star, SubLevel::Star),
};
ContentType::of(top_level, sub_level)
}
ctrs! {
/// Returns a `ContentType` representing `*/*`, i.e., _any_ ContentType.
| any: Star/Star,
/// Returns a `ContentType` representing JSON, i.e, `application/json`.
| json: Application/Json,
/// Returns a `ContentType` representing XML, i.e, `text/xml`.
| xml: Text/Xml,
/// Returns a `ContentType` representing HTML, i.e, `text/html`.
| html: Text/Html,
/// Returns a `ContentType` representing plain text, i.e, `text/plain`.
| plain: Text/Plain
}
/// Returns true if this content type is not one of the standard content
/// types, that if, if it is an "extended" content type.
pub fn is_ext(&self) -> bool {
if let TopLevel::Ext(_) = self.0 {
true
} else if let SubLevel::Ext(_) = self.1 {
true
} else {
false
} }
} }
checkers! { ctrs! {
/// Returns true if the content type is plain text, i.e.: `text/plain`. "any", Any, is_any => "*", "*",
| is_text: Text/Plain, "form", Form, is_form => "application", "x-www-form-urlencoded",
"data form", DataForm, is_data_form => "multipart", "form-data",
/// Returns true if the content type is JSON, i.e: `application/json`. "JSON", JSON, is_json => "application", "json",
| is_json: Application/Json, "XML", XML, is_xml => "text", "xml"; "charset=utf-8",
"HTML", HTML, is_html => "text", "html"; "charset=utf-8",
/// Returns true if the content type is XML, i.e: `text/xml`. "Plain", Plain, is_plain => "text", "plain"; "charset=utf-8",
| is_xml: Text/Xml, "JavaScript", JavaScript, is_javascript => "application", "javascript",
"CSS", CSS, is_css => "text", "css"; "charset=utf-8",
/// Returns true if the content type is any, i.e.: `*/*`. "PNG", PNG, is_png => "image", "png",
| is_any: Star/Star, "GIF", GIF, is_gif => "image", "gif",
"BMP", BMP, is_bmp => "image", "bmp",
/// Returns true if the content type is HTML, i.e.: `text/html`. "JPEG", JPEG, is_jpeg => "image", "jpeg",
| is_html: Text/Html, "PDF", PDF, is_pdf => "application", "pdf"
/// Returns true if the content type is that for non-data HTTP forms,
/// i.e.: `application/x-www-form-urlencoded`.
| is_form: Application/WwwFormUrlEncoded,
/// Returns true if the content type is that for data HTTP forms, i.e.:
/// `multipart/form-data`.
| is_form_data: Multipart/FormData
} }
} }
@ -181,14 +161,7 @@ impl Default for ContentType {
/// Returns a ContentType of `any`, or `*/*`. /// Returns a ContentType of `any`, or `*/*`.
#[inline(always)] #[inline(always)]
fn default() -> ContentType { fn default() -> ContentType {
ContentType::any() ContentType::Any
}
}
#[doc(hidden)]
impl Into<Mime> for ContentType {
fn into(self) -> Mime {
Mime(self.0, self.1, self.2.unwrap_or_default())
} }
} }
@ -205,10 +178,15 @@ impl From<Mime> for ContentType {
fn from(mime: Mime) -> ContentType { fn from(mime: Mime) -> ContentType {
let params = match mime.2.len() { let params = match mime.2.len() {
0 => None, 0 => None,
_ => Some(mime.2), _ => {
Some(mime.2.into_iter()
.map(|(attr, value)| format!("{}={}", attr, value))
.collect::<Vec<_>>()
.join("; "))
}
}; };
ContentType(mime.0, mime.1, params) ContentType::with_params(mime.0.to_string(), mime.1.to_string(), params)
} }
} }
@ -239,8 +217,8 @@ impl FromStr for ContentType {
/// use std::str::FromStr; /// use std::str::FromStr;
/// use rocket::http::ContentType; /// use rocket::http::ContentType;
/// ///
/// let json = ContentType::from_str("application/json"); /// let json = ContentType::from_str("application/json").unwrap();
/// assert_eq!(json, Ok(ContentType::json())); /// assert_eq!(json, ContentType::JSON);
/// ``` /// ```
/// ///
/// Parsing a content-type extension: /// Parsing a content-type extension:
@ -251,9 +229,9 @@ impl FromStr for ContentType {
/// use rocket::http::mime::{TopLevel, SubLevel}; /// use rocket::http::mime::{TopLevel, SubLevel};
/// ///
/// let custom = ContentType::from_str("application/x-custom").unwrap(); /// let custom = ContentType::from_str("application/x-custom").unwrap();
/// assert!(custom.is_ext()); /// assert!(!custom.is_known());
/// assert_eq!(custom.0, TopLevel::Application); /// assert_eq!(custom.ttype, "application");
/// assert_eq!(custom.1, SubLevel::Ext("x-custom".into())); /// assert_eq!(custom.subtype, "x-custom");
/// ``` /// ```
/// ///
/// Parsing an invalid Content-Type value: /// Parsing an invalid Content-Type value:
@ -291,12 +269,10 @@ impl FromStr for ContentType {
return Err("Invalid character in string."); return Err("Invalid character in string.");
} }
let (top_s, sub_s) = (&*top_s.to_lowercase(), &*sub_s.to_lowercase()); let (top_s, sub_s) = (top_s.to_lowercase(), sub_s.to_lowercase());
let top_level = TopLevel::from_str(top_s).map_err(|_| "Bad TopLevel")?;
let sub_level = SubLevel::from_str(sub_s).map_err(|_| "Bad SubLevel")?;
// FIXME: Use `rest` to find params. // FIXME: Use `rest` to find params.
Ok(ContentType::new(top_level, sub_level, None)) Ok(ContentType::new(top_s, sub_s))
} }
} }
@ -308,53 +284,44 @@ impl fmt::Display for ContentType {
/// ```rust /// ```rust
/// use rocket::http::ContentType; /// use rocket::http::ContentType;
/// ///
/// let ct = format!("{}", ContentType::json()); /// let ct = format!("{}", ContentType::JSON);
/// assert_eq!(ct, "application/json".to_string()); /// assert_eq!(ct, "application/json");
/// ``` /// ```
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}/{}", self.0.as_str(), self.1.as_str())?; write!(f, "{}/{}", self.ttype, self.subtype)?;
self.2.as_ref().map_or(Ok(()), |params| { if let Some(ref params) = self.params {
for param in params.iter() { write!(f, "; {}", params)?;
let (ref attr, ref value) = *param; }
write!(f, "; {}={}", attr, value)?;
}
Ok(()) Ok(())
}) }
}
impl Into<Header<'static>> for ContentType {
#[inline]
fn into(self) -> Header<'static> {
Header::new("Content-Type", self.to_string())
} }
} }
impl Collider for ContentType { impl Collider for ContentType {
fn collides_with(&self, other: &ContentType) -> bool { fn collides_with(&self, other: &ContentType) -> bool {
self.0.collides_with(&other.0) && self.1.collides_with(&other.1) (self.ttype == "*" || other.ttype == "*" || self.ttype == other.ttype) &&
} (self.subtype == "*" || other.subtype == "*" || self.subtype == other.subtype)
}
impl Collider for TopLevel {
fn collides_with(&self, other: &TopLevel) -> bool {
*self == TopLevel::Star || *other == TopLevel::Star || *self == *other
}
}
impl Collider for SubLevel {
fn collides_with(&self, other: &SubLevel) -> bool {
*self == SubLevel::Star || *other == SubLevel::Star || *self == *other
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::ContentType; use super::ContentType;
use hyper::mime::{TopLevel, SubLevel};
use std::str::FromStr; use std::str::FromStr;
macro_rules! assert_no_parse { macro_rules! assert_no_parse {
($string:expr) => ({ ($string:expr) => ({
let result = ContentType::from_str($string); let result = ContentType::from_str($string);
if !result.is_err() { if !result.is_err() {
println!("{} parsed!", $string); println!("{} parsed unexpectedly!", $string);
} }
assert!(result.is_err()); assert!(result.is_err());
@ -367,29 +334,34 @@ mod test {
assert!(result.is_ok()); assert!(result.is_ok());
result.unwrap() result.unwrap()
}); });
($string:expr, $top:tt/$sub:tt) => ({
($string:expr, $ct:expr) => ({
let c = assert_parse!($string); let c = assert_parse!($string);
assert_eq!(c.0, TopLevel::$top); assert_eq!(c.ttype, $ct.ttype);
assert_eq!(c.1, SubLevel::$sub); assert_eq!(c.subtype, $ct.subtype);
c c
}) })
} }
#[test] #[test]
fn test_simple() { fn test_simple() {
assert_parse!("application/json", Application/Json); assert_parse!("application/json", ContentType::JSON);
assert_parse!("*/json", Star/Json); assert_parse!("*/json", ContentType::new("*", "json"));
assert_parse!("text/html", Text/Html); assert_parse!("text/html", ContentType::HTML);
assert_parse!("TEXT/html", Text/Html); assert_parse!("TEXT/html", ContentType::HTML);
assert_parse!("*/*", Star/Star); assert_parse!("*/*", ContentType::Any);
assert_parse!("application/*", Application/Star); assert_parse!("application/*", ContentType::new("application", "*"));
} }
#[test] #[test]
fn test_params() { fn test_params() {
assert_parse!("application/json; charset=utf8", Application/Json); // TODO: Test these.
assert_parse!("application/*;charset=utf8;else=1", Application/Star); assert_parse!("application/json; charset=utf-8", ContentType::JSON);
assert_parse!("*/*;charset=utf8;else=1", Star/Star); assert_parse!("*/*;", ContentType::Any);
assert_parse!("application/*;else=1",
ContentType::with_params("application", "*", Some("else=1")));
assert_parse!("*/*;charset=utf8;else=1",
ContentType::with_params("*", "*", Some("charset=utf-8;else=1")));
} }
#[test] #[test]
@ -403,5 +375,6 @@ mod test {
assert_no_parse!("*/"); assert_no_parse!("*/");
assert_no_parse!("/*"); assert_no_parse!("/*");
assert_no_parse!("///"); assert_no_parse!("///");
assert_no_parse!("");
} }
} }

37
lib/src/http/header.rs Normal file
View File

@ -0,0 +1,37 @@
use std::borrow::Cow;
use http::hyper::header::Header as HyperHeader;
use http::hyper::header::HeaderFormat as HyperHeaderFormat;
use http::hyper::header::HeaderFormatter as HyperHeaderFormatter;
use std::fmt;
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct Header<'h> {
pub name: Cow<'h, str>,
pub value: Cow<'h, str>,
}
impl<'h> Header<'h> {
#[inline(always)]
pub fn new<'a: 'h, 'b: 'h, N, V>(name: N, value: V) -> Header<'h>
where N: Into<Cow<'a, str>>, V: Into<Cow<'b, str>>
{
Header {
name: name.into(),
value: value.into()
}
}
}
impl<'h> fmt::Display for Header<'h> {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}: {}", self.name, self.value)
}
}
impl<T> From<T> for Header<'static> where T: HyperHeader + HyperHeaderFormat {
fn from(hyper_header: T) -> Header<'static> {
let formatter = HyperHeaderFormatter(&hyper_header);
Header::new(T::header_name(), format!("{}", formatter))
}
}

View File

@ -26,7 +26,6 @@ pub use hyper::net::NetworkStream as HyperNetworkStream;
pub use hyper::http::h1::HttpReader as HyperHttpReader; pub use hyper::http::h1::HttpReader as HyperHttpReader;
pub use hyper::header; pub use hyper::header;
// This is okay for now.
pub use hyper::status::StatusCode; pub use hyper::status::StatusCode;
// TODO: Remove from Rocket in favor of a more flexible HTTP library. // TODO: Remove from Rocket in favor of a more flexible HTTP library.

View File

@ -11,12 +11,15 @@ pub mod uri;
mod cookies; mod cookies;
mod method; mod method;
mod content_type; mod content_type;
mod status;
mod header;
// TODO: Removed from Rocket in favor of a more flexible HTTP library. // TODO: Removed from Rocket in favor of a more flexible HTTP library.
pub use hyper::mime; pub use hyper::mime;
pub use self::method::Method; pub use self::method::Method;
pub use self::hyper::StatusCode;
pub use self::content_type::ContentType; pub use self::content_type::ContentType;
pub use self::status::Status;
pub use self::header::Header;
pub use self::cookies::{Cookie, Cookies}; pub use self::cookies::{Cookie, Cookies};

139
lib/src/http/status.rs Normal file
View File

@ -0,0 +1,139 @@
use std::fmt;
pub enum Class {
Informational,
Success,
Redirection,
ClientError,
ServerError,
Unknown
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct Status {
/// The HTTP status code associated with this status.
pub code: u16,
/// The HTTP reason phrase associated with this status.
pub reason: &'static str
}
macro_rules! ctrs {
($($code:expr, $code_str:expr, $name:ident => $reason:expr),+) => {
pub fn from_code(code: u16) -> Option<Status> {
match code {
$($code => Some(Status::$name),)+
_ => None
}
}
$(
#[doc="[Status](struct.Status.html) with code <b>"]
#[doc=$code_str]
#[doc="</b> and reason <i>"]
#[doc=$reason]
#[doc="</i>."]
#[allow(non_upper_case_globals)]
pub const $name: Status = Status::new($code, $reason);
)+
};
}
impl Status {
#[inline(always)]
pub const fn new(code: u16, reason: &'static str) -> Status {
Status {
code: code,
reason: reason
}
}
pub fn class(&self) -> Class {
match self.code / 100 {
1 => Class::Informational,
2 => Class::Success,
3 => Class::Redirection,
4 => Class::ClientError,
5 => Class::ServerError,
_ => Class::Unknown
}
}
ctrs! {
100, "100", Continue => "Continue",
101, "101", SwitchingProtocols => "Switching Protocols",
102, "102", Processing => "Processing",
200, "200", Ok => "OK",
201, "201", Created => "Created",
202, "202", Accepted => "Accepted",
203, "203", NonAuthoritativeInformation => "Non-Authoritative Information",
204, "204", NoContent => "No Content",
205, "205", ResetContent => "Reset Content",
206, "206", PartialContent => "Partial Content",
207, "207", MultiStatus => "Multi-Status",
208, "208", AlreadyReported => "Already Reported",
226, "226", ImUsed => "IM Used",
300, "300", MultipleChoices => "Multiple Choices",
301, "301", MovedPermanently => "Moved Permanently",
302, "302", Found => "Found",
303, "303", SeeOther => "See Other",
304, "304", NotModified => "Not Modified",
305, "305", UseProxy => "Use Proxy",
307, "307", TemporaryRedirect => "Temporary Redirect",
308, "308", PermanentRedirect => "Permanent Redirect",
400, "400", BadRequest => "Bad Request",
401, "401", Unauthorized => "Unauthorized",
402, "402", PaymentRequired => "Payment Required",
403, "403", Forbidden => "Forbidden",
404, "404", NotFound => "Not Found",
405, "405", MethodNotAllowed => "Method Not Allowed",
406, "406", NotAcceptable => "Not Acceptable",
407, "407", ProxyAuthenticationRequired => "Proxy Authentication Required",
408, "408", RequestTimeout => "Request Timeout",
409, "409", Conflict => "Conflict",
410, "410", Gone => "Gone",
411, "411", LengthRequired => "Length Required",
412, "412", PreconditionFailed => "Precondition Failed",
413, "413", PayloadTooLarge => "Payload Too Large",
414, "414", UriTooLong => "URI Too Long",
415, "415", UnsupportedMediaType => "Unsupported Media Type",
416, "416", RangeNotSatisfiable => "Range Not Satisfiable",
417, "417", ExpectationFailed => "Expectation Failed",
418, "418", ImATeapot => "I'm a teapot",
421, "421", MisdirectedRequest => "Misdirected Request",
422, "422", UnprocessableEntity => "Unprocessable Entity",
423, "423", Locked => "Locked",
424, "424", FailedDependency => "Failed Dependency",
426, "426", UpgradeRequired => "Upgrade Required",
428, "428", PreconditionRequired => "Precondition Required",
429, "429", TooManyRequests => "Too Many Requests",
431, "431", RequestHeaderFieldsTooLarge => "Request Header Fields Too Large",
451, "451", UnavailableForLegalReasons => "Unavailable For Legal Reasons",
500, "500", InternalServerError => "Internal Server Error",
501, "501", NotImplemented => "Not Implemented",
502, "502", BadGateway => "Bad Gateway",
503, "503", ServiceUnavailable => "Service Unavailable",
504, "504", GatewayTimeout => "Gateway Timeout",
505, "505", HttpVersionNotSupported => "HTTP Version Not Supported",
506, "506", VariantAlsoNegotiates => "Variant Also Negotiates",
507, "507", InsufficientStorage => "Insufficient Storage",
508, "508", LoopDetected => "Loop Detected",
510, "510", NotExtended => "Not Extended",
511, "511", NetworkAuthenticationRequired => "Network Authentication Required"
}
#[doc(hidden)]
#[inline]
pub fn raw(code: u16) -> Status {
match Status::from_code(code) {
Some(status) => status,
None => Status::new(code, "<unknown code>")
}
}
}
impl fmt::Display for Status {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {}", self.code, self.reason)
}
}

View File

@ -1,6 +1,8 @@
#![feature(specialization)] #![feature(specialization)]
#![feature(conservative_impl_trait)] #![feature(conservative_impl_trait)]
#![feature(drop_types_in_const)] #![feature(drop_types_in_const)]
#![feature(associated_consts)]
#![feature(const_fn)]
//! # Rocket - Core API Documentation //! # Rocket - Core API Documentation
//! //!
@ -103,27 +105,14 @@ pub mod response;
pub mod outcome; pub mod outcome;
pub mod config; pub mod config;
pub mod data; pub mod data;
pub mod handler;
mod error; mod error;
mod router; mod router;
mod rocket; mod rocket;
mod codegen; mod codegen;
mod catcher; mod catcher;
mod ext;
/// Defines the types for request and error handlers.
#[doc(hidden)]
pub mod handler {
use data::Data;
use request::Request;
use response::Response;
use error::Error;
/// The type of a request handler.
pub type Handler = for<'r> fn(&'r Request, Data) -> Response<'r>;
/// The type of an error handler.
pub type ErrorHandler = for<'r> fn(Error, &'r Request) -> Response<'r>;
}
#[doc(inline)] pub use response::Response; #[doc(inline)] pub use response::Response;
#[doc(inline)] pub use handler::{Handler, ErrorHandler}; #[doc(inline)] pub use handler::{Handler, ErrorHandler};

View File

@ -27,7 +27,7 @@ use std::marker::PhantomData;
use std::fmt::{self, Debug}; use std::fmt::{self, Debug};
use std::io::Read; use std::io::Read;
use http::StatusCode; use http::Status;
use request::Request; use request::Request;
use data::{self, Data, FromData}; use data::{self, Data, FromData};
use outcome::Outcome::*; use outcome::Outcome::*;
@ -242,13 +242,13 @@ impl<'f, T: FromForm<'f>> FromData for Form<'f, T> where T::Error: Debug {
let mut stream = data.open().take(32768); let mut stream = data.open().take(32768);
if let Err(e) = stream.read_to_string(&mut form_string) { if let Err(e) = stream.read_to_string(&mut form_string) {
error_!("IO Error: {:?}", e); error_!("IO Error: {:?}", e);
Failure((StatusCode::InternalServerError, None)) Failure((Status::InternalServerError, None))
} else { } else {
match Form::new(form_string) { match Form::new(form_string) {
Ok(form) => Success(form), Ok(form) => Success(form),
Err((form_string, e)) => { Err((form_string, e)) => {
error_!("Failed to parse value from form: {:?}", e); error_!("Failed to parse value from form: {:?}", e);
Failure((StatusCode::BadRequest, Some(form_string))) Failure((Status::BadRequest, Some(form_string)))
} }
} }
} }

View File

@ -3,16 +3,16 @@ use std::fmt::Debug;
use outcome::{self, IntoOutcome}; use outcome::{self, IntoOutcome};
use request::Request; use request::Request;
use outcome::Outcome::*; use outcome::Outcome::*;
use http::{StatusCode, ContentType, Method, Cookies}; use http::{Status, ContentType, Method, Cookies};
/// Type alias for the `Outcome` of a `FromRequest` conversion. /// Type alias for the `Outcome` of a `FromRequest` conversion.
pub type Outcome<S, E> = outcome::Outcome<S, (StatusCode, E), ()>; pub type Outcome<S, E> = outcome::Outcome<S, (Status, E), ()>;
impl<S, E> IntoOutcome<S, (StatusCode, E), ()> for Result<S, E> { impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
fn into_outcome(self) -> Outcome<S, E> { fn into_outcome(self) -> Outcome<S, E> {
match self { match self {
Ok(val) => Success(val), Ok(val) => Success(val),
Err(val) => Failure((StatusCode::BadRequest, val)) Err(val) => Failure((Status::BadRequest, val))
} }
} }
} }
@ -49,7 +49,7 @@ impl<S, E> IntoOutcome<S, (StatusCode, E), ()> for Result<S, E> {
/// the value for the corresponding parameter. As long as all other parsed /// the value for the corresponding parameter. As long as all other parsed
/// types succeed, the request will be handled. /// types succeed, the request will be handled.
/// ///
/// * **Failure**(StatusCode, E) /// * **Failure**(Status, E)
/// ///
/// If the `Outcome` is `Failure`, the request will fail with the given status /// If the `Outcome` is `Failure`, the request will fail with the given status
/// code and error. The designated error /// code and error. The designated error
@ -78,7 +78,7 @@ impl<S, E> IntoOutcome<S, (StatusCode, E), ()> for Result<S, E> {
/// # extern crate rocket; /// # extern crate rocket;
/// # /// #
/// use rocket::Outcome; /// use rocket::Outcome;
/// use rocket::http::StatusCode; /// use rocket::http::Status;
/// use rocket::request::{self, Request, FromRequest}; /// use rocket::request::{self, Request, FromRequest};
/// ///
/// struct APIKey(String); /// struct APIKey(String);
@ -93,7 +93,7 @@ impl<S, E> IntoOutcome<S, (StatusCode, E), ()> for Result<S, E> {
/// fn from_request(request: &'r Request) -> request::Outcome<APIKey, ()> { /// fn from_request(request: &'r Request) -> request::Outcome<APIKey, ()> {
/// if let Some(keys) = request.headers().get_raw("x-api-key") { /// if let Some(keys) = request.headers().get_raw("x-api-key") {
/// if keys.len() != 1 { /// if keys.len() != 1 {
/// return Outcome::Failure((StatusCode::BadRequest, ())); /// return Outcome::Failure((Status::BadRequest, ()));
/// } /// }
/// ///
/// if let Ok(key) = String::from_utf8(keys[0].clone()) { /// if let Ok(key) = String::from_utf8(keys[0].clone()) {

View File

@ -140,7 +140,7 @@ impl Request {
#[inline(always)] #[inline(always)]
pub fn content_type(&self) -> ContentType { pub fn content_type(&self) -> ContentType {
let hyp_ct = self.headers().get::<header::ContentType>(); let hyp_ct = self.headers().get::<header::ContentType>();
hyp_ct.map_or(ContentType::any(), |ct| ContentType::from(&ct.0)) hyp_ct.map_or(ContentType::Any, |ct| ContentType::from(&ct.0))
} }
/// <div class="stability" style="margin-left: 0;"> /// <div class="stability" style="margin-left: 0;">
@ -154,10 +154,10 @@ impl Request {
/// Returns the first content-type accepted by this request. /// Returns the first content-type accepted by this request.
pub fn accepts(&self) -> ContentType { pub fn accepts(&self) -> ContentType {
let accept = self.headers().get::<header::Accept>(); let accept = self.headers().get::<header::Accept>();
accept.map_or(ContentType::any(), |accept| { accept.map_or(ContentType::Any, |accept| {
let items = &accept.0; let items = &accept.0;
if items.len() < 1 { if items.len() < 1 {
return ContentType::any(); return ContentType::Any;
} else { } else {
return ContentType::from(items[0].item.clone()); return ContentType::from(items[0].item.clone());
} }

View File

@ -21,10 +21,8 @@
//! let response = content::HTML("<h1>Hello, world!</h1>"); //! let response = content::HTML("<h1>Hello, world!</h1>");
//! ``` //! ```
use response::{Responder, Outcome}; use response::{Response, Responder};
use http::hyper::{header, FreshHyperResponse}; use http::{Status, ContentType};
use http::mime::{Mime, TopLevel, SubLevel};
use http::ContentType;
/// Set the Content-Type to any arbitrary value. /// Set the Content-Type to any arbitrary value.
/// ///
@ -41,57 +39,50 @@ use http::ContentType;
/// let response = Content(ContentType::from_extension("pdf"), "Hi."); /// let response = Content(ContentType::from_extension("pdf"), "Hi.");
/// ``` /// ```
#[derive(Debug)] #[derive(Debug)]
pub struct Content<T: Responder>(pub ContentType, pub T); pub struct Content<R>(pub ContentType, pub R);
/// Sets the Content-Type of the response to the wrapped `ContentType` then /// Sets the Content-Type of the response to the wrapped `ContentType` then
/// delegates the remainder of the response to the wrapped responder. /// delegates the remainder of the response to the wrapped responder.
impl<T: Responder> Responder for Content<T> { impl<'r, R: Responder<'r>> Responder<'r> for Content<R> {
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> { #[inline(always)]
res.headers_mut().set(header::ContentType(self.0.clone().into())); fn respond(self) -> Result<Response<'r>, Status> {
self.1.respond(res) Response::build()
.merge(self.1.respond()?)
.header(self.0)
.ok()
} }
} }
macro_rules! ctrs { macro_rules! ctrs {
($($(#[$attr:meta])* | $name:ident: $top:ident/$sub:ident),+) => { ($($name:ident: $name_str:expr, $ct_str:expr),+) => {
$( $(
$(#[$attr])* #[doc="Set the `Content-Type` of the response to <b>"]
#[doc=$name_str]
#[doc="</b>, or <i>"]
#[doc=$ct_str]
#[doc="</i>."]
/// ///
/// Delagates the remainder of the response to the wrapped responder. /// Delagates the remainder of the response to the wrapped responder.
#[derive(Debug)] #[derive(Debug)]
pub struct $name<T: Responder>(pub T); pub struct $name<R>(pub R);
/// Sets the Content-Type of the response then delegates the /// Sets the Content-Type of the response then delegates the
/// remainder of the response to the wrapped responder. /// remainder of the response to the wrapped responder.
impl<T: Responder> Responder for $name<T> { impl<'r, R: Responder<'r>> Responder<'r> for $name<R> {
#[inline(always)] fn respond(self) -> Result<Response<'r>, Status> {
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> { Content(ContentType::$name, self.0).respond()
let mime = Mime(TopLevel::$top, SubLevel::$sub, vec![]);
res.headers_mut().set(header::ContentType(mime));
self.0.respond(res)
} }
})+ }
)+
} }
} }
ctrs! { ctrs! {
/// Sets the Content-Type of the response to JSON (`application/json`). JSON: "JSON", "application/json",
| JSON: Application/Json, XML: "XML", "text/xml",
HTML: "HTML", "text/html",
/// Sets the Content-Type of the response to XML (`text/xml`). Plain: "plain text", "text/plain",
| XML: Text/Xml, CSS: "CSS", "text/css",
JavaScript: "JavaScript", "application/javascript"
/// Sets the Content-Type of the response to HTML (`text/html`).
| HTML: Text/Html,
/// Sets the Content-Type of the response to plain text (`text/plain`).
| Plain: Text/Plain,
/// Sets the Content-Type of the response to CSS (`text/css`).
| CSS: Text/Css,
/// Sets the Content-Type of the response to JavaScript
/// (`application/javascript`).
| JavaScript: Application/Javascript
} }

View File

@ -1,14 +1,13 @@
use outcome::Outcome; use response::{Response, Responder};
use response::{self, Responder}; use http::Status;
use http::hyper::{FreshHyperResponse, StatusCode};
/// A failing response; simply forwards to the catcher for the given /// A failing response; simply forwards to the catcher for the given
/// `StatusCode`. /// `Status`.
#[derive(Debug)] #[derive(Debug)]
pub struct Failure(pub StatusCode); pub struct Failure(pub Status);
impl Responder for Failure { impl<'r> Responder<'r> for Failure {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> response::Outcome<'a> { fn respond(self) -> Result<Response<'r>, Status> {
Outcome::Forward((self.0, res)) Err(self.0)
} }
} }

View File

@ -1,9 +1,10 @@
use std::convert::AsRef; use std::convert::AsRef;
use outcome::IntoOutcome; use outcome::IntoOutcome;
use response::{self, Responder}; use response::{Response, Responder};
use request::{self, Request, FromRequest}; use request::{self, Request, FromRequest};
use http::hyper::{HyperSetCookie, HyperCookiePair, FreshHyperResponse}; use http::hyper::{HyperSetCookie, HyperCookiePair};
use http::Status;
// The name of the actual flash cookie. // The name of the actual flash cookie.
const FLASH_COOKIE_NAME: &'static str = "_flash"; const FLASH_COOKIE_NAME: &'static str = "_flash";
@ -87,7 +88,7 @@ pub struct Flash<R> {
responder: R, responder: R,
} }
impl<R: Responder> Flash<R> { impl<'r, R: Responder<'r>> Flash<R> {
/// Constructs a new `Flash` message with the given `name`, `msg`, and /// Constructs a new `Flash` message with the given `name`, `msg`, and
/// underlying `responder`. /// underlying `responder`.
/// ///
@ -173,11 +174,13 @@ impl<R: Responder> Flash<R> {
/// response. In other words, simply sets a cookie and delagates the rest of the /// response. In other words, simply sets a cookie and delagates the rest of the
/// response handling to the wrapped responder. As a result, the `Outcome` of /// response handling to the wrapped responder. As a result, the `Outcome` of
/// the response is the `Outcome` of the wrapped `Responder`. /// the response is the `Outcome` of the wrapped `Responder`.
impl<R: Responder> Responder for Flash<R> { impl<'r, R: Responder<'r>> Responder<'r> for Flash<R> {
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> response::Outcome<'b> { fn respond(self) -> Result<Response<'r>, Status> {
trace_!("Flash: setting message: {}:{}", self.name, self.message); trace_!("Flash: setting message: {}:{}", self.name, self.message);
res.headers_mut().set(HyperSetCookie(vec![self.cookie_pair()])); let cookie = vec![self.cookie_pair()];
self.responder.respond(res) Response::build_from(self.responder.respond()?)
.header_adjoin(HyperSetCookie(cookie))
.ok()
} }
} }

View File

@ -24,11 +24,13 @@ mod failure;
pub mod content; pub mod content;
pub mod status; pub mod status;
pub use self::response::Response; pub use self::response::{Response, Body, DEFAULT_CHUNK_SIZE};
pub use self::responder::{Outcome, Responder}; pub use self::responder::Responder;
pub use self::redirect::Redirect; pub use self::redirect::Redirect;
pub use self::flash::Flash; pub use self::flash::Flash;
pub use self::named_file::NamedFile; pub use self::named_file::NamedFile;
pub use self::stream::Stream; pub use self::stream::Stream;
pub use self::content::Content; pub use self::content::Content;
pub use self::failure::Failure; pub use self::failure::Failure;
pub type Result<'r> = ::std::result::Result<self::Response<'r>, ::http::Status>;

View File

@ -3,9 +3,8 @@ use std::path::{Path, PathBuf};
use std::io; use std::io;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use response::{Responder, Outcome}; use response::{Response, Responder};
use http::hyper::{header, FreshHyperResponse}; use http::{Status, ContentType};
use http::ContentType;
/// A file with an associated name; responds with the Content-Type based on the /// A file with an associated name; responds with the Content-Type based on the
/// file extension. /// file extension.
@ -76,18 +75,19 @@ impl NamedFile {
/// ///
/// If reading the file fails permanently at any point during the response, an /// If reading the file fails permanently at any point during the response, an
/// `Outcome` of `Failure` is returned, and the response is terminated abrubtly. /// `Outcome` of `Failure` is returned, and the response is terminated abrubtly.
impl Responder for NamedFile { impl<'r> Responder<'r> for NamedFile {
fn respond<'a>(&mut self, mut res: FreshHyperResponse<'a>) -> Outcome<'a> { fn respond(self) -> Result<Response<'r>, Status> {
let mut response = Response::new();
if let Some(ext) = self.path().extension() { if let Some(ext) = self.path().extension() {
// TODO: Use Cow for lowercase. // TODO: Use Cow for lowercase.
let ext_string = ext.to_string_lossy().to_lowercase(); let ext_string = ext.to_string_lossy().to_lowercase();
let content_type = ContentType::from_extension(&ext_string); let content_type = ContentType::from_extension(&ext_string);
if !content_type.is_any() { if !content_type.is_any() {
res.headers_mut().set(header::ContentType(content_type.into())); response.set_header(content_type)
} }
} }
self.file_mut().respond(res) Ok(response)
} }
} }

View File

@ -1,12 +1,12 @@
use response::{Outcome, Responder}; use response::{Response, Responder};
use http::hyper::{header, FreshHyperResponse, StatusCode}; use http::hyper::header;
use outcome::IntoOutcome; use http::Status;
/// An empty redirect response to a given URL. /// An empty redirect response to a given URL.
/// ///
/// This type simplifies returning a redirect response to the client. /// This type simplifies returning a redirect response to the client.
#[derive(Debug)] #[derive(Debug)]
pub struct Redirect(StatusCode, String); pub struct Redirect(Status, String);
impl Redirect { impl Redirect {
/// Construct a temporary "see other" (303) redirect response. This is the /// Construct a temporary "see other" (303) redirect response. This is the
@ -22,7 +22,7 @@ impl Redirect {
/// let redirect = Redirect::to("/other_url"); /// let redirect = Redirect::to("/other_url");
/// ``` /// ```
pub fn to(uri: &str) -> Redirect { pub fn to(uri: &str) -> Redirect {
Redirect(StatusCode::SeeOther, String::from(uri)) Redirect(Status::SeeOther, String::from(uri))
} }
/// Construct a "temporary" (307) redirect response. This response instructs /// Construct a "temporary" (307) redirect response. This response instructs
@ -39,7 +39,7 @@ impl Redirect {
/// let redirect = Redirect::temporary("/other_url"); /// let redirect = Redirect::temporary("/other_url");
/// ``` /// ```
pub fn temporary(uri: &str) -> Redirect { pub fn temporary(uri: &str) -> Redirect {
Redirect(StatusCode::TemporaryRedirect, String::from(uri)) Redirect(Status::TemporaryRedirect, String::from(uri))
} }
/// Construct a "permanent" (308) redirect response. This redirect must only /// Construct a "permanent" (308) redirect response. This redirect must only
@ -57,7 +57,7 @@ impl Redirect {
/// let redirect = Redirect::permanent("/other_url"); /// let redirect = Redirect::permanent("/other_url");
/// ``` /// ```
pub fn permanent(uri: &str) -> Redirect { pub fn permanent(uri: &str) -> Redirect {
Redirect(StatusCode::PermanentRedirect, String::from(uri)) Redirect(Status::PermanentRedirect, String::from(uri))
} }
/// Construct a temporary "found" (302) redirect response. This response /// Construct a temporary "found" (302) redirect response. This response
@ -75,7 +75,7 @@ impl Redirect {
/// let redirect = Redirect::found("/other_url"); /// let redirect = Redirect::found("/other_url");
/// ``` /// ```
pub fn found(uri: &str) -> Redirect { pub fn found(uri: &str) -> Redirect {
Redirect(StatusCode::Found, String::from(uri)) Redirect(Status::Found, String::from(uri))
} }
/// Construct a permanent "moved" (301) redirect response. This response /// Construct a permanent "moved" (301) redirect response. This response
@ -91,18 +91,19 @@ impl Redirect {
/// let redirect = Redirect::moved("/other_url"); /// let redirect = Redirect::moved("/other_url");
/// ``` /// ```
pub fn moved(uri: &str) -> Redirect { pub fn moved(uri: &str) -> Redirect {
Redirect(StatusCode::MovedPermanently, String::from(uri)) Redirect(Status::MovedPermanently, String::from(uri))
} }
} }
/// Constructs a response with the appropriate status code and the given URL in /// Constructs a response with the appropriate status code and the given URL in
/// the `Location` header field. The body of the response is empty. This /// the `Location` header field. The body of the response is empty. This
/// responder does not fail. /// responder does not fail.
impl<'a> Responder for Redirect { impl Responder<'static> for Redirect {
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> { fn respond(self) -> Result<Response<'static>, Status> {
res.headers_mut().set(header::ContentLength(0)); Response::build()
res.headers_mut().set(header::Location(self.1.clone())); .status(self.0)
*(res.status_mut()) = self.0; .header(header::ContentLength(0))
res.send(b"").into_outcome() .header(header::Location(self.1.clone()))
.ok()
} }
} }

View File

@ -1,24 +1,9 @@
use std::fs::File; use std::fs::File;
use std::io::Cursor;
use std::fmt; use std::fmt;
use http::mime::{Mime, TopLevel, SubLevel}; use http::{Status, ContentType};
use http::hyper::{header, FreshHyperResponse, StatusCode}; use response::{Response, Stream};
use outcome::{self, IntoOutcome};
use outcome::Outcome::*;
use response::Stream;
/// Type alias for the `Outcome` of a `Responder`.
pub type Outcome<'a> = outcome::Outcome<(), (), (StatusCode, FreshHyperResponse<'a>)>;
impl<'a, T, E> IntoOutcome<(), (), (StatusCode, FreshHyperResponse<'a>)> for Result<T, E> {
fn into_outcome(self) -> Outcome<'a> {
match self {
Ok(_) => Success(()),
Err(_) => Failure(())
}
}
}
/// Trait implemented by types that send a response to clients. /// Trait implemented by types that send a response to clients.
/// ///
@ -32,29 +17,16 @@ impl<'a, T, E> IntoOutcome<(), (), (StatusCode, FreshHyperResponse<'a>)> for Res
/// ///
/// In this example, `T` can be any type that implements `Responder`. /// In this example, `T` can be any type that implements `Responder`.
/// ///
/// # Outcomes /// # Return Value
/// ///
/// The returned [Outcome](/rocket/outcome/index.html) of a `respond` call /// A `Responder` returns an `Ok(Response)` or an `Err(Status)`.
/// determines how the response will be processed, if at all.
/// ///
/// * **Success** /// An `Ok` variant means that the `Responder` was successful in generating a
/// new `Response`. The `Response` will be written out to the client.
/// ///
/// An `Outcome` of `Success` indicates that the responder was successful in /// An `Err` variant means that the `Responder` could not or did not generate a
/// sending the response to the client. No further processing will occur as a /// `Response`. The contained `Status` will be used to find the relevant error
/// result. /// catcher to use to generate a proper response.
///
/// * **Failure**
///
/// An `Outcome` of `Failure` indicates that the responder failed after
/// beginning a response. The response is incomplete, and there is no way to
/// salvage the response. No further processing will occur.
///
/// * **Forward**(StatusCode, FreshHyperResponse<'a>)
///
/// If the `Outcome` is `Forward`, the response will be forwarded to the
/// designated error [Catcher](/rocket/struct.Catcher.html) for the given
/// `StatusCode`. This requires that a response wasn't started and thus is
/// still fresh.
/// ///
/// # Provided Implementations /// # Provided Implementations
/// ///
@ -63,42 +35,44 @@ impl<'a, T, E> IntoOutcome<(), (), (StatusCode, FreshHyperResponse<'a>)> for Res
/// overloaded, allowing for two `Responder`s to be used at once, depending on /// overloaded, allowing for two `Responder`s to be used at once, depending on
/// the variant. /// the variant.
/// ///
/// * **impl<'a> Responder for &'a str** /// * **&str**
/// ///
/// Sets the `Content-Type`t to `text/plain` if it is not already set. Sends /// Sets the `Content-Type`t to `text/plain`. The string is used as the body
/// the string as the body of the response. /// of the response, which is fixed size and not streamed. To stream a raw
/// string, use `Stream::from(Cursor::new(string))`.
/// ///
/// * **impl Responder for String** /// * **String**
/// ///
/// Sets the `Content-Type`t to `text/html` if it is not already set. Sends /// Sets the `Content-Type`t to `text/html`. The string is used as the body
/// the string as the body of the response. /// of the response, which is fixed size and not streamed. To stream a
/// string, use `Stream::from(Cursor::new(string))`.
/// ///
/// * **impl Responder for File** /// * **File**
/// ///
/// Streams the `File` to the client. This is essentially an alias to /// Streams the `File` to the client. This is essentially an alias to
/// [Stream](struct.Stream.html)&lt;File>. /// `Stream::from(file)`.
/// ///
/// * **impl Responder for ()** /// * **impl Responder for ()**
/// ///
/// Responds with an empty body. /// Responds with an empty body. No Content-Type is set.
/// ///
/// * **impl&lt;T: Responder> Responder for Option&lt;T>** /// * **Option&lt;T>**
/// ///
/// If the `Option` is `Some`, the wrapped responder is used to respond to /// If the `Option` is `Some`, the wrapped responder is used to respond to
/// respond to the client. Otherwise, the response is forwarded to the 404 /// respond to the client. Otherwise, an `Err` with status **404 Not Found**
/// error catcher and a warning is printed to the console. /// is returned and a warning is printed to the console.
/// ///
/// * **impl&lt;T: Responder, E: Debug> Responder for Result&lt;T, E>** /// * **Result&lt;T, E>** _where_ **E: Debug**
/// ///
/// If the `Result` is `Ok`, the wrapped responder is used to respond to the /// If the `Result` is `Ok`, the wrapped responder is used to respond to the
/// client. Otherwise, the response is forwarded to the 500 error catcher /// client. Otherwise, an `Err` with status **500 Internal Server Error** is
/// and the error is printed to the console using the `Debug` /// returned and the error is printed to the console using the `Debug`
/// implementation. /// implementation.
/// ///
/// * **impl&lt;T: Responder, E: Responder + Debug> Responder for Result&lt;T, E>** /// * **Result&lt;T, E>** _where_ **E: Debug + Responder**
/// ///
/// If the `Result` is `Ok`, the wrapped `Ok` responder is used to respond /// If the `Result` is `Ok`, the wrapped `Ok` responder is used to respond
/// to the client. If the `Result` is `Err`, the wrapped error responder is /// to the client. If the `Result` is `Err`, the wrapped `Err` responder is
/// used to respond to the client. /// used to respond to the client.
/// ///
/// # Implementation Tips /// # Implementation Tips
@ -113,13 +87,13 @@ impl<'a, T, E> IntoOutcome<(), (), (StatusCode, FreshHyperResponse<'a>)> for Res
/// requires its `Err` type to implement `Debug`. Therefore, a type implementing /// requires its `Err` type to implement `Debug`. Therefore, a type implementing
/// `Debug` can more easily be composed. /// `Debug` can more easily be composed.
/// ///
/// ## Check Before Changing /// ## Joining and Merging
/// ///
/// Unless a given type is explicitly designed to change some information in the /// When chaining/wrapping other `Responder`s, use the
/// response, it should first _check_ that some information hasn't been set /// [merge](/rocket/struct.Response.html#method.merge) or
/// before _changing_ that information. For example, before setting the /// [join](/rocket/struct.Response.html#method.join) methods on the `Response`
/// `Content-Type` header of a response, first check that the header hasn't been /// struct. Ensure that you document the merging or joining behavior
/// set. /// appropriately.
/// ///
/// # Example /// # Example
/// ///
@ -151,34 +125,23 @@ impl<'a, T, E> IntoOutcome<(), (), (StatusCode, FreshHyperResponse<'a>)> for Res
/// # #[derive(Debug)] /// # #[derive(Debug)]
/// # struct Person { name: String, age: u16 } /// # struct Person { name: String, age: u16 }
/// # /// #
/// use std::str::FromStr; /// use std::io::Cursor;
/// use std::fmt::Write;
/// ///
/// use rocket::response::{Responder, Outcome}; /// use rocket::response::{self, Response, Responder};
/// use rocket::outcome::IntoOutcome;
/// use rocket::http::hyper::{FreshHyperResponse, header};
/// use rocket::http::ContentType; /// use rocket::http::ContentType;
/// ///
/// impl Responder for Person { /// impl<'r> Responder<'r> for Person {
/// fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> { /// fn respond(self) -> response::Result<'r> {
/// // Set the custom headers. /// Response::build()
/// let name_bytes = self.name.clone().into_bytes(); /// .sized_body(Cursor::new(format!("{:?}", self)))
/// let age_bytes = self.age.to_string().into_bytes(); /// .raw_header("X-Person-Name", self.name)
/// res.headers_mut().set_raw("X-Person-Name", vec![name_bytes]); /// .raw_header("X-Person-Age", self.age.to_string())
/// res.headers_mut().set_raw("X-Person-Age", vec![age_bytes]); /// .header(ContentType::new("application", "x-person"))
/// /// .ok()
/// // Set the custom Content-Type header.
/// let ct = ContentType::from_str("application/x-person").unwrap();
/// res.headers_mut().set(header::ContentType(ct.into()));
///
/// // Write out the "custom" body, here just the debug representation.
/// let mut repr = String::with_capacity(50);
/// write!(&mut repr, "{:?}", *self);
/// res.send(repr.as_bytes()).into_outcome()
/// } /// }
/// } /// }
/// ``` /// ```
pub trait Responder { pub trait Responder<'r> {
/// Attempts to write a response to `res`. /// Attempts to write a response to `res`.
/// ///
/// If writing the response successfully completes, an outcome of `Success` /// If writing the response successfully completes, an outcome of `Success`
@ -186,76 +149,82 @@ pub trait Responder {
/// `Failure` is returned. If writing a response fails before writing /// `Failure` is returned. If writing a response fails before writing
/// anything out, an outcome of `Forward` can be returned, which causes the /// anything out, an outcome of `Forward` can be returned, which causes the
/// response to be written by the appropriate error catcher instead. /// response to be written by the appropriate error catcher instead.
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a>; fn respond(self) -> Result<Response<'r>, Status>;
} }
/// Sets the `Content-Type`t to `text/plain` if it is not already set. Sends the /// Sets the `Content-Type`t to `text/plain` if it is not already set. Sends the
/// string as the body of the response. /// string as the body of the response. Never fails.
impl<'a> Responder for &'a str { ///
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> { /// # Example
if res.headers().get::<header::ContentType>().is_none() { ///
let mime = Mime(TopLevel::Text, SubLevel::Plain, vec![]); /// ```rust
res.headers_mut().set(header::ContentType(mime)); /// use rocket::response::Responder;
} /// use rocket::http::ContentType;
///
res.send(self.as_bytes()).into_outcome() /// let mut response = "Hello".respond().unwrap();
///
/// let body_string = response.body().unwrap().to_string().unwrap();
/// assert_eq!(body_string, "Hello".to_string());
///
/// let content_type: Vec<_> = response.get_header_values("Content-Type").collect();
/// assert_eq!(content_type.len(), 1);
/// assert_eq!(content_type[0], ContentType::Plain.to_string());
/// ```
impl<'r> Responder<'r> for &'r str {
fn respond(self) -> Result<Response<'r>, Status> {
Response::build()
.header(ContentType::Plain)
.sized_body(Cursor::new(self))
.ok()
} }
} }
impl Responder for String { impl Responder<'static> for String {
fn respond<'a>(&mut self, mut res: FreshHyperResponse<'a>) -> Outcome<'a> { fn respond(self) -> Result<Response<'static>, Status> {
if res.headers().get::<header::ContentType>().is_none() { Response::build()
let mime = Mime(TopLevel::Text, SubLevel::Html, vec![]); .header(ContentType::HTML)
res.headers_mut().set(header::ContentType(mime)); .sized_body(Cursor::new(self))
} .ok()
res.send(self.as_bytes()).into_outcome()
} }
} }
/// Essentially aliases Stream<File>. /// Essentially aliases Stream<File>.
impl Responder for File { impl Responder<'static> for File {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a> { fn respond(self) -> Result<Response<'static>, Status> {
Stream::from(self).respond(res) Stream::from(self).respond()
} }
} }
/// Empty response. /// Empty response.
impl Responder for () { impl Responder<'static> for () {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a> { fn respond(self) -> Result<Response<'static>, Status> {
res.send(&[]).into_outcome() Ok(Response::new())
} }
} }
impl<T: Responder> Responder for Option<T> { impl<'r, R: Responder<'r>> Responder<'r> for Option<R> {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a> { fn respond(self) -> Result<Response<'r>, Status> {
if let Some(ref mut val) = *self { self.map_or_else(|| {
val.respond(res)
} else {
warn_!("Response was `None`."); warn_!("Response was `None`.");
Forward((StatusCode::NotFound, res)) Err(Status::NotFound)
} }, |r| r.respond())
} }
} }
impl<T: Responder, E: fmt::Debug> Responder for Result<T, E> { impl<'r, R: Responder<'r>, E: fmt::Debug> Responder<'r> for Result<R, E> {
// prepend with `default` when using impl specialization default fn respond(self) -> Result<Response<'r>, Status> {
default fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a> { self.map(|r| r.respond()).unwrap_or_else(|e| {
match *self { warn_!("Response was `Err`: {:?}.", e);
Ok(ref mut val) => val.respond(res), Err(Status::InternalServerError)
Err(ref e) => { })
error_!("{:?}", e);
Forward((StatusCode::InternalServerError, res))
}
}
} }
} }
impl<T: Responder, E: Responder + fmt::Debug> Responder for Result<T, E> { impl<'r, R: Responder<'r>, E: Responder<'r> + fmt::Debug> Responder<'r> for Result<R, E> {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a> { fn respond(self) -> Result<Response<'r>, Status> {
match *self { match self {
Ok(ref mut responder) => responder.respond(res), Ok(responder) => responder.respond(),
Err(ref mut responder) => responder.respond(res), Err(responder) => responder.respond(),
} }
} }
} }

View File

@ -1,36 +1,342 @@
use data::Data; use std::{io, fmt, str};
use outcome::{self, Outcome}; use std::borrow::{Borrow, Cow};
use http::hyper::StatusCode; use std::collections::HashMap;
use response::{Responder, status};
/// Type alias for the `Outcome` of a `Handler`. use http::Header;
pub type Response<'a> = outcome::Outcome<Box<Responder + 'a>, StatusCode, Data>; use http::Status;
impl<'a> Response<'a> { pub const DEFAULT_CHUNK_SIZE: u64 = 4096;
#[inline(always)]
pub fn success<T: Responder + 'a>(responder: T) -> Response<'a> { pub enum Body<T> {
Outcome::Success(Box::new(responder)) Sized(T, u64),
Chunked(T, u64)
}
impl<T> Body<T> {
pub fn as_mut(&mut self) -> Body<&mut T> {
match *self {
Body::Sized(ref mut b, n) => Body::Sized(b, n),
Body::Chunked(ref mut b, n) => Body::Chunked(b, n)
}
} }
#[inline(always)] pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Body<U> {
pub fn failure(code: StatusCode) -> Response<'static> { match self {
Outcome::Failure(code) Body::Sized(b, n) => Body::Sized(f(b), n),
} Body::Chunked(b, n) => Body::Chunked(f(b), n)
}
#[inline(always)] }
pub fn forward(data: Data) -> Response<'static> { }
Outcome::Forward(data)
} impl<T: io::Read> Body<T> {
pub fn to_string(self) -> Option<String> {
#[inline(always)] let (mut body, mut string) = match self {
pub fn with_raw_status<T: Responder + 'a>(status: u16, body: T) -> Response<'a> { Body::Sized(b, size) => (b, String::with_capacity(size as usize)),
let status_code = StatusCode::from_u16(status); Body::Chunked(b, _) => (b, String::new())
Response::success(status::Custom(status_code, body)) };
}
if let Err(e) = body.read_to_string(&mut string) {
#[doc(hidden)] error_!("Error reading body: {:?}", e);
#[inline(always)] return None;
pub fn responder(self) -> Option<Box<Responder + 'a>> { }
self.succeeded()
Some(string)
}
}
impl<T> fmt::Debug for Body<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Body::Sized(_, n) => writeln!(f, "Sized Body [{} bytes]", n),
Body::Chunked(_, n) => writeln!(f, "Chunked Body [{} bytes]", n),
}
}
}
pub struct ResponseBuilder<'r> {
response: Response<'r>
}
impl<'r> ResponseBuilder<'r> {
#[inline(always)]
pub fn new(base: Response<'r>) -> ResponseBuilder<'r> {
ResponseBuilder {
response: base
}
}
#[inline(always)]
pub fn status(&mut self, status: Status) -> &mut ResponseBuilder<'r> {
self.response.set_status(status);
self
}
#[inline(always)]
pub fn raw_status(&mut self, code: u16, reason: &'static str)
-> &mut ResponseBuilder<'r> {
self.response.set_raw_status(code, reason);
self
}
#[inline(always)]
pub fn header<'h: 'r, H>(&mut self, header: H) -> &mut ResponseBuilder<'r>
where H: Into<Header<'h>>
{
self.response.set_header(header);
self
}
#[inline(always)]
pub fn header_adjoin<'h: 'r, H>(&mut self, header: H) -> &mut ResponseBuilder<'r>
where H: Into<Header<'h>>
{
self.response.adjoin_header(header);
self
}
#[inline(always)]
pub fn raw_header<'a: 'r, 'b: 'r, N, V>(&mut self, name: N, value: V)
-> &mut ResponseBuilder<'r>
where N: Into<Cow<'a, str>>, V: Into<Cow<'b, str>>
{
self.response.set_raw_header(name, value);
self
}
#[inline(always)]
pub fn raw_header_adjoin<'a: 'r, 'b: 'r, N, V>(&mut self, name: N, value: V)
-> &mut ResponseBuilder<'r>
where N: Into<Cow<'a, str>>, V: Into<Cow<'b, str>>
{
self.response.adjoin_raw_header(name, value);
self
}
#[inline(always)]
pub fn sized_body<B>(&mut self, body: B) -> &mut ResponseBuilder<'r>
where B: io::Read + io::Seek + 'r
{
self.response.set_sized_body(body);
self
}
#[inline(always)]
pub fn streamed_body<B>(&mut self, body: B) -> &mut ResponseBuilder<'r>
where B: io::Read + 'r
{
self.response.set_streamed_body(body);
self
}
#[inline(always)]
pub fn chunked_body<B: io::Read + 'r>(&mut self, body: B, chunk_size: u64)
-> &mut ResponseBuilder<'r>
{
self.response.set_chunked_body(body, chunk_size);
self
}
#[inline(always)]
pub fn merge(&mut self, other: Response<'r>) -> &mut ResponseBuilder<'r> {
self.response.merge(other);
self
}
#[inline(always)]
pub fn join(&mut self, other: Response<'r>) -> &mut ResponseBuilder<'r> {
self.response.join(other);
self
}
#[inline(always)]
pub fn finalize(&mut self) -> Response<'r> {
::std::mem::replace(&mut self.response, Response::new())
}
#[inline(always)]
pub fn ok<T>(&mut self) -> Result<Response<'r>, T> {
Ok(self.finalize())
}
}
// `join`? Maybe one does one thing, the other does another? IE: `merge`
// replaces, `join` adds. One more thing that could be done: we could make it
// some that _some_ headers default to replacing, and other to joining.
/// Return type of a thing.
pub struct Response<'r> {
status: Option<Status>,
headers: HashMap<Cow<'r, str>, Vec<Cow<'r, str>>>,
body: Option<Body<Box<io::Read + 'r>>>,
}
impl<'r> Response<'r> {
#[inline(always)]
pub fn new() -> Response<'r> {
Response {
status: None,
headers: HashMap::new(),
body: None,
}
}
#[inline(always)]
pub fn build() -> ResponseBuilder<'r> {
Response::build_from(Response::new())
}
#[inline(always)]
pub fn build_from(other: Response<'r>) -> ResponseBuilder<'r> {
ResponseBuilder::new(other)
}
#[inline(always)]
pub fn status(&self) -> Status {
self.status.unwrap_or(Status::Ok)
}
#[inline(always)]
pub fn set_status(&mut self, status: Status) {
self.status = Some(status);
}
#[inline(always)]
pub fn set_raw_status(&mut self, code: u16, reason: &'static str) {
self.status = Some(Status::new(code, reason));
}
#[inline(always)]
pub fn headers<'a>(&'a self) -> impl Iterator<Item=Header<'a>> {
self.headers.iter().flat_map(|(key, values)| {
values.iter().map(move |val| {
Header::new(key.borrow(), val.borrow())
})
})
}
#[inline(always)]
pub fn get_header_values<'h>(&'h self, name: &str)
-> impl Iterator<Item=&'h str> {
self.headers.get(name).into_iter().flat_map(|values| {
values.iter().map(|val| val.borrow())
})
}
#[inline(always)]
pub fn set_header<'h: 'r, H: Into<Header<'h>>>(&mut self, header: H) {
let header = header.into();
self.headers.insert(header.name, vec![header.value]);
}
#[inline(always)]
pub fn set_raw_header<'a: 'r, 'b: 'r, N, V>(&mut self, name: N, value: V)
where N: Into<Cow<'a, str>>, V: Into<Cow<'b, str>>
{
self.set_header(Header::new(name, value));
}
#[inline(always)]
pub fn adjoin_header<'h: 'r, H: Into<Header<'h>>>(&mut self, header: H) {
let header = header.into();
self.headers.entry(header.name).or_insert(vec![]).push(header.value);
}
#[inline(always)]
pub fn adjoin_raw_header<'a: 'r, 'b: 'r, N, V>(&mut self, name: N, value: V)
where N: Into<Cow<'a, str>>, V: Into<Cow<'b, str>>
{
self.adjoin_header(Header::new(name, value));
}
#[inline(always)]
pub fn remove_header<'h>(&mut self, name: &'h str) {
self.headers.remove(name);
}
#[inline(always)]
pub fn body(&mut self) -> Option<Body<&mut io::Read>> {
// Looks crazy, right? Needed so Rust infers lifetime correctly. Weird.
match self.body.as_mut() {
Some(body) => Some(match body.as_mut() {
Body::Sized(b, u64) => Body::Sized(b, u64),
Body::Chunked(b, u64) => Body::Chunked(b, u64),
}),
None => None
}
}
#[inline(always)]
pub fn take_body(&mut self) -> Option<Body<Box<io::Read + 'r>>> {
self.body.take()
}
#[inline(always)]
pub fn set_sized_body<B>(&mut self, mut body: B)
where B: io::Read + io::Seek + 'r
{
let size = body.seek(io::SeekFrom::End(0))
.expect("Attempted to retrieve size by seeking, but failed.");
body.seek(io::SeekFrom::Start(0))
.expect("Attempted to reset body by seeking after getting size.");
self.body = Some(Body::Sized(Box::new(body), size));
}
#[inline(always)]
pub fn set_streamed_body<B>(&mut self, body: B) where B: io::Read + 'r {
self.set_chunked_body(body, DEFAULT_CHUNK_SIZE);
}
#[inline(always)]
pub fn set_chunked_body<B>(&mut self, body: B, chunk_size: u64)
where B: io::Read + 'r {
self.body = Some(Body::Chunked(Box::new(body), chunk_size));
}
// Replaces this response's status and body with that of `other`, if they
// exist. Any headers that exist in `other` replace the ones in `self`. Any
// in `self` that aren't in `other` remain.
pub fn merge(&mut self, other: Response<'r>) {
if let Some(status) = other.status {
self.status = Some(status);
}
if let Some(body) = other.body {
self.body = Some(body);
}
for (name, values) in other.headers.into_iter() {
self.headers.insert(name, values);
}
}
// Sets `self`'s status and body to that of `other` if they are not already
// set in `self`. Any headers present in both `other` and `self` are
// adjoined.
pub fn join(&mut self, other: Response<'r>) {
if self.status.is_none() {
self.status = other.status;
}
if self.body.is_none() {
self.body = other.body;
}
for (name, mut values) in other.headers.into_iter() {
self.headers.entry(name).or_insert(vec![]).append(&mut values)
}
}
}
impl<'r> fmt::Debug for Response<'r> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "{}", self.status())?;
for header in self.headers() {
writeln!(f, "{}", header)?;
}
match self.body {
Some(ref body) => writeln!(f, "{:?}", body),
None => writeln!(f, "Empty Body")
}
} }
} }

View File

@ -10,9 +10,9 @@
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher; use std::collections::hash_map::DefaultHasher;
use response::{Responder, Outcome}; use response::{Responder, Response};
use outcome::IntoOutcome; use http::hyper::header;
use http::hyper::{StatusCode, FreshHyperResponse, header}; use http::Status;
/// Sets the status of the response to 201 (Created). /// Sets the status of the response to 201 (Created).
/// ///
@ -28,7 +28,7 @@ use http::hyper::{StatusCode, FreshHyperResponse, header};
/// let content = "{ 'resource': 'Hello, world!' }"; /// let content = "{ 'resource': 'Hello, world!' }";
/// let response = status::Created(url, Some(content)); /// let response = status::Created(url, Some(content));
/// ``` /// ```
pub struct Created<R: Responder>(pub String, pub Option<R>); pub struct Created<R>(pub String, pub Option<R>);
/// Sets the status code of the response to 201 Created. Sets the `Location` /// Sets the status code of the response to 201 Created. Sets the `Location`
/// header to the `String` parameter in the constructor. /// header to the `String` parameter in the constructor.
@ -37,14 +37,14 @@ pub struct Created<R: Responder>(pub String, pub Option<R>);
/// responder should write the body of the response so that it contains /// responder should write the body of the response so that it contains
/// information about the created resource. If no responder is provided, the /// information about the created resource. If no responder is provided, the
/// response body will be empty. /// response body will be empty.
impl<R: Responder> Responder for Created<R> { impl<'r, R: Responder<'r>> Responder<'r> for Created<R> {
default fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> { default fn respond(self) -> Result<Response<'r>, Status> {
*res.status_mut() = StatusCode::Created; let mut build = Response::build();
res.headers_mut().set(header::Location(self.0.clone())); if let Some(responder) = self.1 {
match self.1 { build.merge(responder.respond()?);
Some(ref mut r) => r.respond(res),
None => res.send(&[]).into_outcome()
} }
build.status(Status::Created).header(header::Location(self.0)).ok()
} }
} }
@ -52,21 +52,19 @@ impl<R: Responder> Responder for Created<R> {
/// the response with the `Responder`, the `ETag` header is set conditionally if /// the response with the `Responder`, the `ETag` header is set conditionally if
/// a `Responder` is provided that implements `Hash`. The `ETag` header is set /// a `Responder` is provided that implements `Hash`. The `ETag` header is set
/// to a hash value of the responder. /// to a hash value of the responder.
impl<R: Responder + Hash> Responder for Created<R> { impl<'r, R: Responder<'r> + Hash> Responder<'r> for Created<R> {
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> { fn respond(self) -> Result<Response<'r>, Status> {
*res.status_mut() = StatusCode::Created;
res.headers_mut().set(header::Location(self.0.clone()));
let mut hasher = DefaultHasher::default(); let mut hasher = DefaultHasher::default();
match self.1 { let mut build = Response::build();
Some(ref mut responder) => { if let Some(responder) = self.1 {
responder.hash(&mut hasher); responder.hash(&mut hasher);
let tag = header::EntityTag::strong(hasher.finish().to_string()); let hash = hasher.finish().to_string();
res.headers_mut().set(header::ETag(tag));
responder.respond(res) build.merge(responder.respond()?);
} build.header(header::ETag(header::EntityTag::strong(hash)));
None => res.send(&[]).into_outcome()
} }
build.status(Status::Created).header(header::Location(self.0)).ok()
} }
} }
@ -92,17 +90,18 @@ impl<R: Responder + Hash> Responder for Created<R> {
/// ///
/// let response = status::Accepted(Some("processing")); /// let response = status::Accepted(Some("processing"));
/// ``` /// ```
pub struct Accepted<R: Responder>(pub Option<R>); pub struct Accepted<R>(pub Option<R>);
/// Sets the status code of the response to 202 Accepted. If the responder is /// Sets the status code of the response to 202 Accepted. If the responder is
/// `Some`, it is used to finalize the response. /// `Some`, it is used to finalize the response.
impl<R: Responder> Responder for Accepted<R> { impl<'r, R: Responder<'r>> Responder<'r> for Accepted<R> {
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> { fn respond(self) -> Result<Response<'r>, Status> {
*res.status_mut() = StatusCode::Accepted; let mut build = Response::build();
match self.0 { if let Some(responder) = self.0 {
Some(ref mut r) => r.respond(res), build.merge(responder.respond()?);
None => res.send(&[]).into_outcome()
} }
build.status(Status::Accepted).ok()
} }
} }
@ -120,10 +119,9 @@ pub struct NoContent;
/// Sets the status code of the response to 204 No Content. The body of the /// Sets the status code of the response to 204 No Content. The body of the
/// response will be empty. /// response will be empty.
impl Responder for NoContent { impl<'r> Responder<'r> for NoContent {
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> { fn respond(self) -> Result<Response<'r>, Status> {
*res.status_mut() = StatusCode::NoContent; Response::build().status(Status::NoContent).ok()
res.send(&[]).into_outcome()
} }
} }
@ -141,10 +139,9 @@ pub struct Reset;
/// Sets the status code of the response to 205 Reset Content. The body of the /// Sets the status code of the response to 205 Reset Content. The body of the
/// response will be empty. /// response will be empty.
impl Responder for Reset { impl<'r> Responder<'r> for Reset {
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> { fn respond(self) -> Result<Response<'r>, Status> {
*res.status_mut() = StatusCode::ResetContent; Response::build().status(Status::ResetContent).ok()
res.send(&[]).into_outcome()
} }
} }
@ -154,18 +151,19 @@ impl Responder for Reset {
/// ///
/// ```rust /// ```rust
/// use rocket::response::status; /// use rocket::response::status;
/// use rocket::http::StatusCode; /// use rocket::http::Status;
/// ///
/// let response = status::Custom(StatusCode::ImATeapot, "Hi!"); /// let response = status::Custom(Status::ImATeapot, "Hi!");
/// ``` /// ```
pub struct Custom<R: Responder>(pub StatusCode, pub R); pub struct Custom<R>(pub Status, pub R);
/// Sets the status code of the response and then delegates the remainder of the /// Sets the status code of the response and then delegates the remainder of the
/// response to the wrapped responder. /// response to the wrapped responder.
impl<R: Responder> Responder for Custom<R> { impl<'r, R: Responder<'r>> Responder<'r> for Custom<R> {
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> { fn respond(self) -> Result<Response<'r>, Status> {
*(res.status_mut()) = self.0; Response::build_from(self.1.respond()?)
self.1.respond(res) .status(self.0)
.ok()
} }
} }

View File

@ -1,13 +1,8 @@
use std::io::{Read, Write, ErrorKind}; use std::io::Read;
use std::fmt::{self, Debug}; use std::fmt::{self, Debug};
use response::{Responder, Outcome}; use response::{Response, Responder, DEFAULT_CHUNK_SIZE};
use http::hyper::FreshHyperResponse; use http::Status;
use outcome::Outcome::*;
// TODO: Support custom chunk sizes.
/// The default size of each chunk in the streamed response.
pub const CHUNK_SIZE: usize = 4096;
/// Streams a response to a client from an arbitrary `Read`er type. /// Streams a response to a client from an arbitrary `Read`er type.
/// ///
@ -15,7 +10,7 @@ pub const CHUNK_SIZE: usize = 4096;
/// 4KiB. This means that at most 4KiB are stored in memory while the response /// 4KiB. This means that at most 4KiB are stored in memory while the response
/// is being sent. This type should be used when sending responses that are /// is being sent. This type should be used when sending responses that are
/// arbitrarily large in size, such as when streaming from a local socket. /// arbitrarily large in size, such as when streaming from a local socket.
pub struct Stream<T: Read>(T); pub struct Stream<T: Read>(T, u64);
impl<T: Read> Stream<T> { impl<T: Read> Stream<T> {
/// Create a new stream from the given `reader`. /// Create a new stream from the given `reader`.
@ -32,18 +27,26 @@ impl<T: Read> Stream<T> {
/// let response = Stream::from(io::stdin()); /// let response = Stream::from(io::stdin());
/// ``` /// ```
pub fn from(reader: T) -> Stream<T> { pub fn from(reader: T) -> Stream<T> {
Stream(reader) Stream(reader, DEFAULT_CHUNK_SIZE)
} }
// pub fn chunked(mut self, size: usize) -> Self { /// Create a new stream from the given `reader` and sets the chunk size for
// self.1 = size; /// each streamed chunk to `chunk_size` bytes.
// self ///
// } /// # Example
///
// #[inline(always)] /// Stream a response from whatever is in `stdin` with a chunk size of 10
// pub fn chunk_size(&self) -> usize { /// bytes. Note: you probably shouldn't do this.
// self.1 ///
// } /// ```rust
/// use std::io;
/// use rocket::response::Stream;
///
/// let response = Stream::chunked(io::stdin(), 10);
/// ```
pub fn chunked(reader: T, chunk_size: u64) -> Stream<T> {
Stream(reader, chunk_size)
}
} }
impl<T: Read + Debug> Debug for Stream<T> { impl<T: Read + Debug> Debug for Stream<T> {
@ -60,43 +63,8 @@ impl<T: Read + Debug> Debug for Stream<T> {
/// If reading from the input stream fails at any point during the response, the /// If reading from the input stream fails at any point during the response, the
/// response is abandoned, and the response ends abruptly. An error is printed /// response is abandoned, and the response ends abruptly. An error is printed
/// to the console with an indication of what went wrong. /// to the console with an indication of what went wrong.
impl<T: Read> Responder for Stream<T> { impl<'r, T: Read + 'r> Responder<'r> for Stream<T> {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a> { fn respond(self) -> Result<Response<'r>, Status> {
let mut stream = match res.start() { Response::build().chunked_body(self.0, self.1).ok()
Ok(s) => s,
Err(ref err) => {
error_!("Failed opening response stream: {:?}", err);
return Failure(());
}
};
let mut buffer = [0; CHUNK_SIZE];
let mut complete = false;
while !complete {
let mut read = 0;
while read < buffer.len() && !complete {
match self.0.read(&mut buffer[read..]) {
Ok(n) if n == 0 => complete = true,
Ok(n) => read += n,
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
Err(ref e) => {
error_!("Error streaming response: {:?}", e);
return Failure(());
}
}
}
if let Err(e) = stream.write_all(&buffer[..read]) {
error_!("Stream write_all() failed: {:?}", e);
return Failure(());
}
}
if let Err(e) = stream.end() {
error_!("Stream end() failed: {:?}", e);
return Failure(());
}
Success(())
} }
} }

View File

@ -2,22 +2,24 @@ use std::collections::HashMap;
use std::str::from_utf8_unchecked; use std::str::from_utf8_unchecked;
use std::cmp::min; use std::cmp::min;
use std::process; use std::process;
use std::io::{self, Write};
use term_painter::Color::*; use term_painter::Color::*;
use term_painter::ToStyle; use term_painter::ToStyle;
use logger; use logger;
use ext::ReadExt;
use config::{self, Config}; use config::{self, Config};
use request::{Request, FormItems}; use request::{Request, FormItems};
use data::Data; use data::Data;
use response::Responder; use response::{Body, Response};
use router::{Router, Route}; use router::{Router, Route};
use catcher::{self, Catcher}; use catcher::{self, Catcher};
use outcome::Outcome; use outcome::Outcome;
use error::Error; use error::Error;
use http::{Method, StatusCode}; use http::{Method, Status};
use http::hyper::{HyperRequest, FreshHyperResponse}; use http::hyper::{self, HyperRequest, FreshHyperResponse};
use http::hyper::{HyperServer, HyperHandler, HyperSetCookie, header}; use http::hyper::{HyperServer, HyperHandler, HyperSetCookie, header};
/// The main `Rocket` type: used to mount routes and catchers and launch the /// The main `Rocket` type: used to mount routes and catchers and launch the
@ -39,7 +41,7 @@ impl HyperHandler for Rocket {
// response processing. // response processing.
fn handle<'h, 'k>(&self, fn handle<'h, 'k>(&self,
hyp_req: HyperRequest<'h, 'k>, hyp_req: HyperRequest<'h, 'k>,
mut res: FreshHyperResponse<'h>) { res: FreshHyperResponse<'h>) {
// Get all of the information from Hyper. // Get all of the information from Hyper.
let (_, h_method, h_headers, h_uri, _, h_body) = hyp_req.deconstruct(); let (_, h_method, h_headers, h_uri, _, h_body) = hyp_req.deconstruct();
@ -52,8 +54,8 @@ impl HyperHandler for Rocket {
Err(ref reason) => { Err(ref reason) => {
let mock = Request::mock(Method::Get, uri.as_str()); let mock = Request::mock(Method::Get, uri.as_str());
error!("{}: bad request ({}).", mock, reason); error!("{}: bad request ({}).", mock, reason);
self.handle_error(StatusCode::InternalServerError, &mock, res); let r = self.handle_error(Status::InternalServerError, &mock);
return; return self.issue_response(r, res);
} }
}; };
@ -62,49 +64,99 @@ impl HyperHandler for Rocket {
Ok(data) => data, Ok(data) => data,
Err(reason) => { Err(reason) => {
error_!("Bad data in request: {}", reason); error_!("Bad data in request: {}", reason);
self.handle_error(StatusCode::InternalServerError, &request, res); let r = self.handle_error(Status::InternalServerError, &request);
return; return self.issue_response(r, res);
} }
}; };
// Set the common response headers and preprocess the request.
res.headers_mut().set(header::Server("rocket".to_string()));
self.preprocess_request(&mut request, &data);
// Now that we've Rocket-ized everything, actually dispatch the request. // Now that we've Rocket-ized everything, actually dispatch the request.
let mut responder = match self.dispatch(&request, data) { self.preprocess_request(&mut request, &data);
Ok(responder) => responder, let mut response = match self.dispatch(&request, data) {
Err(StatusCode::NotFound) if request.method == Method::Head => { Ok(response) => response,
// TODO: Handle unimplemented HEAD requests automatically. Err(status) => {
info_!("Redirecting to {}.", Green.paint(Method::Get)); if status == Status::NotFound && request.method == Method::Head {
self.handle_error(StatusCode::NotFound, &request, res); // FIXME: Handle unimplemented HEAD requests automatically.
return; info_!("Redirecting to {}.", Green.paint(Method::Get));
} }
Err(code) => {
self.handle_error(code, &request, res); let response = self.handle_error(status, &request);
return; return self.issue_response(response, res);
} }
}; };
// We have a responder. Update the cookies in the header. // We have a response from the user. Update the cookies in the header.
let cookie_delta = request.cookies().delta(); let cookie_delta = request.cookies().delta();
if cookie_delta.len() > 0 { if cookie_delta.len() > 0 {
res.headers_mut().set(HyperSetCookie(cookie_delta)); response.adjoin_header(HyperSetCookie(cookie_delta));
} }
// Actually call the responder. // Actually write out the response.
let outcome = responder.respond(res); return self.issue_response(response, res);
info_!("{} {}", White.paint("Outcome:"), outcome);
// Check if the responder wants to forward to a catcher. If it doesn't,
// it's a success or failure, so we can't do any more processing.
if let Some((code, f_res)) = outcome.forwarded() {
self.handle_error(code, &request, f_res);
}
} }
} }
impl Rocket { impl Rocket {
#[inline]
fn issue_response(&self, mut response: Response, hyp_res: FreshHyperResponse) {
// Add the 'rocket' server header, and write out the response.
// TODO: If removing Hyper, write out `Data` header too.
response.set_header(header::Server("rocket".to_string()));
match self.write_response(response, hyp_res) {
Ok(_) => info_!("{}", Green.paint("Response succeeded.")),
Err(e) => error_!("Failed to write response: {:?}.", e)
}
}
fn write_response(&self, mut response: Response,
mut hyp_res: FreshHyperResponse) -> io::Result<()>
{
*hyp_res.status_mut() = hyper::StatusCode::from_u16(response.status().code);
for header in response.headers() {
let name = header.name.into_owned();
let value = vec![header.value.into_owned().into()];
hyp_res.headers_mut().set_raw(name, value);
}
if response.body().is_none() {
hyp_res.headers_mut().set(header::ContentLength(0));
return hyp_res.start()?.end();
}
match response.body() {
None => {
hyp_res.headers_mut().set(header::ContentLength(0));
hyp_res.start()?.end()
}
Some(Body::Sized(mut body, size)) => {
hyp_res.headers_mut().set(header::ContentLength(size));
let mut stream = hyp_res.start()?;
io::copy(body, &mut stream)?;
stream.end()
}
Some(Body::Chunked(mut body, chunk_size)) => {
// This _might_ happen on a 32-bit machine!
if chunk_size > (usize::max_value() as u64) {
let msg = "chunk size exceeds limits of usize type";
return Err(io::Error::new(io::ErrorKind::Other, msg));
}
// The buffer stores the current chunk being written out.
let mut buffer = vec![0; chunk_size as usize];
let mut stream = hyp_res.start()?;
loop {
match body.read_max(&mut buffer)? {
0 => break,
n => stream.write_all(&buffer[..n])?,
}
}
stream.end()
}
}
}
/// Preprocess the request for Rocket-specific things. At this time, we're /// Preprocess the request for Rocket-specific things. At this time, we're
/// only checking for _method in forms. /// only checking for _method in forms.
fn preprocess_request(&self, req: &mut Request, data: &Data) { fn preprocess_request(&self, req: &mut Request, data: &Data) {
@ -133,7 +185,7 @@ impl Rocket {
/// the request, this function returns an `Err` with the status code. /// the request, this function returns an `Err` with the status code.
#[doc(hidden)] #[doc(hidden)]
pub fn dispatch<'r>(&self, request: &'r Request, mut data: Data) pub fn dispatch<'r>(&self, request: &'r Request, mut data: Data)
-> Result<Box<Responder + 'r>, StatusCode> { -> Result<Response<'r>, Status> {
// Go through the list of matching routes until we fail or succeed. // Go through the list of matching routes until we fail or succeed.
info!("{}:", request); info!("{}:", request);
let matches = self.router.route(&request); let matches = self.router.route(&request);
@ -143,56 +195,41 @@ impl Rocket {
request.set_params(route); request.set_params(route);
// Dispatch the request to the handler. // Dispatch the request to the handler.
let response = (route.handler)(&request, data); let outcome = (route.handler)(&request, data);
// Check if the request processing completed or if the request needs // Check if the request processing completed or if the request needs
// to be forwarded. If it does, continue the loop to try again. // to be forwarded. If it does, continue the loop to try again.
info_!("{} {}", White.paint("Response:"), response); info_!("{} {}", White.paint("Outcome:"), outcome);
match response { match outcome {
Outcome::Success(responder) => return Ok(responder), Outcome::Success(response) => return Ok(response),
Outcome::Failure(status_code) => return Err(status_code), Outcome::Failure(status) => return Err(status),
Outcome::Forward(unused_data) => data = unused_data, Outcome::Forward(unused_data) => data = unused_data,
}; };
} }
error_!("No matching routes."); error_!("No matching routes for {}.", request);
Err(StatusCode::NotFound) Err(Status::NotFound)
} }
// Attempts to send a response to the client by using the catcher for the // TODO: DOC.
// given status code. If no catcher is found (including the defaults), the
// 500 internal server error catcher is used. If the catcher fails to
// respond, this function returns `false`. It returns `true` if a response
// was sucessfully sent to the client.
#[doc(hidden)] #[doc(hidden)]
pub fn handle_error<'r>(&self, pub fn handle_error<'r>(&self, status: Status, req: &'r Request) -> Response<'r> {
code: StatusCode, warn_!("Responding with {} catcher.", Red.paint(&status));
req: &'r Request,
response: FreshHyperResponse) -> bool { // Try to get the active catcher but fallback to user's 500 catcher.
// Find the catcher or use the one for internal server errors. let catcher = self.catchers.get(&status.code).unwrap_or_else(|| {
let catcher = self.catchers.get(&code.to_u16()).unwrap_or_else(|| { error_!("No catcher found for {}. Using 500 catcher.", status);
error_!("No catcher found for {}.", code); self.catchers.get(&500).expect("500 catcher.")
warn_!("Using internal server error catcher.");
self.catchers.get(&500).expect("500 catcher should exist!")
}); });
if let Some(mut responder) = catcher.handle(Error::NoRoute, req).responder() { // Dispatch to the user's catcher. If it fails, use the default 500.
if !responder.respond(response).is_success() { let error = Error::NoRoute;
error_!("Catcher outcome was unsuccessul; aborting response."); catcher.handle(error, req).unwrap_or_else(|err_status| {
return false; error_!("Catcher failed with status: {}!", err_status);
} else { warn_!("Using default 500 error catcher.");
info_!("Responded with {} catcher.", White.paint(code)); let default = self.default_catchers.get(&500).expect("Default 500");
} default.handle(error, req).expect("Default 500 response.")
} else { })
error_!("Catcher returned an incomplete response.");
warn_!("Using default error response.");
let catcher = self.default_catchers.get(&code.to_u16())
.unwrap_or(self.default_catchers.get(&500).expect("500 default"));
let responder = catcher.handle(Error::Internal, req).responder();
responder.unwrap().respond(response).expect("default catcher failed")
}
true
} }
/// Create a new `Rocket` application using the configuration information in /// Create a new `Rocket` application using the configuration information in
@ -300,11 +337,12 @@ impl Rocket {
/// `hi` route. /// `hi` route.
/// ///
/// ```rust /// ```rust
/// use rocket::{Request, Response, Route, Data}; /// use rocket::{Request, Route, Data};
/// use rocket::handler::Outcome;
/// use rocket::http::Method::*; /// use rocket::http::Method::*;
/// ///
/// fn hi(_: &Request, _: Data) -> Response<'static> { /// fn hi(_: &Request, _: Data) -> Outcome {
/// Response::success("Hello!") /// Outcome::of("Hello!")
/// } /// }
/// ///
/// # if false { // We don't actually want to launch the server in an example. /// # if false { // We don't actually want to launch the server in an example.

View File

@ -51,7 +51,7 @@ mod tests {
use router::Collider; use router::Collider;
use request::Request; use request::Request;
use data::Data; use data::Data;
use response::Response; use handler::Outcome;
use router::route::Route; use router::route::Route;
use http::{Method, ContentType}; use http::{Method, ContentType};
use http::uri::URI; use http::uri::URI;
@ -60,8 +60,8 @@ mod tests {
type SimpleRoute = (Method, &'static str); type SimpleRoute = (Method, &'static str);
fn dummy_handler(_req: &Request, _: Data) -> Response<'static> { fn dummy_handler(_req: &Request, _: Data) -> Outcome<'static> {
Response::success("hi") Outcome::of("hi")
} }
fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool { fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool {

View File

@ -72,10 +72,12 @@ mod test {
use http::Method; use http::Method;
use http::Method::*; use http::Method::*;
use http::uri::URI; use http::uri::URI;
use {Response, Request, Data}; use request::Request;
use data::Data;
use handler::Outcome;
fn dummy_handler(_req: &Request, _: Data) -> Response<'static> { fn dummy_handler(_req: &Request, _: Data) -> Outcome<'static> {
Response::success("hi") Outcome::of("hi")
} }
fn router_with_routes(routes: &[&'static str]) -> Router { fn router_with_routes(routes: &[&'static str]) -> Router {

View File

@ -45,7 +45,7 @@ impl Route {
handler: handler, handler: handler,
rank: default_rank(path.as_ref()), rank: default_rank(path.as_ref()),
path: URIBuf::from(path.as_ref()), path: URIBuf::from(path.as_ref()),
content_type: ContentType::any(), content_type: ContentType::Any,
} }
} }
@ -58,7 +58,7 @@ impl Route {
path: URIBuf::from(path.as_ref()), path: URIBuf::from(path.as_ref()),
handler: handler, handler: handler,
rank: rank, rank: rank,
content_type: ContentType::any(), content_type: ContentType::Any,
} }
} }
@ -133,7 +133,7 @@ impl fmt::Debug for Route {
impl<'a> From<&'a StaticRouteInfo> for Route { impl<'a> From<&'a StaticRouteInfo> for Route {
fn from(info: &'a StaticRouteInfo) -> Route { fn from(info: &'a StaticRouteInfo) -> Route {
let mut route = Route::new(info.method, info.path, info.handler); let mut route = Route::new(info.method, info.path, info.handler);
route.content_type = info.format.clone().unwrap_or(ContentType::any()); route.content_type = info.format.clone().unwrap_or(ContentType::Any);
if let Some(rank) = info.rank { if let Some(rank) = info.rank {
route.rank = rank; route.rank = rank;
} }

View File

@ -99,8 +99,6 @@
//! } //! }
//! ``` //! ```
use std::io::Cursor;
use outcome::Outcome::*;
use http::{hyper, Method}; use http::{hyper, Method};
use {Rocket, Request, Data}; use {Rocket, Request, Data};
@ -201,46 +199,20 @@ impl MockRequest {
/// ///
/// # fn main() { /// # fn main() {
/// let rocket = rocket::ignite().mount("/", routes![hello]); /// let rocket = rocket::ignite().mount("/", routes![hello]);
/// let req = MockRequest::new(Get, "/"); /// let result = MockRequest::new(Get, "/").dispatch_with(&rocket);
/// let result = req.dispatch_with(&rocket); /// assert_eq!(&result.unwrap(), "Hello, world!");
/// assert_eq!(result.unwrap().as_str(), "Hello, world!");
/// # } /// # }
/// ``` /// ```
pub fn dispatch_with(mut self, rocket: &Rocket) -> Option<String> { /// FIXME: Can now return Response to get all info!
let request = self.request; pub fn dispatch_with(&mut self, rocket: &Rocket) -> Option<String> {
let data = ::std::mem::replace(&mut self.data, Data::new(vec![])); let data = ::std::mem::replace(&mut self.data, Data::new(vec![]));
let mut response = Cursor::new(vec![]); let mut response = match rocket.dispatch(&self.request, data) {
Ok(response) => response,
// Create a new scope so we can get the inner from response later. // FIXME: Send to catcher? Not sure what user would want.
let ok = { Err(_status) => return None
let mut h_h = hyper::HyperHeaders::new();
let res = hyper::FreshHyperResponse::new(&mut response, &mut h_h);
match rocket.dispatch(&request, data) {
Ok(mut responder) => {
match responder.respond(res) {
Success(_) => true,
Failure(_) => false,
Forward((code, r)) => rocket.handle_error(code, &request, r)
}
}
Err(code) => rocket.handle_error(code, &request, res)
}
}; };
if !ok { response.body().and_then(|body| body.to_string())
return None;
}
match String::from_utf8(response.into_inner()) {
Ok(string) => {
// TODO: Expose the full response (with headers) somewhow.
string.find("\r\n\r\n").map(|i| string[(i + 4)..].to_string())
}
Err(e) => {
error_!("Could not create string from response: {:?}", e);
None
}
}
} }
} }