Completely new raw API.

Summary of changes:

  * Request no longer has a lifetime parameter.
  * Handler type now includes a `Data` parameter.
  * Response is now an enum that is either `Complete` or `Forward`.
  * Outcome enum is now one of: Success, Failure, Forward.
  * Outcome::Foward for Responses must include StatusCode.
  * Responders are now final: they cannot forward to requests. (!!)
  * Responsers may only forward to catchers. (!!)
  * Response no longer provides wrapping methods.
  * Route is now cloneable.

This change is fundamental to enabling streaming requests.
This commit is contained in:
Sergio Benitez 2016-10-07 23:20:49 -07:00
parent 59623d936f
commit 8c0d11feab
24 changed files with 239 additions and 187 deletions

View File

@ -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); let fn_arguments = error.generate_fn_arguments(ecx, err_ident, req_ident);
emit_item(push, quote_item!(ecx, emit_item(push, quote_item!(ecx,
fn $catch_fn_name<'_b, '_r>($err_ident: ::rocket::Error, fn $catch_fn_name<'_b>($err_ident: ::rocket::Error,
$req_ident: &'_b ::rocket::Request<'_r>) $req_ident: &'_b ::rocket::Request)
-> ::rocket::Response<'_b> { -> ::rocket::Response<'_b> {
let result = $user_fn_name($fn_arguments); let result = $user_fn_name($fn_arguments);
rocket::Response::with_raw_status($code, result) rocket::Response::with_raw_status($code, result)
} }

View File

@ -84,7 +84,7 @@ impl RouteGenerateExt for RouteParams {
let $name: $ty = let $name: $ty =
match ::rocket::request::FromForm::from_form_string($form_string) { match ::rocket::request::FromForm::from_form_string($form_string) {
Ok(v) => v, Ok(v) => v,
Err(_) => return ::rocket::Response::forward() Err(_) => return ::rocket::Response::forward(_data)
}; };
).expect("form statement")) ).expect("form statement"))
} }
@ -94,7 +94,8 @@ impl RouteGenerateExt for RouteParams {
let expr = quote_expr!(ecx, let expr = quote_expr!(ecx,
match ::std::str::from_utf8(_req.data.as_slice()) { match ::std::str::from_utf8(_req.data.as_slice()) {
Ok(s) => s, 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, let expr = quote_expr!(ecx,
match _req.uri().query() { match _req.uri().query() {
Some(query) => 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, fn_param_statements.push(quote_stmt!(ecx,
let $ident: $ty = match $expr { let $ident: $ty = match $expr {
Ok(v) => v, Ok(v) => v,
Err(_) => return ::rocket::Response::forward() Err(_) => return ::rocket::Response::forward(_data)
}; };
).expect("declared param parsing statement")); ).expect("declared param parsing statement"));
} }
@ -168,7 +169,7 @@ impl RouteGenerateExt for RouteParams {
let $ident: $ty = match let $ident: $ty = match
<$ty as ::rocket::request::FromRequest>::from_request(&_req) { <$ty as ::rocket::request::FromRequest>::from_request(&_req) {
Ok(v) => v, Ok(v) => v,
Err(_e) => return ::rocket::Response::forward() Err(_e) => return ::rocket::Response::forward(_data)
}; };
).expect("undeclared param parsing statement")); ).expect("undeclared param parsing statement"));
} }
@ -219,7 +220,7 @@ fn generic_route_decorator(known_method: Option<Spanned<Method>>,
let user_fn_name = route.annotated_fn.ident(); let user_fn_name = route.annotated_fn.ident();
let route_fn_name = user_fn_name.prepend(ROUTE_FN_PREFIX); let route_fn_name = user_fn_name.prepend(ROUTE_FN_PREFIX);
emit_item(push, quote_item!(ecx, emit_item(push, quote_item!(ecx,
fn $route_fn_name<'_b, '_r>(_req: &'_b ::rocket::Request<'_r>) fn $route_fn_name<'_b>(_req: &'_b ::rocket::Request, _data: ::rocket::Data)
-> ::rocket::Response<'_b> { -> ::rocket::Response<'_b> {
$form_statement $form_statement
$query_statement $query_statement

View File

@ -18,10 +18,7 @@ fn err1b(_req: &Request) -> &'static str { "hi" }
fn err2a(_err: Error, _req: &Request) -> &'static str { "hi" } fn err2a(_err: Error, _req: &Request) -> &'static str { "hi" }
#[error(404)] #[error(404)]
fn err2b<'r>(_err: Error, _req: &Request<'r>) -> &'r str { "hi" } fn err2b<'a>(_err: Error, _req: &'a Request) -> &'a str { "hi" }
#[error(404)]
fn err2c<'a>(_err: Error, _req: &'a Request) -> &'a str { "hi" }
fn main() { fn main() {
} }

View File

@ -5,6 +5,7 @@ use std::ops::{Deref, DerefMut};
use rocket::request::{Request, FromRequest}; use rocket::request::{Request, FromRequest};
use rocket::response::{Responder, Outcome, ResponseOutcome, data}; use rocket::response::{Responder, Outcome, ResponseOutcome, data};
use rocket::http::StatusCode;
use rocket::http::hyper::FreshHyperResponse; use rocket::http::hyper::FreshHyperResponse;
use self::serde::{Serialize, Deserialize}; use self::serde::{Serialize, Deserialize};
@ -62,9 +63,9 @@ impl<T> JSON<T> {
} }
} }
impl<'r, 'c, T: Deserialize> FromRequest<'r, 'c> for JSON<T> { impl<'r, T: Deserialize> FromRequest<'r> for JSON<T> {
type Error = JSONError; type Error = JSONError;
fn from_request(request: &'r Request<'c>) -> Result<Self, Self::Error> { fn from_request(request: &'r Request) -> Result<Self, Self::Error> {
Ok(JSON(serde_json::from_slice(request.data.as_slice())?)) Ok(JSON(serde_json::from_slice(request.data.as_slice())?))
} }
} }
@ -75,7 +76,7 @@ impl<T: Serialize> Responder for JSON<T> {
Ok(json_string) => data::JSON(json_string).respond(res), Ok(json_string) => data::JSON(json_string).respond(res),
Err(e) => { Err(e) => {
error_!("JSON failed to serialize: {:?}", e); error_!("JSON failed to serialize: {:?}", e);
Outcome::FailStop Outcome::Forward((StatusCode::BadRequest, res))
} }
} }
} }

