摘要

  • 字节偏移只说明片段放在哪里,不能说明它属于资源的哪一个版本。
  • Range 与 If-Range 共同表达双重意图:强验证器相符就处理区间;不符就忽略区间,返回当前完整表示。
  • 这个分支节省第二次请求并避免跨版本拼接,但不保证区间支持、接收完成、持久存储、内容正确或来源可信。

同一个偏移,可能已经指向另一个对象

假设客户端收到第 0 到 999999 个字节后断线。重新连接时,它请求从 1000000 开始的数据。若服务器上的表示没有变化,这个动作只是不重复下载已有部分。若发布者在文件开头插入内容、重新打包归档,或协商结果换成另一种编码,同一偏移便落在另一串字节里。新后缀接上旧前缀,得到的文件可能从未存在过。

这不是 TCP 重连本身能够解决的问题。旧连接结束,只能证明一段传输没有继续;新 HTTP 请求面对的是资源的当前状态。Range 描述空间位置,却没有时间维度。客户端还需要携带一项有边界的版本证据,说明手中片段与服务器当前选择的表示是否能够组成同一对象。

1997 年的 RFC 2068 首次在标准化 HTTP/1.1 中定义 If-Range。文档先说明普通条件请求的不足:客户端可以把 Range 与 If-Match 等条件一起发送,但若实体已经改变,条件失败后还要再发一个请求,才能取得完整的新实体。If-Range 把两步压成一步——未改变就给缺失部分,改变了就给整个新实体。

1999 年的 RFC 2616 延续这项设计。实体标签相符时,服务器以 206 Partial Content 提供指定区间;不相符时,以 200 OK 返回完整实体。条件为假并不等于读取失败。它取消的只是“可以沿用旧片段”这项节省,而不是客户端取得当前内容的权利。

一个请求,预先接受两种形状

客户端可以发送:

Range: bytes=1000000-
If-Range: "release-73"

这句话的实际含义是:若 "release-73" 仍与当前选择的表示强一致,只发送我缺少的区间;否则,发送整个当前表示。后一个分支里,服务器必须忽略 Range,客户端也必须用 200 响应替换旧片段,不能把完整响应继续追加在后面。

因此,If-Range 不能被概括成普通的 HTTP 前置条件。If-Match 失败可以阻止方法执行,并得到 412 Precondition Failed;If-None-Match 可让条件 GET 返回 304 Not Modified。If-Range 不选择“执行还是拒绝”,而选择“局部还是完整”。客户端已经声明:如果旧版本不能续接,完整的新版本仍然有用。

2014 年的 RFC 7233 把区间请求独立成规范,并收紧用法。客户端不得在没有 Range 时发送 If-Range;服务器在缺少 Range、或目标不支持区间时忽略它。验证器相符,服务器应处理区间;不相符,服务器必须忽略区间。它缩短的是“发现变化后再取完整表示”的路径,并不强迫服务器支持任意区间。

现行语义汇总 RFC 9110 把顺序写得更明确:GET 同时带有 Range 与 If-Range 时,条件为真且区间适用,响应为 206;否则忽略 Range,按完整请求返回 200。重定向、权限错误以及在更早阶段可知的失败仍然优先。一个验证器不能绕开服务器原有的表示选择和请求检查。

拼接字节必须使用强比较

HTTP 同时允许强验证器和弱验证器,因为“语义上可以替代”与“逐字节完全相同”是两个问题。缓存一张页面时,空格变化或不影响意义的重新序列化,可能不值得重新传输。但若要把新收到的区间焊接到旧数据后面,一个字节变化都可能破坏位置与内容。

RFC 7232 指出,强验证器应在可观察的表示数据变化时变化,并可用于局部内容区间;弱验证器只适合不要求精确相等的场景。因此,客户端不得把弱 ETag 放入 If-Range。只有在没有实体标签、并且 Last-Modified 日期本身足以成为强验证器时,日期才可作为退路。

