Miko 框架系列(九):中间件与层 (Layer)
注意:此文章主要为了给支持联网搜索的AI提供RAG,此项目还在开发阶段,不建议用于生产。
中间件是现代 Web 框架中用于处理横切关注点(cross-cutting concerns)的核心机制。无论是日志记录、请求认证、CORS 处理、超时控制还是请求压缩,都可以通过中间件来实现,从而避免在每个处理器函数中重复编写相同的逻辑。
Miko 完全建立在 tower 的 Service 和 Layer 抽象之上,这意味着你可以无缝地使用 tower 和 tower-http 生态系统中海量的高质量中间件。
1. 核心概念:Service 和 Layer
在 tower 的世界里:
Service: 是一个异步函数,它接收一个Request并返回一个Response的Future。在 Miko 中,你的每一个处理器(Handler)最终都会被包装成一个Service。Layer: 是一个工厂,它接收一个Service并返回一个新的、被包装过的Service。这个包装过程就是中间件逻辑的注入点。
你可以将 Layer 想象成洋葱皮,一层一层地包裹住核心的处理器 Service。请求从外向内穿过每一层 Layer,响应则从内向外穿过。
2. 应用中间件的多种方式
Miko 提供了多种方式来应用中间件,以适应不同的作用域。
全局中间件
如果你希望某个中间件作用于所有请求,可以在 Router 上使用 with_layer 方法。
use miko::Router;
use tower_http::trace::TraceLayer;
use tower_http::compression::CompressionLayer;
use tower::ServiceBuilder;
use std::time::Duration;
// 方法一:链式调用
let router = Router::new()
.get("/", handler)
.with_layer(TraceLayer::new_for_http()) // 添加日志层
.with_layer(CompressionLayer::new()); // 添加 Gzip 压缩层
// 方法二:使用 ServiceBuilder 组合多个 Layer
let middleware_stack = ServiceBuilder::new()
.layer(TraceLayer::new_for_http())
.layer(CompressionLayer::new())
.timeout(Duration::from_secs(30));
let router = Router::new()
.get("/", handler)
.with_layer(middleware_stack);
路由组中间件
当只有一部分路由需要某个中间件(例如,/admin 下的所有路由都需要认证)时,你可以为一个子 Router 应用 Layer,然后再将其 nest 或 merge 到主 Router 中。
// 定义需要认证的路由
let admin_router = Router::new()
.get("/dashboard", admin_dashboard)
.post("/users", create_user)
.with_layer(auth_middleware_layer()); // 认证中间件只作用于这个 router
// 主路由
let router = Router::new()
.get("/", public_handler)
.nest("/admin", admin_router); // 挂载 admin 路由组
声明式中间件 (#[middleware])
Miko v0.7 引入了全新的声明式中间件宏 #[middleware],它允许你像编写普通处理函数一样编写中间件,而无需手动实现复杂的 tower::Layer 和 Service trait。
use miko::macros::middleware;
// 定义一个简单的日志中间件
// 参数注入:支持像 handler 一样注入 Config, State 等
#[middleware]
async fn simple_logger(#[config("app.name")] app_name: String) -> AppResult<Resp> {
println!("Request incoming to {}", app_name);
// _next: 下一个中间件或 handler
// _req: 当前请求
let resp = _next.run(_req).await?;
println!("Request completed");
Ok(resp)
}
// 应用中间件
#[get("/")]
#[layer(simple_logger())]
async fn hello() -> &'static str {
"Hello"
}
#[middleware] 宏会自动为你生成底层的 Layer 实现。在中间件函数中,你可以访问两个特殊变量:
_req: 当前的 HTTP 请求。_next: 下一层服务的执行器。
这是 Miko 推荐的编写自定义中间件的方式,因为它既保留了 Rust 的类型安全,又极大地降低了心智负担。
模块级中间件 (#[layer])
当使用 #[prefix] 组织模块化路由时,你可以直接在 mod 上使用 #[layer] 宏,将中间件应用于该模块内的所有路由。
use tower_http::timeout::TimeoutLayer;
use std::time::Duration;
#[prefix("/api/v1")]
#[layer(TimeoutLayer::new(Duration::from_secs(10)))] // 10秒超时
mod api_v1 {
#[get("/users")] // 这个路由会自动应用 10 秒超时
async fn list_users() { /* ... */ }
#[get("/posts")] // 这个路由也会自动应用 10 秒超时
async fn list_posts() { /* ... */ }
}
单个路由中间件 (#[layer])
你甚至可以将 #[layer] 直接应用在单个处理器函数上,实现最细粒度的控制。
use tower_http::limit::RateLimitLayer;
use std::time::Duration;
// 对这个特定的路由进行限流,每 5 秒最多 1 个请求
#[get("/sensitive_operation")]
#[layer(RateLimitLayer::new(1, Duration::from_secs(5)))]
async fn sensitive_operation() -> &'static str {
"Operation successful"
}
当在同一个目标上使用多个 #[layer] 时,它们的声明顺序是从上到下,但应用顺序是从内到外。也就是说,最靠近处理器函数的 #[layer] 会被最先应用(在洋葱的最里层)。
3. 常用的 tower-http 中间件
tower-http 提供了许多开箱即用的高质量中间件,这里列举几个常用的:
TraceLayer: 提供详细的请求/响应日志。CompressionLayer: 自动对响应体进行 Gzip 或 Brotli 压缩。CorsLayer: 处理跨域资源共享(CORS)问题。TimeoutLayer: 对请求处理设置超时。RateLimitLayer: 实现请求限流。SetSensitiveHeadersLayer: 防止敏感信息(如Authorization头)在日志中泄露。
4. 快速启用 CORS
对于开发环境,经常需要允许来自任何源的跨域请求。Miko 提供了一个便捷方法 cors_any() 来快速启用一个宽松的 CORS策略。
use miko::Router;
let mut router = Router::new();
// 允许任何来源、任何方法、任何头部的跨域请求
router.cors_any();
对于生产环境,你应该使用 tower_http::cors::CorsLayer 来配置更严格、更安全的 CORS 策略。
use tower_http::cors::{CorsLayer, Any};
use http::{Method, HeaderValue};
let cors_layer = CorsLayer::new()
.allow_origin("https://my-frontend.com".parse::<HeaderValue>().unwrap())
.allow_methods([Method::GET, Method::POST])
.allow_headers(Any);
let router = Router::new().with_layer(cors_layer);
总结
Miko 对 tower Layer 模型的深度集成为其带来了强大的中间件能力。通过 with_layer 方法和 #[layer] 宏,你可以在全局、路由组、模块、甚至单个路由等不同粒度上灵活地应用中间件,从而构建出既清晰又可维护的 Web 应用。善用 tower-http 提供的丰富中间件,可以让你事半功倍。
下一篇预告:Miko 框架系列(十):配置管理