path-tree

A lightweight high performance HTTP request router for Rust

Safety! Docs.rs docs Crates.io version Download Twitter: @_fundon

Features

Benchmark

shell $ cargo bench

From Travis

``` pathinsert/pathtree_insert

                    time:   [285.97 us 286.32 us 286.73 us]

Found 6 outliers among 50 measurements (12.00%)

4 (8.00%) high mild

2 (4.00%) high severe

pathinsert/routerecognizer_add

                    time:   [194.49 us 194.95 us 195.49 us]

Found 4 outliers among 50 measurements (8.00%)

1 (2.00%) high mild

3 (6.00%) high severe

pathinsert/pathtable_setup

                    time:   [94.507 us 94.655 us 94.847 us]

Found 9 outliers among 50 measurements (18.00%)

3 (6.00%) high mild

6 (12.00%) high severe

pathinsert/actixrouter_path

                    time:   [14.998 ms 15.013 ms 15.029 ms]

Found 8 outliers among 50 measurements (16.00%)

2 (4.00%) low mild

3 (6.00%) high mild

3 (6.00%) high severe

pathfind/pathtree_find

                    time:   [375.39 us 375.73 us 376.04 us]

Found 2 outliers among 50 measurements (4.00%)

1 (2.00%) high mild

1 (2.00%) high severe

pathfind/routerecognizer_recognize

                    time:   [1.1090 ms 1.1110 ms 1.1138 ms]

Found 4 outliers among 50 measurements (8.00%)

2 (4.00%) high mild

2 (4.00%) high severe

pathfind/pathtable_route

                    time:   [158.79 us 159.96 us 161.24 us]

Found 4 outliers among 50 measurements (8.00%)

4 (8.00%) high mild

pathfind/actixrouter_recognize

                    time:   [9.1690 ms 9.1891 ms 9.2135 ms]

Found 7 outliers among 50 measurements (14.00%)

3 (6.00%) high mild

4 (8.00%) high severe

```

Path Find

Path Find

Path Insert

Path Insert

Examples

```rust use path_tree::PathTree;

let mut tree = PathTree::::new();

tree.insert("/", 0); tree.insert("/users", 1); tree.insert("/users/:id", 2); tree.insert("/users/:id/:org", 3); tree.insert("/users/:userid/repos", 4); tree.insert("/users/:userid/repos/:id", 5); tree.insert("/users/:user_id/repos/:id/any", 6); tree.insert("/:username", 7); tree.insert("/any", 8); tree.insert("/about", 9); tree.insert("/about/", 10); tree.insert("/about/us", 11); tree.insert("/users/repos/*any", 12);

// Matched "/" let node = tree.find("/"); asserteq!(node.issome(), true); let res = node.unwrap(); asserteq!(*res.0, 0); asserteq!(res.1, []); // Params

// Matched "/:username" let node = tree.find("/username"); asserteq!(node.issome(), true); let res = node.unwrap(); asserteq!(*res.0, 7); asserteq!(res.1, [("username", "username")]); // Params

// Matched "/any" let node = tree.find("/user/s"); let res = node.unwrap(); assert_eq!(res.0, 8); assert_eq!(res.1, [("any", "user/s")]);

// Matched "/users/:id" let node = tree.find("/users/fundon"); let res = node.unwrap(); asserteq!(*res.0, 2); asserteq!(res.1, [("id", "fundon")]); // Params

// Matched "/users/:userid/repos/:id" let node = tree.find("/users/fundon/repos/trek-rs"); let res = node.unwrap(); asserteq!(*res.0, 5); asserteq!(res.1, [("userid", "fundon"), ("id", "trek-rs")]); // Params

// Matched "/users/:userid/repos/:id/*any" let node = tree.find("/users/fundon/repos/trek-rs/noder/issues"); let res = node.unwrap(); asserteq!(*res.0, 6); asserteq!( res.1, [ ("userid", "fundon"), ("id", "trek-rs"), ("any", "noder/issues"), ] ); // Params

// Matched "/users/repos/any" let node = tree.find("/users/repos/"); let res = node.unwrap(); assert_eq!(res.0, 12); assert_eq!(res.1, []); ```

```rust use std::convert::Infallible; use std::future::Future; use std::pin::Pin; use std::sync::Arc;

use hyper::service::{makeservicefn, servicefn}; use hyper::{Body, Request, Response, Server, StatusCode}; use pathtree::PathTree;

static NOT_FOUND: &[u8] = b"Not Found";

type Params = Vec<(String, String)>;

trait Handler: Send + Sync + 'static { fn call<'a>(&'a self, req: Request) -> Pin + Send + 'a>>; }

impl Handler for F where F: Send + Sync + 'static + Fn(Request) -> R, R: Future + Send + 'static, { fn call<'a>(&'a self, req: Request) -> Pin + Send + 'a>> { let fut = (self)(req); Box::pin(async move { fut.await }) } }

async fn index(_: Request) -> Body { Body::from("Hello, Web!") }

async fn helloworld(req: Request) -> Body { let params = req.extensions().get::().unwrap(); let mut s = String::new(); s.pushstr("Hello, World!\n"); for (, v) in params { s.pushstr(&format!("param = {}", v)); } Body::from(s) }

async fn hellouser(req: Request) -> Body { let params = req.extensions().get::().unwrap(); let mut s = String::new(); s.pushstr("Hello, "); for (k, v) in params { s.pushstr(&format!("{} = {}", k, v)); } s.pushstr("!"); Body::from(s) }

async fn hellorust(: Request) -> Body { Body::from("Hello, Rust!") }

async fn login(_req: Request) -> Body { Body::from("I'm logined!") }

[tokio::main]

async fn main() -> Result<(), Box> { let addr = ([127, 0, 0, 1], 3000).into();

let mut tree = PathTree::<Box<dyn Handler>>::new();
tree.insert("/GET/", Box::new(index));
tree.insert("/GET/*", Box::new(hello_world));
tree.insert("/GET/hello/:name", Box::new(hello_user));
tree.insert("/GET/rust", Box::new(hello_rust));
tree.insert("/POST/login", Box::new(login));

let tree = Arc::new(tree);

let make_service = make_service_fn(move |_| {
    let router = Arc::clone(&tree);

    async move {
        Ok::<_, Infallible>(service_fn(move |mut req| {
            let router = router.clone();
            let path = "/".to_owned() + req.method().as_str() + req.uri().path();
            let builder = Response::builder();

            async move {
                Ok::<_, Infallible>(
                    match router.find(&path) {
                        Some((handler, params)) => {
                            let p = params
                                .iter()
                                .map(|p| (p.0.to_owned(), p.1.to_owned()))
                                .collect::<Params>();
                            req.extensions_mut().insert(p);
                            builder.body(handler.call(req).await)
                        }
                        None => builder.status(StatusCode::NOT_FOUND).body(NOT_FOUND.into()),
                    }
                    .unwrap(),
                )
            }
        }))
    }
});

let server = Server::bind(&addr).serve(make_service);

println!("Listening on http://{}", addr);

server.await?;

Ok(())

} ```

Acknowledgements

It is inspired by the:

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.


Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.