1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-28 01:32:57 +01: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

@ -10,7 +10,11 @@ Also it stores application specific state that is shared across all handlers
within same application.
Application acts as namespace for all routes, i.e all routes for specific application
has same url path prefix:
has same url path prefix. Application prefix always contains laading "/" slash.
If supplied prefix 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.
```rust,ignore
# extern crate actix_web;
@ -21,14 +25,14 @@ has same url path prefix:
# }
# fn main() {
let app = Application::new()
.prefix("/prefix")
.prefix("/app")
.resource("/index.html", |r| r.method(Method::GET).f(index))
.finish()
# }
```
In this example application with `/prefix` prefix and `index.html` resource
get created. This resource is available as on `/prefix/index.html` url.
In this example application with `/app` prefix and `index.html` resource
get created. This resource is available as on `/app/index.html` url.
For more information check
[*URL Matching*](./qs_5.html#using-a-application-prefix-to-compose-applications) section.
@ -56,6 +60,10 @@ fn main() {
```
All `/app1` requests route to first application, `/app2` to second and then all other to third.
Applications get matched based on registration order, if application with more general
prefix is registered before less generic, that would effectively block less generic
application to get matched. For example if *application* with prefix "/" get registered
as first application, it would match all incoming requests.
## State

View File

@ -445,7 +445,6 @@ fn main() {
## Using a Application Prefix to Compose Applications
The `Applicaiton::prefix()`" method allows to set specific application prefix.
If route_prefix is supplied to the include method, it must be a string.
This prefix represents a resource prefix that will be prepended to all resource patterns added
by the resource configuration. This can be used to help mount a set of routes at a different
location than the included callable's author intended while still maintaining the same
@ -471,7 +470,7 @@ fn main() {
In the above example, the *show_users* route will have an effective route pattern of
*/users/show* instead of */show* because the application's prefix argument will be prepended
to the pattern. The route will then only match if the URL path is /users/show,
to the pattern. The route will then only match if the URL path is */users/show*,
and when the `HttpRequest.url_for()` function is called with the route name show_users,
it will generate a URL with that same path.

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);