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;