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

基于axum 0.7与reqwest 0.12的HTTP1代理类型不匹配问题求解

解决Axum 0.7.5与Reqwest 0.12的代理类型不匹配问题

问题背景

尝试用Axum 0.7.5和Reqwest 0.12构建HTTP代理时,出现类型不匹配错误,核心代码如下:

#[tokio::main]
async fn main() {
  let app = Router::new()
    .route("/", get(handler))
    .route("/custom/*key", get(proxy).post(proxy));

  // ...(省略服务器启动代码)
}

pub async fn proxy(mut req: Request<Body>) -> Result<Response<Body>, StatusCode> {
  let path_query = req
    .uri()
    .path_and_query()
    .map_or_else(|| req.uri().path(), |v| v.as_str());

  let uri = format!("http://localhost:9000/{}", path_query);

  *req.uri_mut() = Uri::try_from(uri).unwrap();

  let http_client = reqwest::Client::new();

  let res = http_client.execute(req).await.unwrap();

  if res.status().is_server_error() {
    return Err(StatusCode::INTERNAL_SERVER_ERROR);
  }

  Ok(res)
}

错误信息

编译时出现两个类型不匹配错误:

第一个错误:

error[E0308]: mismatched types
     |
35   |     let res = http_client.execute(req).await.unwrap();
     |                           ------- ^^^ expected `Request`, found `Request<Incoming>`
     |                           |
     |                           arguments to this method are incorrect
     |
     = note: `Request<Incoming>` and `reqwest::Request` have similar names, but are actually distinct types
note: `Request<Incoming>` is defined in crate `http`
    --> C:\Users\Fred\.cargo\registry\src\index.crates.io-6f17d22bba15001f\http-1.1.0\src\request.rs:158:1
     |
158  | pub struct Request<T> {
     | ^^^^^^^^^^^^^^^^^^^^^
note: `reqwest::Request` is defined in crate `reqwest`
    --> C:\Users\Fred\.cargo\registry\src\index.crates.io-6f17d22bba15001f\reqwest-0.12.4\src\async_impl\request.rs:22:1
     |
22   | pub struct Request {
     | ^^^^^^^^^^^^^^^^^^

第二个错误:

error[E0308]: mismatched types
    |
43  |     Ok(res)
    |     -- ^^^ expected `Response<Incoming>`, found `Response`
    |     |
    |     arguments to this enum variant are incorrect
    |
     = note: `reqwest::Response` and `Response<Incoming>` have similar names, but are actually distinct types
note: `reqwest::Response` is defined in crate `reqwest`
   --> C:\Users\Fred\.cargo\registry\src\index.crates.io-6f17d22bba15001f\reqwest-0.12.4\src\async_impl\response.rs:29:1

问题原因

Axum使用的是http::Request<axum::body::Body>(即Request<Incoming>),而Reqwest的execute方法接收的是reqwest::Request;同理,Reqwest返回的reqwest::Response也无法直接转换成Axum需要的http::Response<axum::body::Body>。两者虽基于同一http crate,但Reqwest对请求/响应结构体做了封装,不能直接互换。

解决方案

手动完成两种请求/响应类型的转换,修改后的proxy函数如下:

use axum::{
    body::{Body, Bytes},
    http::{Request, Response, StatusCode, Uri},
    routing::{get, post},
    Router,
};
use reqwest::{Client, IntoUrl};
use std::convert::TryInto;

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/", get(handler))
        .route("/custom/*key", get(proxy).post(proxy));

    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

async fn handler() -> &'static str {
    "Hello from root!"
}

pub async fn proxy(req: Request<Body>) -> Result<Response<Body>, StatusCode> {
    // 提取路径和查询参数
    let path_query = req
        .uri()
        .path_and_query()
        .map_or_else(|| req.uri().path(), |v| v.as_str());

    // 构造目标URI
    let target_uri = format!("http://localhost:9000/{}", path_query)
        .try_into()
        .map_err(|_| StatusCode::BAD_REQUEST)?;

    // 全局初始化客户端更优,这里为示例简洁性直接创建
    let client = Client::new();

    // 转换Axum请求为Reqwest请求构建器
    let mut req_builder = client.request(req.method().clone(), target_uri);
    for (name, value) in req.headers() {
        req_builder = req_builder.header(name, value);
    }

    // 读取并转换请求体
    let body_bytes = match req.into_body().collect().await {
        Ok(collected) => collected.to_bytes(),
        Err(_) => return Err(StatusCode::BAD_REQUEST),
    };

    // 发送请求
    let res = req_builder.body(body_bytes)
        .send()
        .await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    if res.status().is_server_error() {
        return Err(StatusCode::INTERNAL_SERVER_ERROR);
    }

    // 转换Reqwest响应为Axum响应
    let mut axum_res = Response::builder().status(res.status());
    for (name, value) in res.headers() {
        axum_res = axum_res.header(name, value);
    }

    let res_body = res.bytes().await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    axum_res.body(Body::from(res_body))
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
}

关键修改点说明

  1. 请求转换:拆解Axum请求的方法、URI、头部和体,用Reqwest的RequestBuilder重新构建请求。
  2. 响应转换:提取Reqwest响应的状态码、头部和体,构建Axum兼容的响应结构。
  3. 性能优化:建议将reqwest::Client全局初始化(比如在main中创建后传入代理函数),避免每次请求重复创建客户端。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 21:51:22