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

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

Summary of changes:

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

View File

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

View File

@ -15,7 +15,6 @@ use syntax::parse::token;
use syntax::ptr::P;
use rocket::http::{Method, ContentType};
use rocket::http::mime::{TopLevel, SubLevel};
fn method_to_path(ecx: &ExtCtxt, method: Method) -> Path {
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>> {
ct.map(|ct| {
let top_level = top_level_to_expr(ecx, &ct.0);
let sub_level = sub_level_to_expr(ecx, &ct.1);
quote_expr!(ecx, ::rocket::http::ContentType($top_level, $sub_level, None))
let (ttype, subtype) = (ct.ttype, ct.subtype);
quote_expr!(ecx, ::rocket::http::ContentType {
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 =
match ::rocket::request::FromForm::from_form_string($form_string) {
Ok(v) => v,
Err(_) => return ::rocket::Response::forward(_data)
Err(_) => return ::rocket::Outcome::Forward(_data)
};
).expect("form statement"))
}
@ -105,11 +90,11 @@ impl RouteGenerateExt for RouteParams {
Some(quote_stmt!(ecx,
let $name: $ty =
match ::rocket::data::FromData::from_data(&_req, _data) {
::rocket::outcome::Outcome::Success(d) => d,
::rocket::outcome::Outcome::Forward(d) =>
return ::rocket::Response::forward(d),
::rocket::outcome::Outcome::Failure((code, _)) => {
return ::rocket::Response::failure(code);
::rocket::Outcome::Success(d) => d,
::rocket::Outcome::Forward(d) =>
return ::rocket::Outcome::Forward(d),
::rocket::Outcome::Failure((code, _)) => {
return ::rocket::Outcome::Failure(code);
}
};
).expect("data statement"))
@ -120,7 +105,7 @@ impl RouteGenerateExt for RouteParams {
let expr = quote_expr!(ecx,
match _req.uri().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 {
Param::Single(_) => quote_expr!(ecx, match _req.get_param_str($i) {
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) {
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) => {
println!(" => Failed to parse '{}': {:?}",
stringify!($original_ident), e);
return ::rocket::Response::forward(_data)
return ::rocket::Outcome::Forward(_data)
}
};
).expect("declared param parsing statement"));
@ -195,9 +180,9 @@ impl RouteGenerateExt for RouteParams {
::rocket::request::FromRequest::from_request(&_req) {
::rocket::outcome::Outcome::Success(v) => v,
::rocket::outcome::Outcome::Forward(_) =>
return ::rocket::Response::forward(_data),
return ::rocket::Outcome::forward(_data),
::rocket::outcome::Outcome::Failure((code, _)) => {
return ::rocket::Response::failure(code)
return ::rocket::Outcome::Failure(code)
},
};
).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);
emit_item(push, quote_item!(ecx,
fn $route_fn_name<'_b>(_req: &'_b ::rocket::Request, _data: ::rocket::Data)
-> ::rocket::Response<'_b> {
-> ::rocket::handler::Outcome<'_b> {
$param_statements
$query_statement
$data_statement
let result = $user_fn_name($fn_arguments);
::rocket::Response::success(result)
let responder = $user_fn_name($fn_arguments);
::rocket::handler::Outcome::of(responder)
}
).unwrap());

View File

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

View File

@ -269,7 +269,7 @@ fn parse_rank(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> isize {
fn parse_format(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> ContentType {
if let LitKind::Str(ref s, _) = *kv.value() {
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);
ecx.span_warn(kv.value.span, &msg);
} else {
@ -286,5 +286,5 @@ fn parse_format(ecx: &ExtCtxt, kv: &KVSpanned<LitKind>) -> ContentType {
content-type accepted. e.g: format = "application/json""#)
.emit();
ContentType::any()
ContentType::Any
}

View File

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

View File

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

View File

@ -4,11 +4,11 @@ use rocket::http::Method::*;
fn test_login<F: Fn(String) -> bool>(username: &str, password: &str, age: isize, test: F) {
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")])
.body(&format!("username={}&password={}&age={}", username, password, age));
let result = req.dispatch_with(&rocket);
let result = result.unwrap();
.body(&format!("username={}&password={}&age={}", username, password, age))
.dispatch_with(&rocket)
.unwrap_or("".to_string());
assert!(test(result));
}
@ -29,5 +29,7 @@ fn test_bad_login() {
#[test]
fn test_bad_form() {
test_login("Sergio&other=blah&", "password", 30, |s| s.contains("400 Bad Request"));
// FIXME: Need to be able to examine the status.
// test_login("Sergio&other=blah&", "password", 30, |s| s.contains("400 Bad Request"));
test_login("Sergio&other=blah&", "password", 30, |s| s.is_empty());
}

View File

@ -39,8 +39,9 @@ mod test {
use rocket::http::Method::*;
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 req = MockRequest::new(Get, "/").headers(headers);
let mut req = MockRequest::new(Get, "/").headers(headers);
let result = req.dispatch_with(&rocket);
assert_eq!(result.unwrap(),
format!("Your request contained {} headers!", headers.len()));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,17 +1,17 @@
use outcome::{self, IntoOutcome};
use outcome::Outcome::*;
use http::StatusCode;
use http::Status;
use request::Request;
use data::Data;
/// 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> {
match self {
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
/// 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
/// code and error. The designated error

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

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

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

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

View File

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

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

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

View File

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

View File

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

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

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

View File

@ -1,6 +1,8 @@
#![feature(specialization)]
#![feature(conservative_impl_trait)]
#![feature(drop_types_in_const)]
#![feature(associated_consts)]
#![feature(const_fn)]
//! # Rocket - Core API Documentation
//!
@ -103,27 +105,14 @@ pub mod response;
pub mod outcome;
pub mod config;
pub mod data;
pub mod handler;
mod error;
mod router;
mod rocket;
mod codegen;
mod catcher;
/// 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>;
}
mod ext;
#[doc(inline)] pub use response::Response;
#[doc(inline)] pub use handler::{Handler, ErrorHandler};

View File

@ -27,7 +27,7 @@ use std::marker::PhantomData;
use std::fmt::{self, Debug};
use std::io::Read;
use http::StatusCode;
use http::Status;
use request::Request;
use data::{self, Data, FromData};
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);
if let Err(e) = stream.read_to_string(&mut form_string) {
error_!("IO Error: {:?}", e);
Failure((StatusCode::InternalServerError, None))
Failure((Status::InternalServerError, None))
} else {
match Form::new(form_string) {
Ok(form) => Success(form),
Err((form_string, e)) => {
error_!("Failed to parse value from form: {:?}", e);
Failure((StatusCode::BadRequest, Some(form_string)))
Failure((Status::BadRequest, Some(form_string)))
}
}
}

View File

@ -3,16 +3,16 @@ use std::fmt::Debug;
use outcome::{self, IntoOutcome};
use request::Request;
use outcome::Outcome::*;
use http::{StatusCode, ContentType, Method, Cookies};
use http::{Status, ContentType, Method, Cookies};
/// 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> {
match self {
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
/// 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
/// code and error. The designated error
@ -78,7 +78,7 @@ impl<S, E> IntoOutcome<S, (StatusCode, E), ()> for Result<S, E> {
/// # extern crate rocket;
/// #
/// use rocket::Outcome;
/// use rocket::http::StatusCode;
/// use rocket::http::Status;
/// use rocket::request::{self, Request, FromRequest};
///
/// 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, ()> {
/// if let Some(keys) = request.headers().get_raw("x-api-key") {
/// if keys.len() != 1 {
/// return Outcome::Failure((StatusCode::BadRequest, ()));
/// return Outcome::Failure((Status::BadRequest, ()));
/// }
///
/// if let Ok(key) = String::from_utf8(keys[0].clone()) {

View File

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

View File

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

View File

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

View File

@ -1,9 +1,10 @@
use std::convert::AsRef;
use outcome::IntoOutcome;
use response::{self, Responder};
use response::{Response, Responder};
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.
const FLASH_COOKIE_NAME: &'static str = "_flash";
@ -87,7 +88,7 @@ pub struct Flash<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
/// 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 handling to the wrapped responder. As a result, the `Outcome` of
/// the response is the `Outcome` of the wrapped `Responder`.
impl<R: Responder> Responder for Flash<R> {
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> response::Outcome<'b> {
impl<'r, R: Responder<'r>> Responder<'r> for Flash<R> {
fn respond(self) -> Result<Response<'r>, Status> {
trace_!("Flash: setting message: {}:{}", self.name, self.message);
res.headers_mut().set(HyperSetCookie(vec![self.cookie_pair()]));
self.responder.respond(res)
let cookie = vec![self.cookie_pair()];
Response::build_from(self.responder.respond()?)
.header_adjoin(HyperSetCookie(cookie))
.ok()
}
}

View File

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

View File

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

View File

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

View File

@ -1,24 +1,9 @@
use std::fs::File;
use std::io::Cursor;
use std::fmt;
use http::mime::{Mime, TopLevel, SubLevel};
use http::hyper::{header, FreshHyperResponse, StatusCode};
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(())
}
}
}
use http::{Status, ContentType};
use response::{Response, Stream};
/// 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`.
///
/// # Outcomes
/// # Return Value
///
/// The returned [Outcome](/rocket/outcome/index.html) of a `respond` call
/// determines how the response will be processed, if at all.
/// A `Responder` returns an `Ok(Response)` or an `Err(Status)`.
///
/// * **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
/// sending the response to the client. No further processing will occur as a
/// result.
///
/// * **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.
/// An `Err` variant means that the `Responder` could not or did not generate a
/// `Response`. The contained `Status` will be used to find the relevant error
/// catcher to use to generate a proper response.
///
/// # 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
/// the variant.
///
/// * **impl<'a> Responder for &'a str**
/// * **&str**
///
/// Sets the `Content-Type`t to `text/plain` if it is not already set. Sends
/// the string as the body of the response.
/// Sets the `Content-Type`t to `text/plain`. The string is used as the body
/// 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
/// the string as the body of the response.
/// Sets the `Content-Type`t to `text/html`. The string is used as the body
/// 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
/// [Stream](struct.Stream.html)&lt;File>.
/// `Stream::from(file)`.
///
/// * **impl Responder for ()**
///
/// Responds with an empty body.
/// Responds with an empty body. No Content-Type is set.
///
/// * **impl&lt;T: Responder> Responder for Option&lt;T>**
/// * **Option&lt;T>**
///
/// If the `Option` is `Some`, the wrapped responder is used to respond to
/// respond to the client. Otherwise, the response is forwarded to the 404
/// error catcher and a warning is printed to the console.
/// respond to the client. Otherwise, an `Err` with status **404 Not Found**
/// is returned and a warning is printed to the console.
///
/// * **impl&lt;T: Responder, E: Debug> Responder for Result&lt;T, E>**
/// * **Result&lt;T, E>** _where_ **E: Debug**
///
/// If the `Result` is `Ok`, the wrapped responder is used to respond to the
/// client. Otherwise, the response is forwarded to the 500 error catcher
/// and the error is printed to the console using the `Debug`
/// client. Otherwise, an `Err` with status **500 Internal Server Error** is
/// returned and the error is printed to the console using the `Debug`
/// implementation.
///
/// * **impl&lt;T: Responder, E: Responder + Debug> Responder for Result&lt;T, E>**
/// * **Result&lt;T, E>** _where_ **E: Debug + Responder**
///
/// If the `Result` is `Ok`, the wrapped `Ok` responder is used to respond
/// 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.
///
/// # 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
/// `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
/// response, it should first _check_ that some information hasn't been set
/// before _changing_ that information. For example, before setting the
/// `Content-Type` header of a response, first check that the header hasn't been
/// set.
/// When chaining/wrapping other `Responder`s, use the
/// [merge](/rocket/struct.Response.html#method.merge) or
/// [join](/rocket/struct.Response.html#method.join) methods on the `Response`
/// struct. Ensure that you document the merging or joining behavior
/// appropriately.
///
/// # Example
///
@ -151,34 +125,23 @@ impl<'a, T, E> IntoOutcome<(), (), (StatusCode, FreshHyperResponse<'a>)> for Res
/// # #[derive(Debug)]
/// # struct Person { name: String, age: u16 }
/// #
/// use std::str::FromStr;
/// use std::fmt::Write;
/// use std::io::Cursor;
///
/// use rocket::response::{Responder, Outcome};
/// use rocket::outcome::IntoOutcome;
/// use rocket::http::hyper::{FreshHyperResponse, header};
/// use rocket::response::{self, Response, Responder};
/// use rocket::http::ContentType;
///
/// impl Responder for Person {
/// fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> {
/// // Set the custom headers.
/// let name_bytes = self.name.clone().into_bytes();
/// let age_bytes = self.age.to_string().into_bytes();
/// res.headers_mut().set_raw("X-Person-Name", vec![name_bytes]);
/// res.headers_mut().set_raw("X-Person-Age", vec![age_bytes]);
///
/// // 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()
/// impl<'r> Responder<'r> for Person {
/// fn respond(self) -> response::Result<'r> {
/// Response::build()
/// .sized_body(Cursor::new(format!("{:?}", self)))
/// .raw_header("X-Person-Name", self.name)
/// .raw_header("X-Person-Age", self.age.to_string())
/// .header(ContentType::new("application", "x-person"))
/// .ok()
/// }
/// }
/// ```
pub trait Responder {
pub trait Responder<'r> {
/// Attempts to write a response to `res`.
///
/// 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
/// anything out, an outcome of `Forward` can be returned, which causes the
/// 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
/// string as the body of the response.
impl<'a> Responder for &'a str {
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> {
if res.headers().get::<header::ContentType>().is_none() {
let mime = Mime(TopLevel::Text, SubLevel::Plain, vec![]);
res.headers_mut().set(header::ContentType(mime));
}
res.send(self.as_bytes()).into_outcome()
/// string as the body of the response. Never fails.
///
/// # Example
///
/// ```rust
/// use rocket::response::Responder;
/// use rocket::http::ContentType;
///
/// 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 {
fn respond<'a>(&mut self, mut res: FreshHyperResponse<'a>) -> Outcome<'a> {
if res.headers().get::<header::ContentType>().is_none() {
let mime = Mime(TopLevel::Text, SubLevel::Html, vec![]);
res.headers_mut().set(header::ContentType(mime));
}
res.send(self.as_bytes()).into_outcome()
impl Responder<'static> for String {
fn respond(self) -> Result<Response<'static>, Status> {
Response::build()
.header(ContentType::HTML)
.sized_body(Cursor::new(self))
.ok()
}
}
/// Essentially aliases Stream<File>.
impl Responder for File {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a> {
Stream::from(self).respond(res)
impl Responder<'static> for File {
fn respond(self) -> Result<Response<'static>, Status> {
Stream::from(self).respond()
}
}
/// Empty response.
impl Responder for () {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a> {
res.send(&[]).into_outcome()
impl Responder<'static> for () {
fn respond(self) -> Result<Response<'static>, Status> {
Ok(Response::new())
}
}
impl<T: Responder> Responder for Option<T> {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a> {
if let Some(ref mut val) = *self {
val.respond(res)
} else {
impl<'r, R: Responder<'r>> Responder<'r> for Option<R> {
fn respond(self) -> Result<Response<'r>, Status> {
self.map_or_else(|| {
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> {
// prepend with `default` when using impl specialization
default fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a> {
match *self {
Ok(ref mut val) => val.respond(res),
Err(ref e) => {
error_!("{:?}", e);
Forward((StatusCode::InternalServerError, res))
}
}
impl<'r, R: Responder<'r>, E: fmt::Debug> Responder<'r> for Result<R, E> {
default fn respond(self) -> Result<Response<'r>, Status> {
self.map(|r| r.respond()).unwrap_or_else(|e| {
warn_!("Response was `Err`: {:?}.", e);
Err(Status::InternalServerError)
})
}
}
impl<T: Responder, E: Responder + fmt::Debug> Responder for Result<T, E> {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a> {
match *self {
Ok(ref mut responder) => responder.respond(res),
Err(ref mut responder) => responder.respond(res),
impl<'r, R: Responder<'r>, E: Responder<'r> + fmt::Debug> Responder<'r> for Result<R, E> {
fn respond(self) -> Result<Response<'r>, Status> {
match self {
Ok(responder) => responder.respond(),
Err(responder) => responder.respond(),
}
}
}

View File

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

View File

@ -10,9 +10,9 @@
use std::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher;
use response::{Responder, Outcome};
use outcome::IntoOutcome;
use http::hyper::{StatusCode, FreshHyperResponse, header};
use response::{Responder, Response};
use http::hyper::header;
use http::Status;
/// 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 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`
/// 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
/// information about the created resource. If no responder is provided, the
/// response body will be empty.
impl<R: Responder> Responder for Created<R> {
default fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> {
*res.status_mut() = StatusCode::Created;
res.headers_mut().set(header::Location(self.0.clone()));
match self.1 {
Some(ref mut r) => r.respond(res),
None => res.send(&[]).into_outcome()
impl<'r, R: Responder<'r>> Responder<'r> for Created<R> {
default fn respond(self) -> Result<Response<'r>, Status> {
let mut build = Response::build();
if let Some(responder) = self.1 {
build.merge(responder.respond()?);
}
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
/// a `Responder` is provided that implements `Hash`. The `ETag` header is set
/// to a hash value of the responder.
impl<R: Responder + Hash> Responder for Created<R> {
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> {
*res.status_mut() = StatusCode::Created;
res.headers_mut().set(header::Location(self.0.clone()));
impl<'r, R: Responder<'r> + Hash> Responder<'r> for Created<R> {
fn respond(self) -> Result<Response<'r>, Status> {
let mut hasher = DefaultHasher::default();
match self.1 {
Some(ref mut responder) => {
responder.hash(&mut hasher);
let tag = header::EntityTag::strong(hasher.finish().to_string());
res.headers_mut().set(header::ETag(tag));
responder.respond(res)
}
None => res.send(&[]).into_outcome()
let mut build = Response::build();
if let Some(responder) = self.1 {
responder.hash(&mut hasher);
let hash = hasher.finish().to_string();
build.merge(responder.respond()?);
build.header(header::ETag(header::EntityTag::strong(hash)));
}
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"));
/// ```
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
/// `Some`, it is used to finalize the response.
impl<R: Responder> Responder for Accepted<R> {
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> {
*res.status_mut() = StatusCode::Accepted;
match self.0 {
Some(ref mut r) => r.respond(res),
None => res.send(&[]).into_outcome()
impl<'r, R: Responder<'r>> Responder<'r> for Accepted<R> {
fn respond(self) -> Result<Response<'r>, Status> {
let mut build = Response::build();
if let Some(responder) = self.0 {
build.merge(responder.respond()?);
}
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
/// response will be empty.
impl Responder for NoContent {
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> {
*res.status_mut() = StatusCode::NoContent;
res.send(&[]).into_outcome()
impl<'r> Responder<'r> for NoContent {
fn respond(self) -> Result<Response<'r>, Status> {
Response::build().status(Status::NoContent).ok()
}
}
@ -141,10 +139,9 @@ pub struct Reset;
/// Sets the status code of the response to 205 Reset Content. The body of the
/// response will be empty.
impl Responder for Reset {
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> {
*res.status_mut() = StatusCode::ResetContent;
res.send(&[]).into_outcome()
impl<'r> Responder<'r> for Reset {
fn respond(self) -> Result<Response<'r>, Status> {
Response::build().status(Status::ResetContent).ok()
}
}
@ -154,18 +151,19 @@ impl Responder for Reset {
///
/// ```rust
/// 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
/// response to the wrapped responder.
impl<R: Responder> Responder for Custom<R> {
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> {
*(res.status_mut()) = self.0;
self.1.respond(res)
impl<'r, R: Responder<'r>> Responder<'r> for Custom<R> {
fn respond(self) -> Result<Response<'r>, Status> {
Response::build_from(self.1.respond()?)
.status(self.0)
.ok()
}
}

View File

@ -1,13 +1,8 @@
use std::io::{Read, Write, ErrorKind};
use std::io::Read;
use std::fmt::{self, Debug};
use response::{Responder, Outcome};
use http::hyper::FreshHyperResponse;
use outcome::Outcome::*;
// TODO: Support custom chunk sizes.
/// The default size of each chunk in the streamed response.
pub const CHUNK_SIZE: usize = 4096;
use response::{Response, Responder, DEFAULT_CHUNK_SIZE};
use http::Status;
/// 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
/// 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.
pub struct Stream<T: Read>(T);
pub struct Stream<T: Read>(T, u64);
impl<T: Read> Stream<T> {
/// Create a new stream from the given `reader`.
@ -32,18 +27,26 @@ impl<T: Read> Stream<T> {
/// let response = Stream::from(io::stdin());
/// ```
pub fn from(reader: T) -> Stream<T> {
Stream(reader)
Stream(reader, DEFAULT_CHUNK_SIZE)
}
// pub fn chunked(mut self, size: usize) -> Self {
// self.1 = size;
// self
// }
// #[inline(always)]
// pub fn chunk_size(&self) -> usize {
// self.1
// }
/// Create a new stream from the given `reader` and sets the chunk size for
/// each streamed chunk to `chunk_size` bytes.
///
/// # Example
///
/// Stream a response from whatever is in `stdin` with a chunk size of 10
/// bytes. Note: you probably shouldn't do this.
///
/// ```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> {
@ -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
/// response is abandoned, and the response ends abruptly. An error is printed
/// to the console with an indication of what went wrong.
impl<T: Read> Responder for Stream<T> {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a> {
let mut stream = match res.start() {
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(())
impl<'r, T: Read + 'r> Responder<'r> for Stream<T> {
fn respond(self) -> Result<Response<'r>, Status> {
Response::build().chunked_body(self.0, self.1).ok()
}
}

View File

@ -2,22 +2,24 @@ use std::collections::HashMap;
use std::str::from_utf8_unchecked;
use std::cmp::min;
use std::process;
use std::io::{self, Write};
use term_painter::Color::*;
use term_painter::ToStyle;
use logger;
use ext::ReadExt;
use config::{self, Config};
use request::{Request, FormItems};
use data::Data;
use response::Responder;
use response::{Body, Response};
use router::{Router, Route};
use catcher::{self, Catcher};
use outcome::Outcome;
use error::Error;
use http::{Method, StatusCode};
use http::hyper::{HyperRequest, FreshHyperResponse};
use http::{Method, Status};
use http::hyper::{self, HyperRequest, FreshHyperResponse};
use http::hyper::{HyperServer, HyperHandler, HyperSetCookie, header};
/// The main `Rocket` type: used to mount routes and catchers and launch the
@ -39,7 +41,7 @@ impl HyperHandler for Rocket {
// response processing.
fn handle<'h, 'k>(&self,
hyp_req: HyperRequest<'h, 'k>,
mut res: FreshHyperResponse<'h>) {
res: FreshHyperResponse<'h>) {
// Get all of the information from Hyper.
let (_, h_method, h_headers, h_uri, _, h_body) = hyp_req.deconstruct();
@ -52,8 +54,8 @@ impl HyperHandler for Rocket {
Err(ref reason) => {
let mock = Request::mock(Method::Get, uri.as_str());
error!("{}: bad request ({}).", mock, reason);
self.handle_error(StatusCode::InternalServerError, &mock, res);
return;
let r = self.handle_error(Status::InternalServerError, &mock);
return self.issue_response(r, res);
}
};
@ -62,49 +64,99 @@ impl HyperHandler for Rocket {
Ok(data) => data,
Err(reason) => {
error_!("Bad data in request: {}", reason);
self.handle_error(StatusCode::InternalServerError, &request, res);
return;
let r = self.handle_error(Status::InternalServerError, &request);
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.
let mut responder = match self.dispatch(&request, data) {
Ok(responder) => responder,
Err(StatusCode::NotFound) if request.method == Method::Head => {
// TODO: Handle unimplemented HEAD requests automatically.
info_!("Redirecting to {}.", Green.paint(Method::Get));
self.handle_error(StatusCode::NotFound, &request, res);
return;
}
Err(code) => {
self.handle_error(code, &request, res);
return;
self.preprocess_request(&mut request, &data);
let mut response = match self.dispatch(&request, data) {
Ok(response) => response,
Err(status) => {
if status == Status::NotFound && request.method == Method::Head {
// FIXME: Handle unimplemented HEAD requests automatically.
info_!("Redirecting to {}.", Green.paint(Method::Get));
}
let response = self.handle_error(status, &request);
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();
if cookie_delta.len() > 0 {
res.headers_mut().set(HyperSetCookie(cookie_delta));
response.adjoin_header(HyperSetCookie(cookie_delta));
}
// Actually call the responder.
let outcome = responder.respond(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);
}
// Actually write out the response.
return self.issue_response(response, res);
}
}
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
/// only checking for _method in forms.
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.
#[doc(hidden)]
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.
info!("{}:", request);
let matches = self.router.route(&request);
@ -143,56 +195,41 @@ impl Rocket {
request.set_params(route);
// 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
// to be forwarded. If it does, continue the loop to try again.
info_!("{} {}", White.paint("Response:"), response);
match response {
Outcome::Success(responder) => return Ok(responder),
Outcome::Failure(status_code) => return Err(status_code),
info_!("{} {}", White.paint("Outcome:"), outcome);
match outcome {
Outcome::Success(response) => return Ok(response),
Outcome::Failure(status) => return Err(status),
Outcome::Forward(unused_data) => data = unused_data,
};
}
error_!("No matching routes.");
Err(StatusCode::NotFound)
error_!("No matching routes for {}.", request);
Err(Status::NotFound)
}
// Attempts to send a response to the client by using the catcher for the
// 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.
// TODO: DOC.
#[doc(hidden)]
pub fn handle_error<'r>(&self,
code: StatusCode,
req: &'r Request,
response: FreshHyperResponse) -> bool {
// Find the catcher or use the one for internal server errors.
let catcher = self.catchers.get(&code.to_u16()).unwrap_or_else(|| {
error_!("No catcher found for {}.", code);
warn_!("Using internal server error catcher.");
self.catchers.get(&500).expect("500 catcher should exist!")
pub fn handle_error<'r>(&self, status: Status, req: &'r Request) -> Response<'r> {
warn_!("Responding with {} catcher.", Red.paint(&status));
// Try to get the active catcher but fallback to user's 500 catcher.
let catcher = self.catchers.get(&status.code).unwrap_or_else(|| {
error_!("No catcher found for {}. Using 500 catcher.", status);
self.catchers.get(&500).expect("500 catcher.")
});
if let Some(mut responder) = catcher.handle(Error::NoRoute, req).responder() {
if !responder.respond(response).is_success() {
error_!("Catcher outcome was unsuccessul; aborting response.");
return false;
} else {
info_!("Responded with {} catcher.", White.paint(code));
}
} 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
// Dispatch to the user's catcher. If it fails, use the default 500.
let error = Error::NoRoute;
catcher.handle(error, req).unwrap_or_else(|err_status| {
error_!("Catcher failed with status: {}!", err_status);
warn_!("Using default 500 error catcher.");
let default = self.default_catchers.get(&500).expect("Default 500");
default.handle(error, req).expect("Default 500 response.")
})
}
/// Create a new `Rocket` application using the configuration information in
@ -300,11 +337,12 @@ impl Rocket {
/// `hi` route.
///
/// ```rust
/// use rocket::{Request, Response, Route, Data};
/// use rocket::{Request, Route, Data};
/// use rocket::handler::Outcome;
/// use rocket::http::Method::*;
///
/// fn hi(_: &Request, _: Data) -> Response<'static> {
/// Response::success("Hello!")
/// fn hi(_: &Request, _: Data) -> Outcome {
/// Outcome::of("Hello!")
/// }
///
/// # if false { // We don't actually want to launch the server in an example.

View File

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

View File

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

View File

@ -45,7 +45,7 @@ impl Route {
handler: handler,
rank: default_rank(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()),
handler: handler,
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 {
fn from(info: &'a StaticRouteInfo) -> Route {
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 {
route.rank = rank;
}

View File

@ -99,8 +99,6 @@
//! }
//! ```
use std::io::Cursor;
use outcome::Outcome::*;
use http::{hyper, Method};
use {Rocket, Request, Data};
@ -201,46 +199,20 @@ impl MockRequest {
///
/// # fn main() {
/// let rocket = rocket::ignite().mount("/", routes![hello]);
/// let req = MockRequest::new(Get, "/");
/// let result = req.dispatch_with(&rocket);
/// assert_eq!(result.unwrap().as_str(), "Hello, world!");
/// let result = MockRequest::new(Get, "/").dispatch_with(&rocket);
/// assert_eq!(&result.unwrap(), "Hello, world!");
/// # }
/// ```
pub fn dispatch_with(mut self, rocket: &Rocket) -> Option<String> {
let request = self.request;
/// FIXME: Can now return Response to get all info!
pub fn dispatch_with(&mut self, rocket: &Rocket) -> Option<String> {
let data = ::std::mem::replace(&mut self.data, Data::new(vec![]));
let mut response = Cursor::new(vec![]);
// Create a new scope so we can get the inner from response later.
let ok = {
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)
}
let mut response = match rocket.dispatch(&self.request, data) {
Ok(response) => response,
// FIXME: Send to catcher? Not sure what user would want.
Err(_status) => return None
};
if !ok {
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
}
}
response.body().and_then(|body| body.to_string())
}
}