flask需手动解析range头并返回206响应:提取bytes范围、校验合法性、设置content-range;不能用send_file或send_from_directory,须以rb模式打开文件,用生成器流式yield分块数据,并显式设置accept-ranges、content-length等头部。

Flask如何正确响应Range请求
断点续传依赖HTTP的Range头,客户端发起下载时会带上类似Range: bytes=1024-2047的请求,服务端必须返回206 Partial Content状态码,并设置Content-Range、Accept-Ranges等响应头。Flask默认不处理Range,需手动解析并构造响应。
关键点:不能直接用send_file()——它不支持分段;也不能用make_response()简单包装字节流——缺少头部校验和边界计算。
- 先读取请求头:
range_header = request.headers.get('Range'),为空则走完整下载流程(返回200) - 用正则提取范围:
bytes=([0-9]+)-([0-9]*),注意末尾可能为空(如bytes=1000-表示“从1000到结尾”) - 文件总大小必须提前获取:
os.path.getsize(filepath),用于校验范围合法性(比如start > filesize要返回416 Range Not Satisfiable) -
Content-Range格式必须严格:bytes {start}-{end}/{filesize},其中end是含末位的偏移量(即end = min(end, filesize - 1))
用生成器流式返回文件片段
大文件不能一次性加载进内存,必须用生成器按块读取。但要注意:Flask的响应体若为生成器,需确保每个yield返回的是bytes而非str,且块大小建议设为8192或65536,太小导致系统调用频繁,太大占用内存。
常见错误是直接open(...).read()——这会把整个片段载入内存;或者用for line in f——文本模式会破坏二进制数据。
- 务必以
'rb'模式打开文件:with open(filepath, 'rb') as f: - 跳过起始位置:
f.seek(start),然后循环f.read(chunk_size)直到读够length字节 - 每次
yield前检查是否已读完,避免多读(例如remaining 时改用<code>f.read(remaining)) - 响应对象要显式设置
content_type='application/octet-stream',否则浏览器可能误判为文本
为什么不能用send_from_directory实现断点续传
send_from_directory()虽方便,但它内部调用的是send_file(),而后者在收到Range头时**不会自动降级为206响应**,而是忽略该头、返回完整文件+200状态码。这意味着客户端收不到分段标识,下次断点请求会被当作新下载,覆盖已有部分。
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
更隐蔽的问题是:某些Nginx或CDN配置下,send_from_directory()返回的响应可能被缓存层截断Content-Range头,导致客户端始终重头下载。
- 实测中,即使手动加
Response(headers={...})也无法覆盖send_from_directory()的内部逻辑 - 替代方案只有自己构造
Response对象,把生成器作为response参数传入 - 若必须复用静态文件路由,可在反向代理层(如Nginx)启用
slice模块,由其处理Range,Flask只负责提供原始文件路径
客户端请求头与服务端兼容性细节
不是所有客户端都发标准Range头。iOS Safari有时发Range: bytes=0-却不带If-Range,Android DownloadManager可能重复发相同范围,Chrome对Content-Range格式异常敏感(空格、大小写错一点就失败)。
服务端必须做容错:接受bytes=0-、bytes=-1000(倒序)、多个范围(虽然极少用),并统一转成单段处理。
- 遇到
bytes=-N时,start = filesize - N,end = filesize - 1 - 忽略
If-Range头(除非你实现了ETag校验逻辑),直接按Range返回 - 响应中必须包含
Accept-Ranges: bytes,否则部分旧版安卓下载器拒绝断点 - 不要设置
Cache-Control: no-cache——这会让浏览器放弃利用本地缓存继续下载,应设为public, max-age=3600配合ETag
最易被忽略的是文件锁和并发读取:同一文件被多个请求同时seek+read时,Python的file对象不保证线程安全,生产环境务必加threading.Lock或改用mmap(但mmap对超大文件有地址空间限制)。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










