Fix search order for normal and prefix paths

This commit is contained in:
Nikolay Kim 2021-06-14 21:41:08 +06:00
parent f4006c72ae
commit 391cf24fa9
5 changed files with 248 additions and 212 deletions

View file

@ -1,5 +1,9 @@
# Changes
## [0.4.4] - 2021-06-14
* Fix search order for normal and prefix paths
## [0.4.3] - 2021-04-03
* Disable some of regex features

View file

@ -1,6 +1,6 @@
[package]
name = "ntex-router"
version = "0.4.3"
version = "0.4.4"
authors = ["ntex contributors <team@ntex.rs>"]
description = "Path router"
keywords = ["ntex"]
@ -21,7 +21,7 @@ serde = "1.0"
bytestring = "1.0"
log = "0.4"
http = { version = "0.2", optional = true }
regex = { version = "1.4.5", default-features = false, features = ["std"] }
regex = { version = "1.5.4", default-features = false, features = ["std"] }
[dev-dependencies]
http = "0.2"

View file

@ -8,22 +8,27 @@ use super::{Resource, ResourcePath};
#[derive(Debug, Clone)]
pub(super) struct Tree {
key: Vec<Segment>,
value: Vec<Value>,
children: Vec<Tree>,
items: Vec<Item>,
}
#[derive(Clone, Debug)]
enum Item {
Value(Value),
Subtree(Tree),
}
#[derive(Copy, Clone, Debug)]
enum Value {
Val(usize),
Slesh(usize),
Slash(usize),
Prefix(usize),
PrefixSlesh(usize),
PrefixSlash(usize),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum PathState {
Empty,
Slesh,
Slash,
Tail,
}
@ -31,9 +36,9 @@ impl Value {
fn value(&self) -> usize {
match self {
Value::Val(v) => *v,
Value::Slesh(v) => *v,
Value::Slash(v) => *v,
Value::Prefix(v) => *v,
Value::PrefixSlesh(v) => *v,
Value::PrefixSlash(v) => *v,
}
}
}
@ -42,8 +47,7 @@ impl Default for Tree {
fn default() -> Tree {
Tree {
key: Vec::new(),
value: Vec::new(),
children: Vec::new(),
items: Vec::new(),
}
}
}
@ -56,9 +60,9 @@ impl Tree {
let val = if resource.tp[0].slesh {
if resource.prefix {
Value::PrefixSlesh(value)
Value::PrefixSlash(value)
} else {
Value::Slesh(value)
Value::Slash(value)
}
} else if resource.prefix {
Value::Prefix(value)
@ -71,9 +75,9 @@ impl Tree {
for seg in &resource.tp[1..] {
let val = if seg.slesh {
if resource.prefix {
Value::PrefixSlesh(value)
Value::PrefixSlash(value)
} else {
Value::Slesh(value)
Value::Slash(value)
}
} else if resource.prefix {
Value::Prefix(value)
@ -86,25 +90,21 @@ impl Tree {
}
fn child(key: Vec<Segment>, value: Option<Value>) -> Tree {
let value = if let Some(val) = value {
vec![val]
let items = if let Some(val) = value {
vec![Item::Value(val)]
} else {
Vec::new()
};
Tree {
key,
value,
children: Vec::new(),
}
Tree { key, items }
}
pub(super) fn insert(&mut self, resource: &ResourceDef, value: usize) {
for seg in &resource.tp {
let value = if seg.slesh {
if resource.prefix {
Value::PrefixSlesh(value)
Value::PrefixSlash(value)
} else {
Value::Slesh(value)
Value::Slash(value)
}
} else if resource.prefix {
Value::Prefix(value)
@ -123,35 +123,36 @@ impl Tree {
if p < self.key.len() {
let child = Tree {
key: self.key.split_off(p),
value: mem::take(&mut self.value),
children: mem::take(&mut self.children),
items: mem::take(&mut self.items),
};
self.children.push(child);
self.items.push(Item::Subtree(child));
}
// update value if key is the same
if p == key.len() {
match value {
Value::PrefixSlesh(v) => {
self.children
.push(Tree::child(Vec::new(), Some(Value::Prefix(v))));
Value::PrefixSlash(v) => {
self.items.push(Item::Subtree(Tree::child(
Vec::new(),
Some(Value::Prefix(v)),
)));
}
value => self.value.push(value),
value => self.items.push(Item::Value(value)),
}
} else {
// insert into sub tree
let mut child = self
.children
.iter_mut()
.find(|x| common_prefix(&x.key, &key[p..]) > 0);
if let Some(ref mut child) = child {
child.insert_path(key[p..].to_vec(), value)
} else {
self.children
.push(Tree::child(key[p..].to_vec(), Some(value)));
for child in &mut self.items {
if let Item::Subtree(ref mut tree) = child {
if common_prefix(&tree.key, &key[p..]) > 0 {
tree.insert_path(key[p..].to_vec(), value);
return;
}
}
}
self.items
.push(Item::Subtree(Tree::child(key[p..].to_vec(), Some(value))));
}
}
pub(crate) fn find<T, R>(&self, resource: &mut R) -> Option<usize>
where
@ -213,17 +214,42 @@ impl Tree {
if self.key.is_empty() {
if path == "/" {
for val in &self.value {
for val in &self.items {
match val {
Item::Value(val) => {
let v = match val {
Value::Slesh(v) | Value::Prefix(v) | Value::PrefixSlesh(v) => *v,
Value::Slash(v)
| Value::Prefix(v)
| Value::PrefixSlash(v) => *v,
_ => continue,
};
if check(v, resource) {
return Some(v);
}
}
Item::Subtree(ref tree) => {
let result = tree.find_inner_wrapped(
"",
resource,
check,
1,
&mut segments,
insensitive,
base_skip - 1,
);
if let Some((val, skip)) = result {
let path = resource.resource_path();
path.segments = segments;
path.skip += skip as u16;
return Some(val);
}
}
}
}
} else if path.is_empty() {
for val in &self.value {
for val in &self.items {
match val {
Item::Value(val) => {
let v = match val {
Value::Val(v) | Value::Prefix(v) => *v,
_ => continue,
@ -232,80 +258,90 @@ impl Tree {
return Some(v);
}
}
Item::Subtree(ref tree) => {
let result = tree.find_inner_wrapped(
"",
resource,
check,
1,
&mut segments,
insensitive,
base_skip,
);
if let Some((val, skip)) = result {
let path = resource.resource_path();
path.segments = segments;
path.skip += skip as u16;
return Some(val);
}
}
}
}
} else {
for val in &self.value {
let subtree_path = if let Some(spath) = path.strip_prefix('/') {
spath
} else {
base_skip -= 1;
path
};
for val in &self.items {
match val {
Item::Value(val) => {
let v = match val {
Value::PrefixSlesh(v) => *v,
Value::PrefixSlash(v) => *v,
_ => continue,
};
if check(v, resource) {
return Some(v);
}
}
}
let path = if let Some(path) = path.strip_prefix('/') {
path
} else {
base_skip -= 1;
path
};
let res = self
.children
.iter()
.map(|x| {
x.find_inner_wrapped(
path,
Item::Subtree(ref tree) => {
let result = tree.find_inner_wrapped(
subtree_path,
resource,
check,
1,
&mut segments,
insensitive,
base_skip,
)
})
.flatten()
.next();
return if let Some((val, skip)) = res {
);
if let Some((val, skip)) = result {
let path = resource.resource_path();
path.segments = segments;
path.skip += skip as u16;
Some(val)
} else {
None
};
return Some(val);
}
if path.is_empty() {
return None;
}
let path = if let Some(path) = path.strip_prefix('/') {
path
}
}
}
} else if !path.is_empty() {
let subtree_path = if let Some(spath) = path.strip_prefix('/') {
spath
} else {
base_skip -= 1;
path
};
if let Some((val, skip)) = self.find_inner_wrapped(
path,
let res = self.find_inner_wrapped(
subtree_path,
resource,
check,
1,
&mut segments,
insensitive,
base_skip,
) {
);
if let Some((val, skip)) = res {
let path = resource.resource_path();
path.segments = segments;
path.skip += skip as u16;
Some(val)
} else {
None
return Some(val);
}
}
None
}
#[allow(clippy::too_many_arguments)]
fn find_inner_wrapped<T, R, F>(
@ -357,9 +393,9 @@ impl Tree {
{
if self.key.is_empty() {
if path.is_empty() {
for val in &self.value {
for val in &self.items {
let v = match val {
Value::Val(v) | Value::Prefix(v) => *v,
Item::Value(Value::Val(v)) | Item::Value(Value::Prefix(v)) => *v,
_ => continue,
};
if check(v, resource) {
@ -367,9 +403,9 @@ impl Tree {
}
}
} else {
for val in &self.value {
for val in &self.items {
let v = match val {
Value::Prefix(v) => *v,
Item::Value(Value::Prefix(v)) => *v,
_ => continue,
};
if check(v, resource) {
@ -434,13 +470,15 @@ impl Tree {
// we have to process checker for tail matches separately
if tail && is_match {
// checker
for val in &self.value {
for val in &self.items {
if let Item::Value(ref val) = val {
let v = val.value();
if check(v, resource) {
return Some((v, skip + idx));
}
}
}
}
is_match
} else {
@ -458,30 +496,37 @@ impl Tree {
return {
if key.is_empty() {
// checker
for val in &self.value {
for val in &self.items {
if let Item::Value(ref val) = val {
let v = match val {
Value::Val(v) | Value::Prefix(v) => *v,
Value::Slesh(_) | Value::PrefixSlesh(_) => continue,
Value::Slash(_) | Value::PrefixSlash(_) => {
continue
}
};
if check(v, resource) {
return Some((v, skip));
}
}
}
}
None
};
} else if key.is_empty() {
path = &path[idx..];
let subtree_path = if path.len() != 1 { &path[1..] } else { path };
let p = if path.is_empty() {
PathState::Empty
} else if path == "/" {
PathState::Slesh
PathState::Slash
} else {
PathState::Tail
};
for val in &self.value {
for val in &self.items {
match val {
Item::Value(val) => {
let v = match val {
Value::Val(v) => {
if p == PathState::Empty {
@ -490,48 +535,24 @@ impl Tree {
continue;
}
}
Value::Slesh(v) => {
if p == PathState::Slesh {
Value::Slash(v) => {
if p == PathState::Slash {
*v
} else {
continue;
}
}
Value::Prefix(v) => {
if p == PathState::Slesh || p == PathState::Tail {
if !self.children.is_empty() {
let p = if path.len() != 1 {
&path[1..]
} else {
path
};
if let Some(res) = self
.children
.iter()
.map(|x| {
x.find_inner_wrapped(
p,
resource,
check,
skip,
segments,
insensitive,
base_skip,
)
})
.flatten()
.next()
if p == PathState::Slash || p == PathState::Tail
{
return Some(res);
}
}
*v
} else {
continue;
}
}
Value::PrefixSlesh(v) => {
if p == PathState::Slesh || p == PathState::Tail {
Value::PrefixSlash(v) => {
if p == PathState::Slash || p == PathState::Tail
{
*v
} else {
continue;
@ -542,25 +563,23 @@ impl Tree {
return Some((v, skip - 1));
}
}
let path = if path.len() != 1 { &path[1..] } else { path };
return self
.children
.iter()
.map(|x| {
x.find_inner_wrapped(
path,
Item::Subtree(ref tree) => {
let result = tree.find_inner_wrapped(
subtree_path,
resource,
check,
skip,
segments,
insensitive,
base_skip,
)
})
.flatten()
.next();
);
if result.is_some() {
return result;
}
}
}
}
return None;
} else {
path = &path[idx + 1..];
}

View file

@ -45,7 +45,7 @@ http-framework = ["h2", "http", "httparse",
[dependencies]
ntex-codec = "0.4.1"
ntex-rt = "0.2.2"
ntex-router = "0.4.3"
ntex-router = "0.4.4"
ntex-service = "0.1.9"
ntex-macros = "0.1.3"
ntex-util = "0.1.1"
@ -55,15 +55,15 @@ base64 = "0.13"
bitflags = "1.2"
bytes = "1.0"
bytestring = { version = "1.0", features = ["serde"] }
derive_more = "0.99.13"
derive_more = "0.99.14"
futures-core = { version = "0.3.15", default-features = false, features = ["alloc"] }
futures-sink = { version = "0.3.15", default-features = false, features = ["alloc"] }
log = "0.4"
mio = "0.7.10"
mio = "0.7.11"
num_cpus = "1.13"
nanorand = { version = "0.5", default-features = false, features = ["std", "wyrand"] }
pin-project-lite = "0.2"
regex = { version = "1.4.5", default-features = false, features = ["std"] }
regex = { version = "1.5.4", default-features = false, features = ["std"] }
sha-1 = "0.9"
slab = "0.4"
serde = { version = "1.0", features=["derive"] }
@ -73,7 +73,7 @@ tokio = { version = "1", default-features = false, features = ["sync"] }
# http/web framework
h2 = { version = "0.3", optional = true }
http = { version = "0.2", optional = true }
httparse = { version = "1.3", optional = true }
httparse = { version = "1.4.1", optional = true }
httpdate = { version = "1.0", optional = true }
encoding_rs = { version = "0.8", optional = true }
mime = { version = "0.3", optional = true }

View file

@ -900,25 +900,38 @@ mod tests {
#[crate::rt_test]
async fn test_scope_guard() {
let srv =
init_service(App::new().service(
web::scope("/app").guard(guard::Get()).service(
let srv = init_service(
App::new()
.service(web::scope("/app").guard(guard::Get()).service(
web::resource("/path1").to(|| async { HttpResponse::Ok() }),
),
))
.service(web::scope("/app").guard(guard::Post()).service(
web::resource("/path1").to(|| async { HttpResponse::NotModified() }),
))
.service(
web::resource("/app/path1")
.to(|| async { HttpResponse::NoContent() }),
),
)
.await;
let req = TestRequest::with_uri("/app/path1")
.method(Method::POST)
.to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
let req = TestRequest::with_uri("/app/path1")
.method(Method::GET)
.to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let req = TestRequest::with_uri("/app/path1")
.method(Method::DELETE)
.to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::NO_CONTENT);
}
#[crate::rt_test]