From 50bc0d69993b055578e6cb3763ffe0fe710d3fbe Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Fri, 1 Apr 2016 16:54:53 -0700 Subject: [PATCH] Major refactoring. Here's the idea: under the `Rocket` namespace should live things critical to writing simple Rocket apps: Request, Response, Error, etc. Nothing should be nested more than one level deep. Only items required for more complex things (implementing uncommon traits, etc.) should be nested one level deep. This commit is the first attempt at realizing this. --- examples/forms/src/main.rs | 2 +- examples/redirect/Cargo.toml | 8 +++ examples/redirect/src/main.rs | 20 ++++++ lib/Cargo.toml | 6 +- lib/src/lib.rs | 132 ++++------------------------------ lib/src/method.rs | 29 ++++---- lib/src/request.rs | 2 + lib/src/response/empty.rs | 2 +- lib/src/response/mod.rs | 10 +-- lib/src/response/redirect.rs | 2 +- lib/src/response/responder.rs | 14 ++-- lib/src/rocket.rs | 84 ++++++++++++++++++++++ lib/src/route.rs | 35 +++++++++ lib/src/router/collider.rs | 50 ++++++++++++- lib/src/router/mod.rs | 2 +- lib/src/router/route.rs | 27 +------ macros/src/route_decorator.rs | 2 +- 17 files changed, 250 insertions(+), 177 deletions(-) create mode 100644 examples/redirect/Cargo.toml create mode 100644 examples/redirect/src/main.rs create mode 100644 lib/src/rocket.rs create mode 100644 lib/src/route.rs diff --git a/examples/forms/src/main.rs b/examples/forms/src/main.rs index 2dc0f610..0e0e1e92 100644 --- a/examples/forms/src/main.rs +++ b/examples/forms/src/main.rs @@ -7,7 +7,7 @@ mod files; use rocket::Rocket; use rocket::response::Redirect; -use rocket::error::Error; +use rocket::Error; #[route(GET, path = "/user/")] fn user_page(username: &str) -> String { diff --git a/examples/redirect/Cargo.toml b/examples/redirect/Cargo.toml new file mode 100644 index 00000000..890ea22c --- /dev/null +++ b/examples/redirect/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "redirect" +version = "0.0.1" +authors = ["Sergio Benitez "] + +[dependencies] +rocket = { path = "../../lib" } +rocket_macros = { path = "../../macros" } diff --git a/examples/redirect/src/main.rs b/examples/redirect/src/main.rs new file mode 100644 index 00000000..69256c60 --- /dev/null +++ b/examples/redirect/src/main.rs @@ -0,0 +1,20 @@ +#![feature(plugin)] +#![plugin(rocket_macros)] +extern crate rocket; + +use rocket::Rocket; +use rocket::response::Redirect; + +#[route(GET, path = "/")] +fn root() -> Redirect { + Redirect::to("/login") +} + +#[route(GET, path = "/login")] +fn login() -> &'static str { + "Hi! Please log in before continuing." +} + +fn main() { + Rocket::new("localhost", 8000).mount_and_launch("/", routes![root, login]); +} diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 50e3b3f5..716eff84 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -4,5 +4,9 @@ version = "0.0.1" authors = ["Sergio Benitez "] [dependencies] -hyper = "*" term-painter = "*" +hyper = "*" + +# [dependencies.hyper] +# git = "https://github.com/hyperium/hyper.git" +# branch = "mio" diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 6ebd739c..b3a2457c 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -4,125 +4,21 @@ extern crate term_painter; extern crate hyper; -pub mod method; -pub mod error; -pub mod response; -pub mod request; -pub mod param; -pub mod router; +mod method; +mod error; +mod param; +mod router; +mod rocket; +mod route; + +pub mod request; +pub mod response; -pub use method::Method; -pub use error::Error; pub use request::Request; +pub use method::Method; +pub use response::{Response, Responder}; +pub use error::Error; pub use param::FromParam; pub use router::Router; -pub use response::{Response, HypResponse, Responder, HypFresh}; - -use std::fmt; -use std::io::Read; -use term_painter::ToStyle; -use term_painter::Color::*; -use hyper::uri::RequestUri; -use hyper::server::Handler as HypHandler; -use hyper::server::Request as HypRequest; -use hyper::Server; - -pub type Handler<'a> = fn(Request) -> Response<'a>; - -// TODO: Figure out if using 'static for Handler is a good idea. -// TODO: Merge this `Route` and route::Route, somewhow. -pub struct Route { - pub method: Method, - pub path: &'static str, - pub handler: Handler<'static> -} - -impl Route { - pub fn new(method: Method, path: &'static str, handler: Handler<'static>) - -> Route { - Route { - method: method, - path: path, - handler: handler - } - } -} - -impl<'a> fmt::Display for Route { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} {:?}", Green.paint(&self.method), Blue.paint(&self.path)) - } -} - -pub struct Rocket { - address: &'static str, - port: isize, - router: Router -} - -impl HypHandler for Rocket { - fn handle<'a, 'k>(&'a self, mut req: HypRequest<'a, 'k>, - res: HypResponse<'a, HypFresh>) { - println!("{} {:?} {:?}", White.paint("Incoming:"), - 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 RequestUri::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 mut response = route.map_or(Response::not_found(), |route| { - let params = route.get_params(uri_str); - let request = Request::new(params, uri_str); - (route.handler)(request) - }); - - println!("{}", Green.paint("\t=> Dispatched request.")); - return response.respond(res); - } - - println!("{}", Yellow.paint("\t=> Debug: Method::from_hyp failed!")); - } - - println!("{}", Red.paint("\t=> Dispatch failed. Returning 404.")); - Response::not_found().respond(res); - } -} - -impl Rocket { - pub fn new(address: &'static str, port: isize) -> Rocket { - Rocket { - address: address, - port: port, - router: Router::new() - } - } - - pub fn mount(&mut self, base: &'static str, routes: &[&Route]) -> &mut Self { - println!("🛰 {} '{}':", Magenta.paint("Mounting"), Blue.paint(base)); - for route in routes { - println!("\t* {}", route); - self.router.add_route(route.method, base, route.path, route.handler); - } - - self - } - - pub fn mount_and_launch(mut self, base: &'static str, routes: &[&Route]) { - self.mount(base, routes); - self.launch(); - } - - pub fn launch(self) { - if self.router.has_collisions() { - println!("{}", Yellow.paint("Warning: route collisions detected!")); - } - - let full_addr = format!("{}:{}", self.address, self.port); - println!("🚀 {} {}...", White.paint("Rocket has launched from"), - White.bold().paint(&full_addr)); - let _ = Server::http(full_addr.as_str()).unwrap().handle(self); - } -} +pub use route::{Route, Handler}; +pub use rocket::Rocket; diff --git a/lib/src/method.rs b/lib/src/method.rs index 8e272015..eeeafc36 100644 --- a/lib/src/method.rs +++ b/lib/src/method.rs @@ -1,8 +1,9 @@ +use super::*; use self::Method::*; + +use std::fmt; use std::str::FromStr; -use std::fmt::{self, Display}; -use error::Error; -use hyper::method::Method as HypMethod; +use hyper::method::Method as HyperMethod; #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub enum Method { @@ -18,17 +19,17 @@ pub enum Method { } impl Method { - pub fn from_hyp(method: HypMethod) -> Option { + pub fn from_hyp(method: HyperMethod) -> Option { match method { - HypMethod::Get => Some(Get), - HypMethod::Put => Some(Put), - HypMethod::Post => Some(Post), - HypMethod::Delete => Some(Delete), - HypMethod::Options => Some(Options), - HypMethod::Head => Some(Head), - HypMethod::Trace => Some(Trace), - HypMethod::Connect => Some(Connect), - HypMethod::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 } } @@ -53,7 +54,7 @@ impl FromStr for Method { } } -impl Display for Method { +impl fmt::Display for Method { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.write_str(match *self { Get => "GET", diff --git a/lib/src/request.rs b/lib/src/request.rs index 77cf0118..f04dfc17 100644 --- a/lib/src/request.rs +++ b/lib/src/request.rs @@ -1,6 +1,8 @@ use error::Error; use param::FromParam; +pub use hyper::server::Request as HyperRequest; + pub struct Request<'a> { params: Vec<&'a str>, uri: &'a str, diff --git a/lib/src/response/empty.rs b/lib/src/response/empty.rs index 2f70888f..efabb5c5 100644 --- a/lib/src/response/empty.rs +++ b/lib/src/response/empty.rs @@ -10,7 +10,7 @@ impl Empty { } impl Responder for Empty { - fn respond<'a>(&mut self, mut res: HypResponse<'a, HypFresh>) { + fn respond<'a>(&mut self, mut res: HyperResponse<'a, HyperFresh>) { res.headers_mut().set(header::ContentLength(0)); *(res.status_mut()) = self.0; diff --git a/lib/src/response/mod.rs b/lib/src/response/mod.rs index 650f582e..97178ba8 100644 --- a/lib/src/response/mod.rs +++ b/lib/src/response/mod.rs @@ -2,15 +2,15 @@ mod empty; mod responder; mod redirect; +pub use hyper::server::Response as HyperResponse; +pub use hyper::net::Fresh as HyperFresh; +pub use hyper::status::StatusCode; +pub use hyper::header; + pub use self::responder::Responder; pub use self::empty::Empty; pub use self::redirect::Redirect; -pub use hyper::server::Response as HypResponse; -pub use hyper::net::Fresh as HypFresh; -pub use hyper::status::StatusCode; -pub use hyper::header; - use std::ops::{Deref, DerefMut}; pub struct Response<'a>(Box); diff --git a/lib/src/response/redirect.rs b/lib/src/response/redirect.rs index 11ecf3f1..d2ab0a9d 100644 --- a/lib/src/response/redirect.rs +++ b/lib/src/response/redirect.rs @@ -22,7 +22,7 @@ impl Redirect { } impl<'a> Responder for Redirect { - fn respond<'b>(&mut self, mut res: HypResponse<'b, HypFresh>) { + fn respond<'b>(&mut self, mut res: HyperResponse<'b, HyperFresh>) { res.headers_mut().set(header::ContentLength(0)); res.headers_mut().set(header::Location(self.1.clone())); *(res.status_mut()) = self.0; diff --git a/lib/src/response/responder.rs b/lib/src/response/responder.rs index 994fab4f..f5666fa3 100644 --- a/lib/src/response/responder.rs +++ b/lib/src/response/responder.rs @@ -4,24 +4,24 @@ use std::fs::File; use std::fmt; pub trait Responder { - fn respond<'a>(&mut self, mut res: HypResponse<'a, HypFresh>); + fn respond<'a>(&mut self, mut res: HyperResponse<'a, HyperFresh>); } impl<'a> Responder for &'a str { - fn respond<'b>(&mut self, res: HypResponse<'b, HypFresh>) { + fn respond<'b>(&mut self, res: HyperResponse<'b, HyperFresh>) { res.send(self.as_bytes()).unwrap(); } } impl Responder for String { - fn respond<'b>(&mut self, res: HypResponse<'b, HypFresh>) { + fn respond<'b>(&mut self, res: HyperResponse<'b, HyperFresh>) { res.send(self.as_bytes()).unwrap(); } } // 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: HypResponse<'b, HypFresh>) { + fn respond<'b>(&mut self, mut res: HyperResponse<'b, HyperFresh>) { let size = self.metadata().unwrap().len(); res.headers_mut().set(header::ContentLength(size)); @@ -36,7 +36,7 @@ impl Responder for File { } impl Responder for Option { - fn respond<'b>(&mut self, res: HypResponse<'b, HypFresh>) { + fn respond<'b>(&mut self, res: HyperResponse<'b, HyperFresh>) { if self.is_none() { println!("Option is none."); // TODO: Should this be a 404 or 500? @@ -49,7 +49,7 @@ impl Responder for Option { impl Responder for Result { // prepend with `default` when using impl specialization - default fn respond<'b>(&mut self, res: HypResponse<'b, HypFresh>) { + default fn respond<'b>(&mut self, res: HyperResponse<'b, HyperFresh>) { if self.is_err() { println!("Error: {:?}", self.as_ref().err().unwrap()); // TODO: Should this be a 404 or 500? @@ -61,7 +61,7 @@ impl Responder for Result { } impl Responder for Result { - fn respond<'b>(&mut self, res: HypResponse<'b, HypFresh>) { + fn respond<'b>(&mut self, res: HyperResponse<'b, HyperFresh>) { match self { &mut Ok(ref mut responder) => responder.respond(res), &mut Err(ref mut responder) => responder.respond(res) diff --git a/lib/src/rocket.rs b/lib/src/rocket.rs new file mode 100644 index 00000000..699c9b45 --- /dev/null +++ b/lib/src/rocket.rs @@ -0,0 +1,84 @@ +use super::*; +use response::{HyperResponse, HyperFresh}; +use request::HyperRequest; + +use std::io::Read; +use term_painter::Color::*; +use term_painter::ToStyle; + +use hyper::uri::RequestUri as HyperRequestUri; +use hyper::server::Server as HyperServer; +use hyper::server::Handler as HyperHandler; + +pub struct Rocket { + address: &'static str, + port: isize, + router: Router +} + +impl HyperHandler for Rocket { + fn handle<'a, 'k>(&'a self, mut req: HyperRequest<'a, 'k>, + res: HyperResponse<'a, HyperFresh>) { + println!("{} {:?} {:?}", White.paint("Incoming:"), + 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 mut response = route.map_or(Response::not_found(), |route| { + let params = route.get_params(uri_str); + let request = Request::new(params, uri_str); + (route.handler)(request) + }); + + println!("{}", Green.paint("\t=> Dispatched request.")); + return response.respond(res); + } + + println!("{}", Yellow.paint("\t=> Debug: Method::from_hyp failed!")); + } + + println!("{}", Red.paint("\t=> Dispatch failed. Returning 404.")); + Response::not_found().respond(res); + } +} + +impl Rocket { + pub fn new(address: &'static str, port: isize) -> Rocket { + Rocket { + address: address, + port: port, + router: Router::new() + } + } + + pub fn mount(&mut self, base: &'static str, routes: &[&Route]) -> &mut Self { + println!("🛰 {} '{}':", Magenta.paint("Mounting"), Blue.paint(base)); + for route in routes { + println!("\t* {}", route); + self.router.add_route(route.method, base, route.path, route.handler); + } + + self + } + + pub fn mount_and_launch(mut self, base: &'static str, routes: &[&Route]) { + self.mount(base, routes); + self.launch(); + } + + pub fn launch(self) { + if self.router.has_collisions() { + println!("{}", Yellow.paint("Warning: route collisions detected!")); + } + + let full_addr = format!("{}:{}", self.address, self.port); + println!("🚀 {} {}...", White.paint("Rocket has launched from"), + White.bold().paint(&full_addr)); + let _ = HyperServer::http(full_addr.as_str()).unwrap().handle(self); + } +} diff --git a/lib/src/route.rs b/lib/src/route.rs new file mode 100644 index 00000000..d38ab175 --- /dev/null +++ b/lib/src/route.rs @@ -0,0 +1,35 @@ +use request::Request; +use response::Response; +use method::Method; + +use std::fmt; +use term_painter::Color::*; +use term_painter::ToStyle; + +pub type Handler<'a> = fn(Request) -> Response<'a>; + +// TODO: Figure out if using 'static for Handler is a good idea. +// TODO: Merge this `Route` and route::Route, somewhow. +pub struct Route { + pub method: Method, + pub path: &'static str, + pub handler: Handler<'static> +} + +impl Route { + pub fn new(method: Method, path: &'static str, handler: Handler<'static>) + -> Route { + Route { + method: method, + path: path, + handler: handler + } + } +} + +impl<'a> fmt::Display for Route { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {:?}", Green.paint(&self.method), Blue.paint(&self.path)) + } +} + diff --git a/lib/src/router/collider.rs b/lib/src/router/collider.rs index 69fa8d77..c0105e2f 100644 --- a/lib/src/router/collider.rs +++ b/lib/src/router/collider.rs @@ -1,4 +1,5 @@ use std::path::Component; +use std::path::Path; pub trait Collider { fn collides_with(&self, other: &T) -> bool; @@ -44,6 +45,31 @@ macro_rules! comp_to_str { ) } +impl Collider for Path { + // TODO: It's expensive to compute the number of components: O(n) per path + // where n == number of chars. + // + // Idea: Create a `CachedPath` type that caches the number of components + // similar to the way `Route` does it. + fn collides_with(&self, b: &Path) -> bool { + if self.components().count() != b.components().count() { + return false; + } + + let mut a_components = self.components(); + let mut b_components = b.components(); + while let Some(ref c1) = a_components.next() { + if let Some(ref c2) = b_components.next() { + if !c1.collides_with(c2) { + return false + } + } + } + + true + } +} + impl<'a> Collider for Component<'a> { fn collides_with(&self, other: &Component<'a>) -> bool { let (a, b) = (comp_to_str!(self), comp_to_str!(other)); @@ -51,6 +77,12 @@ impl<'a> Collider for Component<'a> { } } +impl<'a> Collider for &'a str { + fn collides_with(&self, other: &str) -> bool { + Path::new(self).collides_with(Path::new(other)) + } +} + #[cfg(test)] mod tests { use router::Collider; @@ -84,6 +116,10 @@ mod tests { route_a.collides_with(b) } + fn s_s_collide(a: &'static str, b: &'static str) -> bool { + a.collides_with(b) + } + #[test] fn simple_collisions() { assert!(collide("a", "a")); @@ -177,7 +213,19 @@ mod tests { assert!(!r_s_collide("/a/", "/b/")); } + #[test] fn test_str_collisions() { - // FIXME: Write these. + assert!(!s_s_collide("/a", "/b")); + assert!(!s_s_collide("/a/b", "/a")); + assert!(!s_s_collide("/a/b", "/a/c")); + assert!(!s_s_collide("/a/hello", "/a/c")); + assert!(!s_s_collide("/hello", "/a/c")); + assert!(!s_s_collide("/hello/there", "/hello/there/guy")); + assert!(!s_s_collide("/b/there", "/hi/there")); + assert!(!s_s_collide("//c", "/hi/person")); + assert!(!s_s_collide("//cd", "/hi/e")); + assert!(!s_s_collide("/a/", "/b/")); + assert!(!s_s_collide("/a/", "/b/")); + assert!(!s_s_collide("/a/", "/b/")); } } diff --git a/lib/src/router/mod.rs b/lib/src/router/mod.rs index b636a7ab..e23378b3 100644 --- a/lib/src/router/mod.rs +++ b/lib/src/router/mod.rs @@ -9,7 +9,7 @@ use self::route::Route; use std::collections::hash_map::HashMap; use std::path::Path; use method::Method; -use Handler; +use route::Handler; type Selector = (Method, usize); diff --git a/lib/src/router/route.rs b/lib/src/router/route.rs index 77f8b5d5..69b6d90e 100644 --- a/lib/src/router/route.rs +++ b/lib/src/router/route.rs @@ -5,7 +5,7 @@ use std::fmt; use method::Method; use super::Collider; // :D use std::path::Component; -use Handler; +use route::Handler; // TODO: Add ranking to routes. Give static routes higher rank by default. // FIXME: Take in the handler! Or maybe keep that in `Router`? @@ -89,31 +89,6 @@ impl fmt::Display for Route { } } -impl Collider for Path { - // TODO: It's expensive to compute the number of components: O(n) per path - // where n == number of chars. - // - // Idea: Create a `CachedPath` type that caches the number of components - // similar to the way `Route` does it. - fn collides_with(&self, b: &Path) -> bool { - if self.components().count() != b.components().count() { - return false; - } - - let mut a_components = self.components(); - let mut b_components = b.components(); - while let Some(ref c1) = a_components.next() { - if let Some(ref c2) = b_components.next() { - if !c1.collides_with(c2) { - return false - } - } - } - - true - } -} - impl Collider for Route { fn collides_with(&self, b: &Route) -> bool { if self.n_components != b.n_components || self.method != b.method { diff --git a/macros/src/route_decorator.rs b/macros/src/route_decorator.rs index 4253fc62..a709ac72 100644 --- a/macros/src/route_decorator.rs +++ b/macros/src/route_decorator.rs @@ -6,7 +6,7 @@ use std::collections::HashSet; use syntax::ext::quote::rt::ToTokens; use syntax::codemap::{Span, BytePos, DUMMY_SP, Spanned}; -use syntax::ast::{self, Ident, TokenTree, PatKind, Stmt}; +use syntax::ast::{Ident, TokenTree, PatKind, Stmt}; use syntax::ast::{Item, Expr, ItemKind, MetaItem, MetaItemKind, FnDecl, Ty}; use syntax::ext::base::{Annotatable, ExtCtxt}; use syntax::ptr::P;