Flask流式响应需用Response包装生成器并设direct_passthrough=True禁用Werkzeug缓冲,Content-Type和Content-Disposition头必设,文本用StringIO+csv.writer分块yield,二进制用BytesIO和固定大小bytes chunk,Nginx需关proxy_buffering。

Flask 中用 Response 实现流式响应的关键点
Flask 本身不支持“边生成边发送”的自动 chunked 编码,必须手动构造 Response 并设置 content_type 和 headers。核心是传入一个可迭代对象(如生成器),且不能提前计算长度——否则 Flask 会尝试读取全部内容进内存,失去流式意义。
常见错误是直接返回 jsonify() 或字符串拼接结果,这会让整个响应体被缓存;还有人误用 stream_with_context 却没关掉 Werkzeug 的中间件缓冲,导致客户端收不到实时数据。
- 必须禁用 Werkzeug 的
Response自动缓冲:在构造Response时显式设direct_passthrough=True -
Content-Type应设为"application/octet-stream"或具体 MIME 类型(如"text/csv"),避免浏览器试图解析渲染 - 务必设置
Content-Dispositionheader 指定文件名和下载行为,否则可能被当成内联内容打开 - 生成器函数里不要有阻塞 IO(如同步数据库查询、
time.sleep()),否则会卡住整个响应流
用生成器分块写入大 CSV 文件并下载
这是最典型的大文件流式导出场景:数据量超内存、字段含特殊字符、需保持顺序。关键不是“快”,而是“不崩”和“不断”。生成器每 yield 一行就触发一次 HTTP chunk 发送,内存占用恒定在几 KB 级别。
示例中用 csv.writer 写入 StringIO,再用 .getvalue() 提取单行字符串——注意不能把整个 StringIO 传给 Response,否则又全加载进内存了。
from flask import Response, request
import csv
from io import StringIO
<p>def generate_csv_rows():
yield "name,age,city\n"
for i in range(100000):</p><h1>模拟从数据库或 API 流式拉取</h1><pre class="brush:php;toolbar:false;"> row = [f"user_{i}", str(20 + i % 50), "Shanghai"]
output = StringIO()
writer = csv.writer(output)
writer.writerow(row)
yield output.getvalue()@app.route('/export.csv') def stream_csv(): headers = { "Content-Disposition": 'attachment; filename="users.csv"', "Content-Type": "text/csv; charset=utf-8", } return Response( generate_csv_rows(), headers=headers, direct_passthrough=True # ⚠️ 必须加!否则 Werkzeug 会缓冲 )
处理二进制大文件(如 ZIP、PDF)的流式下载
CSV 是文本,但 ZIP/PDF 是二进制,不能用 StringIO 或字符串 yield。必须用 BytesIO,且 yield 的每个 chunk 必须是 bytes 类型。常见坑是用 open(..., 'r') 打开二进制文件,或者对 bytes 调用 .decode() 导致异常。
如果文件已存在磁盘,最稳妥方式是用 yield from 直接转发文件句柄;如果是动态生成(如用 zipfile 构建 ZIP),就得边写边 yield buffer 内容,且 buffer 大小建议控制在 8192 字节以内,兼顾网络吞吐与延迟。
- 读取已有文件时,用
open(path, 'rb')+iter(lambda: f.read(8192), b'')最简洁安全 - 动态生成 ZIP 时,不要调用
zip_file.close(),改用zip_file.fp.getvalue()只适用于小 ZIP;大 ZIP 必须用zip_file.fp的底层 buffer 并分段读取 - Nginx 或反向代理默认会缓冲响应,若部署在线上,需额外配置
proxy_buffering off;和chunked_transfer_encoding on;
前端如何正确接收流式响应并触发下载
浏览器原生 <a href="/export.csv"></a> 不支持流式响应的进度反馈,且无法捕获失败;用 fetch 获取 Response.body 后,必须用 ReadableStream + pipeTo 写入 WritableStream(如 FileSaver.js),否则容易卡死或只下载空文件。
更现实的做法是:后端返回一个临时 token,前端轮询状态,完成后跳转到预签名 URL 下载——这对真正超大文件(GB 级)更可靠;而纯流式适合百 MB 级、生成耗时可控的场景。
- 不要用
response.json()处理流式响应,它会等待完整 body,直接报错 - 用
response.body.getReader()手动读取 chunks,配合Uint8Array拼接,最后用URL.createObjectURL(new Blob([data]))触发下载 - Chrome 对长连接流式响应有约 5 分钟 idle 超时,后端需在生成器中定期 yield 一个空字节(如
b'\n')保活,尤其当数据源间隔较长时
实际中最容易被忽略的是反向代理层的行为——开发时 localhost 直连 Flask 没问题,一上 Nginx 就卡住或返回 502,这时得查 proxy_buffering 和 proxy_max_temp_file_size 配置,而不是反复改 Python 代码。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











