如何使用Rust的Actix Web高效传输大文件
用Actix-Web实现大文件流式传输解决浏览器崩溃问题
你的核心问题是未启用HTTP分块传输和范围请求支持,导致浏览器被迫一次性加载整个3.5GB文件到内存,最终崩溃。下面是具体的实现方案:
基础流式传输实现
替换NamedFile,改用标准库fs::File结合Actix-Web的流式响应体,让服务器分块发送文件数据,浏览器会逐块加载,不会占用过多内存:
use actix_web::{get, HttpRequest, HttpResponse, Result}; use actix_web::http::{header, ContentDisposition, DispositionType}; use actix_web::body::Body; use std::fs::File; use std::io::SeekFrom; use std::path::Path; const FILE_PATH: &str = "/path/to/my/zip/file/xyz.zip"; #[get("/api/v1/downloads/xyz.zip")] async fn downloader(req: HttpRequest) -> Result<HttpResponse> { let file_path = Path::new(FILE_PATH); let mut file = File::open(file_path)?; let file_size = file.metadata()?.len(); // 处理Content-Disposition头,触发浏览器下载 let content_disposition = ContentDisposition { disposition: DispositionType::Attachment, parameters: vec![], }; // 检查是否有Range请求头 if let Some(range_header) = req.headers().get(header::RANGE) { let range_str = range_header.to_str()?; // 解析Range头,格式为bytes=start-end let range = range_str.strip_prefix("bytes=").ok_or_else(|| { actix_web::error::ErrorBadRequest("Invalid Range header format") })?; let (start, end) = range.split_once('-').ok_or_else(|| { actix_web::error::ErrorBadRequest("Invalid Range header range") })?; let start: u64 = start.parse()?; let end: u64 = end.parse().unwrap_or(file_size - 1); if start >= file_size || end >= file_size || start > end { return Err(actix_web::error::ErrorRequestedRangeNotSatisfiable("Invalid range")); } // 定位文件到起始位置 file.seek(SeekFrom::Start(start))?; let chunk_size = end - start + 1; // 返回206 Partial Content响应 Ok(HttpResponse::PartialContent() .insert_header(header::CONTENT_TYPE, "application/zip") .insert_header(content_disposition) .insert_header(header::ACCEPT_RANGES, "bytes") .insert_header(header::CONTENT_RANGE, format!("bytes {}-{}/{}", start, end, file_size)) .insert_header(header::CONTENT_LENGTH, chunk_size) .body(Body::from_stream(file.take(chunk_size)))) } else { // 返回200 OK,流式传输整个文件 Ok(HttpResponse::Ok() .insert_header(header::CONTENT_TYPE, "application/zip") .insert_header(content_disposition) .insert_header(header::ACCEPT_RANGES, "bytes") .insert_header(header::CONTENT_LENGTH, file_size) .body(Body::from_stream(file))) } }
关键改进点
- 流式响应体:用
Body::from_stream(file)将文件作为流发送,Actix-Web会自动分块传输,不会一次性读取整个文件到内存。 - 范围请求支持:处理
Range头,实现断点续传,浏览器可以分块请求文件的不同部分,进一步降低内存占用。 - 正确的HTTP头:设置
Accept-Ranges: bytes告知浏览器支持分块下载,Content-Length让浏览器知道文件总大小,Content-Disposition触发下载而非直接打开。
额外优化建议
- 调整分块大小:可以通过
file.take(chunk_size)手动指定分块大小(比如64KB或1MB),平衡传输效率和内存占用。 - 前端配合:使用标准的
<a href="/api/v1/downloads/xyz.zip" download>标签触发下载,浏览器会自动处理流式传输,无需额外JS逻辑。 - 错误处理:增加文件不存在、权限不足等场景的错误捕获,返回合适的HTTP状态码。
内容的提问来源于stack exchange,提问作者OddlyGhost
相关产品推荐
相关产品推荐

