mirror of https://github.com/rwf2/Rocket.git
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:
parent
5fca86c84f
commit
44f5f1998d
|
@ -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"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()));
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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!");
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
[global]
|
||||||
|
port = 8000
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 { }
|
||||||
|
|
|
@ -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>;
|
|
@ -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!("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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};
|
||||||
|
|
|
@ -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)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)<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<T: Responder> Responder for Option<T>**
|
/// * **Option<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<T: Responder, E: Debug> Responder for Result<T, E>**
|
/// * **Result<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<T: Responder, E: Responder + Debug> Responder for Result<T, E>**
|
/// * **Result<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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue