gotham/router/
mod.rs

1//! Defines the Gotham `Router` and supporting types.
2
3pub mod builder;
4pub use builder::{build_router, build_simple_router};
5
6pub mod response;
7pub mod route;
8pub mod tree;
9
10mod non_match;
11pub use self::non_match::RouteNonMatch;
12
13use std::pin::Pin;
14use std::sync::Arc;
15
16use futures_util::future::{self, FutureExt, TryFutureExt};
17use hyper::header::ALLOW;
18use hyper::{Body, Response, StatusCode};
19use log::{error, trace};
20
21use crate::handler::{Handler, HandlerFuture, IntoResponse, NewHandler};
22use crate::helpers::http::request::path::RequestPathSegments;
23use crate::helpers::http::response::create_empty_response;
24use crate::router::response::ResponseFinalizer;
25use crate::router::route::{Delegation, Route};
26use crate::router::tree::segment::SegmentMapping;
27use crate::router::tree::Tree;
28use crate::state::{request_id, State};
29
30struct RouterData {
31    tree: Tree,
32    response_finalizer: ResponseFinalizer,
33}
34
35impl RouterData {
36    fn new(tree: Tree, response_finalizer: ResponseFinalizer) -> RouterData {
37        RouterData {
38            tree,
39            response_finalizer,
40        }
41    }
42}
43
44/// Responsible for dispatching HTTP requests to defined routes, and responding with appropriate
45/// error codes when a valid `Route` is unable to be determined or the dispatch cannot be
46/// performed.
47///
48/// A `Router` is constructed through the [`gotham::router::builder`](builder/index.html#functions)
49/// API, and used with the `gotham::start` function when booting a Gotham web application.
50///
51/// The `Router` is capable of delegating requests to secondary `Router` instances, which allows
52/// the support of "modular applications". A modular application contains multiple applications
53/// within a single binary that have clear boundaries established via Rust module separation.
54/// Please see the documentation for `DrawRoutes::delegate` within `gotham::router::builder` in
55/// order to delegate to other `Router` instances.
56#[derive(Clone)]
57pub struct Router {
58    data: Arc<RouterData>,
59}
60
61impl NewHandler for Router {
62    type Instance = Router;
63
64    // Creates a new Router instance to route new HTTP requests
65    fn new_handler(&self) -> anyhow::Result<Self::Instance> {
66        trace!(" cloning instance");
67        Ok(self.clone())
68    }
69}
70
71impl Handler for Router {
72    /// Handles the `Request` by determining the correct `Route` from the internal `Tree`, storing
73    /// any path related variables in `State` and dispatching to the associated `Handler`.
74    fn handle(self, mut state: State) -> Pin<Box<HandlerFuture>> {
75        trace!("[{}] starting", request_id(&state));
76
77        let future = match state.try_take::<RequestPathSegments>() {
78            Some(rps) => {
79                if let Some((node, params, processed)) = self.data.tree.traverse(rps.segments()) {
80                    match node.select_route(&state) {
81                        Ok(route) => match route.delegation() {
82                            Delegation::External => {
83                                trace!("[{}] delegating to secondary router", request_id(&state));
84
85                                state.put(rps.subsegments(processed));
86                                route.dispatch(state)
87                            }
88                            Delegation::Internal => {
89                                trace!("[{}] dispatching to route", request_id(&state));
90                                self.dispatch(state, params, route)
91                            }
92                        },
93                        Err(non_match) => {
94                            let (status, allow) = non_match.deconstruct();
95
96                            trace!("[{}] responding with error status", request_id(&state));
97                            let mut res = create_empty_response(&state, status);
98                            if let StatusCode::METHOD_NOT_ALLOWED = status {
99                                for allowed in allow {
100                                    res.headers_mut().append(
101                                        ALLOW,
102                                        allowed.as_str().to_string().parse().unwrap(),
103                                    );
104                                }
105                            }
106                            future::ok((state, res)).boxed()
107                        }
108                    }
109                } else {
110                    trace!("[{}] did not find routable node", request_id(&state));
111                    let res = create_empty_response(&state, StatusCode::NOT_FOUND);
112                    future::ok((state, res)).boxed()
113                }
114            }
115            None => {
116                trace!("[{}] invalid request path segments", request_id(&state));
117                let res = create_empty_response(&state, StatusCode::INTERNAL_SERVER_ERROR);
118                future::ok((state, res)).boxed()
119            }
120        };
121
122        self.finalize_response(future)
123    }
124}
125
126impl Router {
127    /// Manually assembles a `Router` instance from a provided `Tree`.
128    fn new(tree: Tree, response_finalizer: ResponseFinalizer) -> Router {
129        let router_data = RouterData::new(tree, response_finalizer);
130        Router {
131            data: Arc::new(router_data),
132        }
133    }
134
135    fn dispatch<'a>(
136        &self,
137        mut state: State,
138        params: SegmentMapping<'a>,
139        route: &Box<dyn Route<ResBody = Body> + Send + Sync>,
140    ) -> Pin<Box<HandlerFuture>> {
141        match route.extract_request_path(&mut state, params) {
142            Ok(()) => {
143                trace!("[{}] extracted request path", request_id(&state));
144                match route.extract_query_string(&mut state) {
145                    Ok(()) => {
146                        trace!("[{}] extracted query string", request_id(&state));
147                        trace!("[{}] dispatching", request_id(&state));
148                        route.dispatch(state)
149                    }
150                    Err(_) => {
151                        error!("[{}] the server cannot or will not process the request due to a client error within the query string",
152                               request_id(&state));
153
154                        let mut res = Response::new(Body::empty());
155                        route.extend_response_on_query_string_error(&mut state, &mut res);
156                        future::ok((state, res)).boxed()
157                    }
158                }
159            }
160            Err(_) => {
161                error!(
162                    "[{}] the server cannot or will not process the request due to a client error on the request path",
163                    request_id(&state)
164                );
165                let mut res = Response::new(Body::empty());
166                route.extend_response_on_path_error(&mut state, &mut res);
167                future::ok((state, res)).boxed()
168            }
169        }
170    }
171
172    fn finalize_response(&self, result: Pin<Box<HandlerFuture>>) -> Pin<Box<HandlerFuture>> {
173        let response_finalizer = self.data.response_finalizer.clone();
174        result
175            .or_else(|(state, err)| {
176                trace!(
177                    "[{}] converting error into http response \
178                     during finalization: {:?}",
179                    request_id(&state),
180                    err
181                );
182                let response = err.into_response(&state);
183                future::ok((state, response))
184            })
185            .and_then(move |(state, res)| {
186                trace!("[{}] handler complete", request_id(&state));
187                response_finalizer.finalize(state, res)
188            })
189            .boxed()
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196    use hyper::header::{HeaderMap, CONTENT_LENGTH, CONTENT_TYPE};
197    use hyper::{Body, Method, Uri};
198    use mime::TEXT_PLAIN;
199    use std::str::FromStr;
200
201    use crate::extractor::{NoopPathExtractor, NoopQueryStringExtractor};
202    use crate::handler::HandlerError;
203    use crate::pipeline::{finalize_pipeline_set, new_pipeline_set};
204    use crate::router::response::ResponseFinalizerBuilder;
205    use crate::router::route::dispatch::DispatcherImpl;
206    use crate::router::route::matcher::{
207        AndRouteMatcher, ContentTypeHeaderRouteMatcher, MethodOnlyRouteMatcher,
208    };
209    use crate::router::route::{Extractors, RouteImpl};
210    use crate::router::tree::node::Node;
211    use crate::router::tree::segment::SegmentType;
212    use crate::router::tree::Tree;
213    use crate::state::set_request_id;
214
215    fn handler(state: State) -> (State, Response<Body>) {
216        (state, Response::new(Body::empty()))
217    }
218
219    fn send_request(
220        r: Router,
221        method: Method,
222        uri: &str,
223    ) -> ::std::result::Result<(State, Response<Body>), (State, HandlerError)> {
224        let uri = Uri::from_str(uri).unwrap();
225
226        let mut headers = HeaderMap::new();
227        if method == Method::POST {
228            headers.insert(CONTENT_TYPE, "application/json".parse().unwrap());
229            headers.insert(CONTENT_LENGTH, 0.into());
230        }
231
232        let mut state = State::new();
233        state.put(RequestPathSegments::new(uri.path()));
234        state.put(method);
235        state.put(uri);
236        state.put(headers);
237        set_request_id(&mut state);
238
239        futures_executor::block_on(r.handle(state))
240    }
241
242    #[test]
243    fn internal_server_error_if_no_request_path_segments() {
244        let tree = Tree::new();
245        let router = Router::new(tree, ResponseFinalizerBuilder::new().finalize());
246
247        let method = Method::GET;
248        let uri = Uri::from_str("https://test.gotham.rs").unwrap();
249
250        let mut state = State::new();
251        state.put(method);
252        state.put(uri);
253        state.put(HeaderMap::new());
254        set_request_id(&mut state);
255
256        match futures_executor::block_on(router.handle(state)) {
257            Ok((_state, res)) => {
258                assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
259            }
260            Err(_) => unreachable!("Router should have handled request"),
261        };
262    }
263
264    #[test]
265    fn not_found_error_if_request_path_is_not_found() {
266        let tree = Tree::new();
267        let router = Router::new(tree, ResponseFinalizerBuilder::new().finalize());
268
269        match send_request(router, Method::GET, "https://test.gotham.rs") {
270            Ok((_state, res)) => {
271                assert_eq!(res.status(), StatusCode::NOT_FOUND);
272            }
273            Err(_) => unreachable!("Router should have handled request"),
274        };
275    }
276
277    #[test]
278    fn custom_error_if_leaf_found_but_matching_route_not_found() {
279        let pipeline_set = finalize_pipeline_set(new_pipeline_set());
280        let mut tree = Tree::new();
281
282        let route = {
283            let methods = vec![Method::POST];
284            let matcher = AndRouteMatcher::new(
285                MethodOnlyRouteMatcher::new(methods),
286                ContentTypeHeaderRouteMatcher::new(vec![TEXT_PLAIN]),
287            );
288            let dispatcher = Box::new(DispatcherImpl::new(|| Ok(handler), (), pipeline_set));
289            let extractors: Extractors<NoopPathExtractor, NoopQueryStringExtractor> =
290                Extractors::new();
291            let route = RouteImpl::new(matcher, dispatcher, extractors, Delegation::Internal);
292            Box::new(route)
293        };
294        tree.add_route(route);
295        let router = Router::new(tree, ResponseFinalizerBuilder::new().finalize());
296
297        match send_request(router.clone(), Method::GET, "https://test.gotham.rs") {
298            Ok((_state, res)) => {
299                assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED);
300                assert_eq!(
301                    res.headers()
302                        .get_all(ALLOW)
303                        .iter()
304                        .map(|it| it.to_str().unwrap())
305                        .collect::<Vec<&str>>(),
306                    vec!["POST"]
307                );
308            }
309            Err(_) => unreachable!("Router should have handled request"),
310        };
311
312        match send_request(router, Method::POST, "https://test.gotham.rs") {
313            Ok((_state, res)) => {
314                assert_eq!(res.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
315                assert!(res.headers().get_all(ALLOW).iter().next().is_none());
316            }
317            Err(_) => unreachable!("Router should have handled request"),
318        };
319    }
320
321    #[test]
322    fn success_if_leaf_and_route_found() {
323        let pipeline_set = finalize_pipeline_set(new_pipeline_set());
324        let mut tree = Tree::new();
325
326        let route = {
327            let methods = vec![Method::GET];
328            let matcher = MethodOnlyRouteMatcher::new(methods);
329            let dispatcher = Box::new(DispatcherImpl::new(|| Ok(handler), (), pipeline_set));
330            let extractors: Extractors<NoopPathExtractor, NoopQueryStringExtractor> =
331                Extractors::new();
332            let route = RouteImpl::new(matcher, dispatcher, extractors, Delegation::Internal);
333            Box::new(route)
334        };
335        tree.add_route(route);
336        let router = Router::new(tree, ResponseFinalizerBuilder::new().finalize());
337
338        match send_request(router, Method::GET, "https://test.gotham.rs") {
339            Ok((_state, res)) => {
340                assert_eq!(res.status(), StatusCode::OK);
341            }
342            Err(_) => unreachable!("Router should have handled request"),
343        };
344    }
345
346    #[test]
347    fn delegates_to_secondary_router() {
348        let delegated_router = {
349            let pipeline_set = finalize_pipeline_set(new_pipeline_set());
350            let mut tree = Tree::new();
351
352            let route = {
353                let methods = vec![Method::GET];
354                let matcher = MethodOnlyRouteMatcher::new(methods);
355                let dispatcher = Box::new(DispatcherImpl::new(|| Ok(handler), (), pipeline_set));
356                let extractors: Extractors<NoopPathExtractor, NoopQueryStringExtractor> =
357                    Extractors::new();
358                let route = RouteImpl::new(matcher, dispatcher, extractors, Delegation::Internal);
359                Box::new(route)
360            };
361            tree.add_route(route);
362
363            Router::new(tree, ResponseFinalizerBuilder::new().finalize())
364        };
365
366        let pipeline_set = finalize_pipeline_set(new_pipeline_set());
367        let mut tree = Tree::new();
368        let mut delegated_node = Node::new("var", SegmentType::Dynamic);
369
370        let route = {
371            let methods = vec![Method::GET];
372            let matcher = MethodOnlyRouteMatcher::new(methods);
373            let dispatcher = Box::new(DispatcherImpl::new(delegated_router, (), pipeline_set));
374            let extractors: Extractors<NoopPathExtractor, NoopQueryStringExtractor> =
375                Extractors::new();
376            let route = RouteImpl::new(matcher, dispatcher, extractors, Delegation::External);
377            Box::new(route)
378        };
379
380        delegated_node.add_route(route);
381        tree.add_child(delegated_node);
382        let router = Router::new(tree, ResponseFinalizerBuilder::new().finalize());
383
384        // Ensure that top level tree has no route
385        match send_request(router.clone(), Method::GET, "https://test.gotham.rs") {
386            Ok((_state, res)) => {
387                assert_eq!(res.status(), StatusCode::NOT_FOUND);
388            }
389            Err(_) => unreachable!("Router should have handled request"),
390        };
391
392        // Ensure that top level tree of delegated router has route that responds correctly
393        match send_request(router, Method::GET, "https://test.gotham.rs/api") {
394            Ok((_state, res)) => {
395                assert_eq!(res.status(), StatusCode::OK);
396            }
397            Err(_) => unreachable!("Router should have handled request"),
398        };
399    }
400
401    #[test]
402    fn executes_response_finalizer_when_present() {
403        let tree = Tree::new();
404
405        let mut response_finalizer_builder = ResponseFinalizerBuilder::new();
406        let not_found_extender = |_s: &mut State, r: &mut Response<Body>| {
407            r.headers_mut()
408                .insert(CONTENT_LENGTH, "3".to_owned().parse().unwrap());
409        };
410        response_finalizer_builder.add(StatusCode::NOT_FOUND, Box::new(not_found_extender));
411        let response_finalizer = response_finalizer_builder.finalize();
412        let router = Router::new(tree, response_finalizer);
413
414        match send_request(router, Method::GET, "https://test.gotham.rs/api") {
415            Ok((_state, res)) => {
416                assert_eq!(res.headers().get(CONTENT_LENGTH).unwrap(), "3");
417            }
418            Err(_) => unreachable!("Router should have correctly handled request"),
419        };
420    }
421}