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

Rust Actix Web同时提取JSON时Bytes提取器返回空字节的问题

Actix Web同时提取Json和Bytes时Bytes为空的解决办法

问题原因

Actix Web的请求体是一次性可读流:当你通过web::Json提取器完成反序列化时,整个请求体的字节已经被读取并消耗完毕,后续再尝试提取web::Bytes自然只能得到空数据。


解决方案

方法一:先提取Bytes,手动反序列化JSON

直接先获取完整的请求体字节,再自行用serde_json完成反序列化,同时保留原始字节数据:

use actix_web::*;
use serde_json::Value;

#[tokio::main]
async fn main() -> std::io::Result<()> {
    use std::net::{IpAddr, Ipv4Addr, SocketAddr};

    let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 3000);
    HttpServer::new(move || App::new().service(example))
        .bind(&addr)?
        .run()
        .await
}

#[post("/")]
pub async fn example(
    payload_bytes: web::Bytes,
) -> Result<HttpResponse, Error> {
    // 手动反序列化JSON,失败则返回BadRequest错误
    let json_payload: Value = serde_json::from_slice(&payload_bytes)
        .map_err(|e| error::ErrorBadRequest(format!("无效JSON格式: {}", e)))?;
    
    println!("请求体字节长度: {}", payload_bytes.len());
    println!("解析后的JSON: {}", json_payload);
    
    Ok(HttpResponse::Ok().into())
}

方法二:自定义提取器(复用场景)

如果需要在多个路由中复用“同时获取原始字节和结构化JSON”的逻辑,可以自定义提取器封装这一流程:

use actix_web::{dev::Payload, error, web, Error, FromRequest, HttpResponse};
use serde::de::DeserializeOwned;
use serde_json::Value;
use futures_util::StreamExt;

// 自定义提取器,同时保存原始字节和反序列化后的JSON
#[derive(Debug)]
struct JsonWithBytes<T> {
    pub raw_bytes: web::Bytes,
    pub parsed_json: T,
}

impl<T: DeserializeOwned> FromRequest for JsonWithBytes<T> {
    type Error = Error;
    type Future = std::pin::Pin<Box<dyn std::future::Future<Output = Result<Self, Self::Error>>>>;

    fn from_request(req: &web::HttpRequest, payload: &mut Payload) -> Self::Future {
        let mut payload = payload.take();
        Box::pin(async move {
            // 读取全部请求体字节
            let mut bytes_buf = web::BytesMut::new();
            while let Some(chunk) = payload.next().await {
                bytes_buf.extend_from_slice(&chunk?);
            }
            let raw_bytes = bytes_buf.freeze();

            // 反序列化JSON
            let parsed_json = serde_json::from_slice(&raw_bytes)
                .map_err(|e| error::ErrorBadRequest(format!("无效JSON格式: {}", e)))?;

            Ok(JsonWithBytes { raw_bytes, parsed_json })
        })
    }
}

// 使用自定义提取器的路由
#[post("/")]
pub async fn example(
    data: JsonWithBytes<Value>,
) -> Result<HttpResponse, Error> {
    println!("请求体字节长度: {}", data.raw_bytes.len());
    println!("解析后的JSON: {}", data.parsed_json);
    
    Ok(HttpResponse::Ok().into())
}

#[tokio::main]
async fn main() -> std::io::Result<()> {
    use std::net::{IpAddr, Ipv4Addr, SocketAddr};

    let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 3000);
    HttpServer::new(move || App::new().service(example))
        .bind(&addr)?
        .run()
        .await
}

测试命令

执行以下命令验证效果:

curl -X POST -H "Content-Type: application/json" -d '{"message":"hello world"}' http://localhost:3000

内容的提问来源于stack exchange,提问作者pat.io

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 09:52:52