如何在Axum中通过tower::Layer让自定义IntoResponse获取请求头
可以用
tower::Layer实现需求 当然可以通过tower::Layer实现这个功能,核心思路是让服务函数返回一个**不实现IntoResponse**的自定义类型,然后通过自定义的tower::Layer对应的Service拦截这个返回值,从请求中提取Accept头并完成响应转换,完全不需要把请求头传入自定义类型的构造函数。
具体实现步骤
1. 定义自定义响应载体类型
先创建一个不实现IntoResponse的类型,用来承载业务数据:
// 自定义载体,仅用来传递业务数据,不实现IntoResponse struct NegotiatedResponse<T>(T); // 示例业务数据类型(需实现Serialize用于JSON序列化) #[derive(Debug, serde::Serialize)] struct User { id: u64, name: String, }
2. 实现自定义tower::Layer和Service
通过tower::Layer创建一个中间件,其对应的Service会负责从请求中提取Accept头,并将NegotiatedResponse转换为符合要求的HTTP响应:
use axum::{ http::{Request, Response}, response::{IntoResponse, Json, Html}, extract::Accept, }; use tower::{Layer, Service}; use std::{ future::Future, task::{Context, Poll}, pin::Pin, }; // 空Layer,无需额外状态 struct ContentNegotiationLayer; impl<S> Layer<S> for ContentNegotiationLayer { type Service = ContentNegotiationService<S>; fn layer(&self, inner: S) -> Self::Service { ContentNegotiationService { inner } } } // 处理响应协商的Service struct ContentNegotiationService<S> { inner: S, } impl<S, ReqBody> Service<Request<ReqBody>> for ContentNegotiationService<S> where S: Service<Request<ReqBody>, Response = NegotiatedResponse<User>>, S::Future: Future<Output = Result<NegotiatedResponse<User>, S::Error>> + Send + 'static, S::Error: Send + 'static, { type Response = Response; type Error = S::Error; type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { self.inner.poll_ready(cx) } fn call(&mut self, req: Request<ReqBody>) -> Self::Future { // 用Axum的Accept提取器解析请求头,自动处理权重规则 let accept = Accept::from_headers(req.headers()); let inner_future = self.inner.call(req); Box::pin(async move { // 取出业务数据 let NegotiatedResponse(user) = inner_future.await?; // 根据Accept头协商响应格式 let response = if accept.prefer("application/json") { Json(user).into_response() } else if accept.prefer("text/html") { Html(format!("<div>用户ID: {},用户名: {}</div>", user.id, user.name)).into_response() } else { // 默认返回JSON格式 Json(user).into_response() }; Ok(response) }) } }
3. 在路由中应用Layer
将自定义Layer绑定到路由上,服务函数直接返回NegotiatedResponse即可:
use axum::Router; use serde::Serialize; async fn get_user() -> NegotiatedResponse<User> { NegotiatedResponse(User { id: 1, name: "Alice".to_string(), }) } #[tokio::main] async fn main() { let app = Router::new() .route("/user", axum::routing::get(get_user)) .layer(ContentNegotiationLayer); // 应用协商Layer axum::Server::bind(&"127.0.0.1:3000".parse().unwrap()) .serve(app.into_make_service()) .await .unwrap(); }
关键优势
- 无缝性:服务函数无需关心
Accept头,只需要返回业务数据的载体类型,中间件自动处理响应转换。 - 灵活性:可以在Layer中扩展更复杂的内容协商逻辑(比如处理带权重的
Accept头、支持更多响应格式)。 - 合规性:完全适配tower和Axum的中间件体系,不会违背Axum对服务返回值的要求。
内容的提问来源于stack exchange,提问作者wallefan
相关产品推荐
相关产品推荐

