23 个版本

0.8.4 2024 年 7 月 11 日
0.8.2 2024 年 4 月 28 日
0.8.0 2024 年 3 月 10 日
0.7.3 2023 年 9 月 22 日
0.2.0 2020 年 12 月 23 日

#32 in 网络编程

Download history 803480/week @ 2024-05-03 860600/week @ 2024-05-10 886486/week @ 2024-05-17 837885/week @ 2024-05-24 957489/week @ 2024-05-31 968897/week @ 2024-06-07 930009/week @ 2024-06-14 955613/week @ 2024-06-21 827525/week @ 2024-06-28 856118/week @ 2024-07-05 883753/week @ 2024-07-12 907155/week @ 2024-07-19 918129/week @ 2024-07-26 943867/week @ 2024-08-02 1017237/week @ 2024-08-09 1003649/week @ 2024-08-16

4,062,006 每月下载量
1,762 个 Crates 中使用 (51 直接使用)

MIT 和 BSD-3-Clause

65KB
1K SLoC

matchit

crates.io github docs.rs

高性能、零拷贝 URL 路由器。

use matchit::Router;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut router = Router::new();
    router.insert("/home", "Welcome!")?;
    router.insert("/users/{id}", "A User")?;

    let matched = router.at("/users/978")?;
    assert_eq!(matched.params.get("id"), Some("978"));
    assert_eq!(*matched.value, "A User");

    Ok(())
}

参数

该路由器支持动态路由段。这些段可以是命名参数或通配符参数。

命名参数如 /{id} 匹配直到下一个 / 或路径结束的内容。请注意,命名参数必须后跟一个 / 或路由的结束。目前不支持动态后缀。

let mut m = Router::new();
m.insert("/users/{id}", true)?;

assert_eq!(m.at("/users/1")?.params.get("id"), Some("1"));
assert_eq!(m.at("/users/23")?.params.get("id"), Some("23"));
assert!(m.at("/users").is_err());

通配符参数以 * 开头,匹配直到路径结束的内容。它们必须始终位于路由的 末尾

let mut m = Router::new();
m.insert("/{*p}", true)?;

assert_eq!(m.at("/foo.js")?.params.get("p"), Some("foo.js"));
assert_eq!(m.at("/c/bar.css")?.params.get("p"), Some("c/bar.css"));

// note that this will not match
assert!(m.at("/").is_err());

可以在静态路由中包含字符 {},方法是使用相同的字符进行转义。例如,字符 { 使用 {{ 进行转义,而字符 } 使用 }} 进行转义。

let mut m = Router::new();
m.insert("/{{hello}}", true)?;
m.insert("/{hello}", true)?;

// match the static route
assert!(m.at("/{hello}")?.value);

// match the dynamic route
assert_eq!(m.at("/hello")?.params.get("hello"), Some("hello"));

路由优先级

静态和动态路由段可以重叠。如果重叠,则静态段将具有更高的优先级

let mut m = Router::new();
m.insert("/", "Welcome!").unwrap();      // priority: 1
m.insert("/about", "About Me").unwrap(); // priority: 1
m.insert("/{*filepath}", "...").unwrap();  // priority: 2

它是如何工作的?

路由器利用了 URL 路由通常遵循层次结构的这一事实。将它们存储在基数 trie 中,该 trie 重用共同的前缀。

Priority   Path             Value
9          \                1
3          ├s               None
2          |├earch\         2
1          |└upport\        3
2          ├blog\           4
1          |    └{post}     None
1          |          └\    5
2          ├about-us\       6
1          |        └team\  7
1          └contact\        8

这使我们能够将路由搜索减少到少数几个分支。树的同级子节点也按注册值的数量进行优先排序,增加了第一次尝试就选择正确分支的机会。

基准测试

实际上,这种方法的路由速度非常快。在一个将4条路径与130条注册路由进行匹配的基准测试中,matchit在200纳秒内就找到了正确的路由,比大多数其他路由器快一个数量级。您可以在这里查看基准测试代码。

Compare Routers/matchit 
time:   [175.96 ns 176.39 ns 176.84 ns]

Compare Routers/actix
time:   [26.805 us 26.811 us 26.816 us]

Compare Routers/path-tree
time:   [468.95 ns 470.34 ns 471.65 ns]

Compare Routers/regex
time:   [22.539 us 22.584 us 22.639 us]

Compare Routers/route-recognizer
time:   [3.7552 us 3.7732 us 3.8027 us]

Compare Routers/routefinder
time:   [5.7313 us 5.7405 us 5.7514 us]

致谢

这个包中的大量代码基于Julien Schmidt的httprouter

无运行时依赖