中间件系统
简介
中间件是 Hyperlane 最强大的功能之一。它允许你在请求生命周期的各个阶段拦截和处理请求与响应。无论你需要身份验证、日志记录、跨域支持还是自定义请求转换,Hyperlane 的中间件系统都提供了一种干净灵活的方式来实现这些横切关注点。
理解中间件架构
Hyperlane 的中间件系统围绕 ServerHook trait 构建。中间件是任何实现了 ServerHook 的类型,它提供了两个方法:
new():在新连接建立时调用。用于初始化。handle():对连接上的每个请求调用。这是中间件逻辑的核心所在。
handle() 方法返回一个 Status 枚举,用于控制请求流:
Status::Continue:请求继续传递到下一个中间件或路由处理器。Status::Reject:请求被拒绝,连接可能被关闭。
请求中间件
请求中间件在路由处理器处理请求之前运行。它非常适合用于身份验证、请求验证和设置响应默认值等任务。
以下是一个设置默认响应头部的请求中间件:
struct RequestMiddleware;
impl ServerHook for RequestMiddleware {
async fn new(stream: &mut Stream, _: &mut Context) -> Self {
Self
}
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
ctx.get_mut_response()
.set_version(HttpVersion::Http1_1)
.set_status_code(200);
Status::Continue
}
}
server.request_middleware::<RequestMiddleware>();
在这个示例中,中间件:
- 在连接建立时创建一个新实例(
new()) - 为每个请求设置 HTTP 版本为 1.1 和状态码为 200(
handle()) - 返回
Status::Continue让请求继续处理
响应中间件
响应中间件在路由处理器处理完请求之后运行。它非常适合用于发送响应、添加响应头部和日志记录等任务。
以下是一个发送已构建响应的响应中间件:
struct ResponseMiddleware;
impl ServerHook for ResponseMiddleware {
async fn new(_: &mut Stream, _: &mut Context) -> Self {
Self
}
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
let data: Vec<u8> = ctx.get_mut_response().build();
if stream.try_send(data).await.is_err() {
stream.set_closed(true);
return Status::Reject;
}
Status::Continue
}
}
server.response_middleware::<ResponseMiddleware>();
这个中间件:
- 使用
ctx.get_mut_response().build()将响应构建为字节 - 通过流发送响应数据
- 如果发送失败,标记流为已关闭并拒绝请求
执行顺序
中间件的注册顺序很重要。请求中间件按注册顺序运行,响应中间件按相反顺序运行。你可以使用属性宏语法控制执行顺序:
#[request_middleware(1)]
struct RequestMiddleware1;
#[request_middleware(2)]
struct RequestMiddleware2;
#[response_middleware]
struct ResponseMiddleware;
#[request_middleware(N)] 中的数字参数指定执行优先级。数字越小越先运行。在这个示例中:
RequestMiddleware1(优先级 1)首先运行RequestMiddleware2(优先级 2)其次运行- 路由处理器处理请求
ResponseMiddleware最后运行
实用中间件示例
身份验证中间件
最常见的中间件用例之一是身份验证。以下是一个检查 Authorization 头部的示例:
impl ServerHook for AuthMiddleware {
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
let auth_str: String = ctx
.get_request()
.try_get_header_back(AUTHORIZATION)
.unwrap_or_default();
if auth_str.is_empty() {
let data: Vec<u8> = ctx
.get_mut_response()
.set_status_code(401)
.set_body("Unauthorized")
.build();
if stream.try_send(data).await.is_err() {
stream.set_closed(true);
}
return Status::Reject;
}
Status::Continue
}
}
这个中间件:
- 从请求中提取 Authorization 头部
- 如果头部缺失或为空,发送 401 Unauthorized 响应
- 返回
Status::Reject停止后续处理 - 如果头部存在,返回
Status::Continue继续处理
跨域中间件
跨域资源共享(CORS)对于服务于不同来源客户端的 API 至关重要:
ctx.get_mut_response()
.set_header(ACCESS_CONTROL_ALLOW_ORIGIN, WILDCARD_ANY)
.set_header(ACCESS_CONTROL_ALLOW_METHODS, ALL_METHODS)
.set_header(ACCESS_CONTROL_ALLOW_HEADERS, WILDCARD_ANY);
这设置了标准的 CORS 头部:
Access-Control-Allow-Origin: *— 允许来自任何来源的请求Access-Control-Allow-Methods: *— 允许所有 HTTP 方法Access-Control-Allow-Headers: *— 允许所有请求头部
静态资源中间件
对于提供静态文件服务,可以创建一个从磁盘读取文件的中间件:
let file_path: String = format!("{static_path}{path}");
let file_extension: String = FileExtension::get_extension_name(&file_path);
let content_type: &'static str = FileExtension::parse(&file_extension).get_content_type();
let file_data: String = tokio::fs::read_to_string(&file_path).await.unwrap();
ctx.get_mut_response().set_body(file_data);
这个中间件:
- 从静态目录和请求路径构建文件路径
- 确定文件扩展名
- 将扩展名映射到内容类型
- 读取文件内容并将其设置为响应体
超时中间件
超时中间件确保长时间运行的请求不会阻塞服务器:
spawn(async move {
timeout(
Duration::from_millis(100),
async move {
new_ctx.get_mut_response()
.set_status_code(504)
.set_body("timeout");
}
).await.unwrap();
});
这会生成一个等待 100 毫秒的任务,然后设置 504 Gateway Timeout 响应。
错误处理中间件
Hyperlane 提供了专门用于处理错误和恐慌的中间件:
请求错误处理器
struct RequestErrorHook;
impl ServerHook for RequestErrorHook {
async fn new(_: &mut Stream, ctx: &mut Context) -> Self {
let request_error: RequestError = ctx
.try_get_request_error_data()
.unwrap_or_default();
Self
}
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
let data: Vec<u8> = ctx
.get_mut_response()
.set_status_code(500)
.set_body("error")
.build();
stream.try_send(data).await;
Status::Continue
}
}
server.request_error::<RequestErrorHook>();
任务恐慌处理器
struct TaskPanicHook;
impl ServerHook for TaskPanicHook {
async fn new(_: &mut Stream, ctx: &mut Context) -> Self {
let error: PanicData = ctx
.try_get_task_panic_data()
.unwrap_or_default();
Self
}
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
let data: Vec<u8> = ctx
.get_mut_response()
.set_status_code(500)
.set_body("panic")
.build();
stream.try_send(data).await;
Status::Continue
}
}
server.task_panic::<TaskPanicHook>();
这些钩子在请求处理期间捕获错误和恐慌,并返回适当的错误响应,而不是让服务器崩溃。
属性宏中间件注册
除了编程式 API,你还可以使用属性宏注册中间件:
#[request_middleware(1)]
struct RequestMiddleware1;
#[request_middleware(2)]
struct RequestMiddleware2;
#[response_middleware]
struct ResponseMiddleware;
这种声明式方法更清晰、更易维护,特别是当你有很多中间件组件时。宏会自动处理注册。
中间件最佳实践
-
保持中间件聚焦:每个中间件应该处理一个关注点。不要试图在单个中间件中做所有事情。
-
顺序很重要:按正确的顺序注册中间件。身份验证应该在授权之前,授权应该在日志记录之前。
-
优雅地处理错误:始终处理发送响应失败的情况,检查
stream.try_send()的结果。 -
明智使用
Status::Reject:只有在已经发送了适当的错误响应时才拒绝请求。 -
利用
new()方法:在new()方法中进行连接级初始化,而不是在handle()中为每个请求都做。
总结
Hyperlane 的中间件系统提供了一种强大而灵活的方式来实现 Web 应用中的横切关注点。ServerHook trait 让你完全控制请求和响应处理,而属性宏系统则提供了一种更声明式的方法。通过组合请求中间件、响应中间件和错误处理器,你可以构建健壮且可维护的 Web 服务。
在下一篇文章中,我们将探讨 Hyperlane 的路由系统,了解传入请求如何匹配到对应的处理器。