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!
This commit is contained in:
Sergio Benitez 2016-03-21 02:22:22 -07:00
parent b2fbf0d6bd
commit 5cdb645fc9
7 changed files with 119 additions and 99 deletions

View File

@ -5,3 +5,4 @@ authors = ["Sergio Benitez <sb@sergio.bz>"]
[dependencies] [dependencies]
hyper = "*" hyper = "*"
term-painter = "*"

View File

@ -1,5 +1,6 @@
#![feature(str_char)] #![feature(str_char)]
extern crate term_painter;
extern crate hyper; extern crate hyper;
pub mod method; pub mod method;
@ -16,6 +17,10 @@ pub use request::Request;
pub use param::FromParam; pub use param::FromParam;
pub use router::Router; 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::Handler as HypHandler;
use hyper::server::Request as HypRequest; use hyper::server::Request as HypRequest;
use hyper::Server; use hyper::Server;
@ -30,23 +35,32 @@ pub struct Route<'a> {
pub handler: Handler<'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)] #[allow(dead_code)]
pub struct Rocket { pub struct Rocket {
address: &'static str, address: &'static str,
port: isize, port: isize,
handler: Option<Route<'static>>, // just for testing
router: Router router: Router
} }
impl HypHandler for Rocket { impl HypHandler for Rocket {
fn handle<'a, 'k>(&'a self, req: HypRequest<'a, 'k>, fn handle<'a, 'k>(&'a self, req: HypRequest<'a, 'k>,
res: HypResponse<'a, HypFresh>) { res: HypResponse<'a, HypFresh>) {
println!("Request: {:?}", req.uri); if let RequestUri::AbsolutePath(uri_string) = req.uri {
if self.handler.is_some() { if let Some(method) = Method::from_hyp(req.method) {
let handler = self.handler.as_ref(); println!("Request: {:?}", uri_string);
let mut response = (handler.unwrap().handler)(Request::empty()); self.router.route(method, uri_string.as_str());
response.body.respond(res); res.send(b"Hello, world!").unwrap();
return;
}
} }
Response::not_found().body.respond(res);
} }
} }
@ -55,21 +69,15 @@ impl Rocket {
Rocket { Rocket {
address: address, address: address,
port: port, port: port,
handler: None,
router: Router::new() router: Router::new()
} }
} }
pub fn mount(&mut self, base: &'static str, routes: &[&Route<'static>]) pub fn mount(&mut self, base: &'static str, routes: &[&Route<'static>])
-> &mut Self { -> &mut Self {
println!("🛰 Mounting '{}':", base); println!("🛰 {} '{}':", Magenta.paint("Mounting"), Blue.paint(base));
for route in routes { for route in routes {
if self.handler.is_none() { println!("\t* {}", route);
println!("\t* INSTALLED: {} '{}'", route.method, route.path);
self.handler = Some((*route).clone());
}
println!("\t* {} '{}'", route.method, route.path);
self.router.add_route(route.method.clone(), base, route.path); self.router.add_route(route.method.clone(), base, route.path);
} }
@ -84,11 +92,12 @@ impl Rocket {
pub fn launch(self) { pub fn launch(self) {
if self.router.has_collisions() { 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); 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); let _ = Server::http(full_addr.as_str()).unwrap().handle(self);
} }
} }

View File

