Can now assign outcome to response. Route mismatches now forward.

This means we have almost all of the infrastructure in place to properly use
ranked requests. At the moment, we only use this to allow user error handlers
when a responder fails. But, soon enough, we'll try the next highest ranked
route until there are no more matching routes. Yipee!
This commit is contained in:
Sergio Benitez 2016-04-11 03:57:23 -07:00
parent 3f2954ab5c
commit b8b44a0594
12 changed files with 222 additions and 59 deletions

View File

@ -19,17 +19,17 @@ pub enum Method {
}
impl Method {
pub fn from_hyp(method: HyperMethod) -> Option<Method> {
pub fn from_hyp(method: &HyperMethod) -> Option<Method> {
match method {
HyperMethod::Get => Some(Get),
HyperMethod::Put => Some(Put),
HyperMethod::Post => Some(Post),
HyperMethod::Delete => Some(Delete),
HyperMethod::Options => Some(Options),
HyperMethod::Head => Some(Head),
HyperMethod::Trace => Some(Trace),
HyperMethod::Connect => Some(Connect),
HyperMethod::Patch => Some(Patch),
&HyperMethod::Get => Some(Get),
&HyperMethod::Put => Some(Put),
&HyperMethod::Post => Some(Post),
&HyperMethod::Delete => Some(Delete),
&HyperMethod::Options => Some(Options),
&HyperMethod::Head => Some(Head),
&HyperMethod::Trace => Some(Trace),
&HyperMethod::Connect => Some(Connect),
&HyperMethod::Patch => Some(Patch),
_ => None
}
}

View File

@ -3,6 +3,7 @@ use param::FromParam;
pub use hyper::server::Request as HyperRequest;
#[derive(Clone)]
pub struct Request<'a> {
params: Vec<&'a str>,
pub uri: &'a str,

View File

@ -10,11 +10,20 @@ impl Empty {
}
impl Responder for Empty {
fn respond<'a>(&mut self, mut res: HyperResponse<'a, HyperFresh>) {
fn respond<'a>(&mut self, mut res: FreshHyperResponse<'a>) -> Outcome<'a> {
res.headers_mut().set(header::ContentLength(0));
*(res.status_mut()) = self.0;
let mut stream = res.start().unwrap();
stream.write_all(b"").unwrap();
Outcome::Complete
}
}
pub struct Forward;
impl Responder for Forward {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a> {
Outcome::FailForward(res)
}
}

View File

View File

@ -2,6 +2,7 @@ mod empty;
mod responder;
mod redirect;
mod with_status;
mod outcome;
pub use hyper::server::Response as HyperResponse;
pub use hyper::net::Fresh as HyperFresh;
@ -9,12 +10,15 @@ pub use hyper::status::StatusCode;
pub use hyper::header;
pub use self::responder::Responder;
pub use self::empty::Empty;
pub use self::empty::{Empty, Forward};
pub use self::redirect::Redirect;
pub use self::with_status::StatusResponse;
pub use self::outcome::Outcome;
use std::ops::{Deref, DerefMut};
pub type FreshHyperResponse<'a> = HyperResponse<'a, HyperFresh>;
pub struct Response<'a>(Box<Responder + 'a>);
impl<'a> Response<'a> {
@ -27,6 +31,10 @@ impl<'a> Response<'a> {
Response(Box::new(StatusResponse::new(status, body)))
}
pub fn forward() -> Response<'a> {
Response(Box::new(Forward))
}
pub fn with_raw_status<T: Responder + 'a>(status: u16, body: T)
-> Response<'a> {
let status_code = StatusCode::from_u16(status);

View File

