1
0
mirror of https://github.com/actix/actix-extras.git synced 2025-06-26 02:19:22 +02:00

better application handling, fix url_for method for routes with prefix

This commit is contained in:
Nikolay Kim
2017-12-29 14:04:13 -08:00
parent 491d43aa8c
commit 6ea894547d
5 changed files with 123 additions and 42 deletions

View File

@ -50,7 +50,13 @@ impl<S: 'static> HttpApplication<S> {
impl<S: 'static> HttpHandler for HttpApplication<S> {
fn handle(&mut self, req: HttpRequest) -> Result<Box<HttpHandlerTask>, HttpRequest> {
if req.path().starts_with(&self.prefix) {
let m = {
let path = req.path();
path.starts_with(&self.prefix) && (
path.len() == self.prefix.len() ||
path.split_at(self.prefix.len()).1.starts_with('/'))
};
if m {
let inner = Rc::clone(&self.inner);
let req = req.with_state(Rc::clone(&self.state), self.router.clone());
@ -126,11 +132,14 @@ impl<S> Application<S> where S: 'static {
///
/// Only requests that matches application's prefix get processed by this application.
/// Application prefix always contains laading "/" slash. If supplied prefix
/// does not contain leading slash, it get inserted.
/// does not contain leading slash, it get inserted. Prefix should
/// consists of valud path segments. i.e for application with
/// prefix `/app` any request with following paths `/app`, `/app/` or `/app/test`
/// would match, but path `/application` would not match.
///
/// Inthe following example only requests with "/app/" path prefix
/// get handled. Request with path "/app/test/" will be handled,
/// but request with path "/other/..." will return *NOT FOUND*
/// In the following example only requests with "/app/" path prefix
/// get handled. Request with path "/app/test/" would be handled,
/// but request with path "/application" or "/other/..." would return *NOT FOUND*
///
/// ```rust
/// # extern crate actix_web;
@ -387,4 +396,23 @@ mod tests {
let resp = app.run(req);
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
}
#[test]
fn test_prefix() {
let mut app = Application::new()
.prefix("/test")
.resource("/blah", |r| r.h(httpcodes::HTTPOk))
.finish();
let req = TestRequest::with_uri("/test").finish();
let resp = app.handle(req);
assert!(resp.is_ok());
let req = TestRequest::with_uri("/test/").finish();
let resp = app.handle(req);
assert!(resp.is_ok());
let req = TestRequest::with_uri("/testing").finish();
let resp = app.handle(req);
assert!(resp.is_err());
}
}

View File

@ -823,7 +823,7 @@ mod tests {
resource.name("index");
let mut map = HashMap::new();
map.insert(Pattern::new("index", "/user/{name}.{ext}"), Some(resource));
let (router, _) = Router::new("", ServerSettings::default(), map);
let (router, _) = Router::new("/", ServerSettings::default(), map);
assert!(router.has_route("/user/test.html"));
assert!(!router.has_route("/test/unknown"));
@ -840,6 +840,27 @@ mod tests {
assert_eq!(url.ok().unwrap().as_str(), "http://www.rust-lang.org/user/test.html");
}
#[test]
fn test_url_for_with_prefix() {
let mut headers = HeaderMap::new();
headers.insert(header::HOST,
header::HeaderValue::from_static("www.rust-lang.org"));
let req = HttpRequest::new(
Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None);
let mut resource = Resource::<()>::default();
resource.name("index");
let mut map = HashMap::new();
map.insert(Pattern::new("index", "/user/{name}.{ext}"), Some(resource));
let (router, _) = Router::new("/prefix/", ServerSettings::default(), map);
assert!(router.has_route("/user/test.html"));
assert!(!router.has_route("/prefix/user/test.html"));
let req = req.with_state(Rc::new(()), router);
let url = req.url_for("index", &["test", "html"]);
assert_eq!(url.ok().unwrap().as_str(), "http://www.rust-lang.org/prefix/user/test.html");
}
#[test]
fn test_url_for_external() {
let req = HttpRequest::new(

View File

@ -16,6 +16,7 @@ pub struct Router(Rc<Inner>);
struct Inner {
prefix: String,
prefix_len: usize,
regset: RegexSet,
named: HashMap<String, (Pattern, bool)>,
patterns: Vec<Pattern>,
@ -47,8 +48,10 @@ impl Router {
}
}
let len = prefix.len();
(Router(Rc::new(
Inner{ prefix: prefix,
prefix_len: len,
regset: RegexSet::new(&paths).unwrap(),
named: named,
patterns: patterns,
@ -71,7 +74,10 @@ impl Router {
pub fn recognize<S>(&self, req: &mut HttpRequest<S>) -> Option<usize> {
let mut idx = None;
{
let path = &req.path()[self.0.prefix.len()..];
if self.0.prefix_len > req.path().len() {
return None
}
let path = &req.path()[self.0.prefix_len..];
if path.is_empty() {
if let Some(i) = self.0.regset.matches("/").into_iter().next() {
idx = Some(i);
@ -82,7 +88,7 @@ impl Router {
}
if let Some(idx) = idx {
let path: &str = unsafe{ mem::transmute(&req.path()[self.0.prefix.len()..]) };
let path: &str = unsafe{ mem::transmute(&req.path()[self.0.prefix_len..]) };
self.0.patterns[idx].update_match_info(path, req);
return Some(idx)
} else {
@ -91,13 +97,17 @@ impl Router {
}
/// Check if application contains matching route.
///
/// This method does not take `prefix` into account.
/// For example if prefix is `/test` and router contains route `/name`,
/// following path would be recognizable `/test/name` but `has_route()` call
/// would return `false`.
pub fn has_route(&self, path: &str) -> bool {
let p = &path[self.0.prefix.len()..];
if p.is_empty() {
if path.is_empty() {
if self.0.regset.matches("/").into_iter().next().is_some() {
return true
}
} else if self.0.regset.matches(p).into_iter().next().is_some() {
} else if self.0.regset.matches(path).into_iter().next().is_some() {
return true
}
false
@ -205,12 +215,11 @@ impl Pattern {
{
let mut iter = elements.into_iter();
let mut path = if let Some(prefix) = prefix {
let mut path = String::from(prefix);
path.push('/');
path
format!("{}/", prefix)
} else {
String::new()
};
println!("TEST: {:?} {:?}", path, prefix);
for el in &self.elements {
match *el {
PatternElement::Str(ref s) => path.push_str(s),
@ -297,11 +306,9 @@ impl Hash for Pattern {
#[cfg(test)]
mod tests {
use regex::Regex;
use super::*;
use http::{Uri, Version, Method};
use http::header::HeaderMap;
use std::str::FromStr;
use regex::Regex;
use test::TestRequest;
#[test]
fn test_recognizer() {
@ -314,45 +321,63 @@ mod tests {
routes.insert(Pattern::new("", "{test}/index.html"), Some(Resource::default()));
let (rec, _) = Router::new::<()>("", ServerSettings::default(), routes);
let mut req = HttpRequest::new(
Method::GET, Uri::from_str("/name").unwrap(),
Version::HTTP_11, HeaderMap::new(), None);
let mut req = TestRequest::with_uri("/name").finish();
assert!(rec.recognize(&mut req).is_some());
assert!(req.match_info().is_empty());
let mut req = HttpRequest::new(
Method::GET, Uri::from_str("/name/value").unwrap(),
Version::HTTP_11, HeaderMap::new(), None);
let mut req = TestRequest::with_uri("/name/value").finish();
assert!(rec.recognize(&mut req).is_some());
assert_eq!(req.match_info().get("val").unwrap(), "value");
assert_eq!(&req.match_info()["val"], "value");
let mut req = HttpRequest::new(
Method::GET, Uri::from_str("/name/value2/index.html").unwrap(),
Version::HTTP_11, HeaderMap::new(), None);
let mut req = TestRequest::with_uri("/name/value2/index.html").finish();
assert!(rec.recognize(&mut req).is_some());
assert_eq!(req.match_info().get("val").unwrap(), "value2");
let mut req = HttpRequest::new(
Method::GET, Uri::from_str("/vtest/ttt/index.html").unwrap(),
Version::HTTP_11, HeaderMap::new(), None);
let mut req = TestRequest::with_uri("/vtest/ttt/index.html").finish();
assert!(rec.recognize(&mut req).is_some());
assert_eq!(req.match_info().get("val").unwrap(), "test");
assert_eq!(req.match_info().get("val2").unwrap(), "ttt");
let mut req = HttpRequest::new(
Method::GET, Uri::from_str("/v/blah-blah/index.html").unwrap(),
Version::HTTP_11, HeaderMap::new(), None);
let mut req = TestRequest::with_uri("/v/blah-blah/index.html").finish();
assert!(rec.recognize(&mut req).is_some());
assert_eq!(req.match_info().get("tail").unwrap(), "blah-blah/index.html");
let mut req = HttpRequest::new(
Method::GET, Uri::from_str("/bbb/index.html").unwrap(),
Version::HTTP_11, HeaderMap::new(), None);
let mut req = TestRequest::with_uri("/bbb/index.html").finish();
assert!(rec.recognize(&mut req).is_some());
assert_eq!(req.match_info().get("test").unwrap(), "bbb");
}
#[test]
fn test_recognizer_with_prefix() {
let mut routes = HashMap::new();
routes.insert(Pattern::new("", "/name"), Some(Resource::default()));
routes.insert(Pattern::new("", "/name/{val}"), Some(Resource::default()));
let (rec, _) = Router::new::<()>("/test", ServerSettings::default(), routes);
let mut req = TestRequest::with_uri("/name").finish();
assert!(rec.recognize(&mut req).is_none());
let mut req = TestRequest::with_uri("/test/name").finish();
assert!(rec.recognize(&mut req).is_some());
let mut req = TestRequest::with_uri("/test/name/value").finish();
assert!(rec.recognize(&mut req).is_some());
assert_eq!(req.match_info().get("val").unwrap(), "value");
assert_eq!(&req.match_info()["val"], "value");
// same patterns
let mut routes = HashMap::new();
routes.insert(Pattern::new("", "/name"), Some(Resource::default()));
routes.insert(Pattern::new("", "/name/{val}"), Some(Resource::default()));
let (rec, _) = Router::new::<()>("/test2", ServerSettings::default(), routes);
let mut req = TestRequest::with_uri("/name").finish();
assert!(rec.recognize(&mut req).is_none());
let mut req = TestRequest::with_uri("/test2/name").finish();
assert!(rec.recognize(&mut req).is_some());
}
fn assert_parse(pattern: &str, expected_re: &str) -> Regex {
let (re_str, _) = Pattern::parse(pattern);
assert_eq!(&*re_str, expected_re);