Flask 中需手动处理 Range 请求以支持断点续传:解析 Range 头、计算字节范围、用二进制流式读取文件片段、返回 206 状态码及正确 Content-Range 和 Accept-Ranges 头,同时确保代理层(如 Nginx/uWSGI)透传 Range 请求。

Flask 中如何正确响应 Range 请求?
断点续传依赖 HTTP 的 Range 请求头,客户端会发类似 Range: bytes=1024-2047 的请求,服务端必须返回 206 Partial Content 并设置 Content-Range、Accept-Ranges 等头部,否则浏览器或下载工具会重头开始。
Flask 默认的 send_file() 不支持分块响应,直接用它返回大文件时,即使客户端带了 Range,也会返回完整文件 + 200 OK,导致续传失败。
- 必须手动读取文件片段,不能依赖
send_file() - 需解析
request.headers.get("Range"),提取字节范围 - 响应状态码必须是
206,且Content-Range格式为bytes 1024-2047/100000(注意斜杠后是总大小) - 必须显式设置
Accept-Ranges: bytes,否则某些客户端(如 curl -C)不发起 Range 请求
如何安全读取大文件的指定字节段?
不能把整个文件读进内存,尤其当文件几百 MB 或 GB 时。要用 open(..., "rb") 配合 seek() 和 read() 按需读取。
关键点在于:计算起始偏移、长度、校验是否越界,并处理 Range: bytes=-1024(末尾 N 字节)这类语法。
立即学习“Python免费学习笔记(深入)”;
- 用正则解析
Range:匹配bytes=(\d*)-(\d*)或bytes=-(\d+) - 若起始未指定(如
bytes=-1024),则起始 =file_size - 1024,且需确保 ≥ 0 - 若结束未指定(如
bytes=1024-),则结束 =file_size - 1 - 读取前用
os.path.getsize()获取真实文件大小,避免Content-Range声称的范围超出实际
为什么用 Response 流式返回比 send_file 更可靠?
send_file() 内部做了大量封装(如自动设 MIME 类型、ETag、缓存控制),但绕过了 Range 处理逻辑;而手动构造 Response 可完全掌控 headers 和 body 流,适配各种客户端行为。
流式返回还能避免内存暴涨:用生成器 yield 每次读取的 chunk(如 8192 字节),Flask 自动将其转为 HTTP 分块传输(chunked encoding)。
- 返回类型必须是
Response,body 设为生成器函数 - 生成器中每次
f.read(chunk_size),并检查是否读完指定 range - 必须设置
Content-Length(等于本次返回的字节数),不能依赖 Transfer-Encoding: chunked - 不要设
Content-Encoding: gzip—— Range 请求下压缩会破坏字节偏移,绝大多数客户端不支持压缩+Range
常见踩坑:Nginx / WSGI 代理层吞掉 Range 头
本地 Flask 跑通了,部署到生产环境却总是从头下载?大概率是反向代理(Nginx、Apache)或 WSGI 服务器(Gunicorn、uWSGI)默认禁用了 Range 支持。
Nginx 默认转发 Range,但若配置了 proxy_buffering on 或使用了某些缓存策略,可能把 partial response 合并成 full response;uWSGI 则默认不透传 Range,需显式开启。
- Nginx:确认没配
proxy_ignore_headers Range,且proxy_cache相关指令未干扰 - uWSGI:加配置项
--http-keepalive --http-chunked,并在 ini 中设ignore-body = false - Gunicorn:默认支持,但若用了
--preload且文件路径硬编码,要注意多进程下文件句柄竞争 - 最简验证法:curl -v -H "Range: bytes=100-199" http://your-domain/file.zip,看响应码和
Content-Range是否出现
断点续传真正难的不是 Python 侧逻辑,而是整个请求链路里任意一环(浏览器 → CDN → Nginx → WSGI → Flask)对 Range 的支持是否一致且透传。漏掉一个中间件,就退化成全量下载。


















