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

Axum中间件记录响应体时遭遇类型不匹配问题求助

问题:Axum中间件记录请求/响应体时的类型不匹配错误

我想实现一个记录HTTP请求和响应体的Axum中间件,参考官方示例编写后遇到编译错误,提示类型不匹配:

error[E0308]: mismatched types
   --> src\core.rs:302:24
    |
294 | async fn print_request_response<B>(
    |                                 - this type parameter
...
302 |     let res = next.run(req).await;
    |                    --- ^^^ expected type parameter `B`, found struct `Body`
    |                    |
    |                    arguments to this function are incorrect
    |
    = note: expected struct `hyper::Request<B>`
               found struct `hyper::Request<Body>`

尝试调整类型参数和请求体构造方式都没解决,只要能实现日志功能即可,以下是最小复现代码:

Cargo.toml

[package]
name = "test"
version = "0.1.0"
edition = "2021"

[dependencies]
axum = { version = "0.6.18", features = ["http2"] }
hyper = { version = "0.14", features = ["full"] }
tokio = { version = "1.0", features = ["full"] }
tower = { version = "0.4", features = ["util", "filter"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

main.rs

use std::net::SocketAddr;
use axum::{
    body::{Body, Bytes},
    http::StatusCode,
    middleware::{self, Next},
    response::{IntoResponse, Response},
    routing::post,
    Router,
};
use hyper::Request;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/", post(|| async move { "Hello from `POST /`" }))
        .layer(middleware::from_fn(print_request_response));

    let addr = SocketAddr::from(([0, 0, 0, 0], 8080));
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

async fn print_request_response<B>(
    req: Request<B>,
    next: Next<B>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    let (parts, body) = req.into_parts();
    let bytes = buffer_and_print("request", body).await?;
    let req = Request::from_parts(parts, Body::from(bytes));

    let res = next.run(req).await;

    let (parts, body) = res.into_parts();
    let bytes = buffer_and_print("response", body).await?;
    let res = Response::from_parts(parts, Body::from(bytes));

    Ok(res)
}

async fn buffer_and_print<B>(direction: &str, body: B) -> Result<Bytes, (StatusCode, String)>
{
    let bytes = match hyper::body::to_bytes(body).await {
        Ok(bytes) => bytes,
        Err(err) => {
            return Err((
                StatusCode::BAD_REQUEST,
                format!("failed to read {} body: {}", direction, err),
            ));
        }
    };

    if let Ok(body) = std::str::from_utf8(&bytes) {
        tracing::debug!("{} body = {:?}", direction, body);
    }

    Ok(bytes)
}

解决方案

问题根源

中间件定义了泛型参数B,要求传入的请求体类型和Next<B>期望的类型完全一致,但读取原请求体后重新构造了Request<Body>,和原类型B不匹配,导致编译错误。

修复方案

Axum默认使用axum::body::Body作为请求体类型,直接指定中间件使用Body类型即可解决类型不匹配问题,同时给buffer_and_print添加正确的HttpBody约束,确保能处理合法的请求/响应体。

修改后的完整代码

use std::net::SocketAddr;
use axum::{
    body::{Body, Bytes},
    http::StatusCode,
    middleware::{self, Next},
    response::{IntoResponse, Response},
    routing::post,
    Router,
};
use hyper::{Request, body::HttpBody};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

#[tokio::main]
async fn main() {
    // 初始化tracing日志,确保能输出debug级别的日志
    tracing_subscriber::registry()
        .with(tracing_subscriber::EnvFilter::new("debug"))
        .with(tracing_subscriber::fmt::layer())
        .init();

    let app = Router::new()
        .route("/", post(|| async move { "Hello from `POST /`" }))
        .layer(middleware::from_fn(print_request_response));

    let addr = SocketAddr::from(([0, 0, 0, 0], 8080));
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

async fn print_request_response(
    req: Request<Body>,
    next: Next<Body>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    let (parts, body) = req.into_parts();
    let bytes = buffer_and_print("request", body).await?;
    // 构造和Next<Body>匹配的Request<Body>
    let req = Request::from_parts(parts, Body::from(bytes));

    let res = next.run(req).await;

    let (parts, body) = res.into_parts();
    let bytes = buffer_and_print("response", body).await?;
    let res = Response::from_parts(parts, Body::from(bytes));

    Ok(res)
}

async fn buffer_and_print<B>(direction: &str, body: B) -> Result<Bytes, (StatusCode, String)>
where
    B: HttpBody + Send + 'static,
    B::Error: std::fmt::Display,
{
    let bytes = match hyper::body::to_bytes(body).await {
        Ok(bytes) => bytes,
        Err(err) => {
            return Err((
                StatusCode::BAD_REQUEST,
                format!("failed to read {} body: {}", direction, err),
            ));
        }
    };

    if let Ok(body) = std::str::from_utf8(&bytes) {
        tracing::debug!("{} body = {:?}", direction, body);
    }

    Ok(bytes)
}

关键修改点

  1. 移除中间件的泛型参数B,直接使用Request<Body>和Next<Body>,确保类型完全匹配。
  2. 给buffer_and_print添加HttpBody相关约束,保证函数能处理任何符合Hyper规范的请求/响应体。
  3. 初始化tracing日志系统,确保能看到debug级别的请求/响应体日志。

内容的提问来源于stack exchange,提问作者Tennie

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 20:35:42