6个版本 (破坏性更新)
0.5.0 | 2021年5月12日 |
---|---|
0.4.0 | 2021年3月26日 |
0.3.0 | 2021年3月3日 |
0.2.0 | 2020年12月23日 |
0.0.0 | 2020年11月3日 |
在 #radix-tree 中排名 25
47KB
469 行
HttpRouter
HttpRouter是一个轻量级且高性能的HTTP请求路由器。
此路由器支持路由模式中的变量,并匹配请求方法。它也具有很好的可扩展性。
此路由器针对高性能和小内存占用进行了优化。即使在非常长的路径和大量路由的情况下也能很好地扩展。它使用压缩动态字典树(radix树)结构进行高效匹配。内部,它使用matchit包。
特性
仅显式匹配:与其他路由器不同,请求的URL路径可能匹配多个模式。因此,它们有一些不自然的模式优先级规则,如最长匹配或先注册先匹配。本路由器的设计允许请求只能精确匹配一个或零个路由。因此,也不会出现意外的匹配,这使得它非常适合SEO并提高用户体验。
路径自动纠正:除了无需额外费用即可检测缺失或额外的尾部斜杠外,路由器还可以修复错误情况并删除多余的路径元素(如../或
//
)。如果你的用户之一是CAPTAIN CAPS LOCK吗?HttpRouter可以通过进行不区分大小写的查找并将他重定向到正确的URL来帮助他。
路由模式中的参数:停止解析请求的URL路径,只需给路径段命名,路由器就会将动态值传递给你。由于路由器的设计,路径参数非常便宜。
高性能:HttpRouter依赖于树结构,大量使用公共前缀,基本上是一个radix树。这使得查找非常快。内部,它使用matchit包。
当然,您还可以设置自定义的 NotFound
和 MethodNotAllowed
处理器,提供静态文件服务,以及 自动响应 OPTIONS 请求
用法
以下是一个简单的示例
use httprouter::{Router, Params};
use hyper::{Request, Response, Body, Error};
async fn index(_: Request<Body>) -> Result<Response<Body>, Error> {
Ok(Response::new("Hello, World!".into()))
}
async fn hello(req: Request<Body>) -> Result<Response<Body>, Error> {
let params = req.extensions().get::<Params>().unwrap();
Ok(Response::new(format!("Hello, {}", params.get("user").unwrap()).into()))
}
#[tokio::main]
async fn main() {
let router = Router::default()
.get("/", index)
.get("/hello/:user", hello);
hyper::Server::bind(&([127, 0, 0, 1], 3000).into())
.serve(router.into_service())
.await;
}
命名参数
如您所见,:user
是一个 命名参数。值可以通过 req.extensions().get::<Params>()
访问。
命名参数仅匹配单个路径段
Pattern: /user/:user
/user/gordon match
/user/you match
/user/gordon/profile no match
/user/ no match
注意:由于此路由器只有显式匹配,因此您不能为相同的路径段注册静态路由和参数。例如,您不能同时为相同的请求方法注册模式 /user/new
和 /user/:user
。不同请求方法的路由是相互独立的。
捕获所有参数
第二种是 捕获所有参数,其形式为 *name
。正如其名所示,它们匹配一切。因此,它们必须始终位于模式的 末尾。
Pattern: /src/*filepath
/src/ match
/src/somefile.go match
/src/subdir/somefile.go match
自动 OPTIONS 响应和 CORS
有人可能希望修改对 OPTIONS 请求的自动响应,例如,以支持 CORS 预检请求 或设置其他标题。这可以通过使用 Router::global_options
处理器来实现。
use httprouter::Router;
use hyper::{Request, Response, Body, Error};
async fn cors(_: Request<Body>) -> Result<Response<Body>, Error> {
let res = Response::builder()
.header("Access-Control-Allow-Methods", "Allow")
.header("Access-Control-Allow-Origin", "*")
.body(Body::empty())
.unwrap();
Ok(res)
}
fn main() {
let router = Router::default().global_options(cors);
}
多域名/子域名
这里有一个快速示例:您的服务器是否为多个域名/主机提供服务?您想使用子域名?为每个主机定义一个路由器!
use httprouter::Router;
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Request, Response};
use std::collections::HashMap;
use std::convert::Infallible;
use std::sync::Arc;
pub struct HostSwitch<'a>(HashMap<String, Router<'a>>);
impl HostSwitch<'_> {
async fn serve(&self, req: Request<Body>) -> hyper::Result<Response<Body>> {
let forbidden = || Response::builder()
.status(403)
.body(Body::empty())
.unwrap();
match req.headers().get("host") {
Some(host) => match self.0.get(host.to_str().unwrap()) {
Some(router) => router.serve(req).await,
None => Ok(forbidden()),
},
None => Ok(forbidden()),
}
}
}
#[tokio::main]
async fn main() {
let mut host_switch = HostSwitch(HashMap::new());
host_switch.0.insert("example.com:12345".into(), Router::default());
let host_switch = Arc::new(host_switch);
let make_svc = make_service_fn(move |_| {
let host_switch = host_switch.clone();
async move {
Ok::<_, Infallible>(service_fn(move |req| {
let host_switch = host_switch.clone();
async move { host_switch.serve(req).await }
}))
}
});
hyper::Server::bind(&([127, 0, 0, 1], 3000).into())
.serve(make_svc)
.await;
}
未找到处理器
注意:可能需要将 Router::method_not_allowed
设置为 None
以避免问题。
您可以使用另一个处理器,通过使用 Router::not_found
处理器来处理无法由此路由器匹配的请求。
not_found
处理器可以用于返回 404 页面
use httprouter::Router;
use hyper::{Request, Response, Body, Error};
async fn not_found(req: Request<Body>) -> Result<Response<Body>, Error> {
let res = Response::builder()
.status(404)
.body(Body::empty())
.unwrap();
Ok(res)
}
fn main() {
let router = Router::default().not_found(not_found);
}
静态文件
您可以使用路由器从静态文件目录提供服务
// TODO
依赖关系
~4–5.5MB
~93K SLoC