@ -0,0 +1,88 @@
use response::*;
use term_painter::Color::*;
use term_painter::ToStyle;
use std::fmt;
pub enum Outcome<'h> {
Complete,
FailStop,
FailForward(HyperResponse<'h, HyperFresh>),
}
impl<'h> Outcome<'h> {
pub fn as_str(&self) -> &'static str {
match self {
&Outcome::Complete => "Complete",
&Outcome::FailStop => "FailStop",
&Outcome::FailForward(..) => "FailForward",
}
}
pub fn is_forward(&self) -> bool {
match self {
&Outcome::FailForward(_) => true,
_ => false
}
}
pub fn map_forward<F>(self, f: F)
where F: FnOnce(FreshHyperResponse<'h>) {
match self {
Outcome::FailForward(res) => f(res),
_ => { /* nothing */ }
}
}
pub fn map_forward_or<F, R>(self, default: R, f: F) -> R
where F: FnOnce(FreshHyperResponse<'h>) -> R {
match self {
Outcome::FailForward(res) => f(res),
_ => default
}
}
pub fn is_failure(&self) -> bool {
self == &Outcome::FailStop
}
pub fn is_complete(&self) -> bool {
self == &Outcome::Complete
}
fn as_int(&self) -> isize {
match self {
&Outcome::Complete => 0,
&Outcome::FailStop => 1,
&Outcome::FailForward(..) => 2,
}
}
}
impl<'h> PartialEq for Outcome<'h> {
fn eq(&self, other: &Outcome<'h>) -> bool {
self.as_int() == other.as_int()
}
}
impl<'h> fmt::Debug for Outcome<'h> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Outcome::{}", self.as_str())
}
}
impl<'h> fmt::Display for Outcome<'h> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&Outcome::Complete => {
write!(f, "{}", Green.paint("Complete"))
},
&Outcome::FailStop => {
write!(f, "{}", Red.paint("Failed"))
},
&Outcome::FailForward(..) => {
write!(f, "{}", Yellow.paint("Forwarding"))
},
}
}
}

View File

@ -22,11 +22,12 @@ impl Redirect {
}
impl<'a> Responder for Redirect {
fn respond<'b>(&mut self, mut res: HyperResponse<'b, HyperFresh>) {
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> {
res.headers_mut().set(header::ContentLength(0));
res.headers_mut().set(header::Location(self.1.clone()));
*(res.status_mut()) = self.0;
res.send(b"").unwrap();
Outcome::Complete
}
}

View File

