路由基础
简介
路由是 HTTP 服务器将传入请求映射到相应处理器的机制。在 Hyperlane 中,路由既灵活又强大,支持静态路由、动态路径参数、基于正则的匹配和路由过滤。本文将介绍在 Hyperlane 中设置路由所需了解的一切。
静态路由
最简单的路由形式是静态路由,它匹配精确的 URL 路径:
server.route::<Route>("/test");
这注册了一个仅匹配精确路径 /test 的路由。任何其他路径都不会由此路由处理。静态路由非常适合固定端点,如 /health、/about 或 /api/status。
动态路由
动态路由包含路径参数,用于捕获 URL 的可变部分。参数用花括号 {...} 括起来:
server.route::<Route>("/test/{text}");
在这个示例中,{text} 是一个动态参数,匹配任何单一路径段。例如:
/test/hello—text="hello"/test/world—text="world"/test/123—text="123"
但是,/test/hello/world 不会匹配,因为 {text} 只匹配单个段。
正则动态路由
为了更精确地控制参数匹配的内容,可以使用正则表达式模式:
server.route::<Route>("/test/{number:\\d+}");
在这个示例中,{number:\\d+} 仅匹配一个或多个数字。例如:
/test/123—number="123"(匹配)/test/abc— 不匹配(包含非数字字符)/test/42—number="42"(匹配)
正则语法遵循 Rust 的 regex crate,为你的路由提供了强大的模式匹配能力。
路由参数
路由匹配后,可以从上下文中提取参数值:
获取单个参数
// 返回 Option<String> — 如果参数不存在则返回 None
let param: Option<String> = ctx.try_get_route_param("text");
// 直接返回 String — 如果参数不存在会 panic
let param: String = ctx.get_route_param("text");
当你不确定参数是否存在时,使用 try_get_route_param;当参数肯定存在时(因为路由模式要求它),使用 get_route_param。
获取所有参数
let params = ctx.get_route_params();
这将返回所有路由参数的集合,当你路由中有多个动态段时很有用。
路由过滤
Hyperlane 提供了一个强大的路由过滤系统,允许你为路由添加条件。过滤器作为路由结构体上的属性宏实现:
基于 Host 过滤
根据 Host 头部匹配请求:
#[host("example.com")]
此路由仅匹配 Host 头部为 example.com 的请求。
拒绝 Host 过滤
阻止来自特定主机的请求:
#[reject_host("blocked.com")]
此路由拒绝 Host 头部为 blocked.com 的请求。
基于 Referer 过滤
根据 Referer 头部匹配请求:
#[referer("https://example.com")]
此路由仅匹配来自 https://example.com 的请求。
拒绝 Referer 过滤
阻止来自特定来源的请求:
#[reject_referer("https://malicious.com")]
此路由拒绝来自 https://malicious.com 的请求。
方法过滤
按 HTTP 方法过滤请求:
#[filter(ctx.get_request().get_method() == &RequestMethod::Get)]
此路由仅匹配 GET 请求。你可以使用任何计算结果为布尔值的表达式。
拒绝过滤
拒绝匹配特定条件的请求:
#[reject(ctx.get_request().get_path().len() > 1000)]
此路由拒绝路径长度超过 1000 个字符的请求。
属性宏路由
可以以编程方式注册路由,也可以使用 #[route] 属性宏:
#[route("/test/{text}")]
struct Route;
这种声明式方法更简洁,并将路由路径保持在处理器实现附近。宏会自动处理注册。
组合过滤器
可以在单个路由上组合多个过滤器,实现精细控制:
#[host("example.com")]
#[reject_host("blocked.com")]
#[referer("https://example.com")]
#[reject_referer("https://malicious.com")]
#[filter(ctx.get_request().get_method() == &RequestMethod::Get)]
#[reject(ctx.get_request().get_path().len() > 1000)]
此路由仅在满足以下所有条件时才匹配:
- 主机是
example.com - 主机不是
blocked.com - 来源是
https://example.com - 来源不是
https://malicious.com - 请求方法是 GET
- 路径长度不超过 1000
完整路由示例
以下是一个展示各种路由模式的完整示例:
use hyperlane::*;
use hyperlane_macros::*;
// 静态路由
#[route("/health")]
struct HealthRoute;
// 带文本参数的动态路由
#[route("/users/{name}")]
struct UserRoute;
// 带数字参数的正则动态路由
#[route("/posts/{id:\\d+}")]
struct PostRoute;
// 带主机过滤的路由
#[route("/admin")]
#[host("admin.example.com")]
struct AdminRoute;
// 带方法过滤的路由
#[route("/api/data")]
#[filter(ctx.get_request().get_method() == &RequestMethod::Get)]
struct ApiDataRoute;
#[tokio::main]
async fn main() {
let mut server: Server = Server::default();
let server_control_hook: ServerControlHook = server.run().await.unwrap_or_default();
server_control_hook.wait().await;
}
路由匹配顺序
当多个路由可能匹配一个请求时,Hyperlane 使用以下优先级:
- 静态路由 优先于动态路由
- 更具体的路由 优先于不太具体的路由
- 带过滤器的路由 在路径匹配后进行评估
这意味着如果你同时有 /users/{name} 和 /users/admin,对 /users/admin 的请求会先匹配静态路由。
最佳实践
-
尽可能使用静态路由:它们比动态路由更快、更可预测。
-
对类型化参数使用正则约束:如果参数应该是数字,使用
{id:\\d+}而不是{id}。 -
保持路由路径简洁:深层嵌套的路径如
/api/v1/users/{id}/posts/{post_id}/comments/{comment_id}可能难以维护。考虑扁平化 URL 结构。 -
使用过滤器进行访问控制:主机和方法过滤器比在处理器中检查这些条件更高效。
-
使用属性宏语法:它比编程式路由注册更具可读性和可维护性。
总结
Hyperlane 的路由系统提供了一套全面的工具,用于将 URL 映射到处理器。从简单的静态路由到具有多个过滤器的复杂正则模式,该系统旨在处理任何路由需求。属性宏语法使路由定义简洁且声明式,而编程式 API 在你需要时提供完全的控制。
在下一篇文章中,我们将探讨如何处理传入的请求,包括提取头部、查询参数和请求体数据。