├── .gitignore ├── src ├── lib.rs ├── path.rs ├── router.rs └── tree.rs ├── .travis.yml ├── examples ├── send_file_index.html ├── hello.rs └── echo.rs ├── Cargo.toml ├── CHANGELOG.md ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | .vs -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # radix-router 2 | 3 | // extern crate futures; 4 | // extern crate hyper; 5 | // extern crate tokio_fs; 6 | // extern crate tokio_io; 7 | 8 | pub mod path; 9 | pub mod router; 10 | pub mod tree; 11 | 12 | pub use crate::router::{BoxFut, Router}; 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | rust: 4 | - stable 5 | - beta 6 | - nightly 7 | 8 | cache: cargo 9 | 10 | # addons: 11 | # apt: 12 | # packages: 13 | # - kcov 14 | 15 | # after_success: 16 | # - | 17 | # kcov --exclude-pattern=/.cargo,/usr/lib --verify target/cov target/debug/-* && 18 | # bash <(curl -s https://codecov.io/bash) && 19 | # echo "Uploaded code coverage" 20 | 21 | -------------------------------------------------------------------------------- /examples/send_file_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hyper responding example 5 | 6 | 7 | 8 |

Hyper responding example

9 | index.html Top Level 10 |
11 | big_file.html This page, streamed in chunks 12 |
13 | no_file.html A 404 test, the requested file does not exist 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "radix-router" 3 | version = "0.1.3" 4 | description = "Rust port of httprouter." 5 | readme = "README.md" 6 | documentation = "https://docs.rs/radix-router" 7 | repository = "https://github.com/SunDoge/radix-router" 8 | license = "BSD-3-Clause" 9 | authors = ["SunDoge <384813529@qq.com>"] 10 | edition = "2018" 11 | 12 | [dependencies] 13 | hyper = "0.12" 14 | futures = "0.1" 15 | tokio-fs = "0.1" 16 | tokio-io = "0.1" 17 | 18 | [dev-dependencies] 19 | pretty_env_logger = "0.3" -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | ### Added 9 | - Coverage Status. 10 | 11 | ### Changed 12 | - Optimize `Params`. 13 | - Recommand using `Arc` to wrap `Router`. 14 | - 2018 edition 15 | 16 | ### Removed 17 | - `impl Service for Router`. 18 | - `impl IntoFuture for Router`. 19 | 20 | ## [0.1.2] - 2018-07-28 21 | ### Changed 22 | - Make `Router` attributes `pub`. 23 | - `Handler` in README. 24 | 25 | ## [0.1.1] - 2018-07-18 26 | ### Added 27 | - Docs badge. 28 | - Readme link. 29 | - `hello` example 30 | 31 | ## 0.1.0 - 2018-07-17 32 | ### Added 33 | - `echo` example. 34 | - Travis CI. 35 | - Publish. 36 | 37 | [Unreleased]: https://github.com/SunDoge/radix-router/compare/v0.1.2...HEAD 38 | [0.1.2]: https://github.com/SunDoge/radix-router/compare/v0.1.1...v0.1.2 39 | [0.1.1]: https://github.com/SunDoge/radix-router/compare/v0.1.0...v0.1.1 40 | 41 | 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, SunDoge 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /examples/hello.rs: -------------------------------------------------------------------------------- 1 | extern crate futures; 2 | extern crate hyper; 3 | extern crate pretty_env_logger; 4 | extern crate radix_router; 5 | 6 | use futures::future; 7 | use hyper::rt::{self, Future}; 8 | use hyper::service::service_fn; 9 | use hyper::{Body, Request, Response, Server}; 10 | use radix_router::router::{BoxFut, Handler, Params, Router}; 11 | use std::sync::Arc; 12 | 13 | fn index(_: Request, _: Params) -> BoxFut { 14 | let res = Response::builder().body("welcome!\n".into()).unwrap(); 15 | 16 | Box::new(future::ok(res)) 17 | } 18 | 19 | fn hello(_: Request, ps: Params) -> BoxFut { 20 | // let name = ps.by_name("name").unwrap(); 21 | let name = &ps[0]; 22 | let res = Response::builder() 23 | .body(format!("hello, {}!\n", name).into()) 24 | .unwrap(); 25 | Box::new(future::ok(res)) 26 | } 27 | 28 | fn main() { 29 | pretty_env_logger::init(); 30 | 31 | let addr = ([127, 0, 0, 1], 3000).into(); 32 | let mut router: Router = Router::new(); 33 | router.get("/", Box::new(index)); 34 | router.get("/hello/:name", Box::new(hello)); 35 | let arc_router = Arc::new(router); 36 | // new_service is run for each connection, creating a 'service' 37 | // to handle requests for that specific connection. 38 | let new_service = move || { 39 | // This is the `Service` that will handle the connection. 40 | let router = arc_router.clone(); 41 | service_fn(move |req| router.serve_http(req)) 42 | }; 43 | 44 | let server = Server::bind(&addr) 45 | .serve(new_service) 46 | .map_err(|e| eprintln!("server error: {}", e)); 47 | 48 | println!("Listening on http://{}", addr); 49 | 50 | rt::run(server); 51 | } 52 | -------------------------------------------------------------------------------- /examples/echo.rs: -------------------------------------------------------------------------------- 1 | extern crate futures; 2 | extern crate hyper; 3 | extern crate pretty_env_logger; 4 | extern crate radix_router; 5 | 6 | use futures::future; 7 | use hyper::rt::{self, Future, Stream}; 8 | use hyper::service::service_fn; 9 | use hyper::{Body, Request, Response, Server}; 10 | use radix_router::router::{BoxFut, Handler, Params, Router}; 11 | use std::sync::Arc; 12 | 13 | fn get_echo(_: Request, _: Params) -> BoxFut { 14 | // Box::new(future::ok(Response::new(Body::from("Try POSTing data to /echo")))) 15 | // *response.body_mut() = Body::from("Try POSTing data to /echo"); 16 | let response = Response::builder() 17 | .body(Body::from("Try POSTing data to /echo")) 18 | .unwrap(); 19 | Box::new(future::ok(response)) 20 | } 21 | 22 | fn post_echo(req: Request, _: Params) -> BoxFut { 23 | // Box::new(future::ok(Response::new(req.into_body()))) 24 | // *response.body_mut() = req.into_body(); 25 | let response = Response::builder().body(req.into_body()).unwrap(); 26 | Box::new(future::ok(response)) 27 | } 28 | 29 | fn post_echo_uppercase(req: Request, _: Params) -> BoxFut { 30 | let mapping = req.into_body().map(|chunk| { 31 | chunk 32 | .iter() 33 | .map(|byte| byte.to_ascii_uppercase()) 34 | .collect::>() 35 | }); 36 | 37 | // *response.body_mut() = Body::wrap_stream(mapping); 38 | let response = Response::builder() 39 | .body(Body::wrap_stream(mapping)) 40 | .unwrap(); 41 | Box::new(future::ok(response)) 42 | } 43 | 44 | fn post_echo_reversed(req: Request, _: Params) -> BoxFut { 45 | let reversed = req 46 | .into_body() 47 | .concat2() 48 | .map(move |chunk| { 49 | let body = chunk.iter().rev().cloned().collect::>(); 50 | // *response.body_mut() = Body::from(body); 51 | // response 52 | Response::builder().body(Body::from(body)).unwrap() 53 | }) 54 | .map_err(|e| e.into()); 55 | Box::new(reversed) 56 | } 57 | 58 | fn main() { 59 | pretty_env_logger::init(); 60 | 61 | let addr = ([127, 0, 0, 1], 3000).into(); 62 | let some_str = "Some"; 63 | 64 | // new_service is run for each connection, creating a 'service' 65 | // to handle requests for that specific connection. 66 | let mut router: Router = Router::new(); 67 | router.get("/", Box::new(get_echo)); 68 | router.post("/echo", Box::new(post_echo)); 69 | router.post("/echo/uppercase", Box::new(post_echo_uppercase)); 70 | router.post("/echo/reversed", Box::new(post_echo_reversed)); 71 | router.get( 72 | "/some", 73 | Box::new(move |_, _| -> BoxFut { 74 | Box::new(future::ok( 75 | Response::builder().body(some_str.into()).unwrap(), 76 | )) 77 | }), 78 | ); 79 | router.serve_files("/examples/*filepath", "examples"); 80 | let arc_router = Arc::new(router); 81 | 82 | let new_service = move || { 83 | // This is the `Service` that will handle the connection. 84 | // `service_fn_ok` is a helper to convert a function that 85 | // returns a Response into a `Service`. 86 | // service_fn_ok(|_| { 87 | // Response::new(Body::from(PHRASE)) 88 | // }) 89 | 90 | // router.get("/some", |req, ps| { 91 | // Box::new(future::ok(Response::new(Body::empty()))) 92 | // }); 93 | 94 | let router = arc_router.clone(); 95 | service_fn(move |req| router.serve_http(req)) 96 | }; 97 | 98 | let server = Server::bind(&addr) 99 | .serve(new_service) 100 | .map_err(|e| eprintln!("server error: {}", e)); 101 | 102 | println!("Listening on http://{}", addr); 103 | 104 | rt::run(server); 105 | } 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Radix-Router 2 | [![Build Status](https://travis-ci.org/SunDoge/radix-router.svg?branch=master)](https://travis-ci.org/SunDoge/radix-router) 3 | [![Coverage Status](https://coveralls.io/repos/github/SunDoge/radix-router/badge.svg?branch=master)](https://coveralls.io/github/SunDoge/radix-router?branch=master) 4 | [![crates.io](http://meritbadge.herokuapp.com/radix-router)](https://crates.io/crates/radix-router) 5 | [![Released API docs](https://docs.rs/radix-router/badge.svg)](https://docs.rs/radix-router) 6 | 7 | Radix-Router is a Rust port of [julienschmidt/httprouter](https://github.com/julienschmidt/httprouter). 8 | 9 | ## Usage 10 | This is just a quick introduction. 11 | 12 | Let's start with a `hello world` example: 13 | ```rust 14 | extern crate futures; 15 | extern crate hyper; 16 | extern crate pretty_env_logger; 17 | extern crate radix_router; 18 | 19 | use futures::future; 20 | use hyper::rt::{self, Future}; 21 | use hyper::service::service_fn; 22 | use hyper::{Body, Request, Response, Server}; 23 | use radix_router::router::{BoxFut, Handler, Params, Router}; 24 | use std::sync::Arc; 25 | 26 | fn index(_: Request, _: Params) -> BoxFut { 27 | let res = Response::builder().body("welcome!\n".into()).unwrap(); 28 | Box::new(future::ok(res)) 29 | } 30 | 31 | fn hello(_: Request, ps: Params) -> BoxFut { 32 | // let name = ps.by_name("name").unwrap(); 33 | let name = &ps[0]; 34 | let res = Response::builder() 35 | .body(format!("hello, {}!\n", name).into()) 36 | .unwrap(); 37 | Box::new(future::ok(res)) 38 | } 39 | 40 | fn main() { 41 | pretty_env_logger::init(); 42 | 43 | let addr = ([127, 0, 0, 1], 3000).into(); 44 | let mut router: Router = Router::new(); 45 | router.get("/", Box::new(index)); 46 | router.get("/hello/:name", Box::new(hello)); 47 | let arc_router = Arc::new(router); 48 | // new_service is run for each connection, creating a 'service' 49 | // to handle requests for that specific connection. 50 | let new_service = move || { 51 | // This is the `Service` that will handle the connection. 52 | let router = arc_router.clone(); 53 | service_fn(move |req| router.serve_http(req)) 54 | }; 55 | 56 | let server = Server::bind(&addr) 57 | .serve(new_service) 58 | .map_err(|e| eprintln!("server error: {}", e)); 59 | 60 | println!("Listening on http://{}", addr); 61 | 62 | rt::run(server); 63 | } 64 | ``` 65 | 66 | ### Handler 67 | The handler can be anything. You can store a `T` and get an `Option<&T>`. Notice that `&T` is immutable. We offer a default `radix_router::router::Handler` which can be a `fn` or `closure`. When using closure, you are able to capture outside parameters. For example: 68 | 69 | ```rust 70 | router.get("/", Box::new(get_echo)); 71 | router.post("/echo", Box::new(post_echo)); 72 | router.post("/echo/uppercase", Box::new(post_echo_uppercase)); 73 | router.post("/echo/reversed", Box::new(post_echo_reversed)); 74 | router.get("/some", Box::new(move |_, _| -> BoxFut { 75 | Box::new(future::ok( 76 | Response::builder().body(some_str.into()).unwrap(), 77 | )) 78 | })); 79 | ``` 80 | 81 | ### Named parameters 82 | `:name` is a *named parameter*. The values are accessible via `Option`, which is a wrapped slice of `Param`s. You can get the value of a parameter either by its index in the slice. of by using the `by_name(name)` method. 83 | 84 | Named parameters only match a single path segment: 85 | ``` 86 | Pattern: /user/:user 87 | 88 | /user/gordon match 89 | /user/you match 90 | /user/gordon/profile no match 91 | /user/ no match 92 | ``` 93 | 94 | **Note:** Since this router has only explicit matches, you can not register static routes and parameters for the same path segment. For example you can not register the patterns `/user/new` and `/user/:user` for the same request method at the same time. The routing of different request methods is independent from each other. 95 | 96 | ### Catch-All parameters 97 | 98 | The second type are *catch-all* parameters and have the form `*name`. Like the name suggests, they match everything. Therefore they must always be at the **end** of the pattern: 99 | 100 | ``` 101 | Pattern: /src/*filepath 102 | 103 | /src/ match 104 | /src/somefile.go match 105 | /src/subdir/somefile.go match 106 | ``` 107 | 108 | ### Static files 109 | You can serve static files by using: 110 | ```rust 111 | router.serve_files("/examples/*filepath", "examples"); 112 | ``` 113 | 114 | ## Examples 115 | An echo server example is written. You can test it by running 116 | 117 | ```bash 118 | $ cargo run --example echo 119 | ``` 120 | 121 | ```bash 122 | $ curl http://127.0.0.1:3000/echo 123 | Try POSTing data to /echo 124 | 125 | $ curl -d "param1=1¶m2=2" -X POST http://127.0.0.1:3000/echo 126 | param1=1¶m2=2 127 | ``` 128 | -------------------------------------------------------------------------------- /src/path.rs: -------------------------------------------------------------------------------- 1 | /// CleanPath is the URL version of path.Clean, it returns a canonical URL path 2 | /// for p, eliminating . and .. elements. 3 | /// 4 | /// The following rules are applied iteratively until no further processing can 5 | /// be done: 6 | /// 1. Replace multiple slashes with a single slash. 7 | /// 2. Eliminate each . path name element (the current directory). 8 | /// 3. Eliminate each inner .. path name element (the parent directory) 9 | /// along with the non-.. element that precedes it. 10 | /// 4. Eliminate .. elements that begin a rooted path: 11 | /// that is, replace "/.." by "/" at the beginning of a path. 12 | /// 13 | /// If the result of this process is an empty string, "/" is returned 14 | pub fn clean_path(p: &str) -> String { 15 | // Turn empty string into "/" 16 | if p == "" { 17 | return "/".to_string(); 18 | } 19 | 20 | let n = p.len(); 21 | let mut buf: Vec = Vec::new(); 22 | 23 | // Invariants: 24 | // reading from path; r is index of next byte to process. 25 | // writing to buf; w is index of next byte to write. 26 | 27 | // path must start with '/' 28 | 29 | let mut r = 1; 30 | let mut w = 1; 31 | 32 | if !p.starts_with("/") { 33 | r = 0; 34 | buf.resize(n + 1, 0); 35 | buf[0] = b'/'; 36 | } 37 | 38 | let mut trailing = n > 1 && p.ends_with("/"); 39 | let p = p.as_bytes(); 40 | 41 | // A bit more clunky without a 'lazybuf' like the path package, but the loop 42 | // gets completely inlined (bufApp). So in contrast to the path package this 43 | // loop has no expensive function calls (except 1x make) 44 | 45 | while r < n { 46 | match p[r] { 47 | b'/' => r += 1, // empty path element, trailing slash is added after the end 48 | b'.' => { 49 | if r + 1 == n { 50 | trailing = true; 51 | r += 1; 52 | } else if p[r + 1] == b'/' { 53 | // . element 54 | r += 2; 55 | } else if p[r + 1] == b'.' && (r + 2 == n || p[r + 2] == b'/') { 56 | // .. element: remove to last / 57 | r += 3; 58 | 59 | if w > 1 { 60 | // can backtrack 61 | w -= 1; 62 | 63 | if buf.is_empty() { 64 | while w > 1 && p[w] != b'/' { 65 | w -= 1; 66 | } 67 | } else { 68 | while w > 1 && buf[w] != b'/' { 69 | w -= 1; 70 | } 71 | } 72 | } 73 | } 74 | } 75 | _ => { 76 | // real path element. 77 | // add slash if needed 78 | if w > 1 { 79 | buf_app(&mut buf, p, w, b'/'); 80 | w += 1; 81 | } 82 | 83 | // copy element 84 | while r < n && p[r] != b'/' { 85 | buf_app(&mut buf, p, w, p[r]); 86 | w += 1; 87 | r += 1; 88 | } 89 | } 90 | } 91 | } 92 | 93 | // re-append trailing slash 94 | if trailing && w > 1 { 95 | buf_app(&mut buf, p, w, b'/'); 96 | w += 1; 97 | } 98 | 99 | if buf.is_empty() { 100 | return String::from_utf8(p[..w].to_vec()).unwrap(); 101 | } 102 | String::from_utf8(buf[..w].to_vec()).unwrap() 103 | } 104 | 105 | /// internal helper to lazily create a buffer if necessary 106 | fn buf_app(buf: &mut Vec, s: &[u8], w: usize, c: u8) { 107 | if buf.is_empty() { 108 | if s[w] == c { 109 | return; 110 | } 111 | buf.resize(s.len(), 0); 112 | buf[..w].copy_from_slice(&s[..w]); 113 | } 114 | buf[w] = c; 115 | } 116 | 117 | #[cfg(test)] 118 | mod tests { 119 | use super::*; 120 | 121 | // path, result 122 | fn clean_tests() -> Vec<(&'static str, &'static str)> { 123 | vec![ 124 | // Already clean 125 | ("/", "/"), 126 | ("/abc", "/abc"), 127 | ("/a/b/c", "/a/b/c"), 128 | ("/abc/", "/abc/"), 129 | ("/a/b/c/", "/a/b/c/"), 130 | // missing root 131 | ("", "/"), 132 | ("a/", "/a/"), 133 | ("abc", "/abc"), 134 | ("abc/def", "/abc/def"), 135 | ("a/b/c", "/a/b/c"), 136 | // Remove doubled slash 137 | ("//", "/"), 138 | ("/abc//", "/abc/"), 139 | ("/abc/def//", "/abc/def/"), 140 | ("/a/b/c//", "/a/b/c/"), 141 | ("/abc//def//ghi", "/abc/def/ghi"), 142 | ("//abc", "/abc"), 143 | ("///abc", "/abc"), 144 | ("//abc//", "/abc/"), 145 | // Remove . elements 146 | (".", "/"), 147 | ("./", "/"), 148 | ("/abc/./def", "/abc/def"), 149 | ("/./abc/def", "/abc/def"), 150 | ("/abc/.", "/abc/"), 151 | // Remove .. elements 152 | ("..", "/"), 153 | ("../", "/"), 154 | ("../../", "/"), 155 | ("../..", "/"), 156 | ("../../abc", "/abc"), 157 | ("/abc/def/ghi/../jkl", "/abc/def/jkl"), 158 | ("/abc/def/../ghi/../jkl", "/abc/jkl"), 159 | ("/abc/def/..", "/abc"), 160 | ("/abc/def/../..", "/"), 161 | ("/abc/def/../../..", "/"), 162 | ("/abc/def/../../..", "/"), 163 | ("/abc/def/../../../ghi/jkl/../../../mno", "/mno"), 164 | // Combinations 165 | ("abc/./../def", "/def"), 166 | ("abc//./../def", "/def"), 167 | ("abc/../../././../def", "/def"), 168 | ] 169 | } 170 | 171 | #[test] 172 | fn test_path_clean() { 173 | let tests = clean_tests(); 174 | for test in tests { 175 | let s = clean_path(test.0); 176 | assert_eq!(test.1, s); 177 | 178 | let s = clean_path(test.1); 179 | assert_eq!(test.1, s); 180 | } 181 | } 182 | 183 | // #[test] 184 | // fn test_path_clean_mallocs() { 185 | 186 | // } 187 | } 188 | -------------------------------------------------------------------------------- /src/router.rs: -------------------------------------------------------------------------------- 1 | use futures::future; 2 | use hyper::rt::Future; 3 | use hyper::{Body, Method, Request, Response, StatusCode}; 4 | use crate::path::clean_path; 5 | use std::collections::BTreeMap; 6 | use std::ops::Index; 7 | use std::path::Path; 8 | use tokio_fs; 9 | use tokio_io; 10 | use crate::tree::Node; 11 | use std::error::Error as StdError; 12 | 13 | // TODO: think more about what a handler looks like 14 | // pub type Handle = fn(Request, Response, Option) -> BoxFut; 15 | // pub type ResponseFuture = Box, Error=Error> + Send>; 16 | pub type BoxFut = Box, Error = Box> + Send>; 17 | 18 | 19 | pub trait Handle { 20 | fn handle(&self, req: Request, ps: Params) -> BoxFut; 21 | } 22 | 23 | impl Handle for F 24 | where 25 | F: Fn(Request, Params) -> BoxFut, 26 | { 27 | fn handle(&self, req: Request, ps: Params) -> BoxFut { 28 | (*self)(req, ps) 29 | } 30 | } 31 | 32 | // impl Handle for Handler { 33 | // fn handle(&self, req: Request, ps: Option) -> BoxFut { 34 | // (*self)(req, ps) 35 | // } 36 | // } 37 | 38 | // pub type Handler = fn(Request, Option) -> BoxFut; 39 | 40 | /// Handle is a function that can be registered to a route to handle HTTP 41 | /// requests. It has a third parameter for the values of 42 | /// wildcards (variables). 43 | pub type Handler = Box; 44 | 45 | /// Param is a single URL parameter, consisting of a key and a value. 46 | #[derive(Debug, Clone, PartialEq)] 47 | pub struct Param { 48 | pub key: String, 49 | pub value: String, 50 | } 51 | 52 | impl Param { 53 | pub fn new(key: &str, value: &str) -> Param { 54 | Param { 55 | key: key.to_string(), 56 | value: value.to_string(), 57 | } 58 | } 59 | } 60 | 61 | /// Params is a Param-slice, as returned by the router. 62 | /// The slice is ordered, the first URL parameter is also the first slice value. 63 | /// It is therefore safe to read values by the index. 64 | #[derive(Debug, PartialEq)] 65 | pub struct Params(pub Vec); 66 | 67 | impl Params { 68 | /// ByName returns the value of the first Param which key matches the given name. 69 | /// If no matching Param is found, an empty string is returned. 70 | pub fn by_name(&self, name: &str) -> Option<&str> { 71 | match self.0.iter().find(|param| param.key == name) { 72 | Some(param) => Some(¶m.value), 73 | None => None, 74 | } 75 | } 76 | 77 | /// Empty `Params` 78 | pub fn new() -> Params { 79 | Params(Vec::new()) 80 | } 81 | 82 | pub fn is_empty(&self) -> bool { 83 | self.0.is_empty() 84 | } 85 | 86 | pub fn push(&mut self, p: Param) { 87 | self.0.push(p); 88 | } 89 | } 90 | 91 | impl Index for Params { 92 | type Output = str; 93 | 94 | fn index(&self, i: usize) -> &Self::Output { 95 | &(self.0)[i].value 96 | } 97 | } 98 | 99 | /// Router is container which can be used to dispatch requests to different 100 | /// handler functions via configurable routes 101 | // #[derive(Clone)] 102 | #[allow(dead_code)] 103 | pub struct Router { 104 | pub trees: BTreeMap>, 105 | 106 | // Enables automatic redirection if the current route can't be matched but a 107 | // handler for the path with (without) the trailing slash exists. 108 | // For example if /foo/ is requested but a route only exists for /foo, the 109 | // client is redirected to /foo with http status code 301 for GET requests 110 | // and 307 for all other request methods. 111 | pub redirect_trailing_slash: bool, 112 | 113 | // If enabled, the router tries to fix the current request path, if no 114 | // handle is registered for it. 115 | // First superfluous path elements like ../ or // are removed. 116 | // Afterwards the router does a case-insensitive lookup of the cleaned path. 117 | // If a handle can be found for this route, the router makes a redirection 118 | // to the corrected path with status code 301 for GET requests and 307 for 119 | // all other request methods. 120 | // For example /FOO and /..//Foo could be redirected to /foo. 121 | // RedirectTrailingSlash is independent of this option. 122 | pub redirect_fixed_path: bool, 123 | 124 | // If enabled, the router checks if another method is allowed for the 125 | // current route, if the current request can not be routed. 126 | // If this is the case, the request is answered with 'Method Not Allowed' 127 | // and HTTP status code 405. 128 | // If no other Method is allowed, the request is delegated to the NotFound 129 | // handler. 130 | pub handle_method_not_allowed: bool, 131 | 132 | // If enabled, the router automatically replies to OPTIONS requests. 133 | // Custom OPTIONS handlers take priority over automatic replies. 134 | pub handle_options: bool, 135 | 136 | // Configurable handler which is called when no matching route is 137 | // found. 138 | pub not_found: Option, 139 | 140 | // Configurable handler which is called when a request 141 | // cannot be routed and HandleMethodNotAllowed is true. 142 | // The "Allow" header with allowed request methods is set before the handler 143 | // is called. 144 | pub method_not_allowed: Option, 145 | 146 | // Function to handle panics recovered from http handlers. 147 | // It should be used to generate a error page and return the http error code 148 | // 500 (Internal Server Error). 149 | // The handler can be used to keep your server from crashing because of 150 | // unrecovered panics. 151 | pub panic_handler: Option, 152 | } 153 | 154 | impl Router { 155 | /// New returns a new initialized Router. 156 | /// Path auto-correction, including trailing slashes, is enabled by default. 157 | pub fn new() -> Router { 158 | Router { 159 | trees: BTreeMap::new(), 160 | redirect_trailing_slash: true, 161 | redirect_fixed_path: true, 162 | handle_method_not_allowed: true, 163 | handle_options: true, 164 | not_found: None, 165 | method_not_allowed: None, 166 | panic_handler: None, 167 | } 168 | } 169 | 170 | /// get is a shortcut for router.handle("GET", path, handle) 171 | pub fn get(&mut self, path: &str, handle: T) { 172 | self.handle("GET", path, handle); 173 | } 174 | 175 | /// head is a shortcut for router.handle("HEAD", path, handle) 176 | pub fn head(&mut self, path: &str, handle: T) { 177 | self.handle("HEAD", path, handle); 178 | } 179 | 180 | /// options is a shortcut for router.handle("OPTIONS", path, handle) 181 | pub fn options(&mut self, path: &str, handle: T) { 182 | self.handle("OPTIONS", path, handle); 183 | } 184 | 185 | /// post is a shortcut for router.handle("POST", path, handle) 186 | pub fn post(&mut self, path: &str, handle: T) { 187 | self.handle("POST", path, handle); 188 | } 189 | 190 | /// put is a shortcut for router.handle("PUT", path, handle) 191 | pub fn put(&mut self, path: &str, handle: T) { 192 | self.handle("PUT", path, handle); 193 | } 194 | 195 | /// patch is a shortcut for router.handle("PATCH", path, handle) 196 | pub fn patch(&mut self, path: &str, handle: T) { 197 | self.handle("PATCH", path, handle); 198 | } 199 | 200 | /// delete is a shortcut for router.handle("DELETE", path, handle) 201 | pub fn delete(&mut self, path: &str, handle: T) { 202 | self.handle("DELETE", path, handle); 203 | } 204 | 205 | /// Unimplemented. Perhaps something like 206 | /// 207 | /// # Example 208 | /// 209 | /// ```ignore 210 | /// router.group(vec![middelware], |router| { 211 | /// router.get("/something", somewhere); 212 | /// router.post("/something", somewhere); 213 | /// }) 214 | /// ``` 215 | pub fn group() { 216 | unimplemented!() 217 | } 218 | 219 | /// Handle registers a new request handle with the given path and method. 220 | /// 221 | /// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut 222 | /// functions can be used. 223 | /// 224 | /// This function is intended for bulk loading and to allow the usage of less 225 | /// frequently used, non-standardized or custom methods (e.g. for internal 226 | /// communication with a proxy). 227 | pub fn handle(&mut self, method: &str, path: &str, handle: T) { 228 | if !path.starts_with("/") { 229 | panic!("path must begin with '/' in path '{}'", path); 230 | } 231 | 232 | self.trees 233 | .entry(method.to_string()) 234 | .or_insert(Node::new()) 235 | .add_route(path, handle); 236 | } 237 | 238 | /// Lookup allows the manual lookup of a method + path combo. 239 | /// 240 | /// This is e.g. useful to build a framework around this router. 241 | /// 242 | /// If the path was found, it returns the handle function and the path parameter 243 | /// values. Otherwise the third return value indicates whether a redirection to 244 | /// the same path with an extra / without the trailing slash should be performed. 245 | pub fn lookup(&mut self, method: &str, path: &str) -> (Option<&T>, Params, bool) { 246 | self.trees 247 | .get_mut(method) 248 | .and_then(|n| Some(n.get_value(path))) 249 | .unwrap_or((None, Params::new(), false)) 250 | } 251 | 252 | pub fn allowed(&self, path: &str, req_method: &str) -> String { 253 | let mut allow = String::new(); 254 | if path == "*" { 255 | for method in self.trees.keys() { 256 | if method == "OPTIONS" { 257 | continue; 258 | } 259 | 260 | if allow.is_empty() { 261 | allow.push_str(method); 262 | } else { 263 | allow.push_str(", "); 264 | allow.push_str(method); 265 | } 266 | } 267 | } else { 268 | for method in self.trees.keys() { 269 | if method == req_method || method == "OPTIONS" { 270 | continue; 271 | } 272 | 273 | self.trees.get(method).map(|tree| { 274 | let (handle, _, _) = tree.get_value(path); 275 | 276 | if handle.is_some() { 277 | if allow.is_empty() { 278 | allow.push_str(method); 279 | } else { 280 | allow.push_str(", "); 281 | allow.push_str(method); 282 | } 283 | } 284 | }); 285 | } 286 | } 287 | 288 | if allow.len() > 0 { 289 | allow += ", OPTIONS"; 290 | } 291 | 292 | allow 293 | } 294 | } 295 | 296 | /// Service makes the router capable for Hyper. 297 | // impl Service for Router 298 | 299 | // { 300 | // type ReqBody = Body; 301 | // type ResBody = Body; 302 | // type Error = Error; 303 | // type Future = BoxFut; 304 | 305 | // /// call makes the router implement the `Service` trait. 306 | // fn call(&mut self, req: Request) -> Self::Future { 307 | // // let (handle, p, _) = self.lookup(req.method().as_str(), req.uri().path()); 308 | // // match handle { 309 | // // Some(h) => future::ok(h(req, p)), 310 | // // // Handle 404 311 | // // _ => future::ok(Response::new(Body::from("not found"))), 312 | // // } 313 | // // self.serve_http(req) 314 | // // let method = req.method().as_str(); 315 | // // let path = req.uri().path(); 316 | // // let mut response = Response::new(Body::empty()); 317 | 318 | // let root = self.trees.get(req.method().as_str()); 319 | // if let Some(root) = root { 320 | // let (handle, ps, tsr) = root.get_value(req.uri().path()); 321 | 322 | // if let Some(handle) = handle { 323 | // // return handle(req, response, ps); 324 | // return handle.handle(req, ps); 325 | // } else if req.method() != &Method::CONNECT && req.uri().path() != "/" { 326 | // let code = if req.method() != &Method::GET { 327 | // // StatusCode::from_u16(307).unwrap() 328 | // 307 329 | // } else { 330 | // // StatusCode::from_u16(301).unwrap() 331 | // 301 332 | // }; 333 | 334 | // if tsr && self.redirect_trailing_slash { 335 | // let path = if req.uri().path().len() > 1 && req.uri().path().ends_with("/") { 336 | // req.uri().path()[..req.uri().path().len() - 1].to_string() 337 | // } else { 338 | // req.uri().path().to_string() + "/" 339 | // }; 340 | 341 | // // response.headers_mut().insert(header::LOCATION, header::HeaderValue::from_str(&path).unwrap()); 342 | // // *response.status_mut() = code; 343 | // let response = Response::builder().header("Location", path.as_str()).status(code).body(Body::empty()).unwrap(); 344 | // return Box::new(future::ok(response)); 345 | // } 346 | 347 | // if self.redirect_fixed_path { 348 | // let (fixed_path, found) = root.find_case_insensitive_path(&clean_path(req.uri().path()), self.redirect_trailing_slash); 349 | 350 | // if found { 351 | // // response.headers_mut().insert(header::LOCATION, header::HeaderValue::from_str(&fixed_path).unwrap()); 352 | // // *response.status_mut() = code; 353 | // let response = Response::builder().header("Location", fixed_path.as_str()).status(code).body(Body::empty()).unwrap(); 354 | // return Box::new(future::ok(response)); 355 | // } 356 | // } 357 | // } 358 | // } 359 | 360 | // if req.method() == &Method::OPTIONS && self.handle_options { 361 | // let allow = self.allowed(req.uri().path(), req.method().as_str()); 362 | // if allow.len() > 0 { 363 | // // *response.headers_mut().get_mut("allow").unwrap() = header::HeaderValue::from_str(&allow).unwrap(); 364 | // let response = Response::builder().header("Allow", allow.as_str()).body(Body::empty()).unwrap(); 365 | // return Box::new(future::ok(response)); 366 | // } 367 | 368 | // } else { 369 | // if self.handle_method_not_allowed { 370 | // let allow = self.allowed(req.uri().path(), req.method().as_str()); 371 | 372 | // if allow.len() > 0 { 373 | // let mut response = Response::builder().header("Allow", allow.as_str()).body(Body::empty()).unwrap(); 374 | 375 | // if let Some(ref method_not_allowed) = self.method_not_allowed { 376 | // return method_not_allowed.handle(req, Params::new()); 377 | // } else { 378 | // *response.status_mut() = StatusCode::METHOD_NOT_ALLOWED; 379 | // *response.body_mut() = Body::from("METHOD_NOT_ALLOWED"); 380 | // } 381 | 382 | // return Box::new(future::ok(response)); 383 | // } 384 | // } 385 | 386 | // } 387 | 388 | // // Handle 404 389 | // if let Some(ref not_found) = self.not_found { 390 | // return not_found.handle(req, Params::new()); 391 | // } else { 392 | // // *response.status_mut() = StatusCode::NOT_FOUND; 393 | // let response = Response::builder().status(404).body("NOT_FOUND".into()).unwrap(); 394 | // return Box::new(future::ok(response)); 395 | // } 396 | // } 397 | // } 398 | 399 | // impl IntoFuture for Router{ 400 | // type Future = future::FutureResult; 401 | // type Item = Self; 402 | // type Error = Error; 403 | 404 | // fn into_future(self) -> Self::Future { 405 | // future::ok(self) 406 | // } 407 | // } 408 | 409 | impl Router { 410 | /// ServeFiles serves files from the given file system root. 411 | /// 412 | /// The path must end with "/*filepath", files are then served from the local 413 | /// path /defined/root/dir/*filepath. 414 | /// 415 | /// For example if root is "/etc" and *filepath is "passwd", the local file 416 | /// "/etc/passwd" would be served. 417 | /// 418 | /// ```rust 419 | /// extern crate radix_router; 420 | /// use radix_router::router::{Router, Handler}; 421 | /// let mut router: Router = Router::new(); 422 | /// router.serve_files("/examples/*filepath", "examples"); 423 | /// ``` 424 | pub fn serve_files(&mut self, path: &str, root: &'static str) { 425 | if path.as_bytes().len() < 10 || &path[path.len() - 10..] != "/*filepath" { 426 | panic!("path must end with /*filepath in path '{}'", path); 427 | } 428 | let root_path = Path::new(root); 429 | let get_files = move |_, ps: Params| -> BoxFut { 430 | let filepath = ps.by_name("filepath").unwrap(); 431 | simple_file_send(root_path.join(&filepath[1..]).to_str().unwrap()) 432 | }; 433 | 434 | self.get(path, Box::new(get_files)); 435 | } 436 | 437 | pub fn serve_http(&self, req: Request) -> BoxFut { 438 | let root = self.trees.get(req.method().as_str()); 439 | if let Some(root) = root { 440 | let (handle, ps, tsr) = root.get_value(req.uri().path()); 441 | 442 | if let Some(handle) = handle { 443 | // return handle(req, response, ps); 444 | return handle.handle(req, ps); 445 | } else if req.method() != &Method::CONNECT && req.uri().path() != "/" { 446 | let code = if req.method() != &Method::GET { 447 | // StatusCode::from_u16(307).unwrap() 448 | 307 449 | } else { 450 | // StatusCode::from_u16(301).unwrap() 451 | 301 452 | }; 453 | 454 | if tsr && self.redirect_trailing_slash { 455 | let path = if req.uri().path().len() > 1 && req.uri().path().ends_with("/") { 456 | req.uri().path()[..req.uri().path().len() - 1].to_string() 457 | } else { 458 | req.uri().path().to_string() + "/" 459 | }; 460 | 461 | // response.headers_mut().insert(header::LOCATION, header::HeaderValue::from_str(&path).unwrap()); 462 | // *response.status_mut() = code; 463 | let response = Response::builder() 464 | .header("Location", path.as_str()) 465 | .status(code) 466 | .body(Body::empty()) 467 | .unwrap(); 468 | return Box::new(future::ok(response)); 469 | } 470 | 471 | if self.redirect_fixed_path { 472 | let (fixed_path, found) = root.find_case_insensitive_path( 473 | &clean_path(req.uri().path()), 474 | self.redirect_trailing_slash, 475 | ); 476 | 477 | if found { 478 | // response.headers_mut().insert(header::LOCATION, header::HeaderValue::from_str(&fixed_path).unwrap()); 479 | // *response.status_mut() = code; 480 | let response = Response::builder() 481 | .header("Location", fixed_path.as_str()) 482 | .status(code) 483 | .body(Body::empty()) 484 | .unwrap(); 485 | return Box::new(future::ok(response)); 486 | } 487 | } 488 | } 489 | } 490 | 491 | if req.method() == &Method::OPTIONS && self.handle_options { 492 | let allow = self.allowed(req.uri().path(), req.method().as_str()); 493 | if allow.len() > 0 { 494 | // *response.headers_mut().get_mut("allow").unwrap() = header::HeaderValue::from_str(&allow).unwrap(); 495 | let response = Response::builder() 496 | .header("Allow", allow.as_str()) 497 | .body(Body::empty()) 498 | .unwrap(); 499 | return Box::new(future::ok(response)); 500 | } 501 | } else { 502 | if self.handle_method_not_allowed { 503 | let allow = self.allowed(req.uri().path(), req.method().as_str()); 504 | 505 | if allow.len() > 0 { 506 | let mut response = Response::builder() 507 | .header("Allow", allow.as_str()) 508 | .body(Body::empty()) 509 | .unwrap(); 510 | 511 | if let Some(ref method_not_allowed) = self.method_not_allowed { 512 | return method_not_allowed.handle(req, Params::new()); 513 | } else { 514 | *response.status_mut() = StatusCode::METHOD_NOT_ALLOWED; 515 | *response.body_mut() = Body::from("METHOD_NOT_ALLOWED"); 516 | } 517 | 518 | return Box::new(future::ok(response)); 519 | } 520 | } 521 | } 522 | 523 | // Handle 404 524 | if let Some(ref not_found) = self.not_found { 525 | return not_found.handle(req, Params::new()); 526 | } else { 527 | // *response.status_mut() = StatusCode::NOT_FOUND; 528 | let response = Response::builder() 529 | .status(404) 530 | .body("NOT_FOUND".into()) 531 | .unwrap(); 532 | return Box::new(future::ok(response)); 533 | } 534 | } 535 | } 536 | 537 | fn simple_file_send(f: &str) -> BoxFut { 538 | // Serve a file by asynchronously reading it entirely into memory. 539 | // Uses tokio_fs to open file asynchronously, then tokio_io to read into 540 | // memory asynchronously. 541 | let filename = f.to_string(); // we need to copy for lifetime issues 542 | Box::new( 543 | tokio_fs::file::File::open(filename) 544 | .and_then(|file| { 545 | let buf: Vec = Vec::new(); 546 | tokio_io::io::read_to_end(file, buf) 547 | .and_then(|item| Ok(Response::new(item.1.into()))) 548 | .or_else(|_| { 549 | Ok(Response::builder() 550 | .status(StatusCode::INTERNAL_SERVER_ERROR) 551 | .body(Body::empty()) 552 | .unwrap()) 553 | }) 554 | }) 555 | .or_else(|_| { 556 | Ok(Response::builder() 557 | .status(StatusCode::NOT_FOUND) 558 | .body(Body::from("NOT_FOUND")) 559 | .unwrap()) 560 | }), 561 | ) 562 | } 563 | 564 | // impl NewService for Router 565 | // where 566 | // T: Fn(Request, Response, Option) -> BoxFut, 567 | // { 568 | // type ReqBody = Body; 569 | // type ResBody = Body; 570 | // type Error = Error; 571 | // type Service = Self; 572 | // type Future = future::FutureResult; 573 | // type InitError = Error; 574 | 575 | // fn new_service(&self) -> Self::Future { 576 | // future::ok(*self.clone()) 577 | // } 578 | // } 579 | 580 | #[cfg(test)] 581 | mod tests { 582 | #[test] 583 | fn params() { 584 | use crate::router::{Param, Params}; 585 | 586 | let params = Params(vec![ 587 | Param { 588 | key: "hello".to_owned(), 589 | value: "world".to_owned(), 590 | }, 591 | Param { 592 | key: "lalala".to_string(), 593 | value: "papapa".to_string(), 594 | }, 595 | ]); 596 | 597 | assert_eq!(Some("world"), params.by_name("hello")); 598 | assert_eq!(Some("papapa"), params.by_name("lalala")); 599 | } 600 | 601 | #[test] 602 | #[should_panic(expected = "path must begin with '/' in path 'something'")] 603 | fn handle_ivalid_path() { 604 | // use http::Response; 605 | use futures::future; 606 | use hyper::{Body, Request, Response}; 607 | use crate::router::{BoxFut, Params, Router}; 608 | 609 | let path = "something"; 610 | let mut router = Router::new(); 611 | 612 | router.handle("GET", path, |_req: Request, _: Params| -> BoxFut { 613 | Box::new(future::ok(Response::new(Body::from("test")))) 614 | }); 615 | } 616 | } 617 | -------------------------------------------------------------------------------- /src/tree.rs: -------------------------------------------------------------------------------- 1 | use crate::router::{Param, Params}; 2 | // use std::fmt::Debug; 3 | use std::mem; 4 | use std::str; 5 | 6 | fn min(a: usize, b: usize) -> usize { 7 | if a <= b { 8 | return a; 9 | } 10 | b 11 | } 12 | 13 | fn count_params(path: &[u8]) -> u8 { 14 | let mut n = 0; 15 | for &c in path { 16 | if c != b':' && c != b'*' { 17 | continue; 18 | } 19 | n += 1; 20 | } 21 | if n > 255 { 22 | return 255; 23 | } 24 | n as u8 25 | } 26 | 27 | #[derive(PartialEq, Clone, Debug, PartialOrd)] 28 | pub enum NodeType { 29 | Static, 30 | Root, 31 | Param, 32 | CatchAll, 33 | } 34 | 35 | #[derive(Debug, Clone)] 36 | pub struct Node { 37 | path: Vec, 38 | wild_child: bool, 39 | n_type: NodeType, 40 | max_params: u8, 41 | indices: Vec, 42 | children: Vec>>, 43 | handle: Option, 44 | priority: u32, 45 | } 46 | 47 | impl Node { 48 | pub fn new() -> Node { 49 | Node { 50 | path: Vec::new(), 51 | wild_child: false, 52 | n_type: NodeType::Static, 53 | max_params: 0, 54 | indices: Vec::new(), 55 | children: Vec::new(), 56 | handle: None, 57 | priority: 0, 58 | } 59 | } 60 | 61 | /// increments priority of the given child and reorders if necessary 62 | fn increment_child_prio(&mut self, pos: usize) -> usize { 63 | self.children[pos].priority += 1; 64 | let prio = self.children[pos].priority; 65 | // adjust position (move to front) 66 | let mut new_pos = pos; 67 | 68 | while new_pos > 0 && self.children[new_pos - 1].priority < prio { 69 | // swap node positions 70 | self.children.swap(new_pos - 1, new_pos); 71 | new_pos -= 1; 72 | } 73 | 74 | // build new index char string 75 | if new_pos != pos { 76 | self.indices = [ 77 | &self.indices[..new_pos], // unchanged prefix, might be empty 78 | &self.indices[pos..pos + 1], // the index char we move 79 | &self.indices[new_pos..pos], // rest without char at 'pos' 80 | &self.indices[pos + 1..], // rest without char at 'pos' 81 | ].concat(); 82 | } 83 | 84 | new_pos 85 | } 86 | 87 | /// addRoute adds a node with the given handle to the path. 88 | /// Not concurrency-safe! 89 | pub fn add_route(&mut self, path: &str, handle: T) { 90 | let full_path = path.clone(); 91 | let path = path.as_ref(); 92 | self.priority += 1; 93 | let num_params = count_params(path); 94 | 95 | // non-empty tree 96 | if self.path.len() > 0 || self.children.len() > 0 { 97 | self.add_route_loop(num_params, path, full_path, handle); 98 | } else { 99 | // Empty tree 100 | self.insert_child(num_params, path, full_path, handle); 101 | self.n_type = NodeType::Root; 102 | } 103 | } 104 | 105 | fn add_route_loop(&mut self, num_params: u8, mut path: &[u8], full_path: &str, handle: T) { 106 | // Update max_params of the current node 107 | if num_params > self.max_params { 108 | self.max_params = num_params; 109 | } 110 | 111 | // Find the longest common prefix. 112 | // This also implies that the common prefix contains no ':' or '*' 113 | // since the existing key can't contain those chars. 114 | let mut i = 0; 115 | let max = min(path.len(), self.path.len()); 116 | 117 | while i < max && path[i] == self.path[i] { 118 | i += 1; 119 | } 120 | 121 | // Split edge 122 | if i < self.path.len() { 123 | let mut child = Node { 124 | path: self.path[i..].to_vec(), 125 | wild_child: self.wild_child, 126 | n_type: NodeType::Static, 127 | indices: self.indices.clone(), 128 | children: Vec::new(), 129 | handle: self.handle.take(), 130 | priority: self.priority - 1, 131 | 132 | max_params: 0, 133 | }; 134 | 135 | mem::swap(&mut self.children, &mut child.children); 136 | 137 | // Update max_params (max of all children) 138 | for c in &child.children { 139 | if c.max_params > child.max_params { 140 | child.max_params = c.max_params; 141 | } 142 | } 143 | 144 | self.children = vec![Box::new(child)]; 145 | self.indices = vec![self.path[i]]; 146 | self.path = path[..i].to_vec(); 147 | self.wild_child = false; 148 | } 149 | 150 | // Make new node a child of this node 151 | if i < path.len() { 152 | path = &path[i..]; 153 | 154 | if self.wild_child { 155 | // *n = * {n}.children[0].clone(); 156 | return self.children[0].is_wild_child(num_params, path, full_path, handle); 157 | } 158 | 159 | let c = path[0]; 160 | 161 | // slash after param 162 | if self.n_type == NodeType::Param && c == b'/' && self.children.len() == 1 { 163 | self.children[0].priority += 1; 164 | return self.children[0].add_route_loop(num_params, path, full_path, handle); 165 | } 166 | 167 | // Check if a child with the next path byte exists 168 | for mut i in 0..self.indices.len() { 169 | if c == self.indices[i] { 170 | i = self.increment_child_prio(i); 171 | return self.children[i].add_route_loop(num_params, path, full_path, handle); 172 | } 173 | } 174 | 175 | // Otherwise insert it 176 | if c != b':' && c != b'*' { 177 | self.indices.push(c); 178 | 179 | let len = self.indices.len(); 180 | 181 | let child: Box> = Box::new(Node { 182 | path: Vec::new(), 183 | 184 | wild_child: false, 185 | 186 | n_type: NodeType::Static, 187 | 188 | max_params: num_params, 189 | 190 | indices: Vec::new(), 191 | 192 | children: Vec::new(), 193 | 194 | handle: None, 195 | 196 | priority: 0, 197 | }); 198 | 199 | self.children.push(child); 200 | 201 | let i = self.increment_child_prio(len - 1); 202 | 203 | return self.children[i].insert_child(num_params, path, full_path, handle); 204 | } 205 | 206 | return self.insert_child(num_params, path, full_path, handle); 207 | } else if i == path.len() { 208 | // Make node a (in-path) leaf 209 | if self.handle.is_some() { 210 | panic!("a handle is already registered for path '{}'", full_path); 211 | } 212 | 213 | self.handle = Some(handle); 214 | } 215 | 216 | return; 217 | } 218 | 219 | fn is_wild_child(&mut self, mut num_params: u8, path: &[u8], full_path: &str, handle: T) { 220 | self.priority += 1; 221 | 222 | // Update maxParams of the child node 223 | 224 | if num_params > self.max_params { 225 | self.max_params = num_params; 226 | } 227 | 228 | num_params -= 1; 229 | 230 | // Check if the wildcard matches 231 | 232 | if path.len() >= self.path.len() 233 | && self.path == &path[..self.path.len()] 234 | // Check for longer wildcard, e.g. :name and :names 235 | && (self.path.len() >= path.len() || path[self.path.len()] == b'/') 236 | { 237 | self.add_route_loop(num_params, path, full_path, handle); 238 | } else { 239 | // Wildcard conflict 240 | let path_seg = if self.n_type == NodeType::CatchAll { 241 | str::from_utf8(path).unwrap() 242 | } else { 243 | str::from_utf8(path) 244 | .unwrap() 245 | .splitn(2, '/') 246 | .into_iter() 247 | .next() 248 | .unwrap() 249 | }; 250 | 251 | let prefix = [ 252 | &full_path[..full_path.find(path_seg).unwrap()], 253 | str::from_utf8(&self.path).unwrap(), 254 | ].concat(); 255 | 256 | panic!("'{}' in new path '{}' conflicts with existing wildcard '{}' in existing prefix '{}'", path_seg, full_path, str::from_utf8(&self.path).unwrap(), prefix); 257 | } 258 | } 259 | 260 | fn insert_child(&mut self, num_params: u8, path: &[u8], full_path: &str, handle: T) { 261 | self.insert_child_loop(0, 0, num_params, path, full_path, handle); 262 | } 263 | 264 | fn insert_child_loop( 265 | &mut self, 266 | mut offset: usize, 267 | mut i: usize, 268 | mut num_params: u8, 269 | path: &[u8], 270 | full_path: &str, 271 | handle: T, 272 | ) { 273 | if num_params > 0 { 274 | let max = path.len(); 275 | let c = path[i]; 276 | 277 | // find prefix until first wildcard (beginning with ':'' or '*'') 278 | if c != b':' && c != b'*' { 279 | return self.insert_child_loop(offset, i + 1, num_params, path, full_path, handle); 280 | } 281 | 282 | // find wildcard end (either '/' or path end) 283 | let mut end = i + 1; 284 | while end < max && path[end] != b'/' { 285 | match path[end] { 286 | // the wildcard name must not contain ':' and '*' 287 | b':' | b'*' => panic!( 288 | "only one wildcard per path segment is allowed, has: '{}' in path '{}'", 289 | str::from_utf8(&path[i..]).unwrap(), 290 | full_path 291 | ), 292 | _ => end += 1, 293 | } 294 | } 295 | 296 | // println!("self path: {}", str::from_utf8(&self.path).unwrap()); 297 | // println!("temp path: {}", str::from_utf8(path).unwrap()); 298 | // println!("self {:?}", self.children[0]); 299 | // println!("self {:?}", self.children.len()); 300 | 301 | // check if this Node existing children which would be 302 | // unreachable if we insert the wildcard here 303 | if self.children.len() > 0 { 304 | panic!( 305 | "wildcard route '{}' conflicts with existing children in path '{}'", 306 | str::from_utf8(&path[i..end]).unwrap(), 307 | full_path 308 | ) 309 | } 310 | 311 | // check if the wildcard has a name 312 | if end - i < 2 { 313 | panic!( 314 | "wildcards must be named with a non-empty name in path '{}'", 315 | full_path 316 | ); 317 | } 318 | 319 | if c == b':' { 320 | // Param 321 | // split path at the beginning of the wildcard 322 | if i > 0 { 323 | self.path = path[offset..i].to_vec(); 324 | offset = i; 325 | } 326 | 327 | let child = Box::new(Node { 328 | path: Vec::new(), 329 | wild_child: false, 330 | n_type: NodeType::Param, 331 | max_params: num_params, 332 | indices: Vec::new(), 333 | children: Vec::new(), 334 | handle: None, 335 | priority: 0, 336 | }); 337 | 338 | self.children = vec![child]; 339 | self.wild_child = true; 340 | 341 | self.children[0].priority += 1; 342 | num_params -= 1; 343 | 344 | if end < max { 345 | self.children[0].path = path[offset..end].to_vec(); 346 | offset = end; 347 | 348 | let child: Box> = Box::new(Node { 349 | path: Vec::new(), 350 | wild_child: false, 351 | n_type: NodeType::Static, 352 | max_params: num_params, 353 | indices: Vec::new(), 354 | children: Vec::new(), 355 | handle: None, 356 | priority: 1, 357 | }); 358 | 359 | self.children[0].children.push(child); 360 | self.children[0].children[0].insert_child_loop( 361 | offset, 362 | i + 1, 363 | num_params, 364 | path, 365 | full_path, 366 | handle, 367 | ); 368 | } else { 369 | self.children[0].insert_child_loop( 370 | offset, 371 | i + 1, 372 | num_params, 373 | path, 374 | full_path, 375 | handle, 376 | ); 377 | } 378 | } else { 379 | // CatchAll 380 | if end != max || num_params > 1 { 381 | panic!( 382 | "catch-all routes are only allowed at the end of the path in path '{}'", 383 | full_path 384 | ); 385 | } 386 | 387 | if self.path.len() > 0 && self.path[self.path.len() - 1] == b'/' { 388 | panic!( 389 | "catch-all conflicts with existing handle for the path segment root in path '{}'", 390 | full_path 391 | ); 392 | } 393 | 394 | // currently fixed width 1 for '/' 395 | i -= 1; 396 | if path[i] != b'/' { 397 | panic!("no / before catch-all in path '{}'", full_path); 398 | } 399 | 400 | self.path = path[offset..i].to_vec(); 401 | 402 | // first node: catchAll node with empty path 403 | let child = Box::new(Node { 404 | path: Vec::new(), 405 | wild_child: true, 406 | n_type: NodeType::CatchAll, 407 | max_params: 1, 408 | indices: Vec::new(), 409 | children: Vec::new(), 410 | handle: None, 411 | priority: 0, 412 | }); 413 | 414 | self.children = vec![child]; 415 | 416 | self.indices = vec![path[i]]; 417 | 418 | self.children[0].priority += 1; 419 | 420 | // second node: node holding the variable 421 | let child: Box> = Box::new(Node { 422 | path: path[i..].to_vec(), 423 | wild_child: false, 424 | n_type: NodeType::CatchAll, 425 | max_params: 1, 426 | indices: Vec::new(), 427 | children: Vec::new(), 428 | handle: Some(handle), 429 | priority: 1, 430 | }); 431 | 432 | self.children[0].children.push(child); 433 | 434 | return; 435 | } 436 | } else { 437 | // insert remaining path part and handle to the leaf 438 | self.path = path[offset..].to_vec(); 439 | self.handle = Some(handle); 440 | } 441 | } 442 | 443 | /// Returns the handle registered with the given path (key). The values of 444 | /// wildcards are saved to a map. 445 | /// If no handle can be found, a TSR (trailing slash redirect) recommendation is 446 | /// made if a handle exists with an extra (without the) trailing slash for the 447 | /// given path. 448 | pub fn get_value(&self, path: &str) -> (Option<&T>, Params, bool) { 449 | // let mut handle = None; 450 | self.get_value_loop(path.as_ref(), Params::new()) 451 | } 452 | 453 | /// outer loop for walking the tree 454 | fn get_value_loop(&self, mut path: &[u8], p: Params) -> (Option<&T>, Params, bool) { 455 | if path.len() > self.path.len() { 456 | if self.path == &path[..self.path.len()] { 457 | path = &path[self.path.len()..]; 458 | // If this node does not have a wildcard (param or catchAll) 459 | // child, we can just look up the next child node and continue 460 | // to walk down the tree 461 | if !self.wild_child { 462 | let c = path[0]; 463 | for i in 0..self.indices.len() { 464 | if c == self.indices[i] { 465 | return self.children[i].get_value_loop(path, p); 466 | } 467 | } 468 | // Nothing found. 469 | // We can recommend to redirect to the same URL without a 470 | // trailing slash if a leaf exists for that path. 471 | let tsr = path == [b'/'] && self.handle.is_some(); 472 | return (None, p, tsr); 473 | } 474 | 475 | // handle wildcard child 476 | return self.children[0].handle_wildcard_child(path, p); 477 | } 478 | } else if self.path == path { 479 | // We should have reached the node containing the handle. 480 | // Check if this node has a handle registered. 481 | if self.handle.is_some() { 482 | return (self.handle.as_ref(), p, false); 483 | } 484 | 485 | if path == [b'/'] && self.wild_child && self.n_type != NodeType::Root { 486 | // tsr = true; 487 | return (self.handle.as_ref(), p, true); 488 | } 489 | 490 | // No handle found. Check if a handle for this path + a 491 | // trailing slash exists for trailing slash recommendation 492 | for i in 0..self.indices.len() { 493 | if self.indices[i] == b'/' { 494 | let tsr = (self.path.len() == 1 && self.children[i].handle.is_some()) 495 | || (self.children[i].n_type == NodeType::CatchAll 496 | && self.children[i].children[0].handle.is_some()); 497 | return (self.handle.as_ref(), p, tsr); 498 | } 499 | } 500 | 501 | return (self.handle.as_ref(), p, false); 502 | } 503 | 504 | // Nothing found. We can recommend to redirect to the same URL with an 505 | // extra trailing slash if a leaf exists for that path 506 | let tsr = (path == [b'/']) 507 | || (self.path.len() == path.len() + 1 508 | && self.path[path.len()] == b'/' 509 | && path == &self.path[..self.path.len() - 1] 510 | && self.handle.is_some()); 511 | 512 | return (None, p, tsr); 513 | } 514 | 515 | fn handle_wildcard_child(&self, mut path: &[u8], mut p: Params) -> (Option<&T>, Params, bool) { 516 | match self.n_type { 517 | NodeType::Param => { 518 | // find param end (either '/' or path end) 519 | let mut end = 0; 520 | while end < path.len() && path[end] != b'/' { 521 | end += 1; 522 | } 523 | 524 | // save param value 525 | if p.is_empty() { 526 | // lazy allocation 527 | p = Params(Vec::with_capacity(self.max_params as usize)); 528 | } 529 | 530 | p.push(Param { 531 | key: String::from_utf8(self.path[1..].to_vec()).unwrap(), 532 | value: String::from_utf8(path[..end].to_vec()).unwrap(), 533 | }); 534 | 535 | // we need to go deeper! 536 | if end < path.len() { 537 | if self.children.len() > 0 { 538 | path = &path[end..]; 539 | 540 | return self.children[0].get_value_loop(path, p); 541 | } 542 | 543 | // ... but we can't 544 | let tsr = path.len() == end + 1; 545 | return (None, p, tsr); 546 | } 547 | 548 | if self.handle.is_some() { 549 | return (self.handle.as_ref(), p, false); 550 | } else if self.children.len() == 1 { 551 | // No handle found. Check if a handle for this path + a 552 | // trailing slash exists for TSR recommendation 553 | let tsr = self.children[0].path == &[b'/'] && self.children[0].handle.is_some(); 554 | return (None, p, tsr); 555 | } 556 | 557 | return (None, p, false); 558 | } 559 | NodeType::CatchAll => { 560 | // save param value 561 | if p.is_empty() { 562 | // lazy allocation 563 | p = Params(Vec::with_capacity(self.max_params as usize)); 564 | } 565 | 566 | p.push(Param { 567 | key: String::from_utf8(self.path[2..].to_vec()).unwrap(), 568 | value: String::from_utf8(path.to_vec()).unwrap(), 569 | }); 570 | 571 | return (self.handle.as_ref(), p, false); 572 | } 573 | _ => panic!("invalid node type"), 574 | } 575 | } 576 | 577 | /// Makes a case-insensitive lookup of the given path and tries to find a handler. 578 | /// It can optionally also fix trailing slashes. 579 | /// It returns the case-corrected path and a bool indicating whether the lookup 580 | /// was successful. 581 | pub fn find_case_insensitive_path( 582 | &self, 583 | path: &str, 584 | fix_trailing_slash: bool, 585 | ) -> (String, bool) { 586 | let mut ci_path = Vec::with_capacity(path.len() + 1); 587 | let found = self.find_case_insensitive_path_rec( 588 | path.as_bytes(), 589 | path.to_ascii_lowercase().as_bytes(), 590 | &mut ci_path, 591 | [0; 4], 592 | fix_trailing_slash, 593 | ); 594 | (String::from_utf8(ci_path).unwrap(), found) 595 | } 596 | 597 | /// recursive case-insensitive lookup function used by n.findCaseInsensitivePath 598 | fn find_case_insensitive_path_rec( 599 | &self, 600 | mut path: &[u8], 601 | mut lo_path: &[u8], 602 | ci_path: &mut Vec, 603 | mut rb: [u8; 4], 604 | fix_trailing_slash: bool, 605 | ) -> bool { 606 | // println!("{:?}", self.path); 607 | // let n_path = str::from_utf8(&self.path).expect("ivalid utf8").to_lowercase(); 608 | // let lo_n_path = n_path.as_bytes(); 609 | let lo_n_path: Vec = self.path.iter().map(|u| u.to_ascii_lowercase()).collect(); 610 | 611 | if lo_path.len() >= lo_n_path.len() 612 | && (lo_n_path.len() == 0 || lo_path[1..lo_n_path.len()] == lo_n_path[1..]) 613 | { 614 | // println!("self.path = {}", str::from_utf8(&self.path).unwrap()); 615 | ci_path.append(&mut self.path.clone()); 616 | 617 | path = &path[self.path.len()..]; 618 | 619 | if path.len() > 0 { 620 | let lo_old = lo_path.clone(); 621 | lo_path = &lo_path[lo_n_path.len()..]; 622 | 623 | // If this node does not have a wildcard (param or catchAll) child, 624 | // we can just look up the next child node and continue to walk down 625 | // the tree 626 | if !self.wild_child { 627 | // skip rune bytes already processed 628 | rb = shift_n_rune_bytes(rb, lo_n_path.len()); 629 | 630 | if rb[0] != 0 { 631 | // old rune not finished 632 | for i in 0..self.indices.len() { 633 | if self.indices[i] == rb[0] { 634 | // continue with child node 635 | return self.children[i].find_case_insensitive_path_rec( 636 | path, 637 | lo_path, 638 | ci_path, 639 | rb, 640 | fix_trailing_slash, 641 | ); 642 | } 643 | } 644 | } else { 645 | // process a new rune 646 | let mut rv = 0 as char; 647 | 648 | // find rune start 649 | // runes are up to 4 byte long, 650 | // -4 would definitely be another rune 651 | let mut off = 0; 652 | // println!("loold {:?}", lo_old); 653 | for j in 0..min(lo_n_path.len(), 3) { 654 | let i = lo_n_path.len() - j; 655 | if rune_start(lo_old[i]) { 656 | // read rune from cached lowercase path 657 | rv = str::from_utf8(&lo_old[i..]) 658 | .unwrap() 659 | .chars() 660 | .next() 661 | .unwrap(); 662 | off = j; 663 | break; 664 | } 665 | } 666 | // println!("rv = {}, off = {}", rv, off); 667 | // calculate lowercase bytes of current rune 668 | rv.encode_utf8(&mut rb); 669 | 670 | // skipp already processed bytes 671 | rb = shift_n_rune_bytes(rb, off); 672 | // println!("rb = {:?}", rb); 673 | for i in 0..self.indices.len() { 674 | // lowercase matches 675 | if self.indices[i] == rb[0] { 676 | // must use a recursive approach since both the 677 | // uppercase byte and the lowercase byte might exist 678 | // as an index 679 | let found = self.children[i].find_case_insensitive_path_rec( 680 | path, 681 | lo_path, 682 | ci_path, 683 | rb, 684 | fix_trailing_slash, 685 | ); 686 | 687 | if found { 688 | // println!("cipah = {}", str::from_utf8(&ci_path).unwrap()); 689 | return true; 690 | } 691 | if ci_path.len() > self.children[i].path.len() { 692 | let prev_len = ci_path.len() - self.children[i].path.len(); 693 | ci_path.truncate(prev_len); 694 | } 695 | 696 | break; 697 | } 698 | } 699 | 700 | // same for uppercase rune, if it differs 701 | let up = rv.to_ascii_uppercase(); 702 | if up != rv { 703 | up.encode_utf8(&mut rb); 704 | rb = shift_n_rune_bytes(rb, off); 705 | 706 | for i in 0..self.indices.len() { 707 | if self.indices[i] == rb[0] { 708 | return self.children[i].find_case_insensitive_path_rec( 709 | path, 710 | lo_path, 711 | ci_path, 712 | rb, 713 | fix_trailing_slash, 714 | ); 715 | } 716 | } 717 | } 718 | } 719 | 720 | // Nothing found. We can recommend to redirect to the same URL 721 | // without a trailing slash if a leaf exists for that path 722 | return fix_trailing_slash && path == [b'/'] && self.handle.is_some(); 723 | } 724 | 725 | return self.children[0].find_case_insensitive_path_rec_match( 726 | path, 727 | lo_path, 728 | ci_path, 729 | rb, 730 | fix_trailing_slash, 731 | ); 732 | } else { 733 | if self.handle.is_some() { 734 | return true; 735 | } 736 | 737 | if fix_trailing_slash { 738 | for i in 0..self.indices.len() { 739 | if self.indices[i] == b'/' { 740 | if (self.children[i].path.len() == 1 741 | && self.children[i].handle.is_some()) 742 | || (self.children[i].n_type == NodeType::CatchAll 743 | && self.children[i].children[0].handle.is_some()) 744 | { 745 | ci_path.push(b'/'); 746 | return true; 747 | } 748 | return false; 749 | } 750 | } 751 | } 752 | return false; 753 | } 754 | } 755 | 756 | if fix_trailing_slash { 757 | if path == [b'/'] { 758 | return true; 759 | } 760 | if lo_path.len() + 1 == lo_n_path.len() 761 | && lo_n_path[lo_path.len()] == b'/' 762 | && lo_path[1..] == lo_n_path[1..lo_path.len()] 763 | && self.handle.is_some() 764 | { 765 | ci_path.append(&mut self.path.clone()); 766 | return true; 767 | } 768 | } 769 | 770 | false 771 | } 772 | 773 | /// recursive case-insensitive lookup function used by n.findCaseInsensitivePath 774 | fn find_case_insensitive_path_rec_match( 775 | &self, 776 | mut path: &[u8], 777 | mut lo_path: &[u8], 778 | ci_path: &mut Vec, 779 | rb: [u8; 4], 780 | fix_trailing_slash: bool, 781 | ) -> bool { 782 | match self.n_type { 783 | NodeType::Param => { 784 | let mut k = 0; 785 | while k < path.len() && path[k] != b'/' { 786 | k += 1; 787 | } 788 | let mut path_k = path[..k].to_vec(); 789 | ci_path.append(&mut path_k); 790 | 791 | if k < path.len() { 792 | if self.children.len() > 0 { 793 | lo_path = &lo_path[k..]; 794 | path = &path[k..]; 795 | 796 | return self.children[0].find_case_insensitive_path_rec( 797 | path, 798 | lo_path, 799 | ci_path, 800 | rb, 801 | fix_trailing_slash, 802 | ); 803 | } 804 | 805 | if fix_trailing_slash && path.len() == k + 1 { 806 | return true; 807 | } 808 | return false; 809 | } 810 | 811 | if self.handle.is_some() { 812 | return true; 813 | } else if fix_trailing_slash && self.children.len() == 1 { 814 | if self.children[0].path == [b'/'] && self.children[0].handle.is_some() { 815 | ci_path.push(b'/'); 816 | return true; 817 | } 818 | } 819 | 820 | return false; 821 | } 822 | NodeType::CatchAll => { 823 | ci_path.append(&mut path.to_vec()); 824 | return true; 825 | } 826 | _ => panic!("invalid node type"), 827 | } 828 | } 829 | } 830 | 831 | fn shift_n_rune_bytes(rb: [u8; 4], n: usize) -> [u8; 4] { 832 | match n { 833 | 0 => rb, 834 | 1 => [rb[1], rb[2], rb[3], 0], 835 | 2 => [rb[2], rb[3], 0, 0], 836 | 3 => [rb[3], 0, 0, 0], 837 | _ => [0; 4], 838 | } 839 | } 840 | 841 | /// This function is ported from go. 842 | fn rune_start(b: u8) -> bool { 843 | b & 0xC0 != 0x80 844 | } 845 | 846 | #[cfg(test)] 847 | mod tests { 848 | use super::*; 849 | // use hyper::{Body, Request, Response}; 850 | use crate::router::Params; 851 | use std::panic; 852 | use std::sync::Mutex; 853 | 854 | // fn print_children() {} 855 | 856 | struct TestRequest<'a> { 857 | path: &'a str, 858 | nil_handler: bool, 859 | route: &'a str, 860 | ps: Params, 861 | } 862 | 863 | impl<'a> TestRequest<'a> { 864 | pub fn new( 865 | path: &'a str, 866 | nil_handler: bool, 867 | route: &'a str, 868 | ps: Params, 869 | ) -> TestRequest<'a> { 870 | TestRequest { 871 | path, 872 | nil_handler, 873 | route, 874 | ps, 875 | } 876 | } 877 | } 878 | 879 | type TestRequests<'a> = Vec>; 880 | 881 | fn check_requests String>(tree: &mut Node, requests: TestRequests) { 882 | for request in requests { 883 | let (handler, ps, _) = tree.get_value(request.path); 884 | 885 | if handler.is_none() { 886 | if !request.nil_handler { 887 | panic!( 888 | "handle mismatch for route '{}': Expected non-nil handle", 889 | request.path 890 | ); 891 | } 892 | } else if request.nil_handler { 893 | panic!( 894 | "handle m ismatch for route '{}': Expected nil handle", 895 | request.path 896 | ); 897 | } else { 898 | match handler { 899 | Some(h) => { 900 | let res = h(); 901 | if res != request.route { 902 | panic!( 903 | "handle mismatch for route '{}': Wrong handle ({} != {})", 904 | request.path, res, request.route 905 | ); 906 | } 907 | } 908 | None => { 909 | panic!("handle not found"); 910 | } 911 | } 912 | } 913 | 914 | if ps != request.ps { 915 | panic!("Params mismatch for route '{}'", request.path); 916 | } 917 | } 918 | } 919 | 920 | fn check_priorities String>(n: &mut Node) -> u32 { 921 | // println!("{}", str::from_utf8(&n.path).unwrap()); 922 | let mut prio: u32 = 0; 923 | for i in 0..n.children.len() { 924 | prio += check_priorities(&mut *n.children[i]); 925 | } 926 | 927 | if n.handle.is_some() { 928 | prio += 1; 929 | } 930 | 931 | if n.priority != prio { 932 | panic!( 933 | "priority mismatch for node '{}': is {}, should be {}", 934 | str::from_utf8(&n.path).unwrap(), 935 | n.priority, 936 | prio 937 | ) 938 | } 939 | 940 | prio 941 | } 942 | 943 | fn check_max_params String>(n: &mut Node) -> u8 { 944 | let mut max_params: u8 = 0; 945 | for i in 0..n.children.len() { 946 | let params = check_max_params(&mut *n.children[i]); 947 | 948 | if params > max_params { 949 | max_params = params; 950 | } 951 | } 952 | 953 | if n.n_type > NodeType::Root && !n.wild_child { 954 | max_params += 1; 955 | } 956 | 957 | if n.max_params != max_params { 958 | panic!( 959 | "maxParams mismatch for node '{}': is {}, should be {}", 960 | str::from_utf8(&n.path).unwrap(), 961 | n.max_params, 962 | max_params, 963 | ) 964 | } 965 | 966 | max_params 967 | } 968 | 969 | fn fake_handler(val: &'static str) -> impl Fn() -> String { 970 | move || val.to_string() 971 | } 972 | 973 | #[test] 974 | fn test_count_params() { 975 | assert_eq!( 976 | 2, 977 | count_params("/path/:param1/static/*catch-all".as_bytes()) 978 | ); 979 | assert_eq!(255, count_params("/:param".repeat(256).as_bytes())); 980 | } 981 | 982 | #[test] 983 | fn test_tree_add_and_get() { 984 | let mut tree = Node::new(); 985 | 986 | let routes = vec![ 987 | "/hi", 988 | "/contact", 989 | "/co", 990 | "/c", 991 | "/a", 992 | "/ab", 993 | "/doc/", 994 | "/doc/go_faq.html", 995 | "/doc/go1.html", 996 | "/α", 997 | "/β", 998 | ]; 999 | 1000 | for route in routes { 1001 | tree.add_route(route, fake_handler(route)); 1002 | } 1003 | 1004 | check_requests( 1005 | &mut tree, 1006 | vec![ 1007 | TestRequest::new("/a", false, "/a", Params::new()), 1008 | TestRequest::new("/", true, "", Params::new()), 1009 | TestRequest::new("/hi", false, "/hi", Params::new()), 1010 | TestRequest::new("/contact", false, "/contact", Params::new()), 1011 | TestRequest::new("/co", false, "/co", Params::new()), 1012 | TestRequest::new("/con", true, "", Params::new()), // key mismatch 1013 | TestRequest::new("/cona", true, "", Params::new()), // key mismatch 1014 | TestRequest::new("/no", true, "", Params::new()), // no matching child 1015 | TestRequest::new("/ab", false, "/ab", Params::new()), 1016 | TestRequest::new("/α", false, "/α", Params::new()), 1017 | TestRequest::new("/β", false, "/β", Params::new()), 1018 | ], 1019 | ); 1020 | 1021 | check_priorities(&mut tree); 1022 | check_max_params(&mut tree); 1023 | } 1024 | 1025 | #[test] 1026 | fn test_tree_wildcard() { 1027 | let mut tree = Node::new(); 1028 | 1029 | let routes = vec![ 1030 | "/", 1031 | "/cmd/:tool/:sub", 1032 | "/cmd/:tool/", 1033 | "/src/*filepath", 1034 | "/search/", 1035 | "/search/:query", 1036 | "/user_:name", 1037 | "/user_:name/about", 1038 | "/files/:dir/*filepath", 1039 | "/doc/", 1040 | "/doc/go_faq.html", 1041 | "/doc/go1.html", 1042 | "/info/:user/public", 1043 | "/info/:user/project/:project", 1044 | ]; 1045 | 1046 | for route in routes { 1047 | tree.add_route(route, fake_handler(route)); 1048 | } 1049 | 1050 | check_requests( 1051 | &mut tree, 1052 | vec![ 1053 | TestRequest::new("/", false, "/", Params::new()), 1054 | TestRequest::new( 1055 | "/cmd/test/", 1056 | false, 1057 | "/cmd/:tool/", 1058 | Params(vec![Param::new("tool", "test")]), 1059 | ), 1060 | TestRequest::new( 1061 | "/cmd/test", 1062 | true, 1063 | "", 1064 | Params(vec![Param::new("tool", "test")]), 1065 | ), 1066 | TestRequest::new( 1067 | "/cmd/test/3", 1068 | false, 1069 | "/cmd/:tool/:sub", 1070 | Params(vec![Param::new("tool", "test"), Param::new("sub", "3")]), 1071 | ), 1072 | TestRequest::new( 1073 | "/src/", 1074 | false, 1075 | "/src/*filepath", 1076 | Params(vec![Param::new("filepath", "/")]), 1077 | ), 1078 | TestRequest::new( 1079 | "/src/some/file.png", 1080 | false, 1081 | "/src/*filepath", 1082 | Params(vec![Param::new("filepath", "/some/file.png")]), 1083 | ), 1084 | TestRequest::new("/search/", false, "/search/", Params::new()), 1085 | TestRequest::new( 1086 | "/search/someth!ng+in+ünìcodé", 1087 | false, 1088 | "/search/:query", 1089 | Params(vec![Param::new("query", "someth!ng+in+ünìcodé")]), 1090 | ), 1091 | TestRequest::new( 1092 | "/search/someth!ng+in+ünìcodé/", 1093 | true, 1094 | "", 1095 | Params(vec![Param::new("query", "someth!ng+in+ünìcodé")]), 1096 | ), 1097 | TestRequest::new( 1098 | "/user_gopher", 1099 | false, 1100 | "/user_:name", 1101 | Params(vec![Param::new("name", "gopher")]), 1102 | ), 1103 | TestRequest::new( 1104 | "/user_gopher/about", 1105 | false, 1106 | "/user_:name/about", 1107 | Params(vec![Param::new("name", "gopher")]), 1108 | ), 1109 | TestRequest::new( 1110 | "/files/js/inc/framework.js", 1111 | false, 1112 | "/files/:dir/*filepath", 1113 | Params(vec![ 1114 | Param::new("dir", "js"), 1115 | Param::new("filepath", "/inc/framework.js"), 1116 | ]), 1117 | ), 1118 | TestRequest::new( 1119 | "/info/gordon/public", 1120 | false, 1121 | "/info/:user/public", 1122 | Params(vec![Param::new("user", "gordon")]), 1123 | ), 1124 | TestRequest::new( 1125 | "/info/gordon/project/go", 1126 | false, 1127 | "/info/:user/project/:project", 1128 | Params(vec![ 1129 | Param::new("user", "gordon"), 1130 | Param::new("project", "go"), 1131 | ]), 1132 | ), 1133 | ], 1134 | ); 1135 | 1136 | check_priorities(&mut tree); 1137 | check_max_params(&mut tree); 1138 | } 1139 | 1140 | // path: &str, conflict: bool 1141 | type TestRoute = (&'static str, bool); 1142 | 1143 | fn test_routes(routes: Vec) { 1144 | let tree = Mutex::new(Node::new()); 1145 | // let mut tree = Node::new(); 1146 | 1147 | for route in routes { 1148 | let recv = panic::catch_unwind(|| { 1149 | let mut guard = match tree.lock() { 1150 | Ok(guard) => guard, 1151 | Err(poisoned) => poisoned.into_inner(), 1152 | }; 1153 | guard.add_route(route.0, ()); 1154 | }); 1155 | 1156 | if route.1 { 1157 | if recv.is_ok() { 1158 | panic!("no panic for conflicting route '{}'", route.0); 1159 | } 1160 | } else if recv.is_err() { 1161 | panic!("unexpected panic for route '{}': {:?}", route.0, recv); 1162 | } 1163 | } 1164 | } 1165 | 1166 | #[test] 1167 | fn test_tree_wildcard_conflict() { 1168 | let routes = vec![ 1169 | ("/cmd/:tool/:sub", false), 1170 | ("/cmd/vet", true), 1171 | ("/src/*filepath", false), 1172 | ("/src/*filepathx", true), 1173 | ("/src/", true), 1174 | ("/src1/", false), 1175 | ("/src1/*filepath", true), 1176 | ("/src2*filepath", true), 1177 | ("/search/:query", false), 1178 | ("/search/invalid", true), 1179 | ("/user_:name", false), 1180 | ("/user_x", true), 1181 | // ("/user_:name", false), 1182 | ("/user_:name", true), // Rust is different. Nil handler was not allowed. Or maybe it is a feature? 1183 | ("/id:id", false), 1184 | ("/id/:id", true), 1185 | ]; 1186 | test_routes(routes); 1187 | } 1188 | 1189 | #[test] 1190 | fn test_tree_child_conflict() { 1191 | let routes = vec![ 1192 | ("/cmd/vet", false), 1193 | ("/cmd/:tool/:sub", true), 1194 | ("/src/AUTHORS", false), 1195 | ("/src/*filepath", true), 1196 | ("/user_x", false), 1197 | ("/user_:name", true), 1198 | ("/id/:id", false), 1199 | ("/id:id", true), 1200 | ("/:id", true), 1201 | ("/*filepath", true), 1202 | ]; 1203 | 1204 | test_routes(routes); 1205 | } 1206 | 1207 | #[test] 1208 | fn test_tree_duplicate_path() { 1209 | let tree = Mutex::new(Node::new()); 1210 | 1211 | let routes = vec![ 1212 | "/", 1213 | "/doc/", 1214 | "/src/*filepath", 1215 | "/search/:query", 1216 | "/user_:name", 1217 | ]; 1218 | 1219 | for route in routes { 1220 | let mut recv = panic::catch_unwind(|| { 1221 | let mut guard = match tree.lock() { 1222 | Ok(guard) => guard, 1223 | Err(poisoned) => poisoned.into_inner(), 1224 | }; 1225 | guard.add_route(route, fake_handler(route)); 1226 | }); 1227 | 1228 | if recv.is_err() { 1229 | panic!("panic inserting route '{}': {:?}", route, recv); 1230 | } 1231 | 1232 | recv = panic::catch_unwind(|| { 1233 | let mut guard = match tree.lock() { 1234 | Ok(guard) => guard, 1235 | Err(poisoned) => poisoned.into_inner(), 1236 | }; 1237 | guard.add_route(route, fake_handler(route)); 1238 | }); 1239 | 1240 | if recv.is_ok() { 1241 | panic!("no panic while inserting duplicate route '{}'", route); 1242 | } 1243 | } 1244 | 1245 | check_requests( 1246 | &mut tree.lock().unwrap_or_else(|poisoned| poisoned.into_inner()), 1247 | vec![ 1248 | TestRequest::new("/", false, "/", Params::new()), 1249 | TestRequest::new("/doc/", false, "/doc/", Params::new()), 1250 | TestRequest::new( 1251 | "/src/some/file.png", 1252 | false, 1253 | "/src/*filepath", 1254 | Params(vec![Param::new("filepath", "/some/file.png")]), 1255 | ), 1256 | TestRequest::new( 1257 | "/search/someth!ng+in+ünìcodé", 1258 | false, 1259 | "/search/:query", 1260 | Params(vec![Param::new("query", "someth!ng+in+ünìcodé")]), 1261 | ), 1262 | TestRequest::new( 1263 | "/user_gopher", 1264 | false, 1265 | "/user_:name", 1266 | Params(vec![Param::new("name", "gopher")]), 1267 | ), 1268 | ], 1269 | ); 1270 | } 1271 | 1272 | #[test] 1273 | fn test_empty_wildcard_name() { 1274 | let tree = Mutex::new(Node::new()); 1275 | let routes = vec!["/user:", "/user:/", "/cmd/:/", "/src/*"]; 1276 | 1277 | for route in routes { 1278 | let recv = panic::catch_unwind(|| { 1279 | let mut guard = match tree.lock() { 1280 | Ok(guard) => guard, 1281 | Err(poisoned) => poisoned.into_inner(), 1282 | }; 1283 | guard.add_route(route, fake_handler(route)); 1284 | }); 1285 | 1286 | if recv.is_ok() { 1287 | panic!( 1288 | "no panic while inserting route with empty wildcard name '{}", 1289 | route 1290 | ); 1291 | } 1292 | } 1293 | } 1294 | 1295 | #[test] 1296 | fn test_tree_catch_all_conflict() { 1297 | let routes = vec![ 1298 | ("/src/*filepath/x", true), 1299 | ("/src2/", false), 1300 | ("/src2/*filepath/x", true), 1301 | ]; 1302 | 1303 | test_routes(routes); 1304 | } 1305 | 1306 | #[test] 1307 | fn test_tree_catch_all_conflict_root() { 1308 | let routes = vec![("/", false), ("/*filepath", true)]; 1309 | 1310 | test_routes(routes); 1311 | } 1312 | 1313 | #[test] 1314 | fn test_tree_double_wildcard() { 1315 | let panic_msg = "only one wildcard per path segment is allowed"; 1316 | let routes = vec!["/:foo:bar", "/:foo:bar/", "/:foo*bar"]; 1317 | 1318 | for route in routes { 1319 | let tree = Mutex::new(Node::new()); 1320 | let recv = panic::catch_unwind(|| { 1321 | let mut guard = match tree.lock() { 1322 | Ok(guard) => guard, 1323 | Err(poisoned) => poisoned.into_inner(), 1324 | }; 1325 | guard.add_route(route, fake_handler(route)); 1326 | }); 1327 | 1328 | // [TODO] Not strict enough 1329 | if recv.is_ok() { 1330 | panic!(panic_msg); 1331 | } 1332 | } 1333 | } 1334 | 1335 | #[test] 1336 | fn test_tree_trailing_slash_redirect() { 1337 | let tree = Mutex::new(Node::new()); 1338 | let routes = vec![ 1339 | "/hi", 1340 | "/b/", 1341 | "/search/:query", 1342 | "/cmd/:tool/", 1343 | "/src/*filepath", 1344 | "/x", 1345 | "/x/y", 1346 | "/y/", 1347 | "/y/z", 1348 | "/0/:id", 1349 | "/0/:id/1", 1350 | "/1/:id/", 1351 | "/1/:id/2", 1352 | "/aa", 1353 | "/a/", 1354 | "/admin", 1355 | "/admin/:category", 1356 | "/admin/:category/:page", 1357 | "/doc", 1358 | "/doc/go_faq.html", 1359 | "/doc/go1.html", 1360 | "/no/a", 1361 | "/no/b", 1362 | "/api/hello/:name", 1363 | ]; 1364 | 1365 | for route in routes { 1366 | let recv = panic::catch_unwind(|| { 1367 | let mut guard = match tree.lock() { 1368 | Ok(guard) => guard, 1369 | Err(poisoned) => poisoned.into_inner(), 1370 | }; 1371 | guard.add_route(route, fake_handler(route)); 1372 | }); 1373 | 1374 | if recv.is_err() { 1375 | panic!("panic inserting route '{}': {:?}", route, recv); 1376 | } 1377 | } 1378 | 1379 | let tsr_routes = vec![ 1380 | "/hi/", 1381 | "/b", 1382 | "/search/gopher/", 1383 | "/cmd/vet", 1384 | "/src", 1385 | "/x/", 1386 | "/y", 1387 | "/0/go/", 1388 | "/1/go", 1389 | "/a", 1390 | "/admin/", 1391 | "/admin/config/", 1392 | "/admin/config/permissions/", 1393 | "/doc/", 1394 | ]; 1395 | 1396 | for route in tsr_routes { 1397 | let guard = match tree.lock() { 1398 | Ok(guard) => guard, 1399 | Err(poisoned) => poisoned.into_inner(), 1400 | }; 1401 | let (handler, _, tsr) = guard.get_value(route); 1402 | 1403 | if handler.is_some() { 1404 | panic!("non-nil handler for TSR route '{}'", route); 1405 | } else if !tsr { 1406 | panic!("expected TSR recommendation for route '{}'", route); 1407 | } 1408 | } 1409 | 1410 | let no_tsr_routes = vec!["/", "/no", "/no/", "/_", "/_/", "/api/world/abc"]; 1411 | 1412 | for route in no_tsr_routes { 1413 | let guard = match tree.lock() { 1414 | Ok(guard) => guard, 1415 | Err(poisoned) => poisoned.into_inner(), 1416 | }; 1417 | let (handler, _, tsr) = guard.get_value(route); 1418 | 1419 | if handler.is_some() { 1420 | panic!("non-nil handler for TSR route '{}'", route); 1421 | } else if tsr { 1422 | panic!("expected TSR recommendation for route '{}'", route); 1423 | } 1424 | } 1425 | } 1426 | 1427 | #[test] 1428 | fn test_tree_root_trailing_slash_redirect() { 1429 | let tree = Mutex::new(Node::new()); 1430 | 1431 | let recv = panic::catch_unwind(|| { 1432 | let mut guard = match tree.lock() { 1433 | Ok(guard) => guard, 1434 | Err(poisoned) => poisoned.into_inner(), 1435 | }; 1436 | guard.add_route("/:test", fake_handler("/:test")); 1437 | }); 1438 | 1439 | if recv.is_err() { 1440 | panic!("panic inserting test route: {:?}", recv); 1441 | } 1442 | 1443 | let guard = match tree.lock() { 1444 | Ok(guard) => guard, 1445 | Err(poisoned) => poisoned.into_inner(), 1446 | }; 1447 | let (handler, _, tsr) = guard.get_value("/"); 1448 | 1449 | if handler.is_some() { 1450 | panic!("non-nil handler"); 1451 | } else if tsr { 1452 | panic!("expected no TSR recommendation"); 1453 | } 1454 | } 1455 | 1456 | #[test] 1457 | fn test_tree_find_case_insensitive_path() { 1458 | // let tree = Mutex::new(Node::new()); 1459 | let mut tree = Node::new(); 1460 | 1461 | let routes = vec![ 1462 | "/hi", 1463 | "/b/", 1464 | "/ABC/", 1465 | "/search/:query", 1466 | "/cmd/:tool/", 1467 | "/src/*filepath", 1468 | "/x", 1469 | "/x/y", 1470 | "/y/", 1471 | "/y/z", 1472 | "/0/:id", 1473 | "/0/:id/1", 1474 | "/1/:id/", 1475 | "/1/:id/2", 1476 | "/aa", 1477 | "/a/", 1478 | "/doc", 1479 | "/doc/go_faq.html", 1480 | "/doc/go1.html", 1481 | "/doc/go/away", 1482 | "/no/a", 1483 | "/no/b", 1484 | "/Π", 1485 | "/u/apfêl/", 1486 | "/u/äpfêl/", 1487 | "/u/öpfêl", 1488 | "/v/Äpfêl/", 1489 | "/v/Öpfêl", 1490 | "/w/♬", // 3 byte 1491 | "/w/♭/", // 3 byte, last byte differs 1492 | "/w/𠜎", // 4 byte 1493 | "/w/𠜏/", // 4 byte 1494 | ]; 1495 | 1496 | for route in &routes { 1497 | // let mut recv = panic::catch_unwind(|| { 1498 | // let mut guard = match tree.lock() { 1499 | // Ok(guard) => guard, 1500 | // Err(poisoned) => poisoned.into_inner(), 1501 | // }; 1502 | // guard.add_route(route, fake_handler(route)); 1503 | // }); 1504 | 1505 | // if recv.is_err() { 1506 | // panic!("panic inserting route '{}': {:?}", route, recv); 1507 | // } 1508 | tree.add_route(route, fake_handler(route)); 1509 | } 1510 | 1511 | for route in &routes { 1512 | // let mut guard = match tree.lock() { 1513 | // Ok(guard) => guard, 1514 | // Err(poisoned) => poisoned.into_inner(), 1515 | // }; 1516 | // let (out, found) = guard.find_case_insensitive_path(route, false); 1517 | let (out, found) = tree.find_case_insensitive_path(route, false); 1518 | // println!("{},{}", str::from_utf8(&out).unwrap(), found); 1519 | if !found { 1520 | panic!("Route '{}' not found!", route); 1521 | // println!("Route '{}' not found!", route); 1522 | } else if out != *route { 1523 | panic!("Wrong result for route '{}': {}", route, out); 1524 | } 1525 | } 1526 | } 1527 | } 1528 | --------------------------------------------------------------------------------