diff --git a/codegen/src/decorators/error.rs b/codegen/src/decorators/error.rs index ed7b8793..b4b6a2a2 100644 --- a/codegen/src/decorators/error.rs +++ b/codegen/src/decorators/error.rs @@ -57,9 +57,9 @@ pub fn error_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem, let fn_arguments = error.generate_fn_arguments(ecx, err_ident, req_ident); emit_item(push, quote_item!(ecx, - fn $catch_fn_name<'_b, '_r>($err_ident: ::rocket::Error, - $req_ident: &'_b ::rocket::Request<'_r>) - -> ::rocket::Response<'_b> { + 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) } diff --git a/codegen/src/decorators/route.rs b/codegen/src/decorators/route.rs index 2c3f956c..98492b54 100644 --- a/codegen/src/decorators/route.rs +++ b/codegen/src/decorators/route.rs @@ -84,7 +84,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() + Err(_) => return ::rocket::Response::forward(_data) }; ).expect("form statement")) } @@ -94,7 +94,8 @@ impl RouteGenerateExt for RouteParams { let expr = quote_expr!(ecx, match ::std::str::from_utf8(_req.data.as_slice()) { Ok(s) => s, - Err(_) => return ::rocket::Response::bad_request("form isn't utf8") + Err(_) => return ::rocket::Response::new( + ::rocket::response::Empty::bad_request("form isn't utf8")) } ); @@ -106,7 +107,7 @@ impl RouteGenerateExt for RouteParams { let expr = quote_expr!(ecx, match _req.uri().query() { Some(query) => query, - None => return ::rocket::Response::forward() + None => return ::rocket::Response::forward(_data) } ); @@ -139,7 +140,7 @@ impl RouteGenerateExt for RouteParams { fn_param_statements.push(quote_stmt!(ecx, let $ident: $ty = match $expr { Ok(v) => v, - Err(_) => return ::rocket::Response::forward() + Err(_) => return ::rocket::Response::forward(_data) }; ).expect("declared param parsing statement")); } @@ -168,7 +169,7 @@ impl RouteGenerateExt for RouteParams { let $ident: $ty = match <$ty as ::rocket::request::FromRequest>::from_request(&_req) { Ok(v) => v, - Err(_e) => return ::rocket::Response::forward() + Err(_e) => return ::rocket::Response::forward(_data) }; ).expect("undeclared param parsing statement")); } @@ -219,7 +220,7 @@ fn generic_route_decorator(known_method: Option>, let user_fn_name = route.annotated_fn.ident(); let route_fn_name = user_fn_name.prepend(ROUTE_FN_PREFIX); emit_item(push, quote_item!(ecx, - fn $route_fn_name<'_b, '_r>(_req: &'_b ::rocket::Request<'_r>) + fn $route_fn_name<'_b>(_req: &'_b ::rocket::Request, _data: ::rocket::Data) -> ::rocket::Response<'_b> { $form_statement $query_statement diff --git a/codegen/tests/run-pass/error-handler.rs b/codegen/tests/run-pass/error-handler.rs index 651c3eb3..e268a462 100644 --- a/codegen/tests/run-pass/error-handler.rs +++ b/codegen/tests/run-pass/error-handler.rs @@ -18,10 +18,7 @@ fn err1b(_req: &Request) -> &'static str { "hi" } fn err2a(_err: Error, _req: &Request) -> &'static str { "hi" } #[error(404)] -fn err2b<'r>(_err: Error, _req: &Request<'r>) -> &'r str { "hi" } - -#[error(404)] -fn err2c<'a>(_err: Error, _req: &'a Request) -> &'a str { "hi" } +fn err2b<'a>(_err: Error, _req: &'a Request) -> &'a str { "hi" } fn main() { } diff --git a/contrib/src/json/mod.rs b/contrib/src/json/mod.rs index 2b23c06c..d9670285 100644 --- a/contrib/src/json/mod.rs +++ b/contrib/src/json/mod.rs @@ -5,6 +5,7 @@ use std::ops::{Deref, DerefMut}; use rocket::request::{Request, FromRequest}; use rocket::response::{Responder, Outcome, ResponseOutcome, data}; +use rocket::http::StatusCode; use rocket::http::hyper::FreshHyperResponse; use self::serde::{Serialize, Deserialize}; @@ -62,9 +63,9 @@ impl JSON { } } -impl<'r, 'c, T: Deserialize> FromRequest<'r, 'c> for JSON { +impl<'r, T: Deserialize> FromRequest<'r> for JSON { type Error = JSONError; - fn from_request(request: &'r Request<'c>) -> Result { + fn from_request(request: &'r Request) -> Result { Ok(JSON(serde_json::from_slice(request.data.as_slice())?)) } } @@ -75,7 +76,7 @@ impl Responder for JSON { Ok(json_string) => data::JSON(json_string).respond(res), Err(e) => { error_!("JSON failed to serialize: {:?}", e); - Outcome::FailStop + Outcome::Forward((StatusCode::BadRequest, res)) } } } diff --git a/contrib/src/templates/mod.rs b/contrib/src/templates/mod.rs index cc1d72dc..81f080a3 100644 --- a/contrib/src/templates/mod.rs +++ b/contrib/src/templates/mod.rs @@ -19,7 +19,7 @@ use std::collections::HashMap; use rocket::Rocket; use rocket::response::{Content, Outcome, ResponseOutcome, Responder}; use rocket::http::hyper::FreshHyperResponse; -use rocket::http::ContentType; +use rocket::http::{ContentType, StatusCode}; /// The Template type implements generic support for template rendering in /// Rocket. @@ -155,7 +155,7 @@ impl Responder for Template { match self.0 { Some(ref render) => Content(content_type, render.as_str()).respond(res), - None => Outcome::Bad(res), + None => Outcome::Forward((StatusCode::InternalServerError, res)), } } } diff --git a/examples/from_request/src/main.rs b/examples/from_request/src/main.rs index 5881b863..fa80cbe2 100644 --- a/examples/from_request/src/main.rs +++ b/examples/from_request/src/main.rs @@ -15,9 +15,9 @@ impl fmt::Display for HeaderCount { } } -impl<'r, 'c> FromRequest<'r, 'c> for HeaderCount { +impl<'r> FromRequest<'r> for HeaderCount { type Error = (); - fn from_request(request: &'r Request<'c>) -> Result { + fn from_request(request: &'r Request) -> Result { Ok(HeaderCount(request.headers().len())) } } diff --git a/examples/manual_routes/src/main.rs b/examples/manual_routes/src/main.rs index adf7801a..08f94225 100644 --- a/examples/manual_routes/src/main.rs +++ b/examples/manual_routes/src/main.rs @@ -1,30 +1,38 @@ extern crate rocket; -use rocket::{Request, Response, Route}; +use rocket::{Request, Response, Route, Data}; +use rocket::request::FromParam; use rocket::http::Method::*; -fn root(req: &Request) -> Response<'static> { - let name = req.get_param(0).unwrap_or("unnamed"); - Response::new(format!("Hello, {}!", name)) +fn forward(_req: &Request, data: Data) -> Response<'static> { + Response::forward(data) } -fn name<'a>(req: &'a Request) -> Response<'a> { +fn hi(_req: &Request, _: Data) -> Response<'static> { + Response::new("Hello!") +} + +fn name<'a>(req: &'a Request, _: Data) -> Response<'a> { Response::new(req.get_param(0).unwrap_or("unnamed")) } #[allow(dead_code)] -fn echo_url<'a>(req: &'a Request) -> Response<'a> { - Response::new(req.uri().as_str().split_at(6).1) +fn echo_url<'a>(req: &'a Request, _: Data) -> Response<'a> { + let param = req.uri().as_str().split_at(6).1; + Response::new(String::from_param(param)) } fn main() { - let first = Route::new(Get, "/hello", root); - let second = Route::new(Get, "/hello/", root); - let name = Route::new(Get, "/", name); + let always_forward = Route::ranked(1, Get, "/", forward); + let hello = Route::ranked(2, Get, "/", hi); + let echo = Route::new(Get, "/", echo_url); + let name = Route::new(Get, "/", name); rocket::ignite() - .mount("/", vec![first, second, name]) + .mount("/", vec![always_forward, hello]) + .mount("/hello", vec![name.clone()]) + .mount("/hi", vec![name]) .mount("/echo:", vec![echo]) .launch(); } diff --git a/lib/src/catcher.rs b/lib/src/catcher.rs index b772e1fd..eb3bf3a1 100644 --- a/lib/src/catcher.rs +++ b/lib/src/catcher.rs @@ -14,16 +14,12 @@ pub struct Catcher { is_default: bool, } -// TODO: Should `Catcher` be an interface? Should there be an `ErrorHandler` -// that takes in a `RoutingError` and returns a `Response`? What's the right -// interface here? - impl Catcher { pub fn new(code: u16, handler: ErrorHandler) -> Catcher { Catcher { code: code, handler: handler, is_default: false } } - pub fn handle<'b, 'r>(&self, err: Error, request: &'b Request<'r>) -> Response<'b> { + pub fn handle<'r>(&self, err: Error, request: &'r Request) -> Response<'r> { (self.handler)(err, request) } @@ -59,7 +55,7 @@ macro_rules! error_page_template {
-

"#, $code, " ", $name, r#" +

"#, $code, ": ", $name, r#"

"#, $description, r#"


Rocket @@ -77,8 +73,7 @@ macro_rules! default_errors { $( fn $fn_name<'r>(_: Error, _r: &'r Request) -> Response<'r> { - Response::with_status( - StatusCode::Unregistered($code), + Response::with_raw_status($code, data::HTML(error_page_template!($code, $name, $description)) ) } @@ -96,10 +91,8 @@ pub mod defaults { use std::collections::HashMap; use request::Request; - use response::Response; - use response::data; + use response::{Response, data}; use error::Error; - use http::hyper::StatusCode; pub fn get() -> HashMap { default_errors! { diff --git a/lib/src/lib.rs b/lib/src/lib.rs index ac07f0b9..dd937345 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,5 +1,6 @@ #![feature(question_mark)] #![feature(specialization)] +#![feature(conservative_impl_trait)] //! # Rocket - Core API Documentation //! @@ -83,15 +84,15 @@ mod config; /// Defines the types for request and error handlers. pub mod handler { - use request::Request; + use request::{Request, Data}; use response::Response; use error::Error; /// The type of a request handler. - pub type Handler = for<'b, 'r: 'b> fn(&'b Request<'r>) -> Response<'b>; + pub type Handler = for<'r> fn(&'r Request, Data) -> Response<'r>; /// The type of an error handler. - pub type ErrorHandler = for<'b, 'r: 'b> fn(error: Error, &'b Request<'r>) -> Response<'b>; + pub type ErrorHandler = for<'r> fn(Error, &'r Request) -> Response<'r>; } #[doc(inline)] pub use response::{Response, Responder}; @@ -99,7 +100,7 @@ pub mod handler { #[doc(inline)] pub use logger::LoggingLevel; pub use codegen::{StaticRouteInfo, StaticCatchInfo}; pub use router::Route; -pub use request::Request; +pub use request::{Request, Data}; pub use error::Error; pub use catcher::Catcher; pub use rocket::Rocket; diff --git a/lib/src/outcome.rs b/lib/src/outcome.rs index 6e92c2ea..f21067a1 100644 --- a/lib/src/outcome.rs +++ b/lib/src/outcome.rs @@ -3,40 +3,38 @@ use std::fmt; use term_painter::Color::*; use term_painter::ToStyle; -use http::hyper::FreshHyperResponse; - +#[must_use] pub enum Outcome { - /// Signifies a response that completed sucessfully. + /// Signifies that all processing completed successfully. Success, - /// Signifies a failing response that started responding but fail, so no - /// further processing can occur. - FailStop, - /// Signifies a response that failed internally without beginning to - /// respond but no further processing should occur. - Bad(T), - /// Signifies a failing response that failed internally without beginning to - /// respond. Further processing should be attempted. - FailForward(T), + /// Signifies that some processing occurred that ultimately resulted in + /// failure. As a result, no further processing can occur. + Failure, + /// Signifies that no processing occured and as such, processing can be + /// forwarded to the next available target. + Forward(T), } -pub type ResponseOutcome<'a> = Outcome>; - impl Outcome { pub fn as_str(&self) -> &'static str { match *self { Outcome::Success => "Success", - Outcome::FailStop => "FailStop", - Outcome::Bad(..) => "Bad", - Outcome::FailForward(..) => "FailForward", + Outcome::Failure => "FailStop", + Outcome::Forward(..) => "Forward", } } fn as_int(&self) -> isize { match *self { Outcome::Success => 0, - Outcome::Bad(..) => 1, - Outcome::FailStop => 2, - Outcome::FailForward(..) => 3, + Outcome::Failure => 1, + Outcome::Forward(..) => 2, + } + } + + pub fn expect_success(&self) { + if *self != Outcome::Success { + panic!("expected a successful outcome"); } } } @@ -57,9 +55,8 @@ impl fmt::Display for Outcome { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Outcome::Success => write!(f, "{}", Green.paint("Success")), - Outcome::Bad(..) => write!(f, "{}", Yellow.paint("Bad Completion")), - Outcome::FailStop => write!(f, "{}", Red.paint("Failed")), - Outcome::FailForward(..) => write!(f, "{}", Cyan.paint("Forwarding")), + Outcome::Failure => write!(f, "{}", Red.paint("Failure")), + Outcome::Forward(..) => write!(f, "{}", Cyan.paint("Forwarding")), } } } diff --git a/lib/src/request/data.rs b/lib/src/request/data.rs new file mode 100644 index 00000000..392757ec --- /dev/null +++ b/lib/src/request/data.rs @@ -0,0 +1,26 @@ +use std::io::{BufRead, Read, Cursor, BufReader}; +use std::net::TcpStream; + +use request::Request; + +pub struct Data { + stream: Cursor>, + buffer: Vec +} + +impl Data { + fn open(self) -> impl BufRead { + Cursor::new(self.buffer).chain(BufReader::new(self.stream)) + } + + fn peek(&self) -> &[u8] { + &self.buffer + } + + pub fn new() -> Data { + Data { + stream: Cursor::new(vec![]), + buffer: vec![] + } + } +} diff --git a/lib/src/request/from_request.rs b/lib/src/request/from_request.rs index 2f823958..4c94a891 100644 --- a/lib/src/request/from_request.rs +++ b/lib/src/request/from_request.rs @@ -3,47 +3,47 @@ use std::fmt::Debug; use request::Request; use http::{ContentType, Method, Cookies}; -pub trait FromRequest<'r, 'c>: Sized { +pub trait FromRequest<'r>: Sized { type Error: Debug; - fn from_request(request: &'r Request<'c>) -> Result; + fn from_request(request: &'r Request) -> Result; } -impl<'r, 'c> FromRequest<'r, 'c> for &'r Request<'c> { +impl<'r> FromRequest<'r> for &'r Request { type Error = (); - fn from_request(request: &'r Request<'c>) -> Result { + fn from_request(request: &'r Request) -> Result { Ok(request) } } -impl<'r, 'c> FromRequest<'r, 'c> for Method { +impl<'r> FromRequest<'r> for Method { type Error = (); - fn from_request(request: &'r Request<'c>) -> Result { + fn from_request(request: &'r Request) -> Result { Ok(request.method) } } -impl<'r, 'c> FromRequest<'r, 'c> for &'r Cookies { +impl<'r> FromRequest<'r> for &'r Cookies { type Error = (); - fn from_request(request: &'r Request<'c>) -> Result { + fn from_request(request: &'r Request) -> Result { Ok(request.cookies()) } } -impl<'r, 'c> FromRequest<'r, 'c> for ContentType { +impl<'r> FromRequest<'r> for ContentType { type Error = (); - fn from_request(request: &'r Request<'c>) -> Result { + fn from_request(request: &'r Request) -> Result { Ok(request.content_type()) } } -impl<'r, 'c, T: FromRequest<'r, 'c>> FromRequest<'r, 'c> for Option { +impl<'r, T: FromRequest<'r>> FromRequest<'r> for Option { type Error = (); - fn from_request(request: &'r Request<'c>) -> Result { + fn from_request(request: &'r Request) -> Result { let opt = match T::from_request(request) { Ok(v) => Some(v), Err(_) => None, @@ -53,10 +53,10 @@ impl<'r, 'c, T: FromRequest<'r, 'c>> FromRequest<'r, 'c> for Option { } } -impl<'r, 'c, T: FromRequest<'r, 'c>> FromRequest<'r, 'c> for Result { +impl<'r, T: FromRequest<'r>> FromRequest<'r> for Result { type Error = (); - fn from_request(request: &'r Request<'c>) -> Result { + fn from_request(request: &'r Request) -> Result { Ok(T::from_request(request)) } } diff --git a/lib/src/request/mod.rs b/lib/src/request/mod.rs index 9a6f49eb..b8154993 100644 --- a/lib/src/request/mod.rs +++ b/lib/src/request/mod.rs @@ -3,9 +3,11 @@ mod request; mod param; mod form; +mod data; mod from_request; pub use self::request::Request; pub use self::from_request::FromRequest; pub use self::param::{FromParam, FromSegments}; pub use self::form::{FromForm, FromFormValue, FormItems}; +pub use self::data::{Data}; diff --git a/lib/src/request/request.rs b/lib/src/request/request.rs index 7a6b5daf..d07fab40 100644 --- a/lib/src/request/request.rs +++ b/lib/src/request/request.rs @@ -20,7 +20,7 @@ use http::{Method, ContentType, Cookies}; /// [FromRequest](trait.FromRequest.html) implementations. It contains all of /// the information for a given web request. This includes the HTTP method, URI, /// cookies, headers, and more. -pub struct Request<'a> { +pub struct Request { /// The HTTP method associated with the request. pub method: Method, ///
@@ -37,10 +37,9 @@ pub struct Request<'a> { params: RefCell>, cookies: Cookies, headers: HyperHeaders, // This sucks. - _phantom: Option<&'a str>, } -impl<'a> Request<'a> { +impl Request { /// Retrieves and parses into `T` the `n`th dynamic parameter from the /// request. Returns `Error::NoKey` if `n` is greater than the number of /// params. Returns `Error::BadParse` if the parameter type `T` can't be @@ -108,7 +107,6 @@ impl<'a> Request<'a> { uri: URIBuf::from(uri), data: vec![], headers: HyperHeaders::new(), - _phantom: None, } } @@ -191,7 +189,7 @@ impl<'a> Request<'a> { /// Create a Rocket Request from a Hyper Request. #[doc(hidden)] pub fn from_hyp<'h, 'k>(hyper_req: HyperRequest<'h, 'k>) - -> Result, String> { + -> Result { let (_, h_method, h_headers, h_uri, _, mut h_body) = hyper_req.deconstruct(); let uri = match h_uri { @@ -221,14 +219,13 @@ impl<'a> Request<'a> { uri: uri, data: data, headers: h_headers, - _phantom: None, }; Ok(request) } } -impl<'r> fmt::Display for Request<'r> { +impl fmt::Display for Request { /// Pretty prints a Request. This is primarily used by Rocket's logging /// infrastructure. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/lib/src/response/empty.rs b/lib/src/response/empty.rs index 5756e948..71d7e8d5 100644 --- a/lib/src/response/empty.rs +++ b/lib/src/response/empty.rs @@ -7,9 +7,27 @@ use http::hyper::StatusCode; pub struct Empty(StatusCode); impl Empty { + #[inline(always)] pub fn new(status: StatusCode) -> Empty { Empty(status) } + + #[inline(always)] + pub fn not_found() -> Empty { + Empty::new(StatusCode::NotFound) + } + + #[inline(always)] + pub fn server_error(reason: &str) -> Empty { + warn_!("internal server error: {}", reason); + Empty::new(StatusCode::InternalServerError) + } + + #[inline(always)] + pub fn bad_request(reason: &str) -> Empty { + warn_!("internal server error: {}", reason); + Empty::new(StatusCode::BadRequest) + } } impl Responder for Empty { @@ -22,11 +40,3 @@ impl Responder for Empty { Outcome::Success } } - -pub struct Forward; - -impl Responder for Forward { - fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> { - Outcome::FailForward(res) - } -} diff --git a/lib/src/response/flash.rs b/lib/src/response/flash.rs index f08e4c81..637823aa 100644 --- a/lib/src/response/flash.rs +++ b/lib/src/response/flash.rs @@ -76,10 +76,10 @@ impl Flash<()> { // be dropped needlessly. This may or may not be the intended behavior. // Alternatively, provide a guarantee about the order that from_request params // will be evaluated and recommend that Flash is last. -impl<'r, 'c> FromRequest<'r, 'c> for Flash<()> { +impl<'r> FromRequest<'r> for Flash<()> { type Error = (); - fn from_request(request: &'r Request<'c>) -> Result { + fn from_request(request: &'r Request) -> Result { trace_!("Flash: attemping to retrieve message."); request.cookies().find(FLASH_COOKIE_NAME).ok_or(()).and_then(|cookie| { // Clear the flash message. diff --git a/lib/src/response/mod.rs b/lib/src/response/mod.rs index 4374724d..ae971612 100644 --- a/lib/src/response/mod.rs +++ b/lib/src/response/mod.rs @@ -9,69 +9,60 @@ mod stream; pub mod data; pub use self::responder::Responder; -pub use self::empty::{Empty, Forward}; +pub use self::empty::Empty; pub use self::redirect::Redirect; pub use self::with_status::StatusResponse; pub use self::flash::Flash; pub use self::named_file::NamedFile; pub use self::stream::Stream; pub use self::data::Content; -pub use outcome::{Outcome, ResponseOutcome}; +pub use outcome::Outcome; -use std::ops::{Deref, DerefMut}; -use http::hyper::StatusCode; +use std::fmt; +use request::Data; +use http::hyper::{StatusCode, FreshHyperResponse}; +use term_painter::Color::*; +use term_painter::ToStyle; -pub struct Response<'a>(Box); +pub type ResponseOutcome<'a> = Outcome<(StatusCode, FreshHyperResponse<'a>)>; + +pub enum Response<'a> { + Forward(Data), + Complete(Box) +} impl<'a> Response<'a> { + #[inline(always)] pub fn new(body: T) -> Response<'a> { - Response(Box::new(body)) + Response::Complete(Box::new(body)) } - pub fn with_status(status: StatusCode, - body: T) - -> Response<'a> { - Response(Box::new(StatusResponse::new(status, body))) - } - - pub fn forward() -> Response<'static> { - Response(Box::new(Forward)) + #[inline(always)] + pub fn forward(data: Data) -> Response<'static> { + Response::Forward(data) } + #[inline(always)] pub fn with_raw_status(status: u16, body: T) -> Response<'a> { let status_code = StatusCode::from_u16(status); - Response(Box::new(StatusResponse::new(status_code, body))) + Response::new(StatusResponse::new(status_code, body)) } - pub fn empty() -> Response<'static> { - Response(Box::new(Empty::new(StatusCode::Ok))) - } - - pub fn not_found() -> Response<'static> { - Response(Box::new(Empty::new(StatusCode::NotFound))) - } - - pub fn server_error(reason: &str) -> Response<'static> { - warn_!("internal server error: {}", reason); - Response(Box::new(Empty::new(StatusCode::InternalServerError))) - } - - pub fn bad_request(reason: &str) -> Response<'static> { - warn_!("bad request from user: {}", reason); - Response(Box::new(Empty::new(StatusCode::BadRequest))) + #[doc(hidden)] + #[inline(always)] + pub fn responder(self) -> Option> { + match self { + Response::Complete(responder) => Some(responder), + _ => None + } } } -impl<'a> Deref for Response<'a> { - type Target = Box; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl<'a> DerefMut for Response<'a> { - fn deref_mut(&mut self) -> &mut Box { - &mut self.0 +impl<'a> fmt::Display for Response<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Response::Complete(..) => write!(f, "{}", Green.paint("Complete")), + Response::Forward(..) => write!(f, "{}", Yellow.paint("Forwarding")), + } } } diff --git a/lib/src/response/responder.rs b/lib/src/response/responder.rs index 5e235cf6..22746e12 100644 --- a/lib/src/response/responder.rs +++ b/lib/src/response/responder.rs @@ -37,10 +37,6 @@ impl Responder for String { } } -// FIXME: Should we set a content-type here? Safari needs text/html to render. -// Unfortunately, the file name is gone at this point. Should fix this. There's -// a way to retrieve a file based on its fd, strangely enough. See... -// https://stackoverflow.com/questions/1188757/getting-filename-from-file-descriptor-in-c impl Responder for File { fn respond<'a>(&mut self, mut res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> { let size = self.metadata().unwrap().len(); @@ -60,9 +56,8 @@ impl Responder for File { impl Responder for Option { fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> { if self.is_none() { - trace!("Option is none."); - // TODO: Should this be a 404 or 500? - return Outcome::FailForward(res); + warn_!("response was `None`"); + return Outcome::Forward((StatusCode::NotFound, res)); } self.as_mut().unwrap().respond(res) @@ -74,8 +69,7 @@ impl Responder for Result { default fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> { if self.is_err() { error_!("{:?}", self.as_ref().err().unwrap()); - // TODO: Should this be a 404 or 500? - return Outcome::FailForward(res); + return Outcome::Forward((StatusCode::InternalServerError, res)); } self.as_mut().unwrap().respond(res) diff --git a/lib/src/response/result.rs b/lib/src/response/result.rs new file mode 100644 index 00000000..e69de29b diff --git a/lib/src/response/stream.rs b/lib/src/response/stream.rs index d5923c18..a0c6e6c8 100644 --- a/lib/src/response/stream.rs +++ b/lib/src/response/stream.rs @@ -39,20 +39,20 @@ impl Responder for Stream { Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, Err(ref e) => { error_!("Error streaming response: {:?}", e); - return Outcome::FailStop; + return Outcome::Failure; } } } if let Err(e) = stream.write_all(&buffer[..read]) { error_!("Stream write_all() failed: {:?}", e); - return Outcome::FailStop; + return Outcome::Failure; } } if let Err(e) = stream.end() { error_!("Stream end() failed: {:?}", e); - return Outcome::FailStop; + return Outcome::Failure; } Outcome::Success diff --git a/lib/src/rocket.rs b/lib/src/rocket.rs index f64cb78c..1136578d 100644 --- a/lib/src/rocket.rs +++ b/lib/src/rocket.rs @@ -8,13 +8,14 @@ use term_painter::ToStyle; use config; use logger; -use request::{Request, FormItems}; +use request::{Request, Data, FormItems}; +use response::{Response}; use router::{Router, Route}; use catcher::{self, Catcher}; use outcome::Outcome; use error::Error; -use http::Method; +use http::{Method, StatusCode}; use http::hyper::{HyperRequest, FreshHyperResponse}; use http::hyper::{HyperServer, HyperHandler, HyperSetCookie, header}; @@ -22,6 +23,7 @@ pub struct Rocket { address: String, port: usize, router: Router, + default_catchers: HashMap, catchers: HashMap, } @@ -50,10 +52,14 @@ impl Rocket { Err(ref reason) => { let mock_request = Request::mock(Method::Get, uri.as_str()); debug_!("Bad request: {}", reason); - return self.handle_internal_error(&mock_request, res); + return self.handle_error(StatusCode::InternalServerError, + &mock_request, res); } }; + // Retrieve the data from the request. + let mut data = Data::new(); + info!("{}:", request); let matches = self.router.route(&request); for route in matches { @@ -61,27 +67,39 @@ impl Rocket { info_!("Matched: {}", route); request.set_params(route); - // Dispatch the request to the handler and update the cookies. - let mut responder = (route.handler)(&request); + // Dispatch the request to the handler. + let response = (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); + let mut responder = match response { + Response::Complete(responder) => responder, + Response::Forward(unused_data) => { + data = unused_data; + continue; + } + }; + + // We have a responder. Update the cookies in the header. let cookie_delta = request.cookies().delta(); if cookie_delta.len() > 0 { res.headers_mut().set(HyperSetCookie(cookie_delta)); } - // Get the response. + // Actually process the response. let outcome = responder.respond(res); info_!("{} {}", White.paint("Outcome:"), outcome); // Get the result if we failed forward so we can try again. - res = match outcome { - Outcome::Success | Outcome::FailStop => return, - Outcome::FailForward(r) => r, - Outcome::Bad(r) => return self.handle_internal_error(&request, r), + match outcome { + Outcome::Forward((c, r)) => return self.handle_error(c, &request, r), + Outcome::Success | Outcome::Failure => return, }; } error_!("No matching routes."); - self.handle_not_found(&request, res); + self.handle_error(StatusCode::NotFound, &request, res); } /// Preprocess the request for Rocket-specific things. At this time, we're @@ -105,22 +123,25 @@ impl Rocket { } } - // Call on internal server error. - fn handle_internal_error<'r>(&self, - request: &'r Request<'r>, - response: FreshHyperResponse) { - error_!("Internal server error."); - let catcher = self.catchers.get(&500).unwrap(); - catcher.handle(Error::Internal, request).respond(response); - } - // Call when no route was found. - fn handle_not_found<'r>(&self, - request: &'r Request<'r>, - response: FreshHyperResponse) { - error_!("{} dispatch failed: 404.", request); - let catcher = self.catchers.get(&404).unwrap(); - catcher.handle(Error::NoRoute, request).respond(response); + fn handle_error<'r>(&self, + code: StatusCode, + req: &'r Request, + response: FreshHyperResponse) { + error_!("dispatch failed: {}.", code); + let catcher = self.catchers.get(&code.to_u16()).unwrap(); + + if let Some(mut responder) = catcher.handle(Error::NoRoute, req).responder() { + if responder.respond(response) != Outcome::Success { + error_!("catcher outcome was unsuccessul; aborting response"); + } + } else { + error_!("catcher returned an incomplete response"); + warn_!("using default error response"); + let catcher = self.default_catchers.get(&code.to_u16()).unwrap(); + let responder = catcher.handle(Error::Internal, req).responder(); + responder.unwrap().respond(response).expect_success() + } } pub fn mount(mut self, base: &'static str, routes: Vec) -> Self { @@ -200,6 +221,7 @@ impl Rocket { address: config.active().address.clone(), port: config.active().port, router: Router::new(), + default_catchers: catcher::defaults::get(), catchers: catcher::defaults::get(), } } diff --git a/lib/src/router/collider.rs b/lib/src/router/collider.rs index 299231df..16693e65 100644 --- a/lib/src/router/collider.rs +++ b/lib/src/router/collider.rs @@ -49,7 +49,7 @@ mod tests { use std::str::FromStr; use router::Collider; - use request::Request; + use request::{Request, Data}; use response::Response; use router::route::Route; use http::{Method, ContentType}; @@ -59,8 +59,8 @@ mod tests { type SimpleRoute = (Method, &'static str); - fn dummy_handler(_req: &Request) -> Response<'static> { - Response::empty() + fn dummy_handler(_req: &Request, _: Data) -> Response<'static> { + Response::new("hi") } fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool { diff --git a/lib/src/router/mod.rs b/lib/src/router/mod.rs index 4a804e61..2b396b93 100644 --- a/lib/src/router/mod.rs +++ b/lib/src/router/mod.rs @@ -72,10 +72,10 @@ mod test { use http::Method; use http::Method::*; use http::uri::URI; - use {Response, Request}; + use {Response, Request, Data}; - fn dummy_handler(_req: &Request) -> Response<'static> { - Response::empty() + fn dummy_handler(_req: &Request, _: Data) -> Response<'static> { + Response::new("hi") } fn router_with_routes(routes: &[&'static str]) -> Router { diff --git a/lib/src/router/route.rs b/lib/src/router/route.rs index f332269e..9373149e 100644 --- a/lib/src/router/route.rs +++ b/lib/src/router/route.rs @@ -79,6 +79,18 @@ impl Route { } } +impl Clone for Route { + fn clone(&self) -> Route { + Route { + method: self.method, + handler: self.handler, + rank: self.rank, + path: self.path.clone(), + content_type: self.content_type.clone(), + } + } +} + impl fmt::Display for Route { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} {}", Green.paint(&self.method), Blue.paint(&self.path))?; @@ -122,7 +134,7 @@ impl Collider for Route { } } -impl<'r> Collider> for Route { +impl Collider for Route { fn collides_with(&self, req: &Request) -> bool { self.method == req.method && req.uri().collides_with(&self.path.as_uri())