You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 01:02:39