From 51de7ee05cd9adcf746156db3d27d23cc87ad3d2 Mon Sep 17 00:00:00 2001 From: "leonardo.yvens" Date: Fri, 3 Jul 2015 22:01:12 -0300 Subject: [PATCH] (feat) Allow trailing slash and refactoring. --- src/router.rs | 93 ++++++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/src/router.rs b/src/router.rs index 6f48ef0..eb127b0 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,5 +1,4 @@ use std::collections::HashMap; -use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::error::Error; use std::fmt; @@ -54,11 +53,8 @@ impl Router { pub fn route(&mut self, method: method::Method, glob: S, handler: H) -> &mut Router where H: Handler, S: AsRef { - match self.routers.entry(method) { - Vacant(entry) => entry.insert(Recognizer::new()), - Occupied(entry) => entry.into_mut() - }.add(glob.as_ref().trim_right_matches('/'), - Box::new(handler) as Box); + self.routers.entry(method).or_insert(Recognizer::new()) + .add(glob.as_ref(), Box::new(handler)); self } @@ -97,23 +93,17 @@ impl Router { self.route(method::Options, glob, handler) } - fn recognize<'a>(&'a self, method: &method::Method, path: &str) - -> Option>> { + fn recognize(&self, method: &method::Method, path: &str) + -> Option>> { self.routers.get(method).and_then(|router| router.recognize(path).ok()) } - fn handle_options(&self, req: &mut Request, path: &str) -> IronResult { + fn handle_options(&self, path: &str) -> Response { static METHODS: &'static [method::Method] = &[method::Get, method::Post, method::Post, method::Put, method::Delete, method::Head, method::Patch]; - // If there is an override, use it. - if let Some(matched) = self.recognize(&method::Options, path) { - req.extensions.insert::(matched.params); - return matched.handler.handle(req); - } - - // Else, get all the available methods and return them. + // Get all the available methods and return them. let mut options = vec![]; for method in METHODS.iter() { @@ -123,32 +113,43 @@ impl Router { } }); } + // If GET is there, HEAD is also there. + if options.contains(&method::Get) && !options.contains(&method::Head) { + options.push(method::Head); + } let mut res = Response::with(status::Ok); res.headers.set(headers::Allow(options)); - Ok(res) + res } - fn handle_trailing_slash(&self, req: &mut Request) -> IronResult { + // Tests for a match by adding or removing a trailing slash. + fn redirect_slash(&self, req : &Request) -> Option + { let mut url = req.url.clone(); - - // Pull off as many trailing slashes as possible. - while url.path.len() != 1 && url.path.last() == Some(&String::new()) { - url.path.pop(); + let mut path = url.path.connect("/"); + + if let Some(last_char) = path.chars().last() { + if last_char == '/' { + path.pop(); + url.path.pop(); + } else { + path.push('/'); + url.path.push("".to_string()); + } } - Err(IronError::new(TrailingSlash, (status::MovedPermanently, Redirect(url)))) + self.recognize(&req.method, &path).and( + Some(IronError::new(TrailingSlash, + (status::MovedPermanently, Redirect(url)))) + ) } - fn handle_method(&self, req: &mut Request, path: &str) -> IronResult { - let matched = match self.recognize(&req.method, path) { - Some(matched) => matched, - // No match. - None => return Err(IronError::new(NoRoute, status::NotFound)) - }; - - req.extensions.insert::(matched.params); - matched.handler.handle(req) + fn handle_method(&self, req: &mut Request, path: &str) -> Option> { + if let Some(matched) = self.recognize(&req.method, &path) { + req.extensions.insert::(matched.params); + Some(matched.handler.handle(req)) + } else { self.redirect_slash(req).and_then(|redirect| Some(Err(redirect))) } } } @@ -156,18 +157,21 @@ impl Key for Router { type Value = Params; } impl Handler for Router { fn handle(&self, req: &mut Request) -> IronResult { - if req.url.path.len() != 1 && Some(&String::new()) == req.url.path.last() { - return self.handle_trailing_slash(req); - } - - // No trailing slash let path = req.url.path.connect("/"); - if let method::Options = req.method { - return self.handle_options(req, &*path); - } - - self.handle_method(req, &*path) + self.handle_method(req, &path).unwrap_or_else(|| + match req.method { + method::Options => Ok(self.handle_options(&path)), + // For HEAD, fall back to GET. Hyper ensures no response body is written. + method::Head => { + req.method = method::Get; + self.handle_method(req, &path).unwrap_or( + Err(IronError::new(NoRoute, status::NotFound)) + ) + } + _ => Err(IronError::new(NoRoute, status::NotFound)) + } + ) } } @@ -186,8 +190,8 @@ impl Error for NoRoute { fn description(&self) -> &str { "No Route" } } -/// The error thrown by router if the request had a trailing slash, -/// it is always accompanied by a redirect. +/// The error thrown by router if a request was redirected +/// by adding or removing a trailing slash. #[derive(Debug)] pub struct TrailingSlash; @@ -200,4 +204,3 @@ impl fmt::Display for TrailingSlash { impl Error for TrailingSlash { fn description(&self) -> &str { "Trailing Slash" } } -