StreamingResponse适合大文件传输,因其采用HTTP分块编码边读边发,避免内存溢出和延迟;需用生成器逐块yield字节流,禁用Nginx缓冲并设置正确headers。

StreamingResponse 为什么适合大文件传输
因为 StreamingResponse 不会把整个文件读进内存,而是边读边发,避免 OOM 和响应延迟。它底层用的是 HTTP chunked transfer encoding,客户端(比如浏览器或 curl)能边收边处理,对视频、日志、导出 CSV 等场景很实用。
但注意:FastAPI 默认不启用 gzip 压缩,且中间件(如 GZipMiddleware)可能干扰分块;Nginx 等反向代理默认会缓冲响应,必须显式关闭缓冲才能看到实时分块效果。
如何正确构造 StreamingResponse 返回文件流
核心是传一个可迭代对象(如生成器),每次 yield 一个 bytes 块。不能用 open(...).read(),否则全量加载就失去流的意义。
- 用
open(file_path, "rb")配合.read(chunk_size)循环 yield,推荐chunk_size=8192(8KB)——太小增加 syscall 开销,太大削弱“流感” - 必须设置
media_type(如"application/octet-stream"),否则浏览器可能无法识别下载行为 - 建议加
headers={"Content-Disposition": 'attachment; filename="xxx.bin"'}触发下载而非内嵌预览 - 别在生成器里做耗时操作(如数据库查询、网络请求),否则阻塞整个流
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 的问题,而是反向代理的缓冲策略。
- 在 location 块中加
proxy_buffering off; - 同时禁用缓存相关头:
proxy_cache off;、proxy_http_version 1.1;、chunked_transfer_encoding on; - 如果用了
proxy_redirect或proxy_set_header,确保没覆盖Transfer-Encoding
验证是否生效:用 curl -v http://your-domain/download,看响应头是否有 Transfer-Encoding: chunked,且 body 是分段打印的(不是一次性吐完)。
异步文件读取能否提升性能
不能直接用 asyncio.open()(标准库不支持),但可用 anyio.Path 或 aiopath 实现真正异步 IO。不过对单个大文件流来说,同步 read() + yield 已足够——瓶颈通常在磁盘或网络,不是 Python 线程阻塞。
真正需要异步的场景是:多个并发流共享同一文件句柄、或需在读取过程中穿插其他 await 操作(如权限校验、审计日志)。这时建议用 starlette.background.BackgroundTasks 或拆成独立任务,而不是强行套 async def + 同步 open。
容易忽略的一点:如果文件路径来自用户输入,务必做路径净化(如 pathlib.Path(file_param).resolve().relative_to(allowed_root)),否则 ../ 可能导致任意文件读取。










