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)),否则 ../ 可能导致任意文件读取。