贝利信息

fastapi 如何实现 StreamingResponse 的分块传输大文件

日期:2026-01-23 00:00 / 作者:舞夢輝影
StreamingResponse适合大文件传输,因其采用HTTP分块编码边读边发,避免内存溢出和延迟;需用生成器逐块yield字节流,禁用Nginx缓冲并设置正确headers。

StreamingResponse 为什么适合大文件传输

因为 StreamingResponse 不会把整个文件读进内存,而是边读边发,避免 OOM 和响应延迟。它底层用的是 HTTP chunked transfer encoding,客户端(比如浏览器或 curl)能边收边处理,对视频、日志、导出 CSV 等场景很实用。

但注意:FastAPI 默认不启用 gzip 压缩,且中间件(如 GZipMiddleware)可能干扰分块;Nginx 等反向代理默认会缓冲响应,必须显式关闭缓冲才能看到实时分块效果。

如何正确构造 StreamingResponse 返回文件流

核心是传一个可迭代对象(如生成器),每次 yield 一个 bytes 块。不能用 open(...).read(),否则全量加载就失去流的意义。

from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()

def file_stream(path: str):
    with open(path, "rb") as f:
        while chunk := f.read(8192):
            yield chunk

@app.get("/download")
def download_file():
    return StreamingResponse(
        file_stream("/path/to/big.zip"),
        media_type="application/zip",
        headers={"Content-Disposition": 'attachment; filename="big.zip"'}
    )

为什么 Nginx 会吞掉 chunk,怎么破

默认配置下,Nginx 会等整个响应结束才转发给客户端,导致“卡住几秒后突然下载完成”。这不是 FastAPI 的问题,而是反向代理的缓冲策略。

验证是否生效:用 curl -v http://your-domain/download,看响应头是否有 Transfer-Encoding: chunked,且 body 是分段打印的(不是一次性吐完)。

异步文件读取能否提升性能

不能直接用 asyncio.open()(标准库不支持),但可用 anyio.Pathaiopath 实现真正异步 IO。不过对单个大文件流来说,同步 read() + yield 已足够——瓶颈通常在磁盘或网络,不是 Python 线程阻塞。

真正需要异步的场景是:多个并发流共享同一文件句柄、或需在读取过程中穿插其他 await 操作(如权限校验、审计日志)。这时建议用 starlette.background.BackgroundTasks 或拆成独立任务,而不是强行套 async def + 同步 open

容易忽略的一点:如果文件路径来自用户输入,务必做路径净化(如 pathlib.Path(file_param).resolve().relative_to(allowed_root)),否则 ../ 可能导致任意文件读取。