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

使用Hyper构建Rust HTTP代理:请求Body复制与转发问题咨询

嘿,我刚好在Hyper上折腾过类似的代理场景,你的问题核心其实是Hyper的Request里的请求体是一次性消费的Stream——一旦你读取了它的内容,原请求体就没法再用来转发了,而且直接解构Request后重新构建确实会踩头部处理的坑。不过咱们有个简单直接的办法解决这个问题:先把请求体完整读取到内存里,转换成可克隆的Bytes类型,这样一个副本用来记录日志,另一个用来构建转发请求就行。

核心思路

Hyper的Body本质是一个异步流,它的所有权只能被消费一次。而hyper::body::to_bytes这个方法可以把整个请求体流收集到内存里,转换成Bytes——这是一个支持高效克隆的字节缓冲区,完美解决了复用的问题。

完整代码示例

下面是调整后的代理处理逻辑,包含请求体记录和转发:

use hyper::{Body, Client, Request, Response, Server, StatusCode};
use hyper::service::{make_service_fn, service_fn};
use std::convert::Infallible;

async fn handle_proxy_request(mut req: Request<Body>) -> Result<Response<Body>, Infallible> {
    // 第一步:读取请求体到内存,转为可克隆的Bytes
    let body_bytes = match hyper::body::to_bytes(req.body_mut()).await {
        Ok(bytes) => bytes,
        Err(err) => {
            eprintln!("读取请求体失败: {}", err);
            return Ok(Response::builder()
                .status(StatusCode::BAD_REQUEST)
                .body(Body::empty())
                .unwrap());
        }
    };

    // 第二步:记录请求内容和请求体
    println!("请求URI: {}", req.uri());
    println!("请求头部: {:?}", req.headers());
    println!("请求体内容: {}", String::from_utf8_lossy(&body_bytes));

    // 第三步:构建转发请求
    // 这里需要注意:我们用克隆的Bytes来创建新的请求体,避免原数据被消费
    let target_uri = format!("http://你的目标服务器地址{}", req.uri().path_and_query().unwrap());
    let forwarded_req = Request::builder()
        .uri(target_uri)
        .method(req.method().clone())
        .headers(req.headers().clone())
        .body(Body::from(body_bytes.clone())) // 克隆一份用来转发
        .unwrap();

    // 第四步:发送转发请求并返回响应
    let client = Client::new();
    match client.request(forwarded_req).await {
        Ok(resp) => Ok(resp),
        Err(err) => {
            eprintln!("转发请求失败: {}", err);
            Ok(Response::builder()
                .status(StatusCode::BAD_GATEWAY)
                .body(Body::empty())
                .unwrap())
        }
    }
}

#[tokio::main]
async fn main() {
    let addr = ([127, 0, 0, 1], 8080).into();

    let make_svc = make_service_fn(|_conn| async {
        Ok::<_, Infallible>(service_fn(handle_proxy_request))
    });

    let server = Server::bind(&addr).serve(make_svc);

    if let Err(e) = server.await {
        eprintln!("服务器启动失败: {}", e);
    }
}

关键细节说明

  1. req.body_mut()的使用:通过可变引用获取请求体,这样调用to_bytes后,原请求的body会被清空,但我们已经把内容存到body_bytes里了,后续可以重新设置或者直接构建新请求。
  2. Bytes的克隆:Bytes的克隆是零成本的(它内部用引用计数),所以不用担心内存开销。
  3. 头部处理:直接克隆原请求的headers,避免了解构后重新组装的麻烦,保证转发的请求头部和原请求一致。

如果你的代理需要处理超大请求(比如几百MB的文件上传),这种全量读取到内存的方式可能不合适,这时候可以考虑流式处理:自定义一个Stream,在读取每个chunk的时候同时记录日志,然后把chunk转发出去。不过对于小型代理来说,上面的方案已经足够好用了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 11:56:26