Rust Actix Web分API设置文件上传大小限制(无需接收完整文件)
针对Actix Web不同接口实现独立文件大小限制(无需接收完整文件)
要实现不同接口的差异化文件大小限制,同时在文件接收过程中实时校验大小、超限时立即中断请求,可以从路由级全局请求限制和单个文件精准限制两个层面入手,以下是具体实现方案:
一、路由级全局Multipart请求总大小限制
如果只需限制整个Multipart请求的总大小(包含所有文件和表单字段),可以通过给单个路由单独配置MultipartFormConfig来覆盖全局设置,Actix Web会在接收请求的过程中实时校验总大小,超限时直接返回413错误,无需接收完整内容。
1. 修改全局配置(保留通用设置)
先移除全局的total_limit,只保留内存限制等通用配置:
#[actix_web::main] async fn main() -> std::io::Result<()> { rust_i18n::set_locale("zh-CN"); log4rs::init_file("log4rs.yaml", Default::default()).unwrap(); let port: u16 = get_app_config("texhub.port").parse().unwrap(); let address = ("0.0.0.0", port); consume_sys_events(); HttpServer::new(|| { App::new() // 全局通用配置:内存中最多存2MB,超过写入临时文件 .app_data(MultipartFormConfig::default() .memory_limit(2097152) .error_handler(handle_multipart_error)) // Zip项目上传接口:总大小限制100MB .service( web::resource("/upload/full-proj") .app_data(MultipartFormConfig::default() .total_limit(104857600) // 100MB = 100*1024*1024 .memory_limit(2097152) .error_handler(handle_multipart_error)) .route(web::post().to(upload_full_proj)) ) // 图片上传接口:总大小限制2MB .service( web::resource("/upload/image") .app_data(MultipartFormConfig::default() .total_limit(2097152) // 2MB .memory_limit(2097152) .error_handler(handle_multipart_error)) .route(web::post().to(upload_image)) ) }) .workers(3) .bind(address)? .run() .await }
2. 原结构体和接口无需修改
你的FullProjUpload结构体和upload_full_proj接口可以保持原样,Actix会自动使用路由级的配置进行校验。
二、单个文件精准大小限制(支持实时中断)
如果需要针对单个文件设置大小限制(而非整个请求),可以自定义字段类型,在文件流接收过程中实时计算大小,一旦超过限制立即中断请求。
1. 自定义带大小限制的临时文件类型
use actix_multipart::form::{MultipartField, MultipartFieldReader, TempFile}; use actix_web::error::ErrorPayloadTooLarge; use async_trait::async_trait; use std::io::Write; // 定义不同场景的大小常量 const MAX_ZIP_FILE_SIZE: u64 = 100 * 1024 * 1024; // 100MB const MAX_IMAGE_FILE_SIZE: u64 = 2 * 1024 * 1024; // 2MB // 通用带大小限制的临时文件封装 #[derive(Debug)] struct LimitedTempFile { inner: TempFile, size: u64, } impl LimitedTempFile { async fn from_field(mut field: MultipartField, max_size: u64) -> Result<Self, actix_web::Error> { // 先检查Content-Length头,存在且超限直接返回错误 if let Some(content_length) = field.content_length() { if content_length > max_size { return Err(ErrorPayloadTooLarge(format!( "文件大小超过{}MB", max_size / 1024 / 1024 )).into()); } } // 分块接收并计算大小,超限立即中断 let mut tempfile = tempfile::NamedTempFile::new()?; let mut current_size = 0u64; while let Some(chunk) = field.next().await? { current_size += chunk.len() as u64; if current_size > max_size { return Err(ErrorPayloadTooLarge(format!( "文件大小超过{}MB", max_size / 1024 / 1024 )).into()); } tempfile.write_all(&chunk)?; } tempfile.flush()?; // 转换为Actix的TempFile类型 let inner = TempFile::from_tempfile(tempfile, field.content_disposition().clone()); Ok(Self { inner, size: current_size }) } } // 针对Zip文件的专用类型 #[derive(Debug, MultipartForm)] pub struct ZipTempFile(LimitedTempFile); #[async_trait] impl MultipartFieldReader for ZipTempFile { async fn read(field: MultipartField) -> Result<Self, actix_web::Error> { let limited = LimitedTempFile::from_field(field, MAX_ZIP_FILE_SIZE).await?; Ok(Self(limited)) } } // 针对图片的专用类型 #[derive(Debug, MultipartForm)] pub struct ImageTempFile(LimitedTempFile); #[async_trait] impl MultipartFieldReader for ImageTempFile { async fn read(field: MultipartField) -> Result<Self, actix_web::Error> { let limited = LimitedTempFile::from_field(field, MAX_IMAGE_FILE_SIZE).await?; Ok(Self(limited)) } }
2. 修改结构体使用自定义类型
更新FullProjUpload为Zip文件专用类型:
use actix_multipart::form::{MultipartForm, text::Text}; #[derive(Debug, MultipartForm)] pub struct FullProjUpload { #[multipart(rename = "file")] pub files: Vec<ZipTempFile>, // 替换为自定义的Zip专用类型 pub project_id: Text<String>, pub parent: Text<String> }
图片上传的结构体同理:
#[derive(Debug, MultipartForm)] pub struct ImageUpload { #[multipart(rename = "image")] pub image: ImageTempFile, // 替换为图片专用类型 pub album_id: Text<String> }
3. 在接口中使用
接口代码无需大幅修改,只需在处理文件时通过into_inner()获取原始TempFile:
async fn upload_full_proj( MultipartForm(form): MultipartForm<FullProjUpload>, login_user_info: LoginUserInfo, ) -> HttpResponse { // 转换为原始TempFile供后续保存 let files: Vec<TempFile> = form.files.into_iter().map(|zf| zf.0.inner).collect(); let modified_form = FullProjUpload { files, project_id: form.project_id, parent: form.parent }; save_full_proj(modified_form, &login_user_info).await }
核心优势
- 无需接收完整文件:无论是通过路由级总限制还是自定义字段类型,都会在接收过程中实时校验,超限立即中断请求,节省带宽和服务器资源;
- 差异化配置:不同接口可以设置完全独立的大小限制,互不干扰;
- 兼容两种传输方式:同时支持
Content-Length头校验和分块编码(Transfer-Encoding: chunked)的实时校验。
内容的提问来源于stack exchange,提问作者Dolphin
相关产品推荐
相关产品推荐