View File

@ -19,7 +19,7 @@ use std::collections::HashMap;
use rocket::Rocket; use rocket::Rocket;
use rocket::response::{Content, Outcome, ResponseOutcome, Responder}; use rocket::response::{Content, Outcome, ResponseOutcome, Responder};
use rocket::http::hyper::FreshHyperResponse; use rocket::http::hyper::FreshHyperResponse;
use rocket::http::ContentType; use rocket::http::{ContentType, StatusCode};
/// The Template type implements generic support for template rendering in /// The Template type implements generic support for template rendering in
/// Rocket. /// Rocket.
@ -155,7 +155,7 @@ impl Responder for Template {
match self.0 { match self.0 {
Some(ref render) => Content(content_type, render.as_str()).respond(res), Some(ref render) => Content(content_type, render.as_str()).respond(res),
None => Outcome::Bad(res), None => Outcome::Forward((StatusCode::InternalServerError, res)),
} }
} }
} }

View File

@ -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 = (); type Error = ();
fn from_request(request: &'r Request<'c>) -> Result<Self, Self::Error> { fn from_request(request: &'r Request) -> Result<Self, Self::Error> {
Ok(HeaderCount(request.headers().len())) Ok(HeaderCount(request.headers().len()))
} }
} }

View File

@ -1,30 +1,38 @@
extern crate rocket; extern crate rocket;
use rocket::{Request, Response, Route}; use rocket::{Request, Response, Route, Data};
use rocket::request::FromParam;
use rocket::http::Method::*; use rocket::http::Method::*;
fn root(req: &Request) -> Response<'static> { fn forward(_req: &Request, data: Data) -> Response<'static> {
let name = req.get_param(0).unwrap_or("unnamed"); Response::forward(data)
Response::new(format!("Hello, {}!", name))
} }
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")) Response::new(req.get_param(0).unwrap_or("unnamed"))
} }
#[allow(dead_code)] #[allow(dead_code)]
fn echo_url<'a>(req: &'a Request) -> Response<'a> { fn echo_url<'a>(req: &'a Request, _: Data) -> Response<'a> {
Response::new(req.uri().as_str().split_at(6).1) let param = req.uri().as_str().split_at(6).1;
Response::new(String::from_param(param))
} }
fn main() { fn main() {
let first = Route::new(Get, "/hello", root); let always_forward = Route::ranked(1, Get, "/", forward);
let second = Route::new(Get, "/hello/<any>", root); let hello = Route::ranked(2, Get, "/", hi);
let name = Route::new(Get, "/<name>", name);
let echo = Route::new(Get, "/", echo_url); let echo = Route::new(Get, "/", echo_url);
let name = Route::new(Get, "/<name>", name);
rocket::ignite() rocket::ignite()
.mount("/", vec![first, second, name]) .mount("/", vec![always_forward, hello])
.mount("/hello", vec![name.clone()])
.mount("/hi", vec![name])
.mount("/echo:<str>", vec![echo]) .mount("/echo:<str>", vec![echo])
.launch(); .launch();
} }

