Skip to content

Commit

Permalink
Merge pull request #110 from DuskSystems/8-investigate-url-encodingde…
Browse files Browse the repository at this point in the history
…coding

Handle URL encoding/decoding
  • Loading branch information
CathalMullan authored Aug 19, 2024
2 parents 1ca313f + 60a3fd6 commit 8eed1e1
Show file tree
Hide file tree
Showing 21 changed files with 347 additions and 175 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ smallvec = "1.13"
# NOTE: Keep in sync with `cargo-insta` Nix package.
insta = "1.39.0"

# Encoding
percent-encoding = "2.3"

# Benchmarking
criterion = { version = "0.5", features = ["html_reports"] }
# NOTE: Keep in sync with `cargo-codspeed` Nix package.
Expand All @@ -79,7 +82,6 @@ actix-router = "0.5.3"
matchit = "0.8.3"
ntex-router = "0.5.3"
path-tree = "0.8.1"
regex = "1.10.6"
route-recognizer = "0.3.1"
routefinder = "0.5.4"
xitca-router = "0.3.0"
Expand Down
38 changes: 18 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,32 +134,30 @@ Check out our [codspeed results](https://codspeed.io/DuskSystems/wayfind/benchma
In a router of 130 routes, benchmark matching 4 paths.

| Library | Time |
|-----------|------|
| wayfind | 210.33 ns |
| matchit | 310.24 ns |
| path-tree | 406.26 ns |
| xitca-router | 415.02 ns |
| ntex-router | 1.6291 µs |
| route-recognizer | 4.3608 µs |
| regex | 4.5123 µs |
| routefinder | 6.2077 µs |
| actix-router | 20.722 µs |
|---------|------|
| wayfind | 301.64 ns |
| matchit | 471.11 ns |
| xitca-router | 568.31 ns |
| path-tree | 586.17 ns |
| ntex-router | 1.7905 µs |
| route-recognizer | 4.5652 µs |
| routefinder | 6.6322 µs |
| actix-router | 21.162 µs |

### `path-tree` inspired benches

In a router of 320 routes, benchmark matching 80 paths.

| Library | Time |
|-----------|------|
| wayfind | 3.5117 µs |
| matchit | 6.8657 µs |
| path-tree | 7.5262 µs |
| xitca-router | 8.5490 µs |
| ntex-router | 28.003 µs |
| route-recognizer | 87.400 µs |
| routefinder | 95.115 µs |
| regex | 117.12 µs |
| actix-router | 176.11 µs |
|---------|------|
| wayfind | 3.9211 µs |
| matchit | 8.9698 µs |
| path-tree | 9.5825 µs |
| xitca-router | 10.882 µs |
| ntex-router | 30.931 µs |
| route-recognizer | 90.966 µs |
| routefinder | 98.779 µs |
| actix-router | 178.40 µs |

## Inspirations

Expand Down
46 changes: 17 additions & 29 deletions benches/matchit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use codspeed_criterion_compat::{criterion_group, criterion_main, Criterion};
use matchit_routes::paths;
use percent_encoding::percent_decode;

pub mod matchit_routes;

Expand All @@ -19,7 +20,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for route in paths() {
let search = wayfind.search(route).unwrap();
let path = wayfind::path::Path::new(route).unwrap();
let search = wayfind.search(&path).unwrap();
let _ = search
.parameters
.iter()
Expand All @@ -38,7 +40,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for route in paths() {
let mut path = actix_router::Path::new(route);
let route = percent_decode(route.as_bytes()).decode_utf8().unwrap();
let mut path = actix_router::Path::new(route.as_ref());
actix.recognize(&mut path).unwrap();
let _ = path
.iter()
Expand All @@ -56,7 +59,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for route in paths() {
let at = matchit.at(route).unwrap();
let route = percent_decode(route.as_bytes()).decode_utf8().unwrap();
let at = matchit.at(route.as_ref()).unwrap();
let _ = at
.params
.iter()
Expand All @@ -75,7 +79,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for route in paths() {
let mut path = ntex_router::Path::new(route);
let route = percent_decode(route.as_bytes()).decode_utf8().unwrap();
let mut path = ntex_router::Path::new(route.as_ref());
ntex.recognize(&mut path).unwrap();
let _ = path
.iter()
Expand All @@ -93,7 +98,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for route in paths() {
let route = path_tree.find(route).unwrap();
let route = percent_decode(route.as_bytes()).decode_utf8().unwrap();
let route = path_tree.find(route.as_ref()).unwrap();
let _ = route
.1
.params_iter()
Expand All @@ -103,27 +109,6 @@ fn benchmark(criterion: &mut Criterion) {
});
});

group.bench_function("matchit benchmarks/regex", |bencher| {
let regex_set = regex::RegexSet::new(routes!(regex)).unwrap();
let regexes: Vec<_> = routes!(regex)
.into_iter()
.map(|pattern| regex::Regex::new(pattern).unwrap())
.collect();

bencher.iter(|| {
for route in paths() {
let matches = regex_set.matches(route).into_iter().collect::<Vec<_>>();
let index = matches.first().unwrap();
let captures = regexes[*index].captures(route).unwrap();
let _ = regexes[*index]
.capture_names()
.flatten()
.filter_map(|name| captures.name(name).map(|m| (name, m.as_str())))
.collect::<Vec<(&str, &str)>>();
}
});
});

group.bench_function("matchit benchmarks/route-recognizer", |bencher| {
let mut route_recognizer = route_recognizer::Router::new();
for route in routes!(colon) {
Expand All @@ -132,7 +117,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for route in paths() {
let recognize = route_recognizer.recognize(route).unwrap();
let route = percent_decode(route.as_bytes()).decode_utf8().unwrap();
let recognize = route_recognizer.recognize(route.as_ref()).unwrap();
let _ = recognize
.params()
.iter()
Expand All @@ -150,7 +136,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for route in paths() {
let best_match = routefinder.best_match(route).unwrap();
let route = percent_decode(route.as_bytes()).decode_utf8().unwrap();
let best_match = routefinder.best_match(route.as_ref()).unwrap();
let _ = best_match
.captures()
.iter()
Expand All @@ -168,7 +155,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for route in paths() {
let at = xitca.at(route).unwrap();
let route = percent_decode(route.as_bytes()).decode_utf8().unwrap();
let at = xitca.at(route.as_ref()).unwrap();
let _ = at
.params
.iter()
Expand Down
6 changes: 1 addition & 5 deletions benches/matchit_routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub fn paths() -> impl IntoIterator<Item = &'static str> {
"/user/repos",
"/repos/rust-lang/rust/stargazers",
"/orgs/rust-lang/public_members/nikomatsakis",
"/repos/rust-lang/rust/releases/1.51.0",
"/repos/rust-lang/rust/releases/1%2E51%2E0",
]
}

Expand All @@ -18,10 +18,6 @@ macro_rules! routes {
routes!(finish => "{p1}", "{p2}", "{p3}", "{p4}")
}};

(regex) => {{
routes!(finish => "(?<p1>.*)", "(?<p2>.*)", "(?<p3>.*)", "(?<p4>.*)")
}};

(finish => $p1:literal, $p2:literal, $p3:literal, $p4:literal) => {{
[
concat!("/authorizations"),
Expand Down
47 changes: 17 additions & 30 deletions benches/path_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use codspeed_criterion_compat::{criterion_group, criterion_main, Criterion};
use path_tree_routes::paths;
use percent_encoding::percent_decode;

pub mod path_tree_routes;

Expand All @@ -19,7 +20,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for (index, path) in paths() {
let search = wayfind.search(path).unwrap();
let path = wayfind::path::Path::new(path).unwrap();
let search = wayfind.search(&path).unwrap();
assert_eq!(search.data.value, index);
let _ = search
.parameters
Expand All @@ -39,7 +41,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for (index, path) in paths() {
let mut path = actix_router::Path::new(path);
let path = percent_decode(path.as_bytes()).decode_utf8().unwrap();
let mut path = actix_router::Path::new(path.as_ref());
let n = router.recognize(&mut path).unwrap();
assert_eq!(*n.0, index);
let _ = path
Expand All @@ -58,7 +61,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for (index, path) in paths() {
let n = matcher.at(path).unwrap();
let path = percent_decode(path.as_bytes()).decode_utf8().unwrap();
let n = matcher.at(path.as_ref()).unwrap();
assert_eq!(*n.value, index);
let _ = n
.params
Expand All @@ -78,7 +82,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for (index, path) in paths() {
let mut path = ntex_router::Path::new(path);
let path = percent_decode(path.as_bytes()).decode_utf8().unwrap();
let mut path = ntex_router::Path::new(path.as_ref());
let n = router.recognize(&mut path).unwrap();
assert_eq!(*n.0, index);
let _ = path
Expand All @@ -97,7 +102,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for (index, path) in paths() {
let n = tree.find(path).unwrap();
let path = percent_decode(path.as_bytes()).decode_utf8().unwrap();
let n = tree.find(path.as_ref()).unwrap();
assert_eq!(*n.0, index);
let _ =
n.1.params_iter()
Expand All @@ -107,28 +113,6 @@ fn benchmark(criterion: &mut Criterion) {
});
});

group.bench_function("path-tree benchmarks/regex", |bencher| {
let regex_set = regex::RegexSet::new(routes!(regex)).unwrap();
let regexes: Vec<_> = routes!(regex)
.into_iter()
.map(|pattern| regex::Regex::new(pattern).unwrap())
.collect();

bencher.iter(|| {
for (index, path) in paths() {
let matches = regex_set.matches(path).into_iter().collect::<Vec<_>>();
assert!(matches.contains(&index));
let i = matches.first().unwrap();
let captures = regexes[*i].captures(path).unwrap();
let _ = regexes[*i]
.capture_names()
.flatten()
.filter_map(|name| captures.name(name).map(|m| (name, m.as_str())))
.collect::<Vec<(&str, &str)>>();
}
});
});

group.bench_function("path-tree benchmarks/route-recognizer", |bencher| {
let mut router = route_recognizer::Router::<usize>::new();
for (index, route) in routes!(colon).iter().enumerate() {
Expand All @@ -137,7 +121,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for (index, path) in paths() {
let n = router.recognize(path).unwrap();
let path = percent_decode(path.as_bytes()).decode_utf8().unwrap();
let n = router.recognize(path.as_ref()).unwrap();
assert_eq!(**n.handler(), index);
let _ = n
.params()
Expand All @@ -156,7 +141,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for (index, path) in paths() {
let n = router.best_match(path).unwrap();
let path = percent_decode(path.as_bytes()).decode_utf8().unwrap();
let n = router.best_match(path.as_ref()).unwrap();
assert_eq!(*n, index);
let _ = n
.captures()
Expand All @@ -175,7 +161,8 @@ fn benchmark(criterion: &mut Criterion) {

bencher.iter(|| {
for (index, path) in paths() {
let n = xitca.at(path).unwrap();
let path = percent_decode(path.as_bytes()).decode_utf8().unwrap();
let n = xitca.at(path.as_ref()).unwrap();
assert_eq!(*n.value, index);
let _ = n
.params
Expand Down
6 changes: 1 addition & 5 deletions benches/path_tree_routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub fn paths() -> impl IntoIterator<Item = (usize, &'static str)> {
"/issues",
"/legacy/issues/search/rust-lang/rust/987/1597",
"/legacy/repos/search/1597",
"/legacy/user/email/rust@rust-lang.org",
"/legacy/user/email/rust%40rust-lang.org",
"/legacy/user/search/1597",
"/licenses",
"/licenses/mit",
Expand Down Expand Up @@ -96,10 +96,6 @@ macro_rules! routes {
routes!(finish => "{p1}", "{p2}", "{p3}", "{p4}")
}};

(regex) => {{
routes!(finish => "(?<p1>.*)", "(?<p2>.*)", "(?<p3>.*)", "(?<p4>.*)")
}};

(finish => $p1:literal, $p2:literal, $p3:literal, $p4:literal) => {{
[
concat!("/app"),
Expand Down
10 changes: 6 additions & 4 deletions examples/axum-fork/src/routing/path_router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use axum_core::response::IntoResponse;
use std::{borrow::Cow, collections::HashMap, convert::Infallible, fmt, sync::Arc};
use tower_layer::Layer;
use tower_service::Service;
use wayfind::{errors::insert::InsertError, node::search::Match, router::Router};
use wayfind::{errors::insert::InsertError, node::search::Match, path::Path, router::Router};

use super::{
future::RouteFuture, not_found::NotFound, strip_prefix::StripPrefix, url_params, Endpoint,
Expand Down Expand Up @@ -331,7 +331,8 @@ where
}

let path = req.uri().path().to_owned();
let result = match self.node.matches(&path) {
let wayfind_path = Path::new(&path).expect("Invalid path!");
let result = match self.node.matches(&wayfind_path) {
Some(match_) => {
let id = match_.data.value;

Expand Down Expand Up @@ -365,7 +366,8 @@ where
}

pub(super) fn replace_endpoint(&mut self, path: &str, endpoint: Endpoint<S>) {
if let Some(match_) = self.node.matches(path) {
let wayfind_path = Path::new(path).expect("Invalid path!");
if let Some(match_) = self.node.matches(&wayfind_path) {
let id = match_.data.value;
self.routes.insert(id, endpoint);
return;
Expand Down Expand Up @@ -435,7 +437,7 @@ impl Node {
Ok(())
}

fn matches<'n, 'p>(&'n self, path: &'p str) -> Option<Match<'n, 'p, RouteId>> {
fn matches<'n, 'p>(&'n self, path: &'p Path) -> Option<Match<'n, 'p, RouteId>> {
self.inner.search(path)
}
}
Expand Down
Loading

0 comments on commit 8eed1e1

Please sign in to comment.