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

Rust Actix-Web请求体验证中间件触发空值解包恐慌

Actix-Web 请求体验证中间件 Panic 问题排查

问题背景

在Rust Actix-Web项目中实现请求体验证中间件,运行测试时触发Option::unwrap() panic,错误指向actix-web依赖的request.rs文件。

中间件及辅助函数代码

pub async fn validation_middleware<B>(
    req: ServiceRequest,
    next: Next<B>,
) -> Result<ServiceResponse<actix_web::body::BoxBody>, Error>
where
    B: MessageBody + 'static,
{
    
    let (http_req, mut payload) = req.into_parts();
    let http_req_clone = http_req.clone();
    let body_bytes = payload_to_bytes(payload).await.unwrap();
    let bb_clone = body_bytes.clone();
    let body_string = String::from_utf8_lossy(&body_bytes).to_string();
    let api_request: ApiRequest = serde_json::from_str(&body_string).unwrap();
    if api_request.user_id.is_empty() || api_request.url.is_empty() {
        return Ok(ServiceResponse::new(http_req, actix_web::HttpResponse::BadRequest().finish()));
    }
    
    let payload = bytes_to_payload(bb_clone);

    let new_req = ServiceRequest::from_parts(http_req_clone, payload);
    let res = next.call(new_req).await?;
    Ok(res.map_into_boxed_body())
}
 
async fn payload_to_bytes(mut payload: actix_web::dev::Payload) -> Result<Bytes, actix_web::Error> {
    let mut body = BytesMut::new(); // Accumulator

    while let Some(chunk) = payload.next().await {
        let chunk = chunk?; // Handle errors reading the payload
        body.extend_from_slice(&chunk); // Accumulate the bytes
    }

    Ok(body.freeze()) // Convert BytesMut into Bytes
}

fn bytes_to_payload(data: Bytes) -> actix_web::dev::Payload {
    let stream = futures_util::stream::once(async move { Ok::<Bytes, actix_web::error::PayloadError>(data) });
    actix_web::dev::Payload::from(Box::pin(stream) as Pin<Box<dyn Stream<Item = Result<Bytes, actix_web::error::PayloadError>> + 'static>>)   
}

测试用例代码

mod tests {
    use super::*;
    use actix_web::{test, web, App, HttpResponse};
    use serde_json::json;

    #[tokio::test]
    async fn test_validation_middleware() {
        let mut app = test::init_service(
            App::new()
                .wrap(from_fn(validation_middleware))
                .service(web::resource("/").to(|| async { HttpResponse::Ok() })),
        )
        .await;

        let req = test::TestRequest::post()
            .uri("/")
            .set_json(&json!({"user_id": "123", "url": "http://example.com"}))
            .to_request();
        let res = test::call_service(&mut app, req).await;
        assert_eq!(res.status(), 200);    
    }
}

错误信息

thread 'middleware::validation::tests::test_validation_middleware' panicked at /Users/princeton/.cargo/registry/src/index.crates.io-6f17d22bba15001f/actix-web-4.9.0/src/request.rs:177:43:
called `Option::unwrap()` on a `None` value

问题原因及修复方案

1. ServiceRequest::from_parts的误用

HttpRequest内部包含部分只能被消费一次的状态(如请求扩展数据),你同时持有http_req和http_req_clone两个实例,当用http_req构建错误响应后,http_req_clone的内部必要字段已变为None,后续用它构建新ServiceRequest时触发panic。

修复:移除http_req_clone,验证通过后直接用原始的http_req构建新请求:

// 移除http_req_clone的定义
let (http_req, mut payload) = req.into_parts();
let body_bytes = payload_to_bytes(payload).await.unwrap();
let bb_clone = body_bytes.clone();
// ... 验证逻辑 ...

let payload = bytes_to_payload(bb_clone);
// 使用原始http_req构建新请求
let new_req = ServiceRequest::from_parts(http_req, payload);

2. 未处理的unwrap()风险

代码中多处使用unwrap(),包括请求体读取、JSON反序列化,这些操作在出错时会直接panic,而非返回合法HTTP错误响应。

修复:替换unwrap()为错误处理逻辑,返回对应Actix错误:

// 处理请求体读取错误
let body_bytes = payload_to_bytes(payload).await.map_err(|e| {
    actix_web::error::ErrorBadRequest(format!("Failed to read request body: {}", e))
})?;

// 处理JSON反序列化错误
let api_request: ApiRequest = serde_json::from_str(&body_string).map_err(|e| {
    actix_web::error::ErrorBadRequest(format!("Invalid JSON payload: {}", e))
})?;

3. 错误响应构建问题

验证失败时,直接用拆分出的http_req构建ServiceResponse可能导致内部状态缺失,需确保响应体正确初始化。

修复:完善错误响应的内容:

if api_request.user_id.is_empty() || api_request.url.is_empty() {
    return Ok(ServiceResponse::new(
        http_req,
        actix_web::HttpResponse::BadRequest()
            .body("user_id and url cannot be empty")
            .map_into_boxed_body(),
    ));
}

4. Payload重构简化

bytes_to_payload可以借助Actix-Web的MessageBody trait简化实现:

fn bytes_to_payload(data: Bytes) -> actix_web::dev::Payload {
    use actix_web::body::MessageBody;
    actix_web::dev::Payload::from(data.into_body())
}

修复后的完整中间件代码

pub async fn validation_middleware<B>(
    req: ServiceRequest,
    next: Next<B>,
) -> Result<ServiceResponse<actix_web::body::BoxBody>, Error>
where
    B: MessageBody + 'static,
{
    let (http_req, mut payload) = req.into_parts();
    
    // 读取请求体并处理错误
    let body_bytes = payload_to_bytes(payload).await.map_err(|e| {
        actix_web::error::ErrorBadRequest(format!("Failed to read request body: {}", e))
    })?;
    
    let bb_clone = body_bytes.clone();
    let body_string = String::from_utf8_lossy(&body_bytes).to_string();
    
    // 反序列化JSON并处理错误
    let api_request: ApiRequest = serde_json::from_str(&body_string).map_err(|e| {
        actix_web::error::ErrorBadRequest(format!("Invalid JSON payload: {}", e))
    })?;
    
    // 验证逻辑
    if api_request.user_id.is_empty() || api_request.url.is_empty() {
        return Ok(ServiceResponse::new(
            http_req,
            actix_web::HttpResponse::BadRequest()
                .body("user_id and url cannot be empty")
                .map_into_boxed_body(),
        ));
    }
    
    // 重构payload并继续处理请求
    let payload = bytes_to_payload(bb_clone);
    let new_req = ServiceRequest::from_parts(http_req, payload);
    
    let res = next.call(new_req).await?;
    Ok(res.map_into_boxed_body())
}
 
async fn payload_to_bytes(mut payload: actix_web::dev::Payload) -> Result<Bytes, actix_web::Error> {
    let mut body = BytesMut::new();
    while let Some(chunk) = payload.next().await {
        let chunk = chunk?;
        body.extend_from_slice(&chunk);
    }
    Ok(body.freeze())
}

fn bytes_to_payload(data: Bytes) -> actix_web::dev::Payload {
    use actix_web::body::MessageBody;
    actix_web::dev::Payload::from(data.into_body())
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 00:13:18