radix_router/router.rs
1use futures::{future, IntoFuture};
2use hyper;
3use hyper::error::Error;
4use hyper::rt::Future;
5use hyper::service::Service;
6use hyper::{Body, Method, Request, Response, StatusCode};
7use path::clean_path;
8use std::collections::BTreeMap;
9use std::ops::Index;
10use tokio_fs;
11use tokio_io;
12use tree::Node;
13// use std::sync::Arc;
14use std::path::Path;
15
16// TODO: think more about what a handler looks like
17// pub type Handle = fn(Request<Body>, Response<Body>, Option<Params>) -> BoxFut;
18// pub type ResponseFuture = Box<Future<Item=Response<Body>, Error=Error> + Send>;
19pub type BoxFut = Box<Future<Item = Response<Body>, Error = hyper::Error> + Send>;
20
21pub trait Handle {
22 fn handle(&self, req: Request<Body>, ps: Params) -> BoxFut;
23}
24
25impl<F> Handle for F
26where
27 F: Fn(Request<Body>, Params) -> BoxFut,
28{
29 fn handle(&self, req: Request<Body>, ps: Params) -> BoxFut {
30 (*self)(req, ps)
31 }
32}
33
34// impl Handle for Handler {
35// fn handle(&self, req: Request<Body>, ps: Option<Params>) -> BoxFut {
36// (*self)(req, ps)
37// }
38// }
39
40// pub type Handler = fn(Request<Body>, Option<Params>) -> BoxFut;
41
42/// Handle is a function that can be registered to a route to handle HTTP
43/// requests. It has a third parameter for the values of
44/// wildcards (variables).
45pub type Handler = Box<Handle + Send>;
46
47/// Param is a single URL parameter, consisting of a key and a value.
48#[derive(Debug, Clone, PartialEq)]
49pub struct Param {
50 pub key: String,
51 pub value: String,
52}
53
54impl Param {
55 pub fn new(key: &str, value: &str) -> Param {
56 Param {
57 key: key.to_string(),
58 value: value.to_string(),
59 }
60 }
61}
62
63/// Params is a Param-slice, as returned by the router.
64/// The slice is ordered, the first URL parameter is also the first slice value.
65/// It is therefore safe to read values by the index.
66#[derive(Debug, PartialEq)]
67pub struct Params(pub Vec<Param>);
68
69impl Params {
70 /// ByName returns the value of the first Param which key matches the given name.
71 /// If no matching Param is found, an empty string is returned.
72 pub fn by_name(&self, name: &str) -> Option<&str> {
73 match self.0.iter().find(|param| param.key == name) {
74 Some(param) => Some(¶m.value),
75 None => None,
76 }
77 }
78
79 /// Empty `Params`
80 pub fn new() -> Params {
81 Params(Vec::new())
82 }
83
84 pub fn is_empty(&self) -> bool {
85 self.0.is_empty()
86 }
87
88 pub fn push(&mut self, p: Param) {
89 self.0.push(p);
90 }
91}
92
93impl Index<usize> for Params {
94 type Output = str;
95
96 fn index(&self, i: usize) -> &Self::Output {
97 &(self.0)[i].value
98 }
99}
100
101/// Router is container which can be used to dispatch requests to different
102/// handler functions via configurable routes
103// #[derive(Clone)]
104#[allow(dead_code)]
105pub struct Router<T> {
106 pub trees: BTreeMap<String, Node<T>>,
107
108 // Enables automatic redirection if the current route can't be matched but a
109 // handler for the path with (without) the trailing slash exists.
110 // For example if /foo/ is requested but a route only exists for /foo, the
111 // client is redirected to /foo with http status code 301 for GET requests
112 // and 307 for all other request methods.
113 pub redirect_trailing_slash: bool,
114
115 // If enabled, the router tries to fix the current request path, if no
116 // handle is registered for it.
117 // First superfluous path elements like ../ or // are removed.
118 // Afterwards the router does a case-insensitive lookup of the cleaned path.
119 // If a handle can be found for this route, the router makes a redirection
120 // to the corrected path with status code 301 for GET requests and 307 for
121 // all other request methods.
122 // For example /FOO and /..//Foo could be redirected to /foo.
123 // RedirectTrailingSlash is independent of this option.
124 pub redirect_fixed_path: bool,
125
126 // If enabled, the router checks if another method is allowed for the
127 // current route, if the current request can not be routed.
128 // If this is the case, the request is answered with 'Method Not Allowed'
129 // and HTTP status code 405.
130 // If no other Method is allowed, the request is delegated to the NotFound
131 // handler.
132 pub handle_method_not_allowed: bool,
133
134 // If enabled, the router automatically replies to OPTIONS requests.
135 // Custom OPTIONS handlers take priority over automatic replies.
136 pub handle_options: bool,
137
138 // Configurable handler which is called when no matching route is
139 // found.
140 pub not_found: Option<T>,
141
142 // Configurable handler which is called when a request
143 // cannot be routed and HandleMethodNotAllowed is true.
144 // The "Allow" header with allowed request methods is set before the handler
145 // is called.
146 pub method_not_allowed: Option<T>,
147
148 // Function to handle panics recovered from http handlers.
149 // It should be used to generate a error page and return the http error code
150 // 500 (Internal Server Error).
151 // The handler can be used to keep your server from crashing because of
152 // unrecovered panics.
153 pub panic_handler: Option<T>,
154}
155
156impl<T> Router<T> {
157 /// New returns a new initialized Router.
158 /// Path auto-correction, including trailing slashes, is enabled by default.
159 pub fn new() -> Router<T> {
160 Router {
161 trees: BTreeMap::new(),
162 redirect_trailing_slash: true,
163 redirect_fixed_path: true,
164 handle_method_not_allowed: true,
165 handle_options: true,
166 not_found: None,
167 method_not_allowed: None,
168 panic_handler: None,
169 }
170 }
171
172 /// get is a shortcut for router.handle("GET", path, handle)
173 pub fn get(&mut self, path: &str, handle: T) {
174 self.handle("GET", path, handle);
175 }
176
177 /// head is a shortcut for router.handle("HEAD", path, handle)
178 pub fn head(&mut self, path: &str, handle: T) {
179 self.handle("HEAD", path, handle);
180 }
181
182 /// options is a shortcut for router.handle("OPTIONS", path, handle)
183 pub fn options(&mut self, path: &str, handle: T) {
184 self.handle("OPTIONS", path, handle);
185 }
186
187 /// post is a shortcut for router.handle("POST", path, handle)
188 pub fn post(&mut self, path: &str, handle: T) {
189 self.handle("POST", path, handle);
190 }
191
192 /// put is a shortcut for router.handle("PUT", path, handle)
193 pub fn put(&mut self, path: &str, handle: T) {
194 self.handle("PUT", path, handle);
195 }
196
197 /// patch is a shortcut for router.handle("PATCH", path, handle)
198 pub fn patch(&mut self, path: &str, handle: T) {
199 self.handle("PATCH", path, handle);
200 }
201
202 /// delete is a shortcut for router.handle("DELETE", path, handle)
203 pub fn delete(&mut self, path: &str, handle: T) {
204 self.handle("DELETE", path, handle);
205 }
206
207 /// Unimplemented. Perhaps something like
208 ///
209 /// # Example
210 ///
211 /// ```ignore
212 /// router.group(vec![middelware], |router| {
213 /// router.get("/something", somewhere);
214 /// router.post("/something", somewhere);
215 /// })
216 /// ```
217 pub fn group() {
218 unimplemented!()
219 }
220
221 /// Handle registers a new request handle with the given path and method.
222 ///
223 /// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
224 /// functions can be used.
225 ///
226 /// This function is intended for bulk loading and to allow the usage of less
227 /// frequently used, non-standardized or custom methods (e.g. for internal
228 /// communication with a proxy).
229 pub fn handle(&mut self, method: &str, path: &str, handle: T) {
230 if !path.starts_with("/") {
231 panic!("path must begin with '/' in path '{}'", path);
232 }
233
234 self.trees
235 .entry(method.to_string())
236 .or_insert(Node::new())
237 .add_route(path, handle);
238 }
239
240 /// Lookup allows the manual lookup of a method + path combo.
241 ///
242 /// This is e.g. useful to build a framework around this router.
243 ///
244 /// If the path was found, it returns the handle function and the path parameter
245 /// values. Otherwise the third return value indicates whether a redirection to
246 /// the same path with an extra / without the trailing slash should be performed.
247 pub fn lookup(&mut self, method: &str, path: &str) -> (Option<&T>, Params, bool) {
248 self.trees
249 .get_mut(method)
250 .and_then(|n| Some(n.get_value(path)))
251 .unwrap_or((None, Params::new(), false))
252 }
253
254 pub fn allowed(&self, path: &str, req_method: &str) -> String {
255 let mut allow = String::new();
256 if path == "*" {
257 for method in self.trees.keys() {
258 if method == "OPTIONS" {
259 continue;
260 }
261
262 if allow.is_empty() {
263 allow.push_str(method);
264 } else {
265 allow.push_str(", ");
266 allow.push_str(method);
267 }
268 }
269 } else {
270 for method in self.trees.keys() {
271 if method == req_method || method == "OPTIONS" {
272 continue;
273 }
274
275 self.trees.get(method).map(|tree| {
276 let (handle, _, _) = tree.get_value(path);
277
278 if handle.is_some() {
279 if allow.is_empty() {
280 allow.push_str(method);
281 } else {
282 allow.push_str(", ");
283 allow.push_str(method);
284 }
285 }
286 });
287 }
288 }
289
290 if allow.len() > 0 {
291 allow += ", OPTIONS";
292 }
293
294 allow
295 }
296}
297
298/// Service makes the router capable for Hyper.
299impl Service for Router<Handler> {
300 type ReqBody = Body;
301 type ResBody = Body;
302 type Error = Error;
303 type Future = BoxFut;
304
305 /// call makes the router implement the `Service` trait.
306 fn call(&mut self, req: Request<Self::ReqBody>) -> Self::Future {
307 // let (handle, p, _) = self.lookup(req.method().as_str(), req.uri().path());
308 // match handle {
309 // Some(h) => future::ok(h(req, p)),
310 // // Handle 404
311 // _ => future::ok(Response::new(Body::from("not found"))),
312 // }
313 // self.serve_http(req)
314 // let method = req.method().as_str();
315 // let path = req.uri().path();
316 // let mut response = Response::new(Body::empty());
317
318 let root = self.trees.get(req.method().as_str());
319 if let Some(root) = root {
320 let (handle, ps, tsr) = root.get_value(req.uri().path());
321
322 if let Some(handle) = handle {
323 // return handle(req, response, ps);
324 return handle.handle(req, ps);
325 } else if req.method() != &Method::CONNECT && req.uri().path() != "/" {
326 let code = if req.method() != &Method::GET {
327 // StatusCode::from_u16(307).unwrap()
328 307
329 } else {
330 // StatusCode::from_u16(301).unwrap()
331 301
332 };
333
334 if tsr && self.redirect_trailing_slash {
335 let path = if req.uri().path().len() > 1 && req.uri().path().ends_with("/") {
336 req.uri().path()[..req.uri().path().len() - 1].to_string()
337 } else {
338 req.uri().path().to_string() + "/"
339 };
340
341 // response.headers_mut().insert(header::LOCATION, header::HeaderValue::from_str(&path).unwrap());
342 // *response.status_mut() = code;
343 let response = Response::builder()
344 .header("Location", path.as_str())
345 .status(code)
346 .body(Body::empty())
347 .unwrap();
348 return Box::new(future::ok(response));
349 }
350
351 if self.redirect_fixed_path {
352 let (fixed_path, found) = root.find_case_insensitive_path(
353 &clean_path(req.uri().path()),
354 self.redirect_trailing_slash,
355 );
356
357 if found {
358 // response.headers_mut().insert(header::LOCATION, header::HeaderValue::from_str(&fixed_path).unwrap());
359 // *response.status_mut() = code;
360 let response = Response::builder()
361 .header("Location", fixed_path.as_str())
362 .status(code)
363 .body(Body::empty())
364 .unwrap();
365 return Box::new(future::ok(response));
366 }
367 }
368 }
369 }
370
371 if req.method() == &Method::OPTIONS && self.handle_options {
372 let allow = self.allowed(req.uri().path(), req.method().as_str());
373 if allow.len() > 0 {
374 // *response.headers_mut().get_mut("allow").unwrap() = header::HeaderValue::from_str(&allow).unwrap();
375 let response = Response::builder()
376 .header("Allow", allow.as_str())
377 .body(Body::empty())
378 .unwrap();
379 return Box::new(future::ok(response));
380 }
381 } else {
382 if self.handle_method_not_allowed {
383 let allow = self.allowed(req.uri().path(), req.method().as_str());
384
385 if allow.len() > 0 {
386 let mut response = Response::builder()
387 .header("Allow", allow.as_str())
388 .body(Body::empty())
389 .unwrap();
390
391 if let Some(ref method_not_allowed) = self.method_not_allowed {
392 return method_not_allowed.handle(req, Params::new());
393 } else {
394 *response.status_mut() = StatusCode::METHOD_NOT_ALLOWED;
395 *response.body_mut() = Body::from("METHOD_NOT_ALLOWED");
396 }
397
398 return Box::new(future::ok(response));
399 }
400 }
401 }
402
403 // Handle 404
404 if let Some(ref not_found) = self.not_found {
405 return not_found.handle(req, Params::new());
406 } else {
407 // *response.status_mut() = StatusCode::NOT_FOUND;
408 let response = Response::builder()
409 .status(404)
410 .body("NOT_FOUND".into())
411 .unwrap();
412 return Box::new(future::ok(response));
413 }
414 }
415}
416
417impl IntoFuture for Router<Handler> {
418 type Future = future::FutureResult<Self::Item, Self::Error>;
419 type Item = Self;
420 type Error = Error;
421
422 fn into_future(self) -> Self::Future {
423 future::ok(self)
424 }
425}
426
427impl Router<Handler> {
428 /// ServeFiles serves files from the given file system root.
429 ///
430 /// The path must end with "/*filepath", files are then served from the local
431 /// path /defined/root/dir/*filepath.
432 ///
433 /// For example if root is "/etc" and *filepath is "passwd", the local file
434 /// "/etc/passwd" would be served.
435 ///
436 /// ```rust
437 /// extern crate radix_router;
438 /// use radix_router::router::{Router, Handler};
439 /// let mut router: Router<Handler> = Router::new();
440 /// router.serve_files("/examples/*filepath", "examples");
441 /// ```
442 pub fn serve_files(&mut self, path: &str, root: &'static str) {
443 if path.as_bytes().len() < 10 || &path[path.len() - 10..] != "/*filepath" {
444 panic!("path must end with /*filepath in path '{}'", path);
445 }
446 let root_path = Path::new(root);
447 let get_files = move |_, ps: Params| -> BoxFut {
448 let filepath = ps.by_name("filepath").unwrap();
449 simple_file_send(root_path.join(&filepath[1..]).to_str().unwrap())
450 };
451
452 self.get(path, Box::new(get_files));
453 }
454}
455
456fn simple_file_send(f: &str) -> BoxFut {
457 // Serve a file by asynchronously reading it entirely into memory.
458 // Uses tokio_fs to open file asynchronously, then tokio_io to read into
459 // memory asynchronously.
460 let filename = f.to_string(); // we need to copy for lifetime issues
461 Box::new(
462 tokio_fs::file::File::open(filename)
463 .and_then(|file| {
464 let buf: Vec<u8> = Vec::new();
465 tokio_io::io::read_to_end(file, buf)
466 .and_then(|item| Ok(Response::new(item.1.into())))
467 .or_else(|_| {
468 Ok(Response::builder()
469 .status(StatusCode::INTERNAL_SERVER_ERROR)
470 .body(Body::empty())
471 .unwrap())
472 })
473 })
474 .or_else(|_| {
475 Ok(Response::builder()
476 .status(StatusCode::NOT_FOUND)
477 .body(Body::from("NOT_FOUND"))
478 .unwrap())
479 }),
480 )
481}
482
483// impl<T> NewService for Router<T>
484// where
485// T: Fn(Request<Body>, Response<Body>, Option<Params>) -> BoxFut,
486// {
487// type ReqBody = Body;
488// type ResBody = Body;
489// type Error = Error;
490// type Service = Self;
491// type Future = future::FutureResult<Self::Service, Self::InitError>;
492// type InitError = Error;
493
494// fn new_service(&self) -> Self::Future {
495// future::ok(*self.clone())
496// }
497// }
498
499#[cfg(test)]
500mod tests {
501 #[test]
502 fn params() {
503 use router::{Param, Params};
504
505 let params = Params(vec![
506 Param {
507 key: "hello".to_owned(),
508 value: "world".to_owned(),
509 },
510 Param {
511 key: "lalala".to_string(),
512 value: "papapa".to_string(),
513 },
514 ]);
515
516 assert_eq!(Some("world"), params.by_name("hello"));
517 assert_eq!(Some("papapa"), params.by_name("lalala"));
518 }
519
520 #[test]
521 #[should_panic(expected = "path must begin with '/' in path 'something'")]
522 fn handle_ivalid_path() {
523 // use http::Response;
524 use futures::future;
525 use hyper::{Body, Request, Response};
526 use router::{BoxFut, Params, Router};
527
528 let path = "something";
529 let mut router = Router::new();
530
531 router.handle("GET", path, |_req: Request<Body>, _: Params| -> BoxFut {
532 Box::new(future::ok(Response::new(Body::from("test"))))
533 });
534 }
535}