@ -2,8 +2,9 @@ use self::Method::*;
use std::str::FromStr; use std::str::FromStr;
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use error::Error; 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 { pub enum Method {
Get, Get,
Put, Put,
@ -16,16 +17,25 @@ pub enum Method {
Patch Patch
} }
impl Method {
pub fn from_hyp(method: HypMethod) -> Option<Method> {
match method {
HypMethod::Get => Some(Get),
_ => None
}
}
}
impl FromStr for Method { impl FromStr for Method {
type Err = Error; type Err = Error;
fn from_str(s: &str) -> Result<Method, Error> { fn from_str(s: &str) -> Result<Method, Error> {
match s { match s {
"OPTIONS" => Ok(Options),
"GET" => Ok(Get), "GET" => Ok(Get),
"POST" => Ok(Post),
"PUT" => Ok(Put), "PUT" => Ok(Put),
"POST" => Ok(Post),
"DELETE" => Ok(Delete), "DELETE" => Ok(Delete),
"OPTIONS" => Ok(Options),
"HEAD" => Ok(Head), "HEAD" => Ok(Head),
"TRACE" => Ok(Trace), "TRACE" => Ok(Trace),
"CONNECT" => Ok(Connect), "CONNECT" => Ok(Connect),
@ -38,11 +48,11 @@ impl FromStr for Method {
impl Display for Method { impl Display for Method {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str(match *self { fmt.write_str(match *self {
Options => "OPTIONS",
Get => "GET", Get => "GET",
Post => "POST",
Put => "PUT", Put => "PUT",
Post => "POST",
Delete => "DELETE", Delete => "DELETE",
Options => "OPTIONS",
Head => "HEAD", Head => "HEAD",
Trace => "TRACE", Trace => "TRACE",
Connect => "CONNECT", Connect => "CONNECT",

View File

@ -89,10 +89,9 @@ impl Responder for File {
} }
} }
// TODO: Allow streamed responses.
// const CHUNK_SIZE: u32 = 4096; // const CHUNK_SIZE: u32 = 4096;
// pub struct Stream<T: Read>(T); // pub struct Stream<T: Read>(T);
// impl<T> Responder for Stream<T> { // impl<T> Responder for Stream<T> {
// fn respond<'a>(&self, mut r: HypResponse<'a, HypFresh>) { // fn respond<'a>(&self, mut r: HypResponse<'a, HypFresh>) {
// r.headers_mut().set(header::TransferEncoding(vec![Encoding::Chunked])); // 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]);

View File

@ -65,10 +65,15 @@ mod tests {
route_a.collides_with(&Route::new(Get, "/", b)) 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)) 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] #[test]
fn simple_collisions() { fn simple_collisions() {
assert!(collide("a", "a")); assert!(collide("a", "a"));
@ -136,18 +141,30 @@ mod tests {
#[test] #[test]
fn test_str_non_collisions() { fn test_str_non_collisions() {
assert!(!s_collide("/a", "/b")); assert!(!s_r_collide("/a", "/b"));
assert!(!s_collide("/a/b", "/a")); assert!(!s_r_collide("/a/b", "/a"));
assert!(!s_collide("/a/b", "/a/c")); assert!(!s_r_collide("/a/b", "/a/c"));
assert!(!s_collide("/a/hello", "/a/c")); assert!(!s_r_collide("/a/hello", "/a/c"));
assert!(!s_collide("/hello", "/a/c")); assert!(!s_r_collide("/hello", "/a/c"));
assert!(!s_collide("/hello/there", "/hello/there/guy")); assert!(!s_r_collide("/hello/there", "/hello/there/guy"));
assert!(!s_collide("/b<a>/there", "/hi/there")); assert!(!s_r_collide("/b<a>/there", "/hi/there"));
assert!(!s_collide("/<a>/<b>c", "/hi/person")); assert!(!s_r_collide("/<a>/<b>c", "/hi/person"));
assert!(!s_collide("/<a>/<b>cd", "/hi/<a>e")); assert!(!s_r_collide("/<a>/<b>cd", "/hi/<a>e"));
assert!(!s_collide("/a<a>/<b>", "/b<b>/<a>")); assert!(!s_r_collide("/a<a>/<b>", "/b<b>/<a>"));
assert!(!s_collide("/a/<b>", "/b/<b>")); assert!(!s_r_collide("/a/<b>", "/b/<b>"));
assert!(!s_collide("/a<a>/<b>", "/b/<b>")); assert!(!s_r_collide("/a<a>/<b>", "/b/<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<a>/there", "/hi/there"));
assert!(!r_s_collide("/<a>/<b>c", "/hi/person"));
assert!(!r_s_collide("/<a>/<b>cd", "/hi/<a>e"));
assert!(!r_s_collide("/a<a>/<b>", "/b<b>/<a>"));
assert!(!r_s_collide("/a/<b>", "/b/<b>"));
assert!(!r_s_collide("/a<a>/<b>", "/b/<b>"));
} }
fn test_str_collisions() { fn test_str_collisions() {

View File

@ -3,52 +3,59 @@ mod route;
pub use self::collider::Collider; pub use self::collider::Collider;
use term_painter::ToStyle;
use term_painter::Color::*;
use self::route::Route; use self::route::Route;
use std::collections::hash_map::HashMap; use std::collections::hash_map::HashMap;
use std::path::Path;
use method::Method; use method::Method;
type Selector = (Method, usize);
pub struct Router { pub struct Router {
routes: Vec<Route> // for now, to check for collisions routes: HashMap<Selector, Vec<Route>> // for now
} }
impl Router { impl Router {
pub fn new() -> Router { pub fn new() -> Router {
Router { Router {
routes: Vec::new() routes: HashMap::new()
} }
} }
// FIXME: Take in Handler.
pub fn add_route(&mut self, method: Method, base: &'static str, pub fn add_route(&mut self, method: Method, base: &'static str,
route: &'static str) { route: &'static str) {
let route = Route::new(method, base, route); 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 { pub fn has_collisions(&self) -> bool {
let mut map: HashMap<usize, Vec<&Route>> = 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; let mut result = false;
for (_, routes) in map { for (_, routes) in &self.routes {
for i in 0..routes.len() { for i in 0..routes.len() {
for j in 0..routes.len() { for j in (i + 1)..routes.len() {
if i == j { continue }
let (a_route, b_route) = (&routes[i], &routes[j]); let (a_route, b_route) = (&routes[i], &routes[j]);
if a_route.collides_with(b_route) { if a_route.collides_with(b_route) {
result = true; result = true;
println!("{:?} and {:?} collide!", a_route, b_route); println!("{} {} and {} collide!",
Yellow.bold().paint("Warning:"), a_route, b_route);
} }
} }
} }

View File

@ -1,7 +1,11 @@
use term_painter::ToStyle;
use term_painter::Color::*;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::fmt;
use method::Method; use method::Method;
use super::Collider; // :D use super::Collider; // :D
// FIXME: Take in the handler! Or maybe keep that in `Router`?
#[derive(Debug)] #[derive(Debug)]
pub struct Route { pub struct Route {
method: Method, 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 { impl Collider for Path {
// FIXME: This assume that a and b have the same number of componenets. // FIXME: It's expensive to compute the number of components: O(n) per path
// This is because it's expensive to compute the number of componenets: O(n) // where n == number of chars.
// per path where n == number of chars.
// //
// Idea: Create a `CachedPath` type that caches the number of components // Idea: Create a `CachedPath` type that caches the number of components
// similar to the way `Route` does it. // similar to the way `Route` does it.
@ -87,3 +96,9 @@ impl<'a> Collider<Route> for &'a str {
path.collides_with(&other.path) path.collides_with(&other.path)
} }
} }
impl Collider<str> for Route {
fn collides_with(&self, other: &str) -> bool {
other.collides_with(self)
}
}