SYSTEMS NOTES阅读地图
8个主要章节
1段代码示例
5组图片与图表

从一个具体请求或状态变化出发,追踪数据流与方法成立的条件。

章节速览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字符可能占多个字节,任意字节切片也未必能单独解码成完整文本。

公式R1|单个有界范围的归一化
e = min(b, N − 1)
实际区间 [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到J对应偏移0到9,请求2到5选出CDEF,已有前四字节后从偏移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
2
3
4
5
6
7
8
9
10
11
GET /sample.bin HTTP/1.1
Host: example.com
Range: bytes=2-5

HTTP/1.1 206 Partial Content
Content-Type: application/octet-stream
ETag: "sample-v1"
Content-Range: bytes 2-5/10
Content-Length: 4

CDEF

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,不用文件系统时间或本地时钟冒充替代品。

原强ETag匹配时按206检查并接续,不匹配时忽略Range并获取新完整表示;200不可追加旧前缀。
原创状态对照图;表示版本与保存策略的关系,不是CDN支持率或真实网络轨迹。查看原图

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],比只加下载字节数可靠。

一张可执行的人工核对单

  1. 开始前:记录已选表示的强ETag、内容编码、完整长度与实际保存的前缀区间。没有可靠版本信息时,重新完整获取比猜测拼接条件清楚。
  2. 发请求时:从实际已保存长度p请求bytes=p-,配合原强ETag的If-Range;不要把最后偏移p−1误写成下一偏移。
  3. 收响应时:200走完整候选流程;206核对Content-Range起点等于p,片段长度与声明一致,验证表示身份与编码;416重新核对版本和本地状态。
  4. 中断时:只把确认写入的字节计入保存状态,未写完的网络缓冲区不算持久前缀。TCP分帧所说的“收到一些字节”也不等于整个应用对象完成。
  5. 结束时:核对覆盖与完整性。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],还要确认各片段属于同一表示,不能只加字节数。

参考文献与延伸阅读

  1. RFC 9110:HTTP Semantics(2022)。§14范围请求、§13.1.5 If-Range及§15.3.7/§15.5.17是本文的协议依据。本文只抽取GET与单范围主线,未实现完整协议栈。返回公式R1/返回报文1。
  2. RFC 9111:HTTP Caching(2022)。§3.4组合部分内容要求可组合片段共享强验证器,并保留缓存实现的其他条件;本文的文件覆盖核对单不是完整缓存算法。
  3. 本站衔接阅读:缓存年龄与条件请求区分304与完整传输,TCP分帧区分字节到达和消息完成。两层边界共同解释“连接成功却未得到可用文件”。