luminal_router/service/
mod.rs

1//! Router for mapping `hyper::Method` and a request path to a `hyper::Service`.
2//!
3//! luminal's router uses a simplified radix tree for speedy lookups. `cargo +nightly bench` to see
4//! relative performance across some contrived examples.
5use futures::future;
6use hyper::{self, Method, StatusCode};
7use hyper::server::{Request, Response, Service};
8
9use std::collections::HashMap;
10
11mod builder;
12
13use {LuminalFuture, LuminalService};
14use error::*;
15use tree::RouteTree;
16use route::Route;
17pub use self::builder::{FnRouteBuilder, ServiceRouteBuilder};
18
19/// Router for Hyper.
20#[derive(Default)]
21pub struct Router {
22    routes: HashMap<Method, RouteTree<Route<Box<LuminalService>>>>,
23}
24
25impl Service for Router {
26    type Request = Request;
27    type Response = Response;
28    type Error = hyper::Error;
29    type Future = LuminalFuture;
30
31    fn call(&self, req: Request) -> Self::Future {
32        let route = self.dispatch(req.method(), req.path());
33        if let Some(&Some(ref route)) = route {
34            route.target.call(req)
35        } else {
36            let mut response = Response::new();
37            response.set_status(StatusCode::NotFound);
38            Box::new(future::ok(response))
39        }
40    }
41}
42
43impl Router {
44    pub fn new() -> Self {
45        Router {
46            ..Default::default()
47        }
48    }
49
50    ///
51    /// Add a handler at the specific route path for the given `Method`.
52    pub fn add<
53        S: Service<
54            Request = Request,
55            Response = Response,
56            Error = hyper::Error,
57            Future = LuminalFuture,
58        >
59            + 'static,
60    >(
61        &mut self,
62        method: Method,
63        route: &str,
64        service: S,
65    ) -> Result<()> {
66        {
67            let routing = self.routes
68                .entry(method)
69                .or_insert_with(RouteTree::empty_root);
70            routing.add(route, Route::new(route, Box::new(service)))?;
71        }
72        Ok(())
73    }
74
75    pub fn dispatch<'a>(
76        &'a self,
77        method: &Method,
78        route_path: &str,
79    ) -> Option<&'a Option<Route<Box<LuminalService>>>> {
80        if let Some(routing) = self.routes.get(method) {
81            if let Some(route) = routing.dispatch(route_path) {
82                Some(route)
83            } else {
84                None
85            }
86        } else {
87            None
88        }
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    extern crate tokio_core;
95
96    use hyper::Body;
97    use hyper::header::ContentLength;
98    use futures::Stream;
99    use futures::future::Future;
100
101    use self::tokio_core::reactor::Core;
102
103    use super::*;
104
105    struct StringHandler(String);
106
107    impl Service for StringHandler {
108        type Request = Request;
109        type Response = Response;
110        type Error = hyper::Error;
111        type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
112        fn call(&self, _req: Request) -> Self::Future {
113            Box::new(future::ok(
114                Response::new()
115                    .with_header(ContentLength(self.0.len() as u64))
116                    .with_body(self.0.clone()),
117            ))
118        }
119    }
120
121    impl StringHandler {
122        fn new(msg: &str) -> Self {
123            StringHandler(msg.to_owned())
124        }
125    }
126
127    fn get_bar_handler(_req: Request) -> LuminalFuture {
128        let msg = String::from("Get bar");
129        Box::new(future::ok(
130            Response::new()
131                .with_header(ContentLength(msg.len() as u64))
132                .with_body(msg),
133        ))
134    }
135
136    #[test]
137    fn test_router() {
138        let router = FnRouteBuilder::new()
139            .get("/foo/bar", get_bar_handler)
140            .expect("Should have been able to add route")
141            .service_builder()
142            .get("/foo/baz", StringHandler::new("Baz"))
143            .expect("Should have been able to add route")
144            .post("/foo/bar", StringHandler::new("Post bar"))
145            .expect("Should have been able to add route")
146            .build();
147
148        assert_call(&router, Method::Get, "/foo/bar", "Get bar");
149        assert_call(&router, Method::Post, "/foo/bar", "Post bar");
150        assert_call(&router, Method::Get, "/foo/baz", "Baz");
151    }
152
153    #[test]
154    fn test_not_found() {
155        let router = Router {
156            ..Default::default()
157        };
158
159        let uri = "/foo"
160            .parse()
161            .expect("Should have been able to convert to uri");
162        let req: Request<Body> = Request::new(Method::Get, uri);
163
164        let work = router.call(req);
165
166        let mut core = Core::new().expect("Should have been able to create core");
167
168        let response = core.run(work)
169            .expect("Should have been able to run router call");
170
171        assert_eq!(
172            StatusCode::NotFound,
173            response.status(),
174            "Should have received not found status."
175        );
176    }
177
178    fn assert_call(router: &Router, method: Method, uri: &str, expected: &str) {
179        let uri = uri.parse()
180            .expect("Should have been able to convert to uri");
181        let req: Request<Body> = Request::new(method, uri);
182
183        let work = router.call(req);
184
185        let mut core = Core::new().expect("Should have been able to create core");
186
187        let response = core.run(work)
188            .expect("Should have been able to run router call");
189
190        assert_eq!(
191            StatusCode::Ok,
192            response.status(),
193            "Should have received Ok status."
194        );
195
196        let body = core.run(response.body().concat2())
197            .expect("Should have been able to resolve body concat");
198        let body: &[u8] = &body.to_vec();
199
200        assert_eq!(
201            expected.as_bytes(),
202            body,
203            "Should have received correct body content"
204        );
205    }
206}