Flask默认不支持分块上传,必须手动接管request.stream流式读取,禁用自动解析,配合前端fetch/XHR的onprogress事件实现进度条,并通过Redis等外部存储管理上传状态以保障分布式一致性。

Flask 默认不支持分块上传,必须自己接管请求流
Flask 的 request.files 会把整个文件读进内存或临时文件,大文件(比如 >100MB)容易触发超时、内存溢出或被 WSGI 中间件(如 Gunicorn)截断。进度条的前提是客户端能持续上报已上传字节数,而标准 POST 提交做不到这点——必须改用 multipart/form-data 分块上传(chunked upload),并让 Flask 不自动解析整个 body。
关键做法是:禁用 request.get_data() 自动解析,直接从 request.stream 逐段读取,并实时写入目标文件或缓冲区。同时需配合前端使用 XMLHttpRequest 或 fetch 的 upload.onprogress 事件。
- 在路由函数开头加
request.get_data(parse_form=False, cache=False)防止 Flask 提前消费 stream - 用
for chunk in iter(lambda: request.stream.read(8192), b'')流式读取,避免一次性加载 - 务必设置
app.config['MAX_CONTENT_LENGTH'] = 0(或设为足够大值),否则 Flask 会在读取前就拒绝大请求 - Nginx/Gunicorn 等反向代理默认限制 body 大小,需同步调大
client_max_body_size和timeout
前端必须用 fetch + onprogress,不能靠表单 submit
HTML 表单提交无法监听上传进度;只有 fetch 或 XMLHttpRequest 能绑定 upload.onprogress。后端返回的响应也必须是流式或即时响应(如 200 OK),不能等文件写完才返回,否则前端会卡住。
- 前端示例中
fetch('/upload', { method: 'POST', body: formData })后,需用xhr.upload.onprogress(若用 XHR)或fetch配合ReadableStream(较新浏览器)获取已上传字节数 -
FormData本身不支持分块,所以实际要手动构造 multipart boundary 并分段发送——更稳妥的做法是用第三方库如axios或原生fetch+Blob.slice()拆分文件 - 服务端返回的 JSON 必须包含
{ "status": "uploading", "received": 123456 }这类字段,前端据此更新进度条宽度 - 别依赖
Content-Length响应头来算总大小——它反映的是响应体长度,不是文件大小;应由前端传入file.size作为分母
用 werkzeug.utils.secure_filename() 但别只靠它防路径遍历
secure_filename() 只处理文件名中的非法字符和扩展名,对 ../etc/passwd 这类路径遍历无效。如果用户上传 ../../config.py,直接拼接 os.path.join(upload_dir, filename) 仍可能写到任意位置。
立即学习“Python免费学习笔记(深入)”;
- 必须在保存前做路径规范化:
os.path.realpath(os.path.join(upload_dir, filename)),再检查是否仍在upload_dir内部 - 不要用
os.path.join直接拼接用户传来的原始filename字段——攻击者可伪造 multipart header 中的filename值 - 建议用
tempfile.NamedTemporaryFile(dir=upload_dir, delete=False)先写入临时文件,校验通过后再重命名为目标名 - 上传目录权限设为
750,且 Web 进程用户对该目录无执行权限,防止上传 .py/.sh 文件被意外执行
进度状态不能存在内存里,得用 Redis 或文件记录
单机多进程部署时(比如 Gunicorn 开 4 个 worker),每个进程内存独立。如果把上传进度存在全局 dict 或类属性里,前端轮询时可能查到空数据或旧数据。
- 用 Redis 存储进度最稳妥:
redis.setex(f'upload:{upload_id}', 3600, json.dumps({'received': 102400, 'total': 1048576})) - upload_id 应由前端生成(如 UUID),并在上传请求 header 或 form data 中带上,避免后端重复分配
- 避免用文件存进度——高并发下文件锁竞争严重,且 NFS 等共享存储有缓存延迟问题
- 轮询接口(如
/upload/progress?id=xxx)应设短超时(socket.setdefaulttimeout(2)),防止阻塞
真正难的不是读流或画进度条,而是让上传、存储、状态查询三者在分布式环境下保持一致——Redis key 的 TTL、worker 进程崩溃后的清理、断点续传的支持,这些才是上线后容易出问题的地方。


















