其他
在构建生产级 Rust HTTP API 时,使用…
适用于需要强类型、可预测中间件组合和生产级可观测性/关闭的 Rust HTTP 服务开发。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 48/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 55/100 ×0.15
适用场景
在构建生产级 Rust HTTP API通用
解决思路
- 构建最小服务器:定义 async handler 返回 `Json<Health>`,使用 `Router::new().route("/health", get(health))` 创建路由,用 `tokio::net::TcpListener::bind` 绑定地址,通过 `axum::serve(listener, app).await` 启动服务。
- 在 handler 中使用异步操作,避免阻塞 async runtime;不要使用 `std::thread::sleep` 或阻塞 I/O,需要延迟时使用 `tokio::time::sleep`。
- 使用 Router 和 handlers:将路由分组并嵌套,例如 `Router::new().nest("/api/v1", api)`,其中 `api` 包含 `/users` 等路由,handler 返回实现 `IntoResponse` 的类型。
- 使用 Extractors 在边界解析和验证请求:优先使用 `Path<T>`(类型化路径参数)、`Query<T>`(查询字符串)、`Json<T>`(JSON 请求体)、`State<T>`(共享状态)。例如 `async fn create_user(Json(body): Json<CreateUser>) -> Json<User>`。
- 如果 crate 同时作为库发布,隔离 HTTP 栈:在 `Cargo.toml` 中将 `axum` 和 `tower-http` 设为 `optional = true`,定义 `[features]` 如 `default = ["http-server"]`,`http-server = ["axum", "tower-http"]`,并设置 `[[bin]]` 的 `required-features = ["http-server"]`。这样库消费者不会拉入 HTTP 依赖,而二进制默认包含服务器。
- 共享状态:使用 `State<Arc<AppState>>`,保持状态不可变(`#[derive(Clone)]` 的 `AppState`),通过 `Router::with_state(state)` 注入。
- 结构化错误处理:定义 `AppError` 枚举,实现 `IntoResponse`,将错误变体映射到 `StatusCode` 和 JSON 错误体(如 `ErrorBody { error: msg }`)。不要在请求路径中使用 `unwrap()`。
- 中间件:使用 `tower-http` 的生产级层,如 `TraceLayer::new_for_http()`、`TimeoutLayer::new(Duration::from_secs(10))`、`CorsLayer::new().allow_origin(Any)`,用 `ServiceBuilder::new().layer(...).layer(...)` 组合,然后 `.layer(layers)` 应用到 Router。
- 优雅关闭:实现 `shutdown_signal` 异步函数,用 `tokio::select!` 同时监听 `tokio::signal::ctrl_c()` 和 Unix 上的 `tokio::signal::unix::signal(SignalKind::terminate())`。在 `main` 中调用 `axum::serve(listener, app).with_graceful_shutdown(shutdown_signal()).await`。
- 受管环境下的优雅关闭运维:在 systemd 中设置 `KillSignal=SIGTERM` 和 `TimeoutStopSec=120`(或更高),避免网络存储 fsync 被截断;在 launchd 中使用 `launchctl bootout`(发送 SIGTERM 并等待)而非 `kickstart -k`(SIGKILL);客户端实现指数退避(从 200ms 开始,上限 30s)以透明处理短暂重启。
- 测试路由:使用 `tower::ServiceExt` 的 `oneshot` 方法,无需打开 socket。例如 `app.oneshot(Request::builder().uri("/health").body(Body::empty()).unwrap()).await`,然后断言状态码。
- 框架选型决策:需要 Tower 中间件组合和类型化提取器时选 Axum;需要成熟生态和 actor 风格运行时模型时选 Actix Web;需要函数式过滤器和极简主义时选 Warp。
适用边界 · 注意事项
- 不适用于需要 Actix Web 的 actor 模型或 Warp 的函数式过滤器风格的项目。
- 不适用于无法使用 Tokio 异步运行时的环境。
- 不适用于不希望引入 Tower 中间件生态的项目。