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]
hyper = "*"
term-painter = "*"

View File

@ -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,24 +35,33 @@ 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<Route<'static>>, // 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);
}
}
impl Rocket {
@ -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);
}
}

View File

@ -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<Method> {
match method {
HypMethod::Get => Some(Get),
_ => None
}
}
}
impl FromStr for Method {
type Err = Error;
fn from_str(s: &str) -> Result<Method, Error> {
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",

View File

@ -89,10 +89,9 @@ impl Responder for File {
}
}
// TODO: Allow streamed responses.
// const CHUNK_SIZE: u32 = 4096;
// pub struct Stream<T: Read>(T);
// impl<T> Responder for Stream<T> {
// 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]);

View File

@ -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<a>/there", "/hi/there"));
assert!(!s_collide("/<a>/<b>c", "/hi/person"));
assert!(!s_collide("/<a>/<b>cd", "/hi/<a>e"));
assert!(!s_collide("/a<a>/<b>", "/b<b>/<a>"));
assert!(!s_collide("/a/<b>", "/b/<b>"));
assert!(!s_collide("/a<a>/<b>", "/b/<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<a>/there", "/hi/there"));
assert!(!s_r_collide("/<a>/<b>c", "/hi/person"));
assert!(!s_r_collide("/<a>/<b>cd", "/hi/<a>e"));
assert!(!s_r_collide("/a<a>/<b>", "/b<b>/<a>"));
assert!(!s_r_collide("/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() {

View File

@ -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<Route> // for now, to check for collisions
routes: HashMap<Selector, Vec<Route>> // 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<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;
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);
}
}
}

View File

@ -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<Route> for &'a str {
path.collides_with(&other.path)
}
}
impl Collider<str> for Route {
fn collides_with(&self, other: &str) -> bool {
other.collides_with(self)
}
}