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

如何使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 00:39:59