
安装包或视频刚替换,完整下载正常,暂停后继续却返回 416;有时下载没有报错,最终文件校验却失败。这类问题需要同时核对请求范围和文件版本。同一个 URL 下的内容变了,客户端保存的字节偏移未必还能用于新文件。
本文针对文件更新后的续传故障。基础的 Range 支持、206 响应与大文件缓存已有单独讨论,这里重点检查旧片段、新对象和 CDN 缓存之间的版本关系。示例适用于公开、允许下载的静态文件,域名和 IP 均需替换,未在你的生产环境实测。
416 能说明什么,不能说明什么
416 Range Not Satisfiable 表示请求的范围无法满足。对字节范围而言,常见情况是起始偏移已经超出当前表示的长度;服务端也可能拒绝过多或不合理的范围。响应通常带有类似 Content-Range: bytes */7340032 的头,用于告知当前表示的完整长度。[1]
例如,本地保存了旧文件的前 8 MiB,更新后的文件只有 7 MiB。客户端从第 8 MiB 继续请求,就可能触发 416。但文件变大或长度相同也不保证可以续传:旧前缀和新后缀可能拼成一个无效文件,服务器仍可能正常返回 206。
因此,排查至少需要原始下载 URL、当前请求的 Range、已保存的字节数,以及旧响应的 ETag 或 Last-Modified。签名 URL、Cookie、授权头和私有文件信息应脱敏后再放入工单。
用一个小范围 GET 检查边缘实际返回的内容
以下命令按 Bash 和 curl 编写,Windows 环境可使用 curl.exe 并调整换行语法。选取体积大于 1 KiB 的公开测试文件:
curl -sS --max-time 30 -D range-headers.txt -o first-1k.bin \ -H 'Accept-Encoding: identity' \ -H 'Range: bytes=0-1023' \ https://downloads.example.com/releases/app.zip
正常支持该范围时,通常应返回 206,并通过 Content-Range 描述起止字节和完整长度。对这个具体请求,响应片段应为 1024 字节。单段 206 中的 Content-Length(如有)描述片段长度,完整对象大小应看 Content-Range 的斜杠后部分。不要用一条 HEAD 请求代替实际范围 GET;Accept-Ranges 也是能力线索,不能单独证明每次续传都有效。[3]
服务端可以忽略 Range 并返回完整的 200。所以上面的请求即使只指定 1 KiB,也可能下载较大的完整对象;使用受控测试文件并留意带宽。若返回 200,下载器必须按完整响应处理,不能把它追加到旧片段后面。
记录 ETag、Last-Modified、Content-Encoding、Content-Range、Age 和供应商缓存状态头。identity 用于减少压缩表示差异,但仍需检查实际响应;CDN 若忽略该请求头或改写内容,不能假定字节表示相同。
检查续传请求有没有携带可靠的版本条件
客户端若在旧响应中保存了强 ETag,续传时可以通过 If-Range 判断资源是否仍可按旧偏移继续。例如,下面的偏移与 ETag 都是示例,需要替换为同一次原始下载保存的真实值:
curl -sS --max-time 30 -D resume-headers.txt -o resumed-part.bin \ -H 'Accept-Encoding: identity' \ -H 'Range: bytes=8388608-' \ -H 'If-Range: "saved-strong-etag"' \ https://downloads.example.com/releases/app.zip
此命令把返回内容写入单独文件,不会自动与旧片段合并。若原表示未变且范围可满足,服务器可返回 206;若 If-Range 校验不匹配,应忽略范围并返回完整的 200。下载器此时应重新开始或替换旧临时文件,不能盲目追加。[2]
If-Range 不能使用带 W/ 前缀的弱 ETag。HTTP 日期也可用于 If-Range,但有适用条件;Last-Modified 的精度和可靠性需要结合服务端实现评估。不能随便填一个时间,也不要把对象存储的 ETag 一概当成文件 MD5。ETag 首先是表示的校验器,其生成规则由实现决定。
如果下载器根本没有保存原版本信息,仅凭“本地已有多少字节”继续下载,无法可靠避免跨版本拼接。已知文件更新后,应停止复用未经确认的片段,按产品流程重新下载,并用独立发布的可信校验值验证完整文件。
对比源站与 CDN 的版本,而不只比较状态码
同一个 URL 在不同层可能对应不同对象:某些节点缓存旧文件,另一些已回源获取新文件。需要在相同请求头和访问权限下,比较两侧的 ETag、长度、编码以及实际片段。
有源站访问授权时,可保留主机名、SNI 与证书校验来指定连接目标:
curl -sS --resolve downloads.example.com:443:203.0.113.10 \ -D origin-range-headers.txt -o origin-first-1k.bin \ -H 'Accept-Encoding: identity' \ -H 'Range: bytes=0-1023' \ https://downloads.example.com/releases/app.zip
示例 IP 为文档地址。源站只允许 CDN 访问时,使用批准的内部诊断路径,不要开放公网或用 -k 跳过证书校验。多个源站实例、存储副本和发布目录也需检查,避免请求落到尚未更新的实例。
确认 CDN 是否转发 Range 与 If-Range,是否支持范围缓存,以及压缩或内容转换是否改变了表示。不同供应商的范围回源和缓存实现不同,不能要求所有节点都以同一方式存储分片。Age 和缓存命中头只能辅助定位,不能证明拿到的是预期版本。
精准刷新有用,但不能清除用户已有的旧片段
若源站新版本正确、边缘仍返回旧对象,按实际缓存键刷新原始文件 URL。核对主机名、路径大小写、查询参数、编码变体和相关规则,再按供应商文档确认失效范围。随机加参数只有在参数参与缓存键且访问策略允许时才可能形成新对象,不能当成通用修复办法。
刷新完成后,从小范围 GET、实际续传和完整文件校验三个层面复查。全站刷新可能明显增加回源压力,应控制范围并评估容量。边缘刷新不会删除浏览器或下载器已经保存的临时文件;支持指引需要明确何时保留片段,何时重新下载。
若响应的 ETag 没变而文件内容已被替换,应修复校验器生成或发布流程。只改缓存期无法保证续传安全。还需检查反向代理是否去掉 If-Range,下载器是否在收到 200 后仍追加,以及边缘改写内容后是否仍保留不再适用的校验器。
让每个发布版本有稳定地址
长期更稳妥的方式是使用带版本号或内容哈希的不可变文件路径,例如 /releases/v2/app.zip。保持该地址的文件内容稳定,让下载入口、更新清单或“最新版”页面指向新的版本地址。访问控制和签名有效期仍要覆盖正常下载及重试需求。
发布前准备文件、长度、可信校验值和对应元数据,确保对外可见的一份文件始终完整。避免直接覆盖正在对外读取的文件;本地文件系统可评估原子切换,具体做法取决于存储系统。更新入口前验证新对象,再保留旧版本一段符合业务需求的兼容期。
回滚时恢复经过验证的版本入口,并核查相关缓存。不要把一个已分发的版本化地址悄悄换成另一份内容。删除旧文件前,应结合在途下载、客户端更新周期和存储策略决定保留时间。
最终验收应包括完整下载、暂停后续传、文件更新后旧片段的处理、超出长度的范围请求,以及不同节点的版本一致性。记录状态码、Content-Range、校验器和最终文件校验结果,才能区分范围错误、缓存旧对象与下载器合并错误。
参考资料
资料核验日期:2026 年 10 月 27 日。CDN 的具体范围缓存和请求头转发行为需查所用服务商文档。