日期之所以受限,是因为时钟粒度可能比内容更新慢。同一秒内生成的两个版本可能共享时间戳。If-Range 对日期使用精确匹配,也不同于 If-Unmodified-Since 的“早于或等于”比较。时间证据不够强时,正确做法是让条件为假,宁可重传完整内容,也不假装它能证明字节连续。

ETag 本身仍是一个不透明值。规范没有要求它必须是密码学摘要,客户端也不能从格式推断生成算法。所谓“强”,指来源承诺不会在同一资源的相关时期,把同一验证器赋给可观察字节不同的表示。它不认证来源、不评价内容,也不能证明另一个 URL 上出现相同字符串就拥有相同对象。RFC 2616 已明确指出跨 URI 复用标签不表示实体等价。

验证相符,只是开始处理区间

即使强标签相符,服务器仍需判断区间单位是否受支持、语法是否有效、请求位置是否落在当前长度内,以及该组区间是否合理。支持、有效且可满足的字节区间通常产生 206 Partial Content。单个片段使用 Content-Range 标注其在完整表示中的位置;多个片段使用 multipart/byteranges,每一部分分别带范围。完全超出当前范围的请求可能得到 416 Range Not Satisfiable。

这些元数据各自只拥有一小块权力。Content-Range: bytes 1000000-1999999/5000000 可以说明响应体的位置与总长度,却不能证明客户端先前保存的百万字节来自同一版本。版本连续性依赖共同的强验证器;接收和落盘成功则只能由客户端或缓存自己的记录证明。

这里的风险并不对称。错误地判为不相符,会多传一次完整对象,代价明显且容易发现。错误地判为相符,则可能悄悄产生跨版本混合文件。HTTP 在不确定时选择更贵但连贯的表示,而不是便宜却无法追溯的拼接。

缓存也必须保留版本边界

缓存可能持有不完整的 200、一个 206,或若干不连续片段。当它决定合并这些数据时,就不再只是转发者,而是承担片段保管责任。RFC 7234 仅允许理解 Range 与 Content-Range 的缓存保存不完整响应;多个区间只有在共享同一个强验证器时才可合并。

覆盖全部坐标并不等于来自同一版本。星期一的前缀、星期二的中段和星期三的后缀,可以恰好没有空洞,却仍不是一个真实表示。缓存还必须按组合规则更新响应头,并且在真正完整之前,不能把局部内容伪装成完整 200。

现行缓存规范 RFC 9111 保留这条分界。缓存可以通过后续区间请求补齐不完整响应,但只能在实际补齐后把它当作完整对象;对客户端发送局部内容时必须明确使用 206。If-Range 不远程完成存储和拼装,它只为服务器的一次响应分支提供版本证据。

因此,一份可审计记录要分别保存请求目标、协商输入、Range、旧验证器、当前验证器、比较方式、响应状态、Content-Range 与最终对象哈希。服务器只能证明自己按哪个分支发送了什么;客户端或缓存才能证明哪些片段被接收、组合和保留。

没有全局版本登记处的连续性

If-Range 没有要求互联网建立统一的文件版本编号,也没有要求来源为每个断线客户端锁住资源。来源生成验证器,客户端把它和片段一同保存,服务器比较当前状态,缓存保管自己的字节。每个参与者只处理自己能观察和证明的部分。

共享字段回答的只是一个下一步问题:请求的新片段还能否视为手中表示的另一部分?否定答案只撤销节省带宽的资格,并不撤销获取当前版本。正是这种有限授权,让局部续取能够互操作,而没有把一次下载会话提升为资源的中央权威。

RFC 定义的是协议边界,不能证明当今某个产品正确实现了它。它们也不能证明某个线上 ETag 足够强、中间层没有篡改、服务器实际支持区间,或最终文件已经可靠保存。验证这些事实,需要请求与响应抓包、表示协商输入、验证器历史、各片段哈希以及最终装配记录。