mirror of https://github.com/rwf2/Rocket.git
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:
parent
b2fbf0d6bd
commit
5cdb645fc9
|
@ -5,3 +5,4 @@ authors = ["Sergio Benitez <sb@sergio.bz>"]
|
|||
|
||||
[dependencies]
|
||||
hyper = "*"
|
||||
term-painter = "*"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue