HTTP 断点续传 从字节范围到版本验证
从一个具体请求或状态变化出发,追踪数据流与方法成立的条件。
章节速览8 节 · 展开选择
HTTP缓存回答“已有副本还能不能用”,断点续传则多问一步:“已有的前半段,能不能和这次收到的后半段组成同一个表示?”网络连接重新建立,并不会替应用确认这两段来自同一版本。
本文只讨论GET、已知长度的非空字节表示、单个范围与强ETag的主线。示例资源是十个ASCII字节ABCDEFGHIJ,没有内容编码,偏移从0开始。所有请求和响应都是人工构造的教学报文,不是本站或某个CDN的实测;多范围、日期验证器和空表示另列边界,不把简化规则当成完整HTTP实现。
端点是字节偏移,HTTP字节范围两端都包含。把长度、最后偏移与已保存字节数分开。
状态码、Content-Range、编码与强验证器一起决定保存策略;收到200时不能直接追加。
一个闭区间为何要加一
十字节表示的有效偏移是0到9。Range: bytes=2-5请求C、D、E、F,一共四字节;它没有要求“跳过两个字符再取五个”。字符和字节本来就不同,中文UTF-8字符可能占多个字节,任意字节切片也未必能单独解码成完整文本。
实际区间 [a, e];正文长度 ℓ = e − a + 1
限定N>0、整数0≤a≤b且a<N。若a≥N则这一有界范围不可满足;b<a是无效语法语义,不能混为同一类。所有量均以字节或字节偏移计。依据文献1 §14.1.2。
- N|完整长度
- 十字节示例中N=10,最后有效偏移是N−1=9。
- a、e|闭区间端点
- 请求2—5得到四字节;端点差3不是正文长度。
- p|已保存前缀长度
- 已有[0,p−1]时,下一字节偏移才是p;本例p=4应从4开始。
这条公式先检查条件,再裁剪右端。不能把不满足条件的a硬夹到9,否则bytes=10-20会被错误地变成最后一个字节。请求右端超过资源末尾则不同:bytes=7-99仍能满足,实际得到HIJ。
开放区间和后缀不是同一种省略
bytes=4-省略右端,要求从偏移4一直到末尾,即EFGHIJ。bytes=-4省略左端,要求最后四字节,即GHIJ。二者恰好都含数字4,却没有相同含义。
| Range值 | 实际偏移 | 本例正文 | 需要辨别 |
|---|---|---|---|
| bytes=0-0 | 0—0 | A | 一个字节,而不是空区间 |
| bytes=2-5 | 2—5 | CDEF | 两端均包含 |
| bytes=7-99 | 7—9 | HIJ | 右端裁到末尾 |
| bytes=4- | 4—9 | EFGHIJ | 从指定偏移开始 |
| bytes=-4 | 6—9 | GHIJ | 取指定后缀长度 |
| bytes=-99 | 0—9 | ABCDEFGHIJ | 后缀比非空表示长时取全部 |
| bytes=10- | 无 | 无该范围正文 | 左端已经越界 |
| bytes=-0 | 无 | 无该范围正文 | 零长度后缀不可满足 |
这里没有把“请求可满足”直接写成“服务器必定返回206”。Range是可选能力,服务器可以忽略它而返回完整表示;客户端必须以实际响应为准。Accept-Ranges: bytes可以声明支持,但未出现这个字段不等于客户端绝对不能尝试范围请求,且声明也不保证未来每次请求都成功。
206里的两个长度各指什么
1 | GET /sample.bin |
Content-Range里的10是整个所选表示的长度;Content-Length里的4是本次部分响应正文长度。不能把4当作完整文件大小,也不能因为下载器只显示4/4就宣布整个十字节文件完成。对这个单范围例子,可以用公式R1交叉核对5−2+1=4。
如果GET携带的普通条件先导致304,就不会再按Range生成206;304没有这段待追加正文。缓存验证的“没变化”与续传的“还缺哪一段”需要放在正确的评估顺序上,详见§13.2与§14.2。
为什么单凭文件大小无法安全续传
假设本地已有v1的ABCD,但服务器把十字节资源改成了abcdefghij,大小仍为10。如果只请求4—9再拼接,最终得到ABCDefghij,既不是旧表示也不是新表示。长度正确不证明整体属于同一个版本;即使部分内容碰巧相同,也不能跳过验证。
强ETag用于表示的强比较,不是文件名,也不要求长得像散列值。客户端不应自行发明ETag或假定其编码算法。如果已保存强ETag"sample-v1",可以发送If-Range: "sample-v1":验证器仍匹配时才应用Range;不匹配时忽略Range,按普通GET处理,通常返回新完整表示200,而不是因为If-Range不匹配就返回412。
弱ETag如W/"sample-v1"不能作为客户端生成If-Range的实体标签值;“语义相近”不足以保证逐字节拼接。日期形式也存在,但有严格的强验证条件,本文的实现核对单只采用服务端提供的强ETag,不用文件系统时间或本地时钟冒充替代品。
flowchart TD
A[已有前缀与原强ETag] --> B[发送带版本条件的范围GET]
B --> C{验证器仍匹配吗}
C -->|是并应用范围| D[206并核对Content-Range]
C -->|否| E[忽略Range,按普通GET处理]
E --> F[200使用新完整表示]
D --> G[核对编码与位置后接续]
F --> H[不追加到旧前缀]
416提供长度,但不替你确认完整
在支持范围处理、其余条件允许的示例服务器上,bytes=10-没有可传的字节,可以收到416和Content-Range: bytes */10。星号表示这里没有一个已满足的响应范围,10仍指当前表示长度。416也可能用于拒绝过多小范围或重叠范围,因此不能把所有416简单解释为“文件没变且已下载完”。
即便本地正好也有10字节,仍要验证本地保存的是当前版本。资源可能缩短、替换或在中断期间改变;长度相同不能代替强验证器和内容完整性检查。对无效范围bytes=5-2,服务器可能忽略或拒绝,不要把它当作规范要求唯一416的例子。
内容编码、多范围与并发会增加什么条件
若响应采用gzip等内容编码,字节偏移针对的是编码后的表示序列,不是解压后的文本。下载中途切换Accept-Encoding,就可能使前缀与后缀根本不在同一坐标系。即使请求identity,也要核对实际响应字段与保存方式,不能假定请求头自动消除了所有中间层差异。
多范围206通常使用multipart/byteranges,每个部分有自己的Content-Range,边界和部分头也占响应正文空间。此时整个响应Content-Length不等于所有“文件字节片段长度”之外什么都没有。单范围解析器不得把multipart边界写进目标文件,服务端也可能合并范围或只处理其中部分,不能按请求顺序盲目拼接。
并发分片还需处理覆盖、空洞和版本一致性。例如收到[0,4]和[4,9],长度相加是11,但偏移4重复一次;收到[0,3]与[5,9],所有片段看似正常却漏了偏移4。按偏移覆盖核对完整区间[0,N−1],比只加下载字节数可靠。
flowchart LR A[收到响应] --> B[核对状态码和表示身份] B --> C[核对编码与范围端点] C --> D[按偏移记录覆盖区间] D --> E[检查空洞与冲突] E --> F[覆盖完整且内容校验通过] F --> G[标记完成]
一张可执行的人工核对单
- 开始前:记录已选表示的强ETag、内容编码、完整长度与实际保存的前缀区间。没有可靠版本信息时,重新完整获取比猜测拼接条件清楚。
- 发请求时:从实际已保存长度p请求
bytes=p-,配合原强ETag的If-Range;不要把最后偏移p−1误写成下一偏移。 - 收响应时:200走完整候选流程;206核对Content-Range起点等于p,片段长度与声明一致,验证表示身份与编码;416重新核对版本和本地状态。
- 中断时:只把确认写入的字节计入保存状态,未写完的网络缓冲区不算持久前缀。TCP分帧所说的“收到一些字节”也不等于整个应用对象完成。
- 结束时:核对覆盖与完整性。ETag是HTTP验证器,不默认具备恶意篡改检测能力;可信发布方给出的摘要与传输安全属于另一层保证。
自检1:已有ABCD四字节,应该请求bytes=3-还是bytes=4-?
应从4开始。已有偏移0、1、2、3,下一位置是4。请求3会重复D;如果没有按偏移去重,拼接后长度会错误。
自检2:bytes=7-99是否一定得到416?
不是。对十字节非空表示,它可满足且实际区间为7—9。服务器若应用范围处理,可以返回206及HIJ;也可能忽略Range而返回完整200。
自检3:If-Range不匹配,收到200后能追加吗?
不能。200表示这次是完整响应,应该进入新完整候选的保存流程。If-Range不匹配的语义是忽略Range,而不是把新版本后缀接在旧版本前缀后。
自检4:两个范围长度之和等于N,是否证明完整?
不能,范围可能重叠并留下空洞。必须核对端点覆盖整个[0,N−1],还要确认各片段属于同一表示,不能只加字节数。
参考文献与延伸阅读
- RFC 9110:HTTP Semantics(2022)。§14范围请求、§13.1.5 If-Range及§15.3.7/§15.5.17是本文的协议依据。本文只抽取GET与单范围主线,未实现完整协议栈。返回公式R1/返回报文1。
- RFC 9111:HTTP Caching(2022)。§3.4组合部分内容要求可组合片段共享强验证器,并保留缓存实现的其他条件;本文的文件覆盖核对单不是完整缓存算法。
- 本站衔接阅读:缓存年龄与条件请求区分304与完整传输,TCP分帧区分字节到达和消息完成。两层边界共同解释“连接成功却未得到可用文件”。