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
相关产品推荐
相关产品推荐

