
Flask 本身不支持流式响应大文件,但用 PyMongo 直接操作 GridFS 并配合 Flask 的 Response 流式构造,可以避免内存爆涨、实现毫秒级首字节响应(TTFB),关键在于绕过 gridfs.GridFS.get() 全量读取。
用 gridfs.GridFSBucket 替代 GridFS 获取文件句柄
GridFS 类的 get() 方法返回的是 gridfs.grid_file.GridOut,但它内部会预读整个文件到内存(尤其对大图危险);而 GridFSBucket 的 open_download_stream() 返回真正可迭代的流对象,底层基于 pymongo.cursor.Cursor 分块拉取。
- 必须用
GridFSBucket:它才是为流式设计的接口,GridFS是旧版兼容封装 -
open_download_stream()接收_id或filename,推荐用_id避免文件名编码/冲突问题 - 返回对象支持
read(chunk_size),但更推荐直接传给Response的response参数(它自动 chunk)
Flask 路由中构造流式 Response 并设置正确 headers
直接把 GridOut 或 GridOutCursor 传给 Response 不行——它不是可迭代对象;必须包装成生成器或使用 stream_with_context。同时,Content-Length 不能设(因为流式无总长),但 Content-Type 和 Accept-Ranges 必须显式声明,否则浏览器无法做图片预加载或拖拽进度条。
FastAPI + Flask 混合部署最佳实践,解决路由定义、API 代理等常见问题,适用于同时运行 FastAPI API 与 Flask 前端的场景。
- 用
Response(stream_with_context(...), mimetype=grid_out.content_type) - 务必加
headers={'Accept-Ranges': 'bytes'},否则 Safari / iOS WebView 拒绝播放/显示 - 不要手动设
Content-Length:GridFS 文件可能被并发修改,长度不恒定;设了反而触发 Flask 强制缓冲 - 如果前端需要支持断点续传(如大图懒加载),需额外解析
Rangeheader 并调用bucket.open_download_stream_by_name(..., revision=-1)配合偏移读取(较复杂,一般场景不需)
处理常见错误:GridFS 找不到文件时的 404 和连接泄漏
调用 open_download_stream() 时若 _id 不存在,PyMongo 抛 pymongo.errors.NoFile,不是标准 HTTP 异常;不捕获会导致 500。更隐蔽的问题是:流未消费完就结束请求(如用户关闭页面),GridOut 对象不会自动 close,MongoDB 连接池可能缓慢耗尽。
- 必须
try/except捕获NoFile并返回abort(404) - 在生成器函数末尾加
finally: grid_out.close()—— 即使流被中断也能释放 socket - 别用
with bucket.open_download_stream(...) as f::上下文管理器在生成器 yield 后不触发__exit__ - 检查 MongoDB 连接字符串是否含
maxIdleTimeMS(建议设为 60000),防止空闲连接堆积
真正难的不是写几行流式代码,而是确认每张图从 GridFS 写入时的 content_type 是否准确(比如上传 .webp 却存成 image/jpeg),以及 Nginx 是否透传了 Accept-Ranges。这些错一个,前端图片就卡在 loading 状态,但后端日志完全安静。

















