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}