#router #path #radix-tree #hyper #radix #tree #http-request

httprouter

一个高性能且可扩展的HTTP请求路由器

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

MIT 许可证

47KB
469

HttpRouter

Documentation Version License Actions

HttpRouter是一个轻量级且高性能的HTTP请求路由器。

此路由器支持路由模式中的变量,并匹配请求方法。它也具有很好的可扩展性。

此路由器针对高性能和小内存占用进行了优化。即使在非常长的路径和大量路由的情况下也能很好地扩展。它使用压缩动态字典树(radix树)结构进行高效匹配。内部,它使用matchit包。

特性

仅显式匹配:与其他路由器不同,请求的URL路径可能匹配多个模式。因此,它们有一些不自然的模式优先级规则,如最长匹配先注册先匹配。本路由器的设计允许请求只能精确匹配一个或零个路由。因此,也不会出现意外的匹配,这使得它非常适合SEO并提高用户体验。

路径自动纠正:除了无需额外费用即可检测缺失或额外的尾部斜杠外,路由器还可以修复错误情况并删除多余的路径元素(如..///)。如果你的用户之一是CAPTAIN CAPS LOCK吗?HttpRouter可以通过进行不区分大小写的查找并将他重定向到正确的URL来帮助他。

路由模式中的参数:停止解析请求的URL路径,只需给路径段命名,路由器就会将动态值传递给你。由于路由器的设计,路径参数非常便宜。

高性能:HttpRouter依赖于树结构,大量使用公共前缀,基本上是一个radix树。这使得查找非常快。内部,它使用matchit包。

当然,您还可以设置自定义的 NotFoundMethodNotAllowed 处理器提供静态文件服务,以及 自动响应 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