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,8 +57,8 @@ 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>)
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)

View File

@ -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<Spanned<Method>>,
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

View File

@ -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() {
}

View File

@ -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<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;
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())?))
}
}
@ -75,7 +76,7 @@ impl<T: Serialize> Responder for JSON<T> {
Ok(json_string) => data::JSON(json_string).respond(res),
Err(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::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)),
}
}
}

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 = ();
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()))
}
}

View File

@ -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/<any>", root);
let name = Route::new(Get, "/<name>", 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>", name);
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])
.launch();
}

View File

@ -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 {
</head>
<body align="center">
<div align="center">
<h1>"#, $code, " ", $name, r#"</hi>
<h1>"#, $code, ": ", $name, r#"</h1>
<p>"#, $description, r#"</p>
<hr />
<small>Rocket</small>
@ -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<u16, Catcher> {
default_errors! {

View File

@ -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;

View File

@ -3,40 +3,38 @@ use std::fmt;
use term_painter::Color::*;
use term_painter::ToStyle;
use http::hyper::FreshHyperResponse;
#[must_use]
pub enum Outcome<T> {
/// 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<FreshHyperResponse<'a>>;
impl<T> Outcome<T> {
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<T> fmt::Display for Outcome<T> {
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")),
}
}
}

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 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<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 = ();
fn from_request(request: &'r Request<'c>) -> Result<Self, Self::Error> {
fn from_request(request: &'r Request) -> Result<Self, Self::Error> {
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<Self, Self::Error> {
fn from_request(request: &'r Request) -> Result<Self, Self::Error> {
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<Self, Self::Error> {
fn from_request(request: &'r Request) -> Result<Self, Self::Error> {
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<Self, Self::Error> {
fn from_request(request: &'r Request) -> Result<Self, Self::Error> {
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 = ();
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) {
Ok(v) => Some(v),
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 = ();
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))
}
}

View File

@ -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};

View File

@ -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,
/// <div class="stability" style="margin-left: 0;">
@ -37,10 +37,9 @@ pub struct Request<'a> {
params: RefCell<Vec<&'static str>>,
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<Request<'a>, String> {
-> Result<Request, String> {
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 {

View File

@ -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)
}
}

View File

@ -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<Self, Self::Error> {
fn from_request(request: &'r Request) -> Result<Self, Self::Error> {
trace_!("Flash: attemping to retrieve message.");
request.cookies().find(FLASH_COOKIE_NAME).ok_or(()).and_then(|cookie| {
// Clear the flash message.

View File

@ -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<Responder + 'a>);
pub type ResponseOutcome<'a> = Outcome<(StatusCode, FreshHyperResponse<'a>)>;
pub enum Response<'a> {
Forward(Data),
Complete(Box<Responder + 'a>)
}
impl<'a> Response<'a> {
#[inline(always)]
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,
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<T: Responder + 'a>(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)))
#[doc(hidden)]
#[inline(always)]
pub fn responder(self) -> Option<Box<Responder + 'a>> {
match self {
Response::Complete(responder) => Some(responder),
_ => None
}
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)))
}
}
impl<'a> Deref for Response<'a> {
type Target = Box<Responder + 'a>;
fn deref(&self) -> &Self::Target {
&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")),
}
}
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 {
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<T: Responder> Responder for Option<T> {
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<T: Responder, E: fmt::Debug> Responder for Result<T, E> {
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)

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) => {
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

View File

@ -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<u16, Catcher>,
catchers: HashMap<u16, Catcher>,
}
@ -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>,
fn handle_error<'r>(&self,
code: StatusCode,
req: &'r Request,
response: FreshHyperResponse) {
error_!("{} dispatch failed: 404.", request);
let catcher = self.catchers.get(&404).unwrap();
catcher.handle(Error::NoRoute, request).respond(response);
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<Route>) -> 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(),
}
}

View File

@ -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 {

View File

@ -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 {

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 {
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<Request<'r>> for Route {
impl Collider<Request> for Route {
fn collides_with(&self, req: &Request) -> bool {
self.method == req.method
&& req.uri().collides_with(&self.path.as_uri())