hyper_router/
lib.rs

1#![doc(html_root_url = "https://marad.github.io/hyper-router/doc/hyper_router")]
2
3//! # Hyper Router
4//!
5//! This cargo is a small extension to the great Hyper HTTP library. It basically is
6//! adds the ability to define routes to request handlers and then query for the handlers
7//! by request path.
8//!
9//! ## Usage
10//!
11//! To use the library just add:
12//!
13//! ```text
14//! hyper = "^0.12"
15//! hyper-router = "^0.5"
16//! ```
17//!
18//! to your dependencies.
19//!
20//! ```no_run
21//! extern crate hyper;
22//! extern crate hyper_router;
23//!
24//! use hyper::header::{CONTENT_LENGTH, CONTENT_TYPE};
25//! use hyper::{Request, Response, Body, Method};
26//! use hyper::server::Server;
27//! use hyper::rt::Future;
28//! use hyper_router::{Route, RouterBuilder, RouterService};
29//!
30//! fn request_handler(_: Request<Body>) -> Response<Body> {
31//!     let body = "Hello World";
32//!     Response::builder()
33//!         .header(CONTENT_LENGTH, body.len() as u64)
34//!         .header(CONTENT_TYPE, "text/plain")
35//!         .body(Body::from(body))
36//!         .expect("Failed to construct the response")
37//! }
38//!
39//! fn router_service() -> Result<RouterService, std::io::Error> {
40//!     let router = RouterBuilder::new()
41//!         .add(Route::get("/hello").using(request_handler))
42//!         .add(Route::from(Method::PATCH, "/asd").using(request_handler))
43//!         .build();
44//!
45//!     Ok(RouterService::new(router))
46//! }
47//!
48//! fn main() {
49//!     let addr = "0.0.0.0:8080".parse().unwrap();
50//!     let server = Server::bind(&addr)
51//!         .serve(router_service)
52//!         .map_err(|e| eprintln!("server error: {}", e));
53//!
54//!     hyper::rt::run(server)
55//! }
56//! ```
57//!
58//! This code will start Hyper server and add use router to find handlers for request.
59//! We create the `Route` so that when we visit path `/greet` the `basic_handler` handler
60//! will be called.
61//!
62//! ## Things to note
63//!
64//! * `Path::new` method accepts regular expressions so you can match every path you please.
65//! * If you have request matching multiple paths the one that was first `add`ed will be chosen.
66//! * This library is in an early stage of development so there may be breaking changes comming
67//! (but I'll try as hard as I can not to break backwards compatibility or break it just a little -
68//! I promise I'll try!).
69//!
70//! # Waiting for your feedback
71//!
72//! I've created this little tool to help myself learn Rust and to avoid using big frameworks
73//! like Iron or rustful. I just want to keep things simple.
74//!
75//! Obviously I could make some errors or bad design choices so I'm waiting for your feedback!
76//! You may create an issue at [project's bug tracker](https://github.com/marad/hyper-router/issues).
77
78extern crate futures;
79extern crate hyper;
80
81use futures::future::FutureResult;
82use hyper::header::CONTENT_LENGTH;
83use hyper::service::Service;
84use hyper::{Body, Request, Response};
85
86use hyper::Method;
87use hyper::StatusCode;
88
89mod builder;
90pub mod handlers;
91mod path;
92pub mod route;
93
94pub use self::builder::RouterBuilder;
95pub use self::path::Path;
96pub use self::route::Route;
97pub use self::route::RouteBuilder;
98
99pub type Handler = fn(Request<Body>) -> Response<Body>;
100pub type HttpResult<T> = Result<T, StatusCode>;
101
102/// This is the one. The router.
103#[derive(Debug)]
104pub struct Router {
105    routes: Vec<Route>,
106}
107
108impl Router {
109    /// Finds handler for given Hyper request.
110    ///
111    /// This method uses default error handlers.
112    /// If the request does not match any route than default 404 handler is returned.
113    /// If the request match some routes but http method does not match (used GET but routes are
114    /// defined for POST) than default method not supported handler is returned.
115    pub fn find_handler_with_defaults(&self, request: &Request<Body>) -> Handler {
116        let matching_routes = self.find_matching_routes(request.uri().path());
117        match matching_routes.len() {
118            x if x == 0 => handlers::default_404_handler,
119            _ => self
120                .find_for_method(&matching_routes, request.method())
121                .unwrap_or(handlers::method_not_supported_handler),
122        }
123    }
124
125    /// Finds handler for given Hyper request.
126    ///
127    /// It returns handler if it's found or `StatusCode` for error.
128    /// This method may return `NotFound`, `MethodNotAllowed` or `NotImplemented`
129    /// status codes.
130    pub fn find_handler(&self, request: &Request<Body>) -> HttpResult<Handler> {
131        let matching_routes = self.find_matching_routes(request.uri().path());
132        match matching_routes.len() {
133            x if x == 0 => Err(StatusCode::NOT_FOUND),
134            _ => self
135                .find_for_method(&matching_routes, request.method())
136                .map(Ok)
137                .unwrap_or(Err(StatusCode::METHOD_NOT_ALLOWED)),
138        }
139    }
140
141    /// Returns vector of `Route`s that match to given path.
142    pub fn find_matching_routes(&self, request_path: &str) -> Vec<&Route> {
143        self.routes
144            .iter()
145            .filter(|route| route.path.matcher.is_match(&request_path))
146            .collect()
147    }
148
149    fn find_for_method(&self, routes: &[&Route], method: &Method) -> Option<Handler> {
150        let method = method.clone();
151        routes
152            .iter()
153            .find(|route| route.method == method)
154            .map(|route| route.handler)
155    }
156}
157
158/// The default simple router service.
159#[derive(Debug)]
160pub struct RouterService {
161    pub router: Router,
162    pub error_handler: fn(StatusCode) -> Response<Body>,
163}
164
165impl RouterService {
166    pub fn new(router: Router) -> RouterService {
167        RouterService {
168            router,
169            error_handler: Self::default_error_handler,
170        }
171    }
172
173    fn default_error_handler(status_code: StatusCode) -> Response<Body> {
174        let error = "Routing error: page not found";
175        Response::builder()
176            .header(CONTENT_LENGTH, error.len() as u64)
177            .status(match status_code {
178                StatusCode::NOT_FOUND => StatusCode::NOT_FOUND,
179                _ => StatusCode::INTERNAL_SERVER_ERROR,
180            })
181            .body(Body::from(error))
182            .expect("Failed to construct a response")
183    }
184}
185
186impl Service for RouterService {
187    type ReqBody = Body;
188    type ResBody = Body;
189    type Error = hyper::Error;
190    type Future = FutureResult<Response<Body>, hyper::Error>;
191
192    fn call(&mut self, request: Request<Self::ReqBody>) -> Self::Future {
193        futures::future::ok(match self.router.find_handler(&request) {
194            Ok(handler) => handler(request),
195            Err(status_code) => (self.error_handler)(status_code),
196        })
197    }
198}