摘要

  • RFC 9111 建议带有 must-understand 的响应同时带上 no-store。不认识新指令的旧缓存会忽略前者,却仍能按后者采取保守行为。
  • “理解状态码”不是能解析三个数字,也不是能识别指令名称,而是既认识该状态码,又落实其规范规定的全部缓存相关行为。
  • 符合条件后忽略 no-store,只移除一项禁止。请求方法、授权、共享缓存限制、可缓存性、新鲜度与后续复用仍要分别满足;这组指令也不承诺隐私。

升级机制中不显眼的那一半

设想两台缓存都收到:Cache-Control: must-understand, no-store

第一台部署较早,不认识 must-understand。HTTP 对扩展指令的规则要求它忽略未知项。它随后读到自己认识的 no-store,于是拒绝存储响应。

第二台实现了 must-understand。但这还不够。它必须认识本次响应的状态码,并且已经实现该状态码规范中的全部缓存要求。只有满足这项条件,RFC 9111 才建议它忽略随行的 no-store,再按其余缓存规则判断响应是否可存。

两条路径共同解决了协议升级最现实的问题:网络中的参与者不会在同一时刻全部换代。源站不必先知道路径上每台缓存的版本。旧实现获得一个早已理解的保守指令;新实现则凭本地能力证明进入较宽的分支。

如果操作手册只摘出 must-understand,这套安排就会被误读。它的名字像一项对所有中间层都有效的命令,可一个从未实现它的缓存恰恰必须将其忽略。没有 no-store,旧缓存就收不到本应负责兜底的信号。真正的发布单位是指令对,真正的控制点是进入新分支所需的证据。

“理解”描述行为,而不是词汇表

RFC 9111 对“理解”给出了严格定义:缓存要认识请求方法或响应状态码,同时实现规范为其规定的全部缓存相关行为。

解析器能够读取三位数字,只证明语法通过;界面能显示状态名称,只证明有一张映射表;HTTP 客户端能把未知状态按同一类别处理,只证明具备通用回退。这些都没有说明缓存引擎执行了该状态码特有的存储规则。

规范之间的对照说明了这种差异。RFC 6585 规定,428、429、431 与 511 响应不得被缓存存储。RFC 7538 则把 308 Permanent Redirect 定义为默认可缓存,除非请求方法语义或明确的缓存控制另有规定。这些例子不是完整清单,但足以说明状态码可能带来相反的缓存结果。

假如某产品会把 429 显示为“Too Many Requests”,却把响应当作普通对象保存,它认识数值,却没有按 RFC 9111 所要求的意义理解它。类似地,仅检查 must-understand 能否被解析的功能开关,测到的是词法支持,而非状态行为的完整实现。

一份可信的运行记录至少要分开五件事:响应状态码;是否认识该状态码;是否实现其缓存要求;是否实现 must-understand;以及撤去例外后其余存储和复用条件是否通过。把它们压成一个“支持”字段,就是让条件性互操作退化为自我认证。

旧指令不是重复,而是退路

缓存必须忽略不认识的 Cache-Control 指令,否则任何扩展都可能使已部署软件失效。RFC 9111 因而给行为型扩展规定了一种组合方式:新指令与旧指令一同发送;不认识新指令的实现采用旧行为,认识新指令的实现知道新规则如何修改旧要求。

在这里,no-store 正是旧行为。它并不与 must-understand 自相矛盾,而是定义最安全的默认路径。旧缓存知道停下;符合条件的新缓存知道在什么狭窄情形下可以移开这道限制。

省略它会改变升级结果。只有 must-understand 时,旧缓存将忽略唯一收到的新信号。它或许会因为其他条件而不存储,但那不再是这套兼容设计提供的保障。

RFC 9111 对“应同时带上 no-store”以及“符合条件的缓存应忽略它”都使用 SHOULD。这个规范词不是随意建议,也不是无条件 MUST。偏离可以有技术理由,却必须讲得清楚;节省几个字节并不能回答旧实现会怎样处理。

移去一道禁止,不等于满足全部条件

“忽略 no-store”常被误写成“允许缓存”。两者不是同一判断。

RFC 9111 第 3 节列出的其他存储条件仍然存在:请求方法必须允许;响应状态必须是最终状态;若为特定状态或存在 must-understand,缓存必须理解状态码;授权与共享缓存规则可能适用;共享缓存仍要服从 private;响应还需要明确控制或状态默认规则提供可缓存依据。

存储之后还有复用。一个已保存响应只有在请求匹配,并且它仍新鲜、已成功验证或符合陈旧响应许可时,才可用于回答后续请求。must-understand 不会令对象自动新鲜,不会完成验证,也不会修正跨用户的错误缓存键。

因此可观测性应当记录一条决策链,而不是 must_understand=true:采用了哪个状态码定义,什么版本和测试证明实现其行为,两条指令是否都在,其他存储条件如何,最终是否写入,以及日后是否复用。只有这样,条件例外才不会在报表里变成永久豁免。

注册表提供坐标,不提供合格证

IANA 的 HTTP 缓存指令注册表分别列出 must-understandno-store,状态码注册表则列出数值、名称与定义文档。它们提供共同坐标,避免命名冲突,并把实现者带到正确的规范。

注册并不认证软件。一个词出现在 IANA 表中,不代表某个二进制已经实现;一个状态码拥有正式条目,也不代表某台缓存落实了它的专门要求。即使一个状态的测试已经通过,也不能推断未来新增状态自动得到支持。

因此,状态行为是实际依赖。模块版本、启用范围、规范版本和测试结果要随能力声明一起保存。must-understand 提供了在协议层询问这项能力的分支点,却不能代替实施证据。

RFC 9111 的变更说明指出,该指令被引入后,缓存通常不必理解新响应状态码的语义,除非响应带有它。这让源站可以表示“本响应的状态特有规则很重要”。源站不能借此远程宣布一台未知缓存已经胜任。

这不是隐私或保密承诺

RFC 9111 明确提醒,no-store 不是可靠或充分的隐私机制。恶意或受控缓存可能不识别、不服从,通信网络也可能被窃听。与 must-understand 配对不会扩大它的安全承诺。

两条指令也不决定谁获准接收响应,不保证共享缓存正确隔离用户,不提供传输保密,更不说明数据落盘后如何保护。若把它当作数据分类印章,反而会掩盖真正负责访问、加密和留存的控制。

本文没有测试任何缓存产品,也没有测量支持率、存储泄漏或事故频率。所有路径都只是依据公开规范构造的分析情形。

来源与证据边界

注册表与勘误状态只代表研究截止时的快照。来源支持协议语义和治理推论,不构成产品普遍支持或合规性的证据。