use futures::{future, IntoFuture};
use hyper::error::Error;
use hyper::rt::Future;
use hyper::service::{ Service};
use hyper::{Body, Request, Response, Method, StatusCode};
use std::collections::BTreeMap;
use tree::Node;
use path::clean_path;
use tokio_fs;
use tokio_io;
use hyper;
use std::ops::Index;
use std::path::Path;
pub type BoxFut = Box<Future<Item = Response<Body>, Error = hyper::Error> + Send>;
pub trait Handle {
fn handle(&self, req: Request<Body>, ps: Params) -> BoxFut;
}
impl<F> Handle for F where F: Fn(Request<Body>, Params) -> BoxFut {
fn handle(&self, req: Request<Body>, ps: Params) -> BoxFut {
(*self)(req, ps)
}
}
pub type Handler = Box<Handle + Send>;
#[derive(Debug, Clone, PartialEq)]
pub struct Param {
pub key: String,
pub value: String,
}
impl Param {
pub fn new(key: &str, value: &str) -> Param {
Param {
key: key.to_string(),
value: value.to_string(),
}
}
}
#[derive(Debug, PartialEq)]
pub struct Params(pub Vec<Param>);
impl Params {
pub fn by_name(&self, name: &str) -> Option<&str> {
match self.0.iter().find(|param| param.key == name) {
Some(param) => Some(¶m.value),
None => None,
}
}
pub fn new() -> Params{
Params(Vec::new())
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn push(&mut self, p: Param) {
self.0.push(p);
}
}
impl Index<usize> for Params {
type Output = str;
fn index(&self, i: usize) -> &Self::Output{
&(self.0)[i].value
}
}
#[allow(dead_code)]
pub struct Router<T> {
pub trees: BTreeMap<String, Node<T>>,
redirect_trailing_slash: bool,
redirect_fixed_path: bool,
handle_method_not_allowed: bool,
handle_options: bool,
not_found: Option<T>,
method_not_allowed: Option<T>,
panic_handler: Option<T>,
}
impl<T> Router<T> {
pub fn new() -> Router<T>{
Router {
trees: BTreeMap::new(),
redirect_trailing_slash: true,
redirect_fixed_path: true,
handle_method_not_allowed: true,
handle_options: true,
not_found: None,
method_not_allowed: None,
panic_handler: None,
}
}
pub fn get(&mut self, path: &str, handle: T) {
self.handle("GET", path, handle);
}
pub fn head(&mut self, path: &str, handle: T) {
self.handle("HEAD", path, handle);
}
pub fn options(&mut self, path: &str, handle: T) {
self.handle("OPTIONS", path, handle);
}
pub fn post(&mut self, path: &str, handle: T) {
self.handle("POST", path, handle);
}
pub fn put(&mut self, path: &str, handle: T) {
self.handle("PUT", path, handle);
}
pub fn patch(&mut self, path: &str, handle: T) {
self.handle("PATCH", path, handle);
}
pub fn delete(&mut self, path: &str, handle: T) {
self.handle("DELETE", path, handle);
}
pub fn group() {
unimplemented!()
}
pub fn handle(&mut self, method: &str, path: &str, handle: T) {
if !path.starts_with("/") {
panic!("path must begin with '/' in path '{}'", path);
}
self.trees
.entry(method.to_string())
.or_insert(Node::new())
.add_route(path, handle);
}
pub fn lookup(&mut self, method: &str, path: &str) -> (Option<&T>, Params, bool) {
self.trees
.get_mut(method)
.and_then(|n| Some(n.get_value(path)))
.unwrap_or((None, Params::new(), false))
}
pub fn allowed(&self, path: &str, req_method: &str)-> String {
let mut allow = String::new();
if path == "*" {
for method in self.trees.keys() {
if method == "OPTIONS" {
continue;
}
if allow.is_empty() {
allow.push_str(method);
} else {
allow.push_str(", ");
allow.push_str(method);
}
}
} else {
for method in self.trees.keys() {
if method == req_method || method == "OPTIONS" {
continue;
}
self.trees.get(method).map(|tree| {
let (handle, _, _) = tree.get_value(path);
if handle.is_some() {
if allow.is_empty() {
allow.push_str(method);
} else {
allow.push_str(", ");
allow.push_str(method);
}
}
});
}
}
if allow.len() > 0 {
allow += ", OPTIONS";
}
allow
}
}
impl Service for Router<Handler>
{
type ReqBody = Body;
type ResBody = Body;
type Error = Error;
type Future = BoxFut;
fn call(&mut self, req: Request<Self::ReqBody>) -> Self::Future {
let root = self.trees.get(req.method().as_str());
if let Some(root) = root {
let (handle, ps, tsr) = root.get_value(req.uri().path());
if let Some(handle) = handle {
return handle.handle(req, ps);
} else if req.method() != &Method::CONNECT && req.uri().path() != "/" {
let code = if req.method() != &Method::GET {
307
} else {
301
};
if tsr && self.redirect_trailing_slash {
let path = if req.uri().path().len() > 1 && req.uri().path().ends_with("/") {
req.uri().path()[..req.uri().path().len() - 1].to_string()
} else {
req.uri().path().to_string() + "/"
};
let response = Response::builder().header("Location", path.as_str()).status(code).body(Body::empty()).unwrap();
return Box::new(future::ok(response));
}
if self.redirect_fixed_path {
let (fixed_path, found) = root.find_case_insensitive_path(&clean_path(req.uri().path()), self.redirect_trailing_slash);
if found {
let response = Response::builder().header("Location", fixed_path.as_str()).status(code).body(Body::empty()).unwrap();
return Box::new(future::ok(response));
}
}
}
}
if req.method() == &Method::OPTIONS && self.handle_options {
let allow = self.allowed(req.uri().path(), req.method().as_str());
if allow.len() > 0 {
let response = Response::builder().header("Allow", allow.as_str()).body(Body::empty()).unwrap();
return Box::new(future::ok(response));
}
} else {
if self.handle_method_not_allowed {
let allow = self.allowed(req.uri().path(), req.method().as_str());
if allow.len() > 0 {
let mut response = Response::builder().header("Allow", allow.as_str()).body(Body::empty()).unwrap();
if let Some(ref method_not_allowed) = self.method_not_allowed {
return method_not_allowed.handle(req, Params::new());
} else {
*response.status_mut() = StatusCode::METHOD_NOT_ALLOWED;
*response.body_mut() = Body::from("METHOD_NOT_ALLOWED");
}
return Box::new(future::ok(response));
}
}
}
if let Some(ref not_found) = self.not_found {
return not_found.handle(req, Params::new());
} else {
let response = Response::builder().status(404).body("NOT_FOUND".into()).unwrap();
return Box::new(future::ok(response));
}
}
}
impl IntoFuture for Router<Handler>{
type Future = future::FutureResult<Self::Item, Self::Error>;
type Item = Self;
type Error = Error;
fn into_future(self) -> Self::Future {
future::ok(self)
}
}
impl Router<Handler> {
pub fn serve_files(&mut self, path: &str, root: &'static str) {
if path.as_bytes().len() < 10 || &path[path.len() - 10..] != "/*filepath" {
panic!("path must end with /*filepath in path '{}'", path);
}
let root_path = Path::new(root);
let get_files = move |_, ps: Params| -> BoxFut{
let filepath = ps.by_name("filepath").unwrap();
simple_file_send(root_path.join(&filepath[1..]).to_str().unwrap())
};
self.get(path, Box::new(get_files));
}
}
fn simple_file_send(f: &str) -> BoxFut {
let filename = f.to_string(); Box::new(tokio_fs::file::File::open(filename)
.and_then(|file| {
let buf: Vec<u8> = Vec::new();
tokio_io::io::read_to_end(file, buf)
.and_then(|item| {
Ok(Response::new(item.1.into()))
})
.or_else(|_| {
Ok(Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::empty())
.unwrap())
})
})
.or_else(|_| {
Ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::from("NOT_FOUND"))
.unwrap())
}))
}
#[cfg(test)]
mod tests {
#[test]
fn params() {
use router::{Param, Params};
let params = Params(vec![
Param {
key: "hello".to_owned(),
value: "world".to_owned(),
},
Param {
key: "lalala".to_string(),
value: "papapa".to_string(),
},
]);
assert_eq!(Some("world"), params.by_name("hello"));
assert_eq!(Some("papapa"), params.by_name("lalala"));
}
#[test]
#[should_panic(expected = "path must begin with '/' in path 'something'")]
fn handle_ivalid_path() {
use hyper::{Body, Request, Response};
use router::{Router, BoxFut, Params};
use futures::future;
let path = "something";
let mut router = Router::new();
router.handle("GET", path, |_req: Request<Body>, _: Params| -> BoxFut {
Box::new(future::ok(Response::new(Body::from("test"))))
});
}
}