@ -8,24 +8,26 @@ use std::fmt;
// In particular, we want to try the next ranked route when when parsing
// parameters doesn't work out.
pub trait Responder {
fn respond<'a>(&mut self, mut res: HyperResponse<'a, HyperFresh>);
fn respond<'a>(&mut self, mut res: FreshHyperResponse<'a>) -> Outcome<'a>;
}
impl<'a> Responder for &'a str {
fn respond<'b>(&mut self, res: HyperResponse<'b, HyperFresh>) {
fn respond<'b>(&mut self, res: FreshHyperResponse<'b>) -> Outcome<'b> {
res.send(self.as_bytes()).unwrap();
Outcome::Complete
}
}
impl Responder for String {
fn respond<'b>(&mut self, res: HyperResponse<'b, HyperFresh>) {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a> {
res.send(self.as_bytes()).unwrap();
Outcome::Complete
}
}
// FIXME: Should we set a content-type here? Safari needs text/html to render.
impl Responder for File {
fn respond<'b>(&mut self, mut res: HyperResponse<'b, HyperFresh>) {
fn respond<'a>(&mut self, mut res: FreshHyperResponse<'a>) -> Outcome<'a> {
let size = self.metadata().unwrap().len();
res.headers_mut().set(header::ContentLength(size));
@ -36,36 +38,38 @@ impl Responder for File {
let mut stream = res.start().unwrap();
stream.write_all(&v).unwrap();
Outcome::Complete
}
}
impl<T: Responder> Responder for Option<T> {
fn respond<'b>(&mut self, res: HyperResponse<'b, HyperFresh>) {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a> {
if self.is_none() {
println!("Option is none.");
// TODO: Should this be a 404 or 500?
return Empty::new(StatusCode::NotFound).respond(res)
}
self.as_mut().unwrap().respond(res);
self.as_mut().unwrap().respond(res)
}
}
impl<T: Responder, E: fmt::Debug> Responder for Result<T, E> {
// prepend with `default` when using impl specialization
default fn respond<'b>(&mut self, res: HyperResponse<'b, HyperFresh>) {
default fn respond<'a>(&mut self, res: FreshHyperResponse<'a>)
-> Outcome<'a> {
if self.is_err() {
println!("Error: {:?}", self.as_ref().err().unwrap());
// TODO: Should this be a 404 or 500?
return Empty::new(StatusCode::NotFound).respond(res)
}
self.as_mut().unwrap().respond(res);
self.as_mut().unwrap().respond(res)
}
}
impl<T: Responder, E: Responder + fmt::Debug> Responder for Result<T, E> {
fn respond<'b>(&mut self, res: HyperResponse<'b, HyperFresh>) {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a> {
match self {
&mut Ok(ref mut responder) => responder.respond(res),
&mut Err(ref mut responder) => responder.respond(res)

View File

@ -15,9 +15,9 @@ impl<R: Responder> StatusResponse<R> {
}
impl<R: Responder> Responder for StatusResponse<R> {
fn respond<'b>(&mut self, mut res: HyperResponse<'b, HyperFresh>) {
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> {
*(res.status_mut()) = self.status;
self.responder.respond(res);
self.responder.respond(res)
}
}

View File

@ -1,5 +1,5 @@
use super::*;
use response::{HyperResponse, HyperFresh};
use response::FreshHyperResponse;
use request::HyperRequest;
use catcher;
@ -9,6 +9,7 @@ use term_painter::Color::*;
use term_painter::ToStyle;
use hyper::uri::RequestUri as HyperRequestUri;
use hyper::method::Method as HyperMethod;
use hyper::server::Server as HyperServer;
use hyper::server::Handler as HyperHandler;
@ -19,46 +20,94 @@ pub struct Rocket {
catchers: HashMap<u16, Catcher>,
}
fn uri_is_absolute(uri: &HyperRequestUri) -> bool {
match uri {
&HyperRequestUri::AbsolutePath(_) => true,
_ => false
}
}
fn unwrap_absolute_path<'a>(uri: &'a HyperRequestUri) -> &'a str {
match uri {
&HyperRequestUri::AbsolutePath(ref s) => s.as_str(),
_ => panic!("Can only accept absolute paths!")
}
}
fn method_is_valid(method: &HyperMethod) -> bool {
Method::from_hyp(method).is_some()
}
impl HyperHandler for Rocket {
fn handle<'a, 'k>(&'a self, mut req: HyperRequest<'a, 'k>,
res: HyperResponse<'a, HyperFresh>) {
println!("{:?} {:?}", Green.paint(&req.method), Blue.paint(&req.uri));
fn handle<'a, 'k>(&'a self, req: HyperRequest<'a, 'k>,
res: FreshHyperResponse<'a>) {
println!("{:?} '{}'", Green.paint(&req.method), Blue.paint(&req.uri));
let mut buf = vec![];
req.read_to_end(&mut buf); // FIXME: Simple DOS attack here.
if let HyperRequestUri::AbsolutePath(uri_string) = req.uri {
if let Some(method) = Method::from_hyp(req.method) {
let uri_str = uri_string.as_str();
let route = self.router.route(method, uri_str);
let finalize = |mut req: HyperRequest, _res: FreshHyperResponse| {
let mut buf = vec![];
// FIXME: Simple DOS attack here. Working around Hyper bug.
let _ = req.read_to_end(&mut buf);
};
if route.is_some() {
let route = route.unwrap();
let params = route.get_params(uri_str);
let request = Request::new(params, uri_str, &buf);
println!("{}", Green.paint("\t=> Dispatching request."));
// FIXME: Responder should be able to say it didn't work.
return (route.handler)(request).respond(res);
} else {
// FIXME: Try next highest ranking route, not just 404.
let request = Request::new(vec![], uri_str, &buf);
let handler_404 = self.catchers.get(&404).unwrap().handler;
let msg = "\t=> Dispatch failed. Returning 404.";
println!("{}", Red.paint(msg));
return handler_404(request).respond(res);
}
}
println!("{}", Yellow.paint("\t=> Debug: Method::from_hyp failed!"));
if !uri_is_absolute(&req.uri) {
println!("{}", Red.paint("\t=> Internal failure. Bad URI."));
println!("{} {:?}", Yellow.paint("\t=> Debug:"), req.uri);
return finalize(req, res);
}
println!("{}", Red.paint("\t=> Internal failure. Bad method or path."));
Response::server_error().respond(res);
if !method_is_valid(&req.method) {
println!("{}", Yellow.paint("\t=> Internal failure. Bad method."));
println!("{} {:?}", Yellow.paint("\t=> Debug:"), req.method);
return finalize(req, res);
}
return self.dispatch(req, res);
}
}
impl Rocket {
fn dispatch<'h, 'k>(&self, mut req: HyperRequest<'h, 'k>,
mut res: FreshHyperResponse<'h>) {
// We read all of the contents now because we have to do it at some
// point thanks to Hyper. FIXME: Simple DOS attack here.
let mut buf = vec![];
let _ = req.read_to_end(&mut buf);
// Extract the method, uri, and try to find a route.
let method = Method::from_hyp(&req.method).unwrap();
let uri = unwrap_absolute_path(&req.uri);
let route = self.router.route(method, uri);
// A closure which we call when we know there is no route.
let handle_not_found = |response: FreshHyperResponse| {
let request = Request::new(vec![], uri, &buf);
let handler_404 = self.catchers.get(&404).unwrap().handler;
println!("{}", Red.paint("\t<= Dispatch failed. Returning 404."));
handler_404(request).respond(response);
};
// No route found. Handle the not_found error and return.
if route.is_none() {
println!("{}", Red.paint("\t=> No matching routes."));
return handle_not_found(res);
}
// Okay, we've got a route. Unwrap it, generate a request, and try to
// dispatch. TODO: keep trying lower ranked routes before dispatching a
// not found error.
println!("\t=> {}", Magenta.paint("Dispatching request."));
let route = route.unwrap();
let params = route.get_params(uri);
let request = Request::new(params, uri, &buf);
let outcome = (route.handler)(request).respond(res);
println!("\t=> {} {}", White.paint("Outcome:"), outcome);
outcome.map_forward(|res| {
println!("{}", Red.paint("\t=> No further matching routes."));
handle_not_found(res);
});
}
pub fn new(address: &'static str, port: isize) -> Rocket {
Rocket {
address: address,
@ -68,7 +117,8 @@ impl Rocket {
}
}
pub fn mount(&mut self, base: &'static str, routes: Vec<Route>) -> &mut Self {
pub fn mount(&mut self, base: &'static str, routes: Vec<Route>)
-> &mut Self {
println!("🛰 {} '{}':", Magenta.paint("Mounting"), Blue.paint(base));
for mut route in routes {
let path = format!("{}/{}", base, route.path.as_str());

View File

@ -30,6 +30,8 @@ impl Router {
// TODO: Make a `Router` trait with this function. Rename this `Router`
// struct to something like `RocketRouter`. If that happens, returning a
// `Route` structure is inflexible. Have it be an associated type.
// FIXME: Figure out a way to get more than one route, i.e., to correctly
// handle ranking.
pub fn route<'b>(&'b self, method: Method, uri: &str) -> Option<&'b Route> {
let mut matched_route: Option<&Route> = None;
@ -37,7 +39,7 @@ impl Router {
let num_segments = path.segment_count();
if let Some(routes) = self.routes.get(&(method, num_segments)) {
for route in routes.iter().filter(|r| r.collides_with(uri)) {
println!("\t=> Matched {} to: {}", uri, route);
println!("\t=> {} {}", Magenta.paint("Matched:"), route);
if let Some(existing_route) = matched_route {
if route.rank > existing_route.rank {
matched_route = Some(route);

View File

@ -359,7 +359,7 @@ pub fn route_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
let param_fn_item = quote_stmt!(ecx,
let $param_ident: $param_ty = match _req.get_param($i) {
Ok(v) => v,
Err(_) => return rocket::Response::not_found()
Err(_) => return rocket::Response::forward()
};
).unwrap();