From 5cdb645fc93478ace497423761f7d1cc2a6ed8db Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Mon, 21 Mar 2016 02:22:22 -0700 Subject: [PATCH] Routing is functional! Sort-of. Pretty console colors. Okay, so, given a URI, we can figure out which route is corresponds to. Unfortunately, the handler is not yet part of that route, and we're not parsing the parameters from the path quite yet. But, we're almost there! --- lib/Cargo.toml | 1 + lib/src/lib.rs | 41 ++++++++++++++++++------------ lib/src/method.rs | 20 +++++++++++---- lib/src/response.rs | 41 +----------------------------- lib/src/router/collider.rs | 43 ++++++++++++++++++++++---------- lib/src/router/mod.rs | 51 ++++++++++++++++++++++---------------- lib/src/router/route.rs | 21 +++++++++++++--- 7 files changed, 119 insertions(+), 99 deletions(-) diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 7c75dbcc..50e3b3f5 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -5,3 +5,4 @@ authors = ["Sergio Benitez "] [dependencies] hyper = "*" +term-painter = "*" diff --git a/lib/src/lib.rs b/lib/src/lib.rs index cc9721fc..8dd77b0c 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,5 +1,6 @@ #![feature(str_char)] +extern crate term_painter; extern crate hyper; pub mod method; @@ -16,6 +17,10 @@ pub use request::Request; pub use param::FromParam; pub use router::Router; +use std::fmt; +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; @@ -30,23 +35,32 @@ pub struct Route<'a> { pub handler: Handler<'a> } +impl<'a> fmt::Display for Route<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {:?}", Green.paint(&self.method), Blue.paint(&self.path)) + } +} + #[allow(dead_code)] pub struct Rocket { address: &'static str, port: isize, - handler: Option>, // just for testing router: Router } impl HypHandler for Rocket { fn handle<'a, 'k>(&'a self, req: HypRequest<'a, 'k>, res: HypResponse<'a, HypFresh>) { - println!("Request: {:?}", req.uri); - if self.handler.is_some() { - let handler = self.handler.as_ref(); - let mut response = (handler.unwrap().handler)(Request::empty()); - response.body.respond(res); + if let RequestUri::AbsolutePath(uri_string) = req.uri { + if let Some(method) = Method::from_hyp(req.method) { + println!("Request: {:?}", uri_string); + self.router.route(method, uri_string.as_str()); + res.send(b"Hello, world!").unwrap(); + return; + } } + + Response::not_found().body.respond(res); } } @@ -55,21 +69,15 @@ impl Rocket { Rocket { address: address, port: port, - handler: None, router: Router::new() } } pub fn mount(&mut self, base: &'static str, routes: &[&Route<'static>]) -> &mut Self { - println!("🛰 Mounting '{}':", base); + println!("🛰 {} '{}':", Magenta.paint("Mounting"), Blue.paint(base)); for route in routes { - if self.handler.is_none() { - println!("\t* INSTALLED: {} '{}'", route.method, route.path); - self.handler = Some((*route).clone()); - } - - println!("\t* {} '{}'", route.method, route.path); + println!("\t* {}", route); self.router.add_route(route.method.clone(), base, route.path); } @@ -84,11 +92,12 @@ impl Rocket { pub fn launch(self) { if self.router.has_collisions() { - println!("Warning: route collisions detected!"); + println!("{}", Yellow.paint("Warning: route collisions detected!")); } let full_addr = format!("{}:{}", self.address, self.port); - println!("🚀 Rocket has launched from {}...", full_addr); + println!("🚀 {} {}...", White.paint("Rocket has launched from"), + White.bold().paint(&full_addr)); let _ = Server::http(full_addr.as_str()).unwrap().handle(self); } } diff --git a/lib/src/method.rs b/lib/src/method.rs index 80680141..d3d1f41a 100644 --- a/lib/src/method.rs +++ b/lib/src/method.rs @@ -2,8 +2,9 @@ use self::Method::*; use std::str::FromStr; use std::fmt::{self, Display}; use error::Error; +use hyper::method::Method as HypMethod; -#[derive(Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub enum Method { Get, Put, @@ -16,16 +17,25 @@ pub enum Method { Patch } +impl Method { + pub fn from_hyp(method: HypMethod) -> Option { + match method { + HypMethod::Get => Some(Get), + _ => None + } + } +} + impl FromStr for Method { type Err = Error; fn from_str(s: &str) -> Result { match s { - "OPTIONS" => Ok(Options), "GET" => Ok(Get), - "POST" => Ok(Post), "PUT" => Ok(Put), + "POST" => Ok(Post), "DELETE" => Ok(Delete), + "OPTIONS" => Ok(Options), "HEAD" => Ok(Head), "TRACE" => Ok(Trace), "CONNECT" => Ok(Connect), @@ -38,11 +48,11 @@ impl FromStr for Method { impl Display for Method { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.write_str(match *self { - Options => "OPTIONS", Get => "GET", - Post => "POST", Put => "PUT", + Post => "POST", Delete => "DELETE", + Options => "OPTIONS", Head => "HEAD", Trace => "TRACE", Connect => "CONNECT", diff --git a/lib/src/response.rs b/lib/src/response.rs index c9be8a3a..9c9c51a0 100644 --- a/lib/src/response.rs +++ b/lib/src/response.rs @@ -89,10 +89,9 @@ impl Responder for File { } } +// TODO: Allow streamed responses. // const CHUNK_SIZE: u32 = 4096; - // pub struct Stream(T); - // impl Responder for Stream { // fn respond<'a>(&self, mut r: HypResponse<'a, HypFresh>) { // r.headers_mut().set(header::TransferEncoding(vec![Encoding::Chunked])); @@ -107,41 +106,3 @@ impl Responder for File { // } // } // } - -// macro_rules! impl_from_lengthed { -// ($name:ident, $T:ty) => ( -// impl<'a> From<$T> for Response<'a> { -// fn from(s: $T) -> Self { -// Response { -// status: StatusCode::Ok, -// headers: Headers::new(), -// body: Body::$name(s) -// } -// } -// } -// ) -// } - -// impl_from_lengthed!(Str, &'a str); -// impl_from_lengthed!(String, String); -// impl_from_lengthed!(Bytes, &'a [u8]); -// impl_from_lengthed!(File, File); - -// macro_rules! impl_from_reader { -// ($T:ty) => ( -// impl<'a> From<&'a $T> for Response<'a> { -// fn from(r: &'a $T) -> Self { -// let mut headers = Headers::new(); -// headers.set(header::TransferEncoding(vec![Encoding::Chunked])); -// Response { -// status: StatusCode::Ok, -// headers: headers, -// body: Body::Stream(r) -// } -// } -// } -// ) -// } - -// impl_from_reader!(File); -// impl_from_reader!(&'a [u8]); diff --git a/lib/src/router/collider.rs b/lib/src/router/collider.rs index 2f172262..25ac9470 100644 --- a/lib/src/router/collider.rs +++ b/lib/src/router/collider.rs @@ -65,10 +65,15 @@ mod tests { route_a.collides_with(&Route::new(Get, "/", b)) } - fn s_collide(a: &'static str, b: &'static str) -> bool { + fn s_r_collide(a: &'static str, b: &'static str) -> bool { a.collides_with(&Route::new(Get, "/", b)) } + fn r_s_collide(a: &'static str, b: &'static str) -> bool { + let route_a = Route::new(Get, "/", a); + route_a.collides_with(b) + } + #[test] fn simple_collisions() { assert!(collide("a", "a")); @@ -136,18 +141,30 @@ mod tests { #[test] fn test_str_non_collisions() { - assert!(!s_collide("/a", "/b")); - assert!(!s_collide("/a/b", "/a")); - assert!(!s_collide("/a/b", "/a/c")); - assert!(!s_collide("/a/hello", "/a/c")); - assert!(!s_collide("/hello", "/a/c")); - assert!(!s_collide("/hello/there", "/hello/there/guy")); - assert!(!s_collide("/b/there", "/hi/there")); - assert!(!s_collide("//c", "/hi/person")); - assert!(!s_collide("//cd", "/hi/e")); - assert!(!s_collide("/a/", "/b/")); - assert!(!s_collide("/a/", "/b/")); - assert!(!s_collide("/a/", "/b/")); + assert!(!s_r_collide("/a", "/b")); + assert!(!s_r_collide("/a/b", "/a")); + assert!(!s_r_collide("/a/b", "/a/c")); + assert!(!s_r_collide("/a/hello", "/a/c")); + assert!(!s_r_collide("/hello", "/a/c")); + assert!(!s_r_collide("/hello/there", "/hello/there/guy")); + assert!(!s_r_collide("/b/there", "/hi/there")); + assert!(!s_r_collide("//c", "/hi/person")); + assert!(!s_r_collide("//cd", "/hi/e")); + assert!(!s_r_collide("/a/", "/b/")); + assert!(!s_r_collide("/a/", "/b/")); + assert!(!s_r_collide("/a/", "/b/")); + assert!(!r_s_collide("/a", "/b")); + assert!(!r_s_collide("/a/b", "/a")); + assert!(!r_s_collide("/a/b", "/a/c")); + assert!(!r_s_collide("/a/hello", "/a/c")); + assert!(!r_s_collide("/hello", "/a/c")); + assert!(!r_s_collide("/hello/there", "/hello/there/guy")); + assert!(!r_s_collide("/b/there", "/hi/there")); + assert!(!r_s_collide("//c", "/hi/person")); + assert!(!r_s_collide("//cd", "/hi/e")); + assert!(!r_s_collide("/a/", "/b/")); + assert!(!r_s_collide("/a/", "/b/")); + assert!(!r_s_collide("/a/", "/b/")); } fn test_str_collisions() { diff --git a/lib/src/router/mod.rs b/lib/src/router/mod.rs index f3909bb0..69b2f3de 100644 --- a/lib/src/router/mod.rs +++ b/lib/src/router/mod.rs @@ -3,52 +3,59 @@ mod route; pub use self::collider::Collider; +use term_painter::ToStyle; +use term_painter::Color::*; use self::route::Route; use std::collections::hash_map::HashMap; +use std::path::Path; use method::Method; +type Selector = (Method, usize); + pub struct Router { - routes: Vec // for now, to check for collisions + routes: HashMap> // for now } impl Router { pub fn new() -> Router { Router { - routes: Vec::new() + routes: HashMap::new() } } + // FIXME: Take in Handler. pub fn add_route(&mut self, method: Method, base: &'static str, route: &'static str) { let route = Route::new(method, base, route); - self.routes.push(route); + let selector = (method, route.component_count()); + self.routes.entry(selector).or_insert(vec![]).push(route); + } + + // TODO: Make a `Router` trait with this function. Rename this `Router` + // struct to something like `RocketRouter`. + // TODO: Return an array of matches to the parameters. + pub fn route<'a>(&self, method: Method, uri: &'a str) { + let path = Path::new(uri); + let num_components = path.components().count(); + if let Some(routes) = self.routes.get(&(method, num_components)) { + for route in routes { + if route.collides_with(uri) { + println!("Matched {} to: {}", uri, route); + } + } + } } pub fn has_collisions(&self) -> bool { - let mut map: HashMap> = HashMap::new(); - - for route in &self.routes { - let num_components = route.component_count(); - let mut list = if map.contains_key(&num_components) { - map.get_mut(&num_components).unwrap() - } else { - map.insert(num_components, Vec::new()); - map.get_mut(&num_components).unwrap() - }; - - list.push(&route); - } - let mut result = false; - for (_, routes) in map { + for (_, routes) in &self.routes { for i in 0..routes.len() { - for j in 0..routes.len() { - if i == j { continue } - + for j in (i + 1)..routes.len() { let (a_route, b_route) = (&routes[i], &routes[j]); if a_route.collides_with(b_route) { result = true; - println!("{:?} and {:?} collide!", a_route, b_route); + println!("{} {} and {} collide!", + Yellow.bold().paint("Warning:"), a_route, b_route); } } } diff --git a/lib/src/router/route.rs b/lib/src/router/route.rs index 39d6799d..830b7475 100644 --- a/lib/src/router/route.rs +++ b/lib/src/router/route.rs @@ -1,7 +1,11 @@ +use term_painter::ToStyle; +use term_painter::Color::*; use std::path::{Path, PathBuf}; +use std::fmt; use method::Method; use super::Collider; // :D +// FIXME: Take in the handler! Or maybe keep that in `Router`? #[derive(Debug)] pub struct Route { method: Method, @@ -45,10 +49,15 @@ impl Route { } } +impl fmt::Display for Route { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {:?}", Green.paint(&self.method), Blue.paint(&self.path)) + } +} + impl Collider for Path { - // FIXME: This assume that a and b have the same number of componenets. - // This is because it's expensive to compute the number of componenets: O(n) - // per path where n == number of chars. + // FIXME: 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. @@ -87,3 +96,9 @@ impl<'a> Collider for &'a str { path.collides_with(&other.path) } } + +impl Collider for Route { + fn collides_with(&self, other: &str) -> bool { + other.collides_with(self) + } +}