View File

@ -14,16 +14,12 @@ pub struct Catcher {
is_default: bool, 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 { impl Catcher {
pub fn new(code: u16, handler: ErrorHandler) -> Catcher { pub fn new(code: u16, handler: ErrorHandler) -> Catcher {
Catcher { code: code, handler: handler, is_default: false } 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) (self.handler)(err, request)
} }
@ -59,7 +55,7 @@ macro_rules! error_page_template {
</head> </head>
<body align="center"> <body align="center">
<div align="center"> <div align="center">
<h1>"#, $code, " ", $name, r#"</hi> <h1>"#, $code, ": ", $name, r#"</h1>
<p>"#, $description, r#"</p> <p>"#, $description, r#"</p>
<hr /> <hr />
<small>Rocket</small> <small>Rocket</small>
@ -77,8 +73,7 @@ macro_rules! default_errors {
$( $(
fn $fn_name<'r>(_: Error, _r: &'r Request) -> Response<'r> { fn $fn_name<'r>(_: Error, _r: &'r Request) -> Response<'r> {
Response::with_status( Response::with_raw_status($code,
StatusCode::Unregistered($code),
data::HTML(error_page_template!($code, $name, $description)) data::HTML(error_page_template!($code, $name, $description))
) )
} }
@ -96,10 +91,8 @@ pub mod defaults {
use std::collections::HashMap; use std::collections::HashMap;
use request::Request; use request::Request;
use response::Response; use response::{Response, data};
use response::data;
use error::Error; use error::Error;
use http::hyper::StatusCode;
pub fn get() -> HashMap<u16, Catcher> { pub fn get() -> HashMap<u16, Catcher> {
default_errors! { default_errors! {

View File

@ -1,5 +1,6 @@
#![feature(question_mark)] #![feature(question_mark)]
#![feature(specialization)] #![feature(specialization)]
#![feature(conservative_impl_trait)]
//! # Rocket - Core API Documentation //! # Rocket - Core API Documentation
//! //!
@ -83,15 +84,15 @@ mod config;
/// Defines the types for request and error handlers. /// Defines the types for request and error handlers.
pub mod handler { pub mod handler {
use request::Request; use request::{Request, Data};
use response::Response; use response::Response;
use error::Error; use error::Error;
/// The type of a request handler. /// 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. /// 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}; #[doc(inline)] pub use response::{Response, Responder};
@ -99,7 +100,7 @@ pub mod handler {
#[doc(inline)] pub use logger::LoggingLevel; #[doc(inline)] pub use logger::LoggingLevel;
pub use codegen::{StaticRouteInfo, StaticCatchInfo}; pub use codegen::{StaticRouteInfo, StaticCatchInfo};
pub use router::Route; pub use router::Route;
pub use request::Request; pub use request::{Request, Data};
pub use error::Error; pub use error::Error;
pub use catcher::Catcher; pub use catcher::Catcher;
pub use rocket::Rocket; pub use rocket::Rocket;

View File

@ -3,40 +3,38 @@ use std::fmt;
use term_painter::Color::*; use term_painter::Color::*;
use term_painter::ToStyle; use term_painter::ToStyle;
use http::hyper::FreshHyperResponse; #[must_use]
pub enum Outcome<T> { pub enum Outcome<T> {
/// Signifies a response that completed sucessfully. /// Signifies that all processing completed successfully.
Success, Success,
/// Signifies a failing response that started responding but fail, so no /// Signifies that some processing occurred that ultimately resulted in
/// further processing can occur. /// failure. As a result, no further processing can occur.
FailStop, Failure,
/// Signifies a response that failed internally without beginning to /// Signifies that no processing occured and as such, processing can be
/// respond but no further processing should occur. /// forwarded to the next available target.
Bad(T), Forward(T),
/// Signifies a failing response that failed internally without beginning to
/// respond. Further processing should be attempted.
FailForward(T),
} }
pub type ResponseOutcome<'a> = Outcome<FreshHyperResponse<'a>>;
impl<T> Outcome<T> { impl<T> Outcome<T> {
pub fn as_str(&self) -> &'static str { pub fn as_str(&self) -> &'static str {
match *self { match *self {
Outcome::Success => "Success", Outcome::Success => "Success",
Outcome::FailStop => "FailStop", Outcome::Failure => "FailStop",
Outcome::Bad(..) => "Bad", Outcome::Forward(..) => "Forward",
Outcome::FailForward(..) => "FailForward",
} }
} }
fn as_int(&self) -> isize { fn as_int(&self) -> isize {
match *self { match *self {
Outcome::Success => 0, Outcome::Success => 0,
Outcome::Bad(..) => 1, Outcome::Failure => 1,
Outcome::FailStop => 2, Outcome::Forward(..) => 2,
Outcome::FailForward(..) => 3, }
}
pub fn expect_success(&self) {
if *self != Outcome::Success {
panic!("expected a successful outcome");
} }
} }
} }
@ -57,9 +55,8 @@ impl<T> fmt::Display for Outcome<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { match *self {
Outcome::Success => write!(f, "{}", Green.paint("Success")), Outcome::Success => write!(f, "{}", Green.paint("Success")),
Outcome::Bad(..) => write!(f, "{}", Yellow.paint("Bad Completion")), Outcome::Failure => write!(f, "{}", Red.paint("Failure")),
Outcome::FailStop => write!(f, "{}", Red.paint("Failed")), Outcome::Forward(..) => write!(f, "{}", Cyan.paint("Forwarding")),
Outcome::FailForward(..) => write!(f, "{}", Cyan.paint("Forwarding")),
} }
} }
} }

26
lib/src/request/data.rs Normal file
View File

@ -0,0 +1,26 @@
use std::io::{BufRead, Read, Cursor, BufReader};
use std::net::TcpStream;
use request::Request;
pub struct Data {
stream: Cursor<Vec<u8>>,
buffer: Vec<u8>
}
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![]
}
}
}

View File

@ -3,47 +3,47 @@ use std::fmt::Debug;
use request::Request; use request::Request;
use http::{ContentType, Method, Cookies}; use http::{ContentType, Method, Cookies};
pub trait FromRequest<'r, 'c>: Sized { pub trait FromRequest<'r>: Sized {
type Error: Debug; type Error: Debug;
fn from_request(request: &'r Request<'c>) -> Result<Self, Self::Error>; fn from_request(request: &'r Request) -> Result<Self, Self::Error>;
} }
impl<'r, 'c> FromRequest<'r, 'c> for &'r Request<'c> { impl<'r> FromRequest<'r> for &'r Request {
type Error = (); type Error = ();
fn from_request(request: &'r Request<'c>) -> Result<Self, Self::Error> { fn from_request(request: &'r Request) -> Result<Self, Self::Error> {
Ok(request) Ok(request)
} }
} }
impl<'r, 'c> FromRequest<'r, 'c> for Method { impl<'r> FromRequest<'r> for Method {
type Error = (); type Error = ();
fn from_request(request: &'r Request<'c>) -> Result<Self, Self::Error> { fn from_request(request: &'r Request) -> Result<Self, Self::Error> {
Ok(request.method) Ok(request.method)
} }
} }
impl<'r, 'c> FromRequest<'r, 'c> for &'r Cookies { impl<'r> FromRequest<'r> for &'r Cookies {
type Error = (); type Error = ();
fn from_request(request: &'r Request<'c>) -> Result<Self, Self::Error> { fn from_request(request: &'r Request) -> Result<Self, Self::Error> {
Ok(request.cookies()) Ok(request.cookies())
} }
} }
impl<'r, 'c> FromRequest<'r, 'c> for ContentType { impl<'r> FromRequest<'r> for ContentType {
type Error = (); type Error = ();
fn from_request(request: &'r Request<'c>) -> Result<Self, Self::Error> { fn from_request(request: &'r Request) -> Result<Self, Self::Error> {
Ok(request.content_type()) Ok(request.content_type())
} }
} }
impl<'r, 'c, T: FromRequest<'r, 'c>> FromRequest<'r, 'c> for Option<T> { impl<'r, T: FromRequest<'r>> FromRequest<'r> for Option<T> {
type Error = (); type Error = ();
fn from_request(request: &'r Request<'c>) -> Result<Self, Self::Error> { fn from_request(request: &'r Request) -> Result<Self, Self::Error> {
let opt = match T::from_request(request) { let opt = match T::from_request(request) {
Ok(v) => Some(v), Ok(v) => Some(v),
Err(_) => None, Err(_) => None,
@ -53,10 +53,10 @@ impl<'r, 'c, T: FromRequest<'r, 'c>> FromRequest<'r, 'c> for Option<T> {
} }
} }
impl<'r, 'c, T: FromRequest<'r, 'c>> FromRequest<'r, 'c> for Result<T, T::Error> { impl<'r, T: FromRequest<'r>> FromRequest<'r> for Result<T, T::Error> {
type Error = (); type Error = ();
fn from_request(request: &'r Request<'c>) -> Result<Self, Self::Error> { fn from_request(request: &'r Request) -> Result<Self, Self::Error> {
Ok(T::from_request(request)) Ok(T::from_request(request))
} }
} }

View File

@ -3,9 +3,11 @@
mod request; mod request;
mod param; mod param;
mod form; mod form;
mod data;
mod from_request; mod from_request;
pub use self::request::Request; pub use self::request::Request;
pub use self::from_request::FromRequest; pub use self::from_request::FromRequest;
pub use self::param::{FromParam, FromSegments}; pub use self::param::{FromParam, FromSegments};
pub use self::form::{FromForm, FromFormValue, FormItems}; pub use self::form::{FromForm, FromFormValue, FormItems};
pub use self::data::{Data};

View File

@ -20,7 +20,7 @@ use http::{Method, ContentType, Cookies};
/// [FromRequest](trait.FromRequest.html) implementations. It contains all of /// [FromRequest](trait.FromRequest.html) implementations. It contains all of
/// the information for a given web request. This includes the HTTP method, URI, /// the information for a given web request. This includes the HTTP method, URI,
/// cookies, headers, and more. /// cookies, headers, and more.
pub struct Request<'a> { pub struct Request {
/// The HTTP method associated with the request. /// The HTTP method associated with the request.
pub method: Method, pub method: Method,
/// <div class="stability" style="margin-left: 0;"> /// <div class="stability" style="margin-left: 0;">
@ -37,10 +37,9 @@ pub struct Request<'a> {
params: RefCell<Vec<&'static str>>, params: RefCell<Vec<&'static str>>,
cookies: Cookies, cookies: Cookies,
headers: HyperHeaders, // This sucks. 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 /// Retrieves and parses into `T` the `n`th dynamic parameter from the
/// request. Returns `Error::NoKey` if `n` is greater than the number of /// request. Returns `Error::NoKey` if `n` is greater than the number of
/// params. Returns `Error::BadParse` if the parameter type `T` can't be /// params. Returns `Error::BadParse` if the parameter type `T` can't be
@ -108,7 +107,6 @@ impl<'a> Request<'a> {
uri: URIBuf::from(uri), uri: URIBuf::from(uri),
data: vec![], data: vec![],
headers: HyperHeaders::new(), headers: HyperHeaders::new(),
_phantom: None,
} }
} }
@ -191,7 +189,7 @@ impl<'a> Request<'a> {
/// Create a Rocket Request from a Hyper Request. /// Create a Rocket Request from a Hyper Request.
#[doc(hidden)] #[doc(hidden)]
pub fn from_hyp<'h, 'k>(hyper_req: HyperRequest<'h, 'k>) pub fn from_hyp<'h, 'k>(hyper_req: HyperRequest<'h, 'k>)
-> Result<Request<'a>, String> { -> Result<Request, String> {
let (_, h_method, h_headers, h_uri, _, mut h_body) = hyper_req.deconstruct(); let (_, h_method, h_headers, h_uri, _, mut h_body) = hyper_req.deconstruct();
let uri = match h_uri { let uri = match h_uri {
@ -221,14 +219,13 @@ impl<'a> Request<'a> {
uri: uri, uri: uri,
data: data, data: data,
headers: h_headers, headers: h_headers,
_phantom: None,
}; };
Ok(request) 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 /// Pretty prints a Request. This is primarily used by Rocket's logging
/// infrastructure. /// infrastructure.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

View File

@ -7,9 +7,27 @@ use http::hyper::StatusCode;
pub struct Empty(StatusCode); pub struct Empty(StatusCode);
impl Empty { impl Empty {
#[inline(always)]
pub fn new(status: StatusCode) -> Empty { pub fn new(status: StatusCode) -> Empty {
Empty(status) 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 { impl Responder for Empty {
@ -22,11 +40,3 @@ impl Responder for Empty {
Outcome::Success Outcome::Success
} }
} }
pub struct Forward;
impl Responder for Forward {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> {
Outcome::FailForward(res)
}
}

View File

@ -76,10 +76,10 @@ impl Flash<()> {
// be dropped needlessly. This may or may not be the intended behavior. // be dropped needlessly. This may or may not be the intended behavior.
// Alternatively, provide a guarantee about the order that from_request params // Alternatively, provide a guarantee about the order that from_request params
// will be evaluated and recommend that Flash is last. // 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 = (); type Error = ();
fn from_request(request: &'r Request<'c>) -> Result<Self, Self::Error> { fn from_request(request: &'r Request) -> Result<Self, Self::Error> {
trace_!("Flash: attemping to retrieve message."); trace_!("Flash: attemping to retrieve message.");
request.cookies().find(FLASH_COOKIE_NAME).ok_or(()).and_then(|cookie| { request.cookies().find(FLASH_COOKIE_NAME).ok_or(()).and_then(|cookie| {
// Clear the flash message. // Clear the flash message.

View File

@ -9,69 +9,60 @@ mod stream;
pub mod data; pub mod data;
pub use self::responder::Responder; pub use self::responder::Responder;
pub use self::empty::{Empty, Forward}; pub use self::empty::Empty;
pub use self::redirect::Redirect; pub use self::redirect::Redirect;
pub use self::with_status::StatusResponse; pub use self::with_status::StatusResponse;
pub use self::flash::Flash; pub use self::flash::Flash;
pub use self::named_file::NamedFile; pub use self::named_file::NamedFile;
pub use self::stream::Stream; pub use self::stream::Stream;
pub use self::data::Content; pub use self::data::Content;
pub use outcome::{Outcome, ResponseOutcome}; pub use outcome::Outcome;
use std::ops::{Deref, DerefMut}; use std::fmt;
use http::hyper::StatusCode; use request::Data;
use http::hyper::{StatusCode, FreshHyperResponse};
use term_painter::Color::*;
use term_painter::ToStyle;
pub struct Response<'a>(Box<Responder + 'a>); pub type ResponseOutcome<'a> = Outcome<(StatusCode, FreshHyperResponse<'a>)>;
pub enum Response<'a> {
Forward(Data),
Complete(Box<Responder + 'a>)
}
impl<'a> Response<'a> { impl<'a> Response<'a> {
#[inline(always)]
pub fn new<T: Responder + 'a>(body: T) -> Response<'a> { pub fn new<T: Responder + 'a>(body: T) -> Response<'a> {
Response(Box::new(body)) Response::Complete(Box::new(body))
} }
pub fn with_status<T: Responder + 'a>(status: StatusCode, #[inline(always)]
body: T) pub fn forward(data: Data) -> Response<'static> {
-> Response<'a> { Response::Forward(data)
Response(Box::new(StatusResponse::new(status, body)))
}
pub fn forward() -> Response<'static> {
Response(Box::new(Forward))
} }
#[inline(always)]
pub fn with_raw_status<T: Responder + 'a>(status: u16, body: T) -> Response<'a> { pub fn with_raw_status<T: Responder + 'a>(status: u16, body: T) -> Response<'a> {
let status_code = StatusCode::from_u16(status); 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> { #[doc(hidden)]
Response(Box::new(Empty::new(StatusCode::Ok))) #[inline(always)]
} pub fn responder(self) -> Option<Box<Responder + 'a>> {
match self {
pub fn not_found() -> Response<'static> { Response::Complete(responder) => Some(responder),
Response(Box::new(Empty::new(StatusCode::NotFound))) _ => None
} }
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)))
} }
} }
impl<'a> Deref for Response<'a> { impl<'a> fmt::Display for Response<'a> {
type Target = Box<Responder + 'a>; fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
fn deref(&self) -> &Self::Target { Response::Complete(..) => write!(f, "{}", Green.paint("Complete")),
&self.0 Response::Forward(..) => write!(f, "{}", Yellow.paint("Forwarding")),
} }
}
impl<'a> DerefMut for Response<'a> {
fn deref_mut(&mut self) -> &mut Box<Responder + 'a> {
&mut self.0
} }
} }

