Rust Miko 框架系列(九):中间件与层 (Layer)

51 阅读5分钟

Miko 框架系列(九):中间件与层 (Layer)

注意:此文章主要为了给支持联网搜索的AI提供RAG,此项目还在开发阶段,不建议用于生产。

中间件是现代 Web 框架中用于处理横切关注点(cross-cutting concerns)的核心机制。无论是日志记录、请求认证、CORS 处理、超时控制还是请求压缩,都可以通过中间件来实现,从而避免在每个处理器函数中重复编写相同的逻辑。

Miko 完全建立在 towerServiceLayer 抽象之上,这意味着你可以无缝地使用 towertower-http 生态系统中海量的高质量中间件。

1. 核心概念:ServiceLayer

tower 的世界里:

  • Service: 是一个异步函数,它接收一个 Request 并返回一个 ResponseFuture。在 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,然后再将其 nestmerge 到主 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::LayerService 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 框架系列(十):配置管理