radix_router/
router.rs

1use futures::{future, IntoFuture};
2use hyper;
3use hyper::error::Error;
4use hyper::rt::Future;
5use hyper::service::Service;
6use hyper::{Body, Method, Request, Response, StatusCode};
7use path::clean_path;
8use std::collections::BTreeMap;
9use std::ops::Index;
10use tokio_fs;
11use tokio_io;
12use tree::Node;
13// use std::sync::Arc;
14use std::path::Path;
15
16// TODO: think more about what a handler looks like
17// pub type Handle = fn(Request<Body>, Response<Body>, Option<Params>) -> BoxFut;
18// pub type ResponseFuture = Box<Future<Item=Response<Body>, Error=Error> + Send>;
19pub type BoxFut = Box<Future<Item = Response<Body>, Error = hyper::Error> + Send>;
20
21pub trait Handle {
22    fn handle(&self, req: Request<Body>, ps: Params) -> BoxFut;
23}
24
25impl<F> Handle for F
26where
27    F: Fn(Request<Body>, Params) -> BoxFut,
28{
29    fn handle(&self, req: Request<Body>, ps: Params) -> BoxFut {
30        (*self)(req, ps)
31    }
32}
33
34// impl Handle for Handler {
35//     fn handle(&self, req: Request<Body>, ps: Option<Params>) -> BoxFut {
36//         (*self)(req, ps)
37//     }
38// }
39
40// pub type Handler = fn(Request<Body>, Option<Params>) -> BoxFut;
41
42/// Handle is a function that can be registered to a route to handle HTTP
43/// requests. It has a third parameter for the values of
44/// wildcards (variables).
45pub type Handler = Box<Handle + Send>;
46
47/// Param is a single URL parameter, consisting of a key and a value.
48#[derive(Debug, Clone, PartialEq)]
49pub struct Param {
50    pub key: String,
51    pub value: String,
52}
53
54impl Param {
55    pub fn new(key: &str, value: &str) -> Param {
56        Param {
57            key: key.to_string(),
58            value: value.to_string(),
59        }
60    }
61}
62
63/// Params is a Param-slice, as returned by the router.
64/// The slice is ordered, the first URL parameter is also the first slice value.
65/// It is therefore safe to read values by the index.
66#[derive(Debug, PartialEq)]
67pub struct Params(pub Vec<Param>);
68
69impl Params {
70    /// ByName returns the value of the first Param which key matches the given name.
71    /// If no matching Param is found, an empty string is returned.
72    pub fn by_name(&self, name: &str) -> Option<&str> {
73        match self.0.iter().find(|param| param.key == name) {
74            Some(param) => Some(&param.value),
75            None => None,
76        }
77    }
78
79    /// Empty `Params`
80    pub fn new() -> Params {
81        Params(Vec::new())
82    }
83
84    pub fn is_empty(&self) -> bool {
85        self.0.is_empty()
86    }
87
88    pub fn push(&mut self, p: Param) {
89        self.0.push(p);
90    }
91}
92
93impl Index<usize> for Params {
94    type Output = str;
95
96    fn index(&self, i: usize) -> &Self::Output {
97        &(self.0)[i].value
98    }
99}
100
101/// Router is container which can be used to dispatch requests to different
102/// handler functions via configurable routes
103// #[derive(Clone)]
104#[allow(dead_code)]
105pub struct Router<T> {
106    pub trees: BTreeMap<String, Node<T>>,
107
108    // Enables automatic redirection if the current route can't be matched but a
109    // handler for the path with (without) the trailing slash exists.
110    // For example if /foo/ is requested but a route only exists for /foo, the
111    // client is redirected to /foo with http status code 301 for GET requests
112    // and 307 for all other request methods.
113    pub redirect_trailing_slash: bool,
114
115    // If enabled, the router tries to fix the current request path, if no
116    // handle is registered for it.
117    // First superfluous path elements like ../ or // are removed.
118    // Afterwards the router does a case-insensitive lookup of the cleaned path.
119    // If a handle can be found for this route, the router makes a redirection
120    // to the corrected path with status code 301 for GET requests and 307 for
121    // all other request methods.
122    // For example /FOO and /..//Foo could be redirected to /foo.
123    // RedirectTrailingSlash is independent of this option.
124    pub redirect_fixed_path: bool,
125
126    // If enabled, the router checks if another method is allowed for the
127    // current route, if the current request can not be routed.
128    // If this is the case, the request is answered with 'Method Not Allowed'
129    // and HTTP status code 405.
130    // If no other Method is allowed, the request is delegated to the NotFound
131    // handler.
132    pub handle_method_not_allowed: bool,
133
134    // If enabled, the router automatically replies to OPTIONS requests.
135    // Custom OPTIONS handlers take priority over automatic replies.
136    pub handle_options: bool,
137
138    // Configurable handler which is called when no matching route is
139    // found.
140    pub not_found: Option<T>,
141
142    // Configurable handler which is called when a request
143    // cannot be routed and HandleMethodNotAllowed is true.
144    // The "Allow" header with allowed request methods is set before the handler
145    // is called.
146    pub method_not_allowed: Option<T>,
147
148    // Function to handle panics recovered from http handlers.
149    // It should be used to generate a error page and return the http error code
150    // 500 (Internal Server Error).
151    // The handler can be used to keep your server from crashing because of
152    // unrecovered panics.
153    pub panic_handler: Option<T>,
154}
155
156impl<T> Router<T> {
157    /// New returns a new initialized Router.
158    /// Path auto-correction, including trailing slashes, is enabled by default.
159    pub fn new() -> Router<T> {
160        Router {
161            trees: BTreeMap::new(),
162            redirect_trailing_slash: true,
163            redirect_fixed_path: true,
164            handle_method_not_allowed: true,
165            handle_options: true,
166            not_found: None,
167            method_not_allowed: None,
168            panic_handler: None,
169        }
170    }
171
172    /// get is a shortcut for router.handle("GET", path, handle)
173    pub fn get(&mut self, path: &str, handle: T) {
174        self.handle("GET", path, handle);
175    }
176
177    /// head is a shortcut for router.handle("HEAD", path, handle)
178    pub fn head(&mut self, path: &str, handle: T) {
179        self.handle("HEAD", path, handle);
180    }
181
182    /// options is a shortcut for router.handle("OPTIONS", path, handle)
183    pub fn options(&mut self, path: &str, handle: T) {
184        self.handle("OPTIONS", path, handle);
185    }
186
187    /// post is a shortcut for router.handle("POST", path, handle)
188    pub fn post(&mut self, path: &str, handle: T) {
189        self.handle("POST", path, handle);
190    }
191
192    /// put is a shortcut for router.handle("PUT", path, handle)
193    pub fn put(&mut self, path: &str, handle: T) {
194        self.handle("PUT", path, handle);
195    }
196
197    /// patch is a shortcut for router.handle("PATCH", path, handle)
198    pub fn patch(&mut self, path: &str, handle: T) {
199        self.handle("PATCH", path, handle);
200    }
201
202    /// delete is a shortcut for router.handle("DELETE", path, handle)
203    pub fn delete(&mut self, path: &str, handle: T) {
204        self.handle("DELETE", path, handle);
205    }
206
207    /// Unimplemented. Perhaps something like
208    ///
209    /// # Example
210    ///
211    /// ```ignore
212    /// router.group(vec![middelware], |router| {
213    ///     router.get("/something", somewhere);
214    ///     router.post("/something", somewhere);
215    /// })
216    /// ```
217    pub fn group() {
218        unimplemented!()
219    }
220
221    /// Handle registers a new request handle with the given path and method.
222    ///
223    /// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
224    /// functions can be used.
225    ///
226    /// This function is intended for bulk loading and to allow the usage of less
227    /// frequently used, non-standardized or custom methods (e.g. for internal
228    /// communication with a proxy).
229    pub fn handle(&mut self, method: &str, path: &str, handle: T) {
230        if !path.starts_with("/") {
231            panic!("path must begin with '/' in path '{}'", path);
232        }
233
234        self.trees
235            .entry(method.to_string())
236            .or_insert(Node::new())
237            .add_route(path, handle);
238    }
239
240    /// Lookup allows the manual lookup of a method + path combo.
241    ///
242    /// This is e.g. useful to build a framework around this router.
243    ///
244    /// If the path was found, it returns the handle function and the path parameter
245    /// values. Otherwise the third return value indicates whether a redirection to
246    /// the same path with an extra / without the trailing slash should be performed.
247    pub fn lookup(&mut self, method: &str, path: &str) -> (Option<&T>, Params, bool) {
248        self.trees
249            .get_mut(method)
250            .and_then(|n| Some(n.get_value(path)))
251            .unwrap_or((None, Params::new(), false))
252    }
253
254    pub fn allowed(&self, path: &str, req_method: &str) -> String {
255        let mut allow = String::new();
256        if path == "*" {
257            for method in self.trees.keys() {
258                if method == "OPTIONS" {
259                    continue;
260                }
261
262                if allow.is_empty() {
263                    allow.push_str(method);
264                } else {
265                    allow.push_str(", ");
266                    allow.push_str(method);
267                }
268            }
269        } else {
270            for method in self.trees.keys() {
271                if method == req_method || method == "OPTIONS" {
272                    continue;
273                }
274
275                self.trees.get(method).map(|tree| {
276                    let (handle, _, _) = tree.get_value(path);
277
278                    if handle.is_some() {
279                        if allow.is_empty() {
280                            allow.push_str(method);
281                        } else {
282                            allow.push_str(", ");
283                            allow.push_str(method);
284                        }
285                    }
286                });
287            }
288        }
289
290        if allow.len() > 0 {
291            allow += ", OPTIONS";
292        }
293
294        allow
295    }
296}
297
298/// Service makes the router capable for Hyper.
299impl Service for Router<Handler> {
300    type ReqBody = Body;
301    type ResBody = Body;
302    type Error = Error;
303    type Future = BoxFut;
304
305    /// call makes the router implement the `Service` trait.
306    fn call(&mut self, req: Request<Self::ReqBody>) -> Self::Future {
307        // let (handle, p, _) = self.lookup(req.method().as_str(), req.uri().path());
308        // match handle {
309        //     Some(h) => future::ok(h(req, p)),
310        //     // Handle 404
311        //     _ => future::ok(Response::new(Body::from("not found"))),
312        // }
313        // self.serve_http(req)
314        // let method = req.method().as_str();
315        // let path = req.uri().path();
316        // let mut response = Response::new(Body::empty());
317
318        let root = self.trees.get(req.method().as_str());
319        if let Some(root) = root {
320            let (handle, ps, tsr) = root.get_value(req.uri().path());
321
322            if let Some(handle) = handle {
323                // return handle(req, response, ps);
324                return handle.handle(req, ps);
325            } else if req.method() != &Method::CONNECT && req.uri().path() != "/" {
326                let code = if req.method() != &Method::GET {
327                    // StatusCode::from_u16(307).unwrap()
328                    307
329                } else {
330                    // StatusCode::from_u16(301).unwrap()
331                    301
332                };
333
334                if tsr && self.redirect_trailing_slash {
335                    let path = if req.uri().path().len() > 1 && req.uri().path().ends_with("/") {
336                        req.uri().path()[..req.uri().path().len() - 1].to_string()
337                    } else {
338                        req.uri().path().to_string() + "/"
339                    };
340
341                    // response.headers_mut().insert(header::LOCATION, header::HeaderValue::from_str(&path).unwrap());
342                    // *response.status_mut() = code;
343                    let response = Response::builder()
344                        .header("Location", path.as_str())
345                        .status(code)
346                        .body(Body::empty())
347                        .unwrap();
348                    return Box::new(future::ok(response));
349                }
350
351                if self.redirect_fixed_path {
352                    let (fixed_path, found) = root.find_case_insensitive_path(
353                        &clean_path(req.uri().path()),
354                        self.redirect_trailing_slash,
355                    );
356
357                    if found {
358                        //  response.headers_mut().insert(header::LOCATION, header::HeaderValue::from_str(&fixed_path).unwrap());
359                        // *response.status_mut() = code;
360                        let response = Response::builder()
361                            .header("Location", fixed_path.as_str())
362                            .status(code)
363                            .body(Body::empty())
364                            .unwrap();
365                        return Box::new(future::ok(response));
366                    }
367                }
368            }
369        }
370
371        if req.method() == &Method::OPTIONS && self.handle_options {
372            let allow = self.allowed(req.uri().path(), req.method().as_str());
373            if allow.len() > 0 {
374                // *response.headers_mut().get_mut("allow").unwrap() = header::HeaderValue::from_str(&allow).unwrap();
375                let response = Response::builder()
376                    .header("Allow", allow.as_str())
377                    .body(Body::empty())
378                    .unwrap();
379                return Box::new(future::ok(response));
380            }
381        } else {
382            if self.handle_method_not_allowed {
383                let allow = self.allowed(req.uri().path(), req.method().as_str());
384
385                if allow.len() > 0 {
386                    let mut response = Response::builder()
387                        .header("Allow", allow.as_str())
388                        .body(Body::empty())
389                        .unwrap();
390
391                    if let Some(ref method_not_allowed) = self.method_not_allowed {
392                        return method_not_allowed.handle(req, Params::new());
393                    } else {
394                        *response.status_mut() = StatusCode::METHOD_NOT_ALLOWED;
395                        *response.body_mut() = Body::from("METHOD_NOT_ALLOWED");
396                    }
397
398                    return Box::new(future::ok(response));
399                }
400            }
401        }
402
403        // Handle 404
404        if let Some(ref not_found) = self.not_found {
405            return not_found.handle(req, Params::new());
406        } else {
407            // *response.status_mut() = StatusCode::NOT_FOUND;
408            let response = Response::builder()
409                .status(404)
410                .body("NOT_FOUND".into())
411                .unwrap();
412            return Box::new(future::ok(response));
413        }
414    }
415}
416
417impl IntoFuture for Router<Handler> {
418    type Future = future::FutureResult<Self::Item, Self::Error>;
419    type Item = Self;
420    type Error = Error;
421
422    fn into_future(self) -> Self::Future {
423        future::ok(self)
424    }
425}
426
427impl Router<Handler> {
428    /// ServeFiles serves files from the given file system root.
429    ///
430    /// The path must end with "/*filepath", files are then served from the local
431    /// path /defined/root/dir/*filepath.
432    ///
433    /// For example if root is "/etc" and *filepath is "passwd", the local file
434    /// "/etc/passwd" would be served.
435    ///
436    /// ```rust
437    /// extern crate radix_router;
438    /// use radix_router::router::{Router, Handler};
439    /// let mut router: Router<Handler> = Router::new();
440    /// router.serve_files("/examples/*filepath", "examples");
441    /// ```
442    pub fn serve_files(&mut self, path: &str, root: &'static str) {
443        if path.as_bytes().len() < 10 || &path[path.len() - 10..] != "/*filepath" {
444            panic!("path must end with /*filepath in path '{}'", path);
445        }
446        let root_path = Path::new(root);
447        let get_files = move |_, ps: Params| -> BoxFut {
448            let filepath = ps.by_name("filepath").unwrap();
449            simple_file_send(root_path.join(&filepath[1..]).to_str().unwrap())
450        };
451
452        self.get(path, Box::new(get_files));
453    }
454}
455
456fn simple_file_send(f: &str) -> BoxFut {
457    // Serve a file by asynchronously reading it entirely into memory.
458    // Uses tokio_fs to open file asynchronously, then tokio_io to read into
459    // memory asynchronously.
460    let filename = f.to_string(); // we need to copy for lifetime issues
461    Box::new(
462        tokio_fs::file::File::open(filename)
463            .and_then(|file| {
464                let buf: Vec<u8> = Vec::new();
465                tokio_io::io::read_to_end(file, buf)
466                    .and_then(|item| Ok(Response::new(item.1.into())))
467                    .or_else(|_| {
468                        Ok(Response::builder()
469                            .status(StatusCode::INTERNAL_SERVER_ERROR)
470                            .body(Body::empty())
471                            .unwrap())
472                    })
473            })
474            .or_else(|_| {
475                Ok(Response::builder()
476                    .status(StatusCode::NOT_FOUND)
477                    .body(Body::from("NOT_FOUND"))
478                    .unwrap())
479            }),
480    )
481}
482
483// impl<T> NewService for Router<T>
484// where
485//     T: Fn(Request<Body>, Response<Body>, Option<Params>) -> BoxFut,
486// {
487//     type ReqBody = Body;
488//     type ResBody = Body;
489//     type Error = Error;
490//     type Service = Self;
491//     type Future = future::FutureResult<Self::Service, Self::InitError>;
492//     type InitError = Error;
493
494//     fn new_service(&self) -> Self::Future {
495//         future::ok(*self.clone())
496//     }
497// }
498
499#[cfg(test)]
500mod tests {
501    #[test]
502    fn params() {
503        use router::{Param, Params};
504
505        let params = Params(vec![
506            Param {
507                key: "hello".to_owned(),
508                value: "world".to_owned(),
509            },
510            Param {
511                key: "lalala".to_string(),
512                value: "papapa".to_string(),
513            },
514        ]);
515
516        assert_eq!(Some("world"), params.by_name("hello"));
517        assert_eq!(Some("papapa"), params.by_name("lalala"));
518    }
519
520    #[test]
521    #[should_panic(expected = "path must begin with '/' in path 'something'")]
522    fn handle_ivalid_path() {
523        // use http::Response;
524        use futures::future;
525        use hyper::{Body, Request, Response};
526        use router::{BoxFut, Params, Router};
527
528        let path = "something";
529        let mut router = Router::new();
530
531        router.handle("GET", path, |_req: Request<Body>, _: Params| -> BoxFut {
532            Box::new(future::ok(Response::new(Body::from("test"))))
533        });
534    }
535}