1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
use std::collections::HashMap;

use http;

use {
    box_handler, Body, BodyStream, BoxedHandler, BoxedResponse, Handler, Responder, ResponseBuilder,
};

/// A simple request router, which determines which handler to call based on the request URI's path.
///
/// This is a simple request router, inspired by Go's [`net/http` `ServeMux`].
///
/// It matches the path component of a HTTP request's URI against a collection of registered path
/// prefixes, and calls the handler whose path prefix most closely matches. (e.g. if both `/path/a`
/// and `/path/ab` are registered, a request for `/path/abc/` will call the latter).
///
/// This router is intended primarily to serve as an example of writing complex middleware using
/// aitch, and library users are encouraged to read its [source code].
///
/// The router is not intended for production applications, and lacks many features that are present
/// in the routers of other web frameworks/toolkits.
///
/// [`net/http` `ServeMux`]: https://golang.org/pkg/net/http/#ServeMux
/// [source code]: ../../src/aitch/middlewares/router.rs.html
///
/// # Example
///
/// ```no_run
/// extern crate aitch;
/// extern crate http;
///
/// use aitch::servers::hyper::Server;
/// use aitch::{middlewares, Responder, ResponseBuilder, Result};
/// use http::Request;
///
/// fn handler1(_req: Request<()>, mut resp: ResponseBuilder) -> impl Responder {
///     resp.body("Handler 1!".to_owned())
/// }
///
/// fn handler2(_req: Request<()>, mut resp: ResponseBuilder) -> impl Responder {
///     resp.body("Handler 2!".to_owned())
/// }
///
/// fn main() -> Result<()> {
///     let mut router = middlewares::SimpleRouter::new();
///     router.register_handler("/", handler1);
///     router.register_handler("/handler2", handler2);
///
///     let handler = middlewares::with_stdout_logging(router);
///
///     let addr = "127.0.0.1:3000".parse()?;
///     println!("Listening on http://{}", addr);
///     Server::new(addr, handler)?.run()
/// }
/// ```

#[derive(Default)]
pub struct SimpleRouter {
    handlers: HashMap<String, BoxedHandler>,
}

impl SimpleRouter {
    /// Creates a new `SimpleRouter`, with no routes registered.
    pub fn new() -> Self {
        SimpleRouter::default()
    }

    /// Registers a handler with the given pattern.
    ///
    /// This method registers a new handler with the router, using the provided pattern.
    ///
    /// See the [module level documentation] for more details on how patterns are matched.
    ///
    /// [module level documentation]: ./index.html
    ///
    /// # Panics
    ///
    /// This method panics if a handler is already registered with the provided pattern.
    pub fn register_handler<S, H, ReqBody>(&mut self, pattern: S, handler: H)
    where
        S: Into<String>,
        H: Handler<ReqBody>,
        ReqBody: Body,
    {
        let pattern = pattern.into();
        if self.handlers.contains_key(&pattern) {
            panic!("SimpleRouter: Tried to register pattern twice: {}", pattern);
        }
        self.handlers.insert(pattern, box_handler(handler));
    }

    /// Returns the handler to be used for a request with the given URI.
    ///
    /// Returns `None` if no handler matches the URI.
    pub fn handler(&self, uri: &http::Uri) -> Option<(&String, &BoxedHandler)> {
        self.handlers
            .iter()
            .filter(|&(pattern, _)| uri.path().starts_with(pattern))
            .max_by_key(|&(pattern, _)| pattern.len())
    }
}

impl Handler<BodyStream> for SimpleRouter {
    type Resp = BoxedResponse;

    fn handle(&self, req: http::Request<BodyStream>, mut resp: ResponseBuilder) -> BoxedResponse {
        match self.handler(req.uri()) {
            Some((_, handler)) => handler.handle(req, resp),
            None => resp.status(http::StatusCode::NOT_FOUND)
                .body(())
                .into_response(),
        }
    }
}