摘要

  • RFC 3229 扩展了条件 GET:客户端用 If-None-Match 声明已有实例,用 A-IM 声明可接受的变换,服务器可返回 226 和 IM 描述,而非完整副本。
  • 重建链含有三个身份:Delta-Base 指向旧基底,正文承载差分指令,响应 ETag 指向重建后的当前实例,绝不指向差分字节本身。
  • 新状态码还是一道兼容屏障。旧缓存可能忽略陌生头字段,把差分当完整内容保存;226 让“正文不是完整实例”这个语义变化进入它不易误处理的位置。

条件请求原本只能省下全部或什么也省不下

缓存很早就减少了 Web 的重复传输。若保存的响应仍然有效,服务器返回 304 Not Modified 即可。可只要资源发生变化,哪怕只改了一行,常规路径仍要发送完整的新值。

旧实例因此只有两种用途:原样复用,或者对下一次传输毫无帮助。大量未变化内容无法抵扣新的下载成本。

2002 年 1 月发布的 RFC 3229 让缓存成为重建材料。若客户端持有旧实例,服务器可以计算它与当前实例之间的差异,只传输恢复新状态所需的指令。

这套扩展从一开始就是可选的。目标是在不增加往返的情况下缩小平均响应,并与不理解它的 HTTP/1.0、HTTP/1.1 实现共存。它没有承诺每种内容或每次变化都值得做差分。

差分不是压缩,也不是范围

规范把“实例”定义为:为选定变体执行内容编码之后、实例操作与传输编码之前,一个 200 GET 将返回的值。

压缩数据通常不依赖旧副本即可解码。差分则必须知道它是相对于哪个基底计算的。范围请求从一个当前实例里选择坐标;差分描述两个实例之间如何变化。两者都可能少传字节,却依赖完全不同的证据。

226 也不是 PATCH。它是 GET 的响应,告诉接收方如何恢复当前表示,并不要求源站改变资源。RFC 3229 没有把这套机制定义给其他方法的响应。

把这些边界分开,才能避免所有“增量”技术被当成同一种东西。

客户端必须证明它从哪里出发

If-None-Match 列出客户端确实保留的旧实例 ETag,A-IM 列出它能够执行的实例操作。

若某个 ETag 仍对应当前实例,传统 304 就足够。若资源改变,具备能力的服务器可从所报基底中选择一个,计算兼容差分。若没有保留旧版本、不支持算法或判断不划算,仍可返回普通 200。

这是一份能力要约,不是执行命令。客户端控制自己持有哪些基底、会哪些算法;源站控制保留哪些历史、是否承担计算、选择哪个基底以及何时发送完整副本。

在双方回答“相对于什么”和“怎样应用”之前,“差异”并没有可共享的含义。

一个响应协调了三件不同的东西

旧基底已经存在于缓存。226 正文携带变换指令。执行指令后得到当前实例。

当请求给出多个旧 ETag 时,Delta-Base 明确服务器选了哪一个。226 上的 ETag 则命名重建结果。RFC 3229 特别指出,把这个 ETag 与差分值绑定没有意义,因为差分不是一个独立实例。

这意味着正文可以完全正确,却不能单独使用。它的意义取决于精确基底、精确变换和预期输出身份。

Content-Length 测量的也是差分正文,而不是重建后实例的长度。若把两者混同,协议节省的字节会被误认为截断内容。

变换顺序属于证据链

规范给出一条消息生成链:选择资源与变体,应用内容编码,分配实例 ETag,应用差分或范围等实例操作,最后应用传输编码。

这些步骤不可随意交换。先差分再取范围,未必等于先取范围再差分。压缩若作为内容编码,会参与实例身份;若作为后续操作,则属于另一层。

A-IMIM 不只记录操作名称,也保留顺序。缓存不能把它们当作无序集合,重放时自行排列。算法、参数、次序和基底共同组成重建配方。

差分之所以节省,是因为伴随字节的元数据仍能证明这些字节如何成为表示。