View File

@ -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 { impl Responder for File {
fn respond<'a>(&mut self, mut res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> { fn respond<'a>(&mut self, mut res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> {
let size = self.metadata().unwrap().len(); let size = self.metadata().unwrap().len();
@ -60,9 +56,8 @@ impl Responder for File {
impl<T: Responder> Responder for Option<T> { impl<T: Responder> Responder for Option<T> {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> { fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> {
if self.is_none() { if self.is_none() {
trace!("Option is none."); warn_!("response was `None`");
// TODO: Should this be a 404 or 500? return Outcome::Forward((StatusCode::NotFound, res));
return Outcome::FailForward(res);
} }
self.as_mut().unwrap().respond(res) self.as_mut().unwrap().respond(res)
@ -74,8 +69,7 @@ impl<T: Responder, E: fmt::Debug> Responder for Result<T, E> {
default fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> { default fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> {
if self.is_err() { if self.is_err() {
error_!("{:?}", self.as_ref().err().unwrap()); error_!("{:?}", self.as_ref().err().unwrap());
// TODO: Should this be a 404 or 500? return Outcome::Forward((StatusCode::InternalServerError, res));
return Outcome::FailForward(res);
} }
self.as_mut().unwrap().respond(res) self.as_mut().unwrap().respond(res)

View File

View File

@ -39,20 +39,20 @@ impl<T: Read> Responder for Stream<T> {
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
Err(ref e) => { Err(ref e) => {
error_!("Error streaming response: {:?}", e); error_!("Error streaming response: {:?}", e);
return Outcome::FailStop; return Outcome::Failure;
} }
} }
} }
if let Err(e) = stream.write_all(&buffer[..read]) { if let Err(e) = stream.write_all(&buffer[..read]) {
error_!("Stream write_all() failed: {:?}", e); error_!("Stream write_all() failed: {:?}", e);
return Outcome::FailStop; return Outcome::Failure;
} }
} }
if let Err(e) = stream.end() { if let Err(e) = stream.end() {
error_!("Stream end() failed: {:?}", e); error_!("Stream end() failed: {:?}", e);
return Outcome::FailStop; return Outcome::Failure;
} }
Outcome::Success Outcome::Success

View File

@ -8,13 +8,14 @@ use term_painter::ToStyle;
use config; use config;
use logger; use logger;
use request::{Request, FormItems}; use request::{Request, Data, FormItems};
use response::{Response};
use router::{Router, Route}; use router::{Router, Route};
use catcher::{self, Catcher}; use catcher::{self, Catcher};
use outcome::Outcome; use outcome::Outcome;
use error::Error; use error::Error;
use http::Method; use http::{Method, StatusCode};
use http::hyper::{HyperRequest, FreshHyperResponse}; use http::hyper::{HyperRequest, FreshHyperResponse};
use http::hyper::{HyperServer, HyperHandler, HyperSetCookie, header}; use http::hyper::{HyperServer, HyperHandler, HyperSetCookie, header};
@ -22,6 +23,7 @@ pub struct Rocket {
address: String, address: String,
port: usize, port: usize,
router: Router, router: Router,
default_catchers: HashMap<u16, Catcher>,
catchers: HashMap<u16, Catcher>, catchers: HashMap<u16, Catcher>,
} }
@ -50,10 +52,14 @@ impl Rocket {
Err(ref reason) => { Err(ref reason) => {
let mock_request = Request::mock(Method::Get, uri.as_str()); let mock_request = Request::mock(Method::Get, uri.as_str());
debug_!("Bad request: {}", reason); 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); info!("{}:", request);
let matches = self.router.route(&request); let matches = self.router.route(&request);
for route in matches { for route in matches {
@ -61,27 +67,39 @@ impl Rocket {
info_!("Matched: {}", route); info_!("Matched: {}", route);
request.set_params(route); request.set_params(route);
// Dispatch the request to the handler and update the cookies. // Dispatch the request to the handler.
let mut responder = (route.handler)(&request); 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(); let cookie_delta = request.cookies().delta();
if cookie_delta.len() > 0 { if cookie_delta.len() > 0 {
res.headers_mut().set(HyperSetCookie(cookie_delta)); res.headers_mut().set(HyperSetCookie(cookie_delta));
} }
// Get the response. // Actually process the response.
let outcome = responder.respond(res); let outcome = responder.respond(res);
info_!("{} {}", White.paint("Outcome:"), outcome); info_!("{} {}", White.paint("Outcome:"), outcome);
// Get the result if we failed forward so we can try again. // Get the result if we failed forward so we can try again.
res = match outcome { match outcome {
Outcome::Success | Outcome::FailStop => return, Outcome::Forward((c, r)) => return self.handle_error(c, &request, r),
Outcome::FailForward(r) => r, Outcome::Success | Outcome::Failure => return,
Outcome::Bad(r) => return self.handle_internal_error(&request, r),
}; };
} }
error_!("No matching routes."); 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 /// 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. // Call when no route was found.
fn handle_not_found<'r>(&self, fn handle_error<'r>(&self,
request: &'r Request<'r>, code: StatusCode,
response: FreshHyperResponse) { req: &'r Request,
error_!("{} dispatch failed: 404.", request); response: FreshHyperResponse) {
let catcher = self.catchers.get(&404).unwrap(); error_!("dispatch failed: {}.", code);
catcher.handle(Error::NoRoute, request).respond(response); 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<Route>) -> Self { pub fn mount(mut self, base: &'static str, routes: Vec<Route>) -> Self {
@ -200,6 +221,7 @@ impl Rocket {
address: config.active().address.clone(), address: config.active().address.clone(),
port: config.active().port, port: config.active().port,
router: Router::new(), router: Router::new(),
default_catchers: catcher::defaults::get(),
catchers: catcher::defaults::get(), catchers: catcher::defaults::get(),
} }
} }

View File

@ -49,7 +49,7 @@ mod tests {
use std::str::FromStr; use std::str::FromStr;
use router::Collider; use router::Collider;
use request::Request; use request::{Request, Data};
use response::Response; use response::Response;
use router::route::Route; use router::route::Route;
use http::{Method, ContentType}; use http::{Method, ContentType};
@ -59,8 +59,8 @@ mod tests {
type SimpleRoute = (Method, &'static str); type SimpleRoute = (Method, &'static str);
fn dummy_handler(_req: &Request) -> Response<'static> { fn dummy_handler(_req: &Request, _: Data) -> Response<'static> {
Response::empty() Response::new("hi")
} }
fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool { fn m_collide(a: SimpleRoute, b: SimpleRoute) -> bool {

View File

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

View File

@ -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 { impl fmt::Display for Route {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {}", Green.paint(&self.method), Blue.paint(&self.path))?; write!(f, "{} {}", Green.paint(&self.method), Blue.paint(&self.path))?;
@ -122,7 +134,7 @@ impl Collider for Route {
} }
} }
impl<'r> Collider<Request<'r>> for Route { impl Collider<Request> for Route {
fn collides_with(&self, req: &Request) -> bool { fn collides_with(&self, req: &Request) -> bool {
self.method == req.method self.method == req.method
&& req.uri().collides_with(&self.path.as_uri()) && req.uri().collides_with(&self.path.as_uri())