mirror of
https://github.com/actix/actix-web.git
synced 2024-11-26 11:31:09 +00:00
checks nested scopes in has_resource()
This commit is contained in:
parent
22385505a3
commit
2dd57a48d6
5 changed files with 143 additions and 59 deletions
|
@ -1,10 +1,10 @@
|
|||
# Changes
|
||||
|
||||
## [0.7.0] - 2018-07-10
|
||||
## [0.7.0] - 2018-07-17
|
||||
|
||||
### Added
|
||||
|
||||
* Add `.has_prefixed_route()` method to `router::RouteInfo` for route matching with prefix awareness
|
||||
* Add `.has_prefixed_resource()` method to `router::ResourceInfo` for route matching with prefix awareness
|
||||
|
||||
* Add `HttpMessage::readlines()` for reading line by line.
|
||||
|
||||
|
|
|
@ -92,7 +92,7 @@ impl<S> Handler<S> for NormalizePath {
|
|||
// merge slashes
|
||||
let p = self.re_merge.replace_all(req.path(), "/");
|
||||
if p.len() != req.path().len() {
|
||||
if req.resource().has_prefixed_route(p.as_ref()) {
|
||||
if req.resource().has_prefixed_resource(p.as_ref()) {
|
||||
let p = if !query.is_empty() {
|
||||
p + "?" + query
|
||||
} else {
|
||||
|
@ -105,7 +105,7 @@ impl<S> Handler<S> for NormalizePath {
|
|||
// merge slashes and append trailing slash
|
||||
if self.append && !p.ends_with('/') {
|
||||
let p = p.as_ref().to_owned() + "/";
|
||||
if req.resource().has_prefixed_route(&p) {
|
||||
if req.resource().has_prefixed_resource(&p) {
|
||||
let p = if !query.is_empty() {
|
||||
p + "?" + query
|
||||
} else {
|
||||
|
@ -120,7 +120,7 @@ impl<S> Handler<S> for NormalizePath {
|
|||
// try to remove trailing slash
|
||||
if p.ends_with('/') {
|
||||
let p = p.as_ref().trim_right_matches('/');
|
||||
if req.resource().has_prefixed_route(p) {
|
||||
if req.resource().has_prefixed_resource(p) {
|
||||
let mut req = HttpResponse::build(self.redirect);
|
||||
return if !query.is_empty() {
|
||||
req.header(
|
||||
|
@ -135,7 +135,7 @@ impl<S> Handler<S> for NormalizePath {
|
|||
} else if p.ends_with('/') {
|
||||
// try to remove trailing slash
|
||||
let p = p.as_ref().trim_right_matches('/');
|
||||
if req.resource().has_prefixed_route(p) {
|
||||
if req.resource().has_prefixed_resource(p) {
|
||||
let mut req = HttpResponse::build(self.redirect);
|
||||
return if !query.is_empty() {
|
||||
req.header(
|
||||
|
@ -151,7 +151,7 @@ impl<S> Handler<S> for NormalizePath {
|
|||
// append trailing slash
|
||||
if self.append && !req.path().ends_with('/') {
|
||||
let p = req.path().to_owned() + "/";
|
||||
if req.resource().has_prefixed_route(&p) {
|
||||
if req.resource().has_prefixed_resource(&p) {
|
||||
let p = if !query.is_empty() {
|
||||
p + "?" + query
|
||||
} else {
|
||||
|
|
|
@ -436,10 +436,10 @@ mod tests {
|
|||
router.register_resource(resource);
|
||||
|
||||
let info = router.default_route_info();
|
||||
assert!(info.has_route("/user/test.html"));
|
||||
assert!(info.has_prefixed_route("/user/test.html"));
|
||||
assert!(!info.has_route("/test/unknown"));
|
||||
assert!(!info.has_prefixed_route("/test/unknown"));
|
||||
assert!(info.has_resource("/user/test.html"));
|
||||
assert!(info.has_prefixed_resource("/user/test.html"));
|
||||
assert!(!info.has_resource("/test/unknown"));
|
||||
assert!(!info.has_prefixed_resource("/test/unknown"));
|
||||
|
||||
let req = TestRequest::with_header(header::HOST, "www.rust-lang.org")
|
||||
.finish_with_router(router);
|
||||
|
@ -468,10 +468,10 @@ mod tests {
|
|||
|
||||
let mut info = router.default_route_info();
|
||||
info.set_prefix(7);
|
||||
assert!(info.has_route("/user/test.html"));
|
||||
assert!(!info.has_prefixed_route("/user/test.html"));
|
||||
assert!(!info.has_route("/prefix/user/test.html"));
|
||||
assert!(info.has_prefixed_route("/prefix/user/test.html"));
|
||||
assert!(info.has_resource("/user/test.html"));
|
||||
assert!(!info.has_prefixed_resource("/user/test.html"));
|
||||
assert!(!info.has_resource("/prefix/user/test.html"));
|
||||
assert!(info.has_prefixed_resource("/prefix/user/test.html"));
|
||||
|
||||
let req = TestRequest::with_uri("/prefix/test")
|
||||
.prefix(7)
|
||||
|
@ -493,10 +493,10 @@ mod tests {
|
|||
|
||||
let mut info = router.default_route_info();
|
||||
info.set_prefix(7);
|
||||
assert!(info.has_route("/index.html"));
|
||||
assert!(!info.has_prefixed_route("/index.html"));
|
||||
assert!(!info.has_route("/prefix/index.html"));
|
||||
assert!(info.has_prefixed_route("/prefix/index.html"));
|
||||
assert!(info.has_resource("/index.html"));
|
||||
assert!(!info.has_prefixed_resource("/index.html"));
|
||||
assert!(!info.has_resource("/prefix/index.html"));
|
||||
assert!(info.has_prefixed_resource("/prefix/index.html"));
|
||||
|
||||
let req = TestRequest::with_uri("/prefix/test")
|
||||
.prefix(7)
|
||||
|
@ -518,8 +518,8 @@ mod tests {
|
|||
);
|
||||
|
||||
let info = router.default_route_info();
|
||||
assert!(!info.has_route("https://youtube.com/watch/unknown"));
|
||||
assert!(!info.has_prefixed_route("https://youtube.com/watch/unknown"));
|
||||
assert!(!info.has_resource("https://youtube.com/watch/unknown"));
|
||||
assert!(!info.has_prefixed_resource("https://youtube.com/watch/unknown"));
|
||||
|
||||
let req = TestRequest::default().finish_with_router(router);
|
||||
let url = req.url_for("youtube", &["oHg5SJYRHA0"]);
|
||||
|
|
156
src/router.rs
156
src/router.rs
|
@ -37,7 +37,7 @@ enum ResourceItem<S> {
|
|||
|
||||
/// Interface for application router.
|
||||
pub struct Router<S> {
|
||||
defs: Rc<Inner>,
|
||||
rmap: Rc<ResourceMap>,
|
||||
patterns: Vec<ResourcePattern<S>>,
|
||||
resources: Vec<ResourceItem<S>>,
|
||||
default: Option<DefaultResource<S>>,
|
||||
|
@ -46,7 +46,7 @@ pub struct Router<S> {
|
|||
/// Information about current resource
|
||||
#[derive(Clone)]
|
||||
pub struct ResourceInfo {
|
||||
router: Rc<Inner>,
|
||||
rmap: Rc<ResourceMap>,
|
||||
resource: ResourceId,
|
||||
params: Params,
|
||||
prefix: u16,
|
||||
|
@ -57,7 +57,7 @@ impl ResourceInfo {
|
|||
#[inline]
|
||||
pub fn name(&self) -> &str {
|
||||
if let ResourceId::Normal(idx) = self.resource {
|
||||
self.router.patterns[idx as usize].name()
|
||||
self.rmap.patterns[idx as usize].0.name()
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ impl ResourceInfo {
|
|||
#[inline]
|
||||
pub fn rdef(&self) -> Option<&ResourceDef> {
|
||||
if let ResourceId::Normal(idx) = self.resource {
|
||||
Some(&self.router.patterns[idx as usize])
|
||||
Some(&self.rmap.patterns[idx as usize].0)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ impl ResourceInfo {
|
|||
U: IntoIterator<Item = I>,
|
||||
I: AsRef<str>,
|
||||
{
|
||||
if let Some(pattern) = self.router.named.get(name) {
|
||||
if let Some(pattern) = self.rmap.named.get(name) {
|
||||
let path =
|
||||
pattern.resource_path(elements, &req.path()[..(self.prefix as usize)])?;
|
||||
if path.starts_with('/') {
|
||||
|
@ -130,24 +130,17 @@ impl ResourceInfo {
|
|||
}
|
||||
}
|
||||
|
||||
/// Check if application contains matching route.
|
||||
/// Check if application contains matching resource.
|
||||
///
|
||||
/// 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
|
||||
/// following path would be recognizable `/test/name` but `has_resource()` call
|
||||
/// would return `false`.
|
||||
pub fn has_route(&self, path: &str) -> bool {
|
||||
let path = if path.is_empty() { "/" } else { path };
|
||||
|
||||
for pattern in &self.router.patterns {
|
||||
if pattern.is_match(path) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
pub fn has_resource(&self, path: &str) -> bool {
|
||||
self.rmap.has_resource(path)
|
||||
}
|
||||
|
||||
/// Check if application contains matching route.
|
||||
/// Check if application contains matching resource.
|
||||
///
|
||||
/// This method does take `prefix` into account
|
||||
/// but behaves like `has_route` in case `prefix` is not set in the router.
|
||||
|
@ -157,18 +150,35 @@ impl ResourceInfo {
|
|||
/// would return `true`.
|
||||
/// It will not match against prefix in case it's not given. For example for `/name`
|
||||
/// with a `/test` prefix would return `false`
|
||||
pub fn has_prefixed_route(&self, path: &str) -> bool {
|
||||
pub fn has_prefixed_resource(&self, path: &str) -> bool {
|
||||
let prefix = self.prefix as usize;
|
||||
if prefix >= path.len() {
|
||||
return false;
|
||||
}
|
||||
self.has_route(&path[prefix..])
|
||||
self.rmap.has_resource(&path[prefix..])
|
||||
}
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
pub(crate) struct ResourceMap {
|
||||
named: HashMap<String, ResourceDef>,
|
||||
patterns: Vec<ResourceDef>,
|
||||
patterns: Vec<(ResourceDef, Option<Rc<ResourceMap>>)>,
|
||||
}
|
||||
|
||||
impl ResourceMap {
|
||||
pub fn has_resource(&self, path: &str) -> bool {
|
||||
let path = if path.is_empty() { "/" } else { path };
|
||||
|
||||
for (pattern, rmap) in &self.patterns {
|
||||
if let Some(ref rmap) = rmap {
|
||||
if let Some(plen) = pattern.is_prefix_match(path) {
|
||||
return rmap.has_resource(&path[plen..]);
|
||||
}
|
||||
} else if pattern.is_match(path) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static> Default for Router<S> {
|
||||
|
@ -180,7 +190,7 @@ impl<S: 'static> Default for Router<S> {
|
|||
impl<S: 'static> Router<S> {
|
||||
pub(crate) fn new() -> Self {
|
||||
Router {
|
||||
defs: Rc::new(Inner {
|
||||
rmap: Rc::new(ResourceMap {
|
||||
named: HashMap::new(),
|
||||
patterns: Vec::new(),
|
||||
}),
|
||||
|
@ -195,7 +205,7 @@ impl<S: 'static> Router<S> {
|
|||
ResourceInfo {
|
||||
params,
|
||||
prefix: 0,
|
||||
router: self.defs.clone(),
|
||||
rmap: self.rmap.clone(),
|
||||
resource: ResourceId::Normal(idx),
|
||||
}
|
||||
}
|
||||
|
@ -208,7 +218,7 @@ impl<S: 'static> Router<S> {
|
|||
ResourceInfo {
|
||||
params,
|
||||
prefix: 0,
|
||||
router: self.defs.clone(),
|
||||
rmap: self.rmap.clone(),
|
||||
resource: ResourceId::Default,
|
||||
}
|
||||
}
|
||||
|
@ -217,7 +227,7 @@ impl<S: 'static> Router<S> {
|
|||
pub(crate) fn default_route_info(&self) -> ResourceInfo {
|
||||
ResourceInfo {
|
||||
params: Params::new(),
|
||||
router: self.defs.clone(),
|
||||
rmap: self.rmap.clone(),
|
||||
resource: ResourceId::Default,
|
||||
prefix: 0,
|
||||
}
|
||||
|
@ -225,18 +235,18 @@ impl<S: 'static> Router<S> {
|
|||
|
||||
pub(crate) fn register_resource(&mut self, resource: Resource<S>) {
|
||||
{
|
||||
let inner = Rc::get_mut(&mut self.defs).unwrap();
|
||||
let rmap = Rc::get_mut(&mut self.rmap).unwrap();
|
||||
|
||||
let name = resource.get_name();
|
||||
if !name.is_empty() {
|
||||
assert!(
|
||||
!inner.named.contains_key(name),
|
||||
!rmap.named.contains_key(name),
|
||||
"Named resource {:?} is registered.",
|
||||
name
|
||||
);
|
||||
inner.named.insert(name.to_owned(), resource.rdef().clone());
|
||||
rmap.named.insert(name.to_owned(), resource.rdef().clone());
|
||||
}
|
||||
inner.patterns.push(resource.rdef().clone());
|
||||
rmap.patterns.push((resource.rdef().clone(), None));
|
||||
}
|
||||
self.patterns
|
||||
.push(ResourcePattern::Resource(resource.rdef().clone()));
|
||||
|
@ -244,10 +254,10 @@ impl<S: 'static> Router<S> {
|
|||
}
|
||||
|
||||
pub(crate) fn register_scope(&mut self, mut scope: Scope<S>) {
|
||||
Rc::get_mut(&mut self.defs)
|
||||
Rc::get_mut(&mut self.rmap)
|
||||
.unwrap()
|
||||
.patterns
|
||||
.push(scope.rdef().clone());
|
||||
.push((scope.rdef().clone(), Some(scope.router().rmap.clone())));
|
||||
let filters = scope.take_filters();
|
||||
self.patterns
|
||||
.push(ResourcePattern::Scope(scope.rdef().clone(), filters));
|
||||
|
@ -259,10 +269,10 @@ impl<S: 'static> Router<S> {
|
|||
filters: Option<Vec<Box<Predicate<S>>>>,
|
||||
) {
|
||||
let rdef = ResourceDef::prefix(path);
|
||||
Rc::get_mut(&mut self.defs)
|
||||
Rc::get_mut(&mut self.rmap)
|
||||
.unwrap()
|
||||
.patterns
|
||||
.push(rdef.clone());
|
||||
.push((rdef.clone(), None));
|
||||
self.resources.push(ResourceItem::Handler(hnd));
|
||||
self.patterns.push(ResourcePattern::Handler(rdef, filters));
|
||||
}
|
||||
|
@ -298,13 +308,13 @@ impl<S: 'static> Router<S> {
|
|||
}
|
||||
|
||||
pub(crate) fn register_external(&mut self, name: &str, rdef: ResourceDef) {
|
||||
let inner = Rc::get_mut(&mut self.defs).unwrap();
|
||||
let rmap = Rc::get_mut(&mut self.rmap).unwrap();
|
||||
assert!(
|
||||
!inner.named.contains_key(name),
|
||||
!rmap.named.contains_key(name),
|
||||
"Named resource {:?} is registered.",
|
||||
name
|
||||
);
|
||||
inner.named.insert(name.to_owned(), rdef);
|
||||
rmap.named.insert(name.to_owned(), rdef);
|
||||
}
|
||||
|
||||
pub(crate) fn register_route<T, F, R>(&mut self, path: &str, method: Method, f: F)
|
||||
|
@ -406,7 +416,7 @@ impl<S: 'static> Router<S> {
|
|||
ResourceInfo {
|
||||
prefix: tail as u16,
|
||||
params: Params::new(),
|
||||
router: self.defs.clone(),
|
||||
rmap: self.rmap.clone(),
|
||||
resource: ResourceId::Default,
|
||||
}
|
||||
}
|
||||
|
@ -534,6 +544,54 @@ impl ResourceDef {
|
|||
}
|
||||
}
|
||||
|
||||
fn is_prefix_match(&self, path: &str) -> Option<usize> {
|
||||
let plen = path.len();
|
||||
let path = if path.is_empty() { "/" } else { path };
|
||||
|
||||
match self.tp {
|
||||
PatternType::Static(ref s) => if s == path {
|
||||
Some(plen)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
PatternType::Dynamic(ref re, _, len) => {
|
||||
if let Some(captures) = re.captures(path) {
|
||||
let mut pos = 0;
|
||||
let mut passed = false;
|
||||
for capture in captures.iter() {
|
||||
if let Some(ref m) = capture {
|
||||
if !passed {
|
||||
passed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
pos = m.end();
|
||||
}
|
||||
}
|
||||
Some(plen + pos + len)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
PatternType::Prefix(ref s) => {
|
||||
let len = if path == s {
|
||||
s.len()
|
||||
} else if path.starts_with(s)
|
||||
&& (s.ends_with('/') || path.split_at(s.len()).1.starts_with('/'))
|
||||
{
|
||||
if s.ends_with('/') {
|
||||
s.len() - 1
|
||||
} else {
|
||||
s.len()
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
Some(min(plen, len))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Are the given path and parameters a match against this resource?
|
||||
pub fn match_with_params(&self, req: &Request, plen: usize) -> Option<Params> {
|
||||
let path = &req.path()[plen..];
|
||||
|
@ -588,7 +646,9 @@ impl ResourceDef {
|
|||
|
||||
match self.tp {
|
||||
PatternType::Static(ref s) => if s == path {
|
||||
Some(Params::with_url(req.url()))
|
||||
let mut params = Params::with_url(req.url());
|
||||
params.set_tail(req.path().len() as u16);
|
||||
Some(params)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
|
@ -1008,4 +1068,24 @@ mod tests {
|
|||
assert_eq!(info.resource, ResourceId::Normal(1));
|
||||
assert_eq!(info.name(), "r2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_resource() {
|
||||
let mut router = Router::<()>::new();
|
||||
let scope = Scope::new("/test").resource("/name", |_| "done");
|
||||
router.register_scope(scope);
|
||||
|
||||
{
|
||||
let info = router.default_route_info();
|
||||
assert!(!info.has_resource("/test"));
|
||||
assert!(info.has_resource("/test/name"));
|
||||
}
|
||||
|
||||
let scope =
|
||||
Scope::new("/test2").nested("/test10", |s| s.resource("/name", |_| "done"));
|
||||
router.register_scope(scope);
|
||||
|
||||
let info = router.default_route_info();
|
||||
assert!(info.has_resource("/test2/test10/name"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,6 +73,10 @@ impl<S: 'static> Scope<S> {
|
|||
&self.rdef
|
||||
}
|
||||
|
||||
pub(crate) fn router(&self) -> &Router<S> {
|
||||
self.router.as_ref()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn take_filters(&mut self) -> Vec<Box<Predicate<S>>> {
|
||||
mem::replace(&mut self.filters, Vec::new())
|
||||
|
|
Loading…
Reference in a new issue