为什么必须再造一个 2xx

设计者原本不愿增加状态码,真正的压力来自已经部署的缓存。

HTTP 通常要求接收方忽略不认识的头字段。旧代理可能因此忽略 IM,保存差分正文,随后把它交给另一个不懂差分的客户端,并声称这就是完整资源。扩展兼容原则会反过来造成静默损坏。

陌生的 226 构成更明显的语义隔离。RFC 的观察是,旧代理似乎会转发未知状态,却不尝试缓存。它可以不理解地搬运,但不容易把差分降格成普通 200。

规范还警告,只有 Vary: If-None-Match, A-IM 至少在一种错误验证场景中并不充分。变化必须出现在旧逻辑无法轻易抹掉的位置。

IM 描述配方,226 阻止完整副本假设

差分响应必须使用 226,并在 IM 中至少列出采用的差分编码。请求必须在 A-IM 中接受这种操作,并在 If-None-Match 中提供基底。

226 表示 GET 已通过一个或多个实例操作得到满足。依照具体操作,当前实例可能只有与先前或后续响应组合后才真正可用。

所以 226 不只是“差分”的别名。框架也可以组合范围等其他操作。IM 保存有序配方,226 则提醒接收方正文经过操作,不能直接当作完整实例。

简短的 “IM Used” 原因短语不是恢复算法;真正可执行的语义位于结构化字段中。

懂得机制的缓存可以完成重建

若一律禁止缓存,优化价值会大幅下降。RFC 3229 因此区分不理解机制的缓存和能遵守完整契约的缓存。

有能力的缓存可以解开全部实例操作,把当前实例作为普通 200 保存;也可以保留最后的范围选择,把结果作为合规 206 保存;还可以在专门规则下保留原始 226。

原始 226 的复用非常严格。后续请求必须接受相同操作、参数与相关顺序,还必须满足新鲜度、基底与身份条件。保存的差分不会自动变成可回答任意 GET 的独立变体。

Cache-Control 的 im 扩展划出能力边界:no-store 约束普通缓存,而完整理解 226、A-IMIM 的实现才可以遵循为其准备的缓存指令。

保留过去也会产生账单

只有基底仍在,差分才有价值。客户端可以保留多个旧实例,并上报多个 ETag;服务器若也持有相应历史,可以选择成本合适的基底,并用 Delta-Base 命名。

可是缓存空间有限,源站历史有限,比较多个候选会消耗 CPU、内存与 I/O。保留提示也不是服务器永远保存版本的承诺。

这项优化改变了存储激励:旧副本即使不能直接展示,也可能因为能重建未来版本而被留下;源站只应在预期收益足以覆盖成本时保存版本或预计算差分。

服务器随时退回 200 的权利是控制面的一部分。差分更大、更慢或不存在时,发送完整内容并不是协议失败,而是拒绝一次不划算的优化。

当前注册表保留了这套词汇

IANA HTTP 状态码注册表 仍将 226 IM Used 指向 RFC 3229。IANA HTTP 字段名注册表 仍把 A-IMDelta-BaseIM 列为永久字段。

注册并不证明现实部署率。它保证这些符号仍指向明确契约,不能随意被另一个“增量”方案改作他用。

这段历史的重点也不是宣称差分统治了现代 Web,而是说明:当 HTTP 想少传一些时,必须多保留多少身份信息才能确保结果仍然正确。

副本只在重建之后出现

226 把线上消息和当前表示分开。网络传送的是变换,却不声称变换本身就是结果。

客户端证明基底,服务器选择并命名基底,IM 固定操作顺序,响应 ETag 命名输出,缓存要么理解整条链,要么拒绝把操作后的字节伪装成完整文档。

没有这条沿革,差分不是高效的事实,而是一小段携带隐含前提的字节。

HTTP 226 的成就在于让前提无法被忽略。接收方没有直接收到新副本;它收到的是一种可验证的方法,用自己能证明持有的旧副本造出新副本。