Internet Engineering Task Force (IETF) R. Fielding, Editor
Request for Comments: 9111 Adobe
Obsoletes: 7234 M. Nottingham, Editor
STD: 98 Fastly
Category: Standards Track J. Reschke, Editor
ISSN: 2070-1721 greenbytes
June 2022

HTTP 缓存


摘要

超文本传输协议 (HTTP) 是一种用于分布式、协作性超文本信息系统的无状态应用层协议。本文档定义了 HTTP 缓存以及用于控制缓存行为或指示可缓存响应消息的相关头字段。

本文件取代 RFC 7234。

本备忘录的状态

这是互联网标准轨道的文档。

本文件由因特网工程任务组 (IETF) 制作,代表 IETF 社区的共识。它已通过公开审查并已被因特网工程指导小组 (IESG) 批准发布。有关互联网标准的更多信息,请参见 RFC 7841 第 2 节

可在 https://www.rfc-editor.org/info/rfc9111 获取有关本文件当前状态、任何勘误以及如何提供反馈的信息。

Copyright Notice

Copyright (c) 2022 IETF Trust and the persons identified as the document authors. All rights reserved.

This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.

This document may contain material from IETF Documents or IETF Contributions published or made publicly available before November 10, 2008. The person(s) controlling the copyright in some of this material may not have granted the IETF Trust the right to allow modifications of such material outside the IETF Standards Process. Without obtaining an adequate license from the person(s) controlling the copyright in such materials, this document may not be modified outside the IETF Standards Process, and derivative works of it may not be created outside the IETF Standards Process, except to format it for publication as an RFC or to translate it into languages other than English.

1. 介绍

超文本传输协议 (HTTP) 是一种无状态的应用层请求/响应协议,使用可扩展的语义和自描述消息以便与基于网络的超文本信息系统进行灵活交互。它通常用于分布式信息系统,其中使用响应缓存可以提高性能。本文档定义了与缓存和重用响应消息有关的 HTTP 方面。

HTTP 的 cache 是响应消息的本地存储以及控制其存储、检索和删除的子系统。缓存存储可缓存的响应,以在将来的等价请求上减少响应时间和网络带宽消耗。任何客户端或服务器 MAY 使用缓存,尽管在作为隧道时不适用(第 3.7 节,见 [HTTP])。

shared cache 是为多个用户重用而存储响应的缓存;共享缓存通常(但不总是)作为中间体的一部分部署。相反,private cache 专用于单个用户;它们通常作为用户代理的一个组件部署。

HTTP 缓存的目标是通过重用先前的响应消息来满足当前请求,从而显著提高性能。缓存将已存储的响应视为“新鲜的”(在 第 4.2 节 中定义),如果可以在不进行“验证”(向源服务器检查缓存响应对于该请求是否仍然有效)的情况下重用它。一次新鲜的响应因此可以在缓存每次重用时减少延迟和网络开销。当缓存的响应不再新鲜时,如果可以通过验证使其变为新鲜(第 4.3 节)或源不可用(第 4.2.4 节),它仍可能可重用。

本文件取代 RFC 7234,变更汇总见 附录 B

1.1. 需求表示法

在本文档中,关键字 "MUST"、"MUST NOT"、"REQUIRED"、"SHALL"、"SHALL NOT"、"SHOULD"、"SHOULD NOT"、"RECOMMENDED"、"NOT RECOMMENDED"、"MAY" 和 "OPTIONAL" 在并且仅在以全部大写字母出现时,应按 BCP 14 [RFC2119][RFC8174] 中所述进行解释。

第 2 节[HTTP] 中定义了合规性标准并包含有关错误处理的注意事项。

1.2. 语法表示法

本规范使用 [RFC5234] 的扩充巴科斯-诺尔范式 (ABNF) 表示法,并辅以在 [RFC7405] 中定义的字符串大小写敏感记法扩展。

它还使用在 第 5.6.1 节 中定义的列表扩展,该扩展允许使用 "#" 运算符对逗号分隔列表进行紧凑定义(类似于 "*" 运算符表示重复)。附录 A 显示了将所有列表运算符展开为标准 ABNF 表示法后的汇集语法。

1.2.1. 导入规则

下面的核心规则按引用包含,如在 [RFC5234]附录 B.1 中所定义:DIGIT(十进制 0-9)。

[HTTP] 定义了以下规则:

  HTTP-date     = <HTTP-date, see [HTTP], Section 5.6.7>
  OWS           = <OWS, see [HTTP], Section 5.6.3>
  field-name    = <field-name, see [HTTP], Section 5.1>
  quoted-string = <quoted-string, see [HTTP], Section 5.6.4>
  token         = <token, see [HTTP], Section 5.6.2>

1.2.2. 增量秒数

delta-seconds 规则指定一个非负整数,表示以秒为单位的时间。

解析 delta-seconds 值并将其转换为二进制形式的接收方应使用至少具有 31 位非负整数范围的算术类型。如果缓存收到大于其能表示的最大整数的 delta-seconds 值,或者其任何后续计算发生溢出,缓存 MUST 将该值视为 2147483648 (231) 或者它能够方便表示的最大正整数。

2. 缓存操作概述

正确的缓存操作在减少已在缓存中持有的信息传输的同时保留 HTTP 传输的语义。有关一般术语和 HTTP 的核心概念,请参见 第 3 节,见 [HTTP]

尽管缓存是 HTTP 的一个完全 OPTIONAL 功能,但可以假定重用缓存响应是可取的,并且当没有要求或本地配置阻止它时,这种重用是默认行为。因此,HTTP 缓存要求侧重于防止缓存存储不可重用的响应或不恰当地重用已存储的响应,而不是强制缓存始终存储和重用特定响应。

cache key 是缓存用来选择响应的信息,至少由用于检索已存储响应的请求方法和目标 URI 组成;方法决定在何种情况下该响应可用于满足后续请求。然而,如今许多常用的 HTTP 缓存仅缓存 GET 响应,因此仅使用 URI 作为缓存键。

对于受内容协商影响的请求目标,缓存可能存储多个响应。缓存通过将原始请求的某些头字段合并到缓存键中来区分这些响应,使用 Vary 响应头字段中的信息,详见 第 4.1 节

缓存可能将附加内容纳入缓存键。例如,用户代理缓存可能包括引用站点的身份,从而“二重键控”缓存以避免某些隐私风险(参见 第 7.2 节)。

缓存最常见地存储检索请求的成功结果:即对 GET 请求的 200 (OK) 响应,其中包含目标资源的表示(见 第 9.3.1 节,见 [HTTP])。然而,也可以存储重定向、否定结果(例如 404 (Not Found))、不完整结果(例如 206 (Partial Content))以及对 GET 以外的方法的响应(如果该方法的定义允许此类缓存并定义了适合作为缓存键的内容)。

当缓存无法联系源服务器或无法为请求找到转发路径时,缓存为 disconnected。在某些情况下,断开连接的缓存可以提供陈旧的响应(参见 第 4.2.4 节)。

3. 将响应存储在缓存中

缓存 MUST NOT 存储对请求的响应,除非满足以下条件:

  • 请求方法为缓存所理解;

  • 响应状态码为最终状态(参见 第 15 节,见 [HTTP]);

  • 如果响应状态码为 206 或 304,或存在 must-understand 缓存指令(见 第 5.2.2.3 节):缓存理解该响应状态码;

  • 响应中未出现 no-store 缓存指令(参见 第 5.2.2.5 节);

  • 如果缓存为共享:private 响应指令要么不存在,要么允许共享缓存存储经过修改的响应;参见 第 5.2.2.7 节);

  • 如果缓存为共享:请求中不存在 Authorization 头字段(参见 第 11.6.2 节,见 [HTTP]),或者存在明确允许共享缓存的响应指令(参见 第 3.5 节);并且

  • 响应包含以下至少一项:

注意,缓存扩展可以覆盖所列的任何要求;见 第 5.2.3 节

在此上下文中,如果缓存识别某请求方法或某响应状态码并实现所有指定的与缓存相关的行为,则称其“已理解”该方法或状态码。

注意,在正常操作中,一些缓存不会存储既没有缓存验证器也没有显式过期时间的响应,因为此类响应通常不适合存储。然而,并未禁止缓存存储此类响应。

3.1. 存储头部和尾部字段

缓存在存储响应时 MUST 包含所有接收到的响应头字段 — 包括未识别的字段 —;这保证了新的 HTTP 头字段可以成功部署。但作以下例外:

  • The Connection 头字段以及其列出的字段根据 第 7.6.1 节 要求在转发消息前被移除。此操作 MAY 在存储之前完成以实现该要求。
  • 同样,某些字段的语义要求在转发消息前移除这些字段,该操作 MAY 在存储之前完成;示例见 第 7.6.1 节
  • no-cache(参见 第 5.2.2.4 节)和 private(参见 第 5.2.2.7 节)缓存指令可以带有参数,分别阻止所有缓存或共享缓存存储某些头字段。
  • 针对缓存在转发请求时所使用代理特有的头字段 MUST NOT 被存储,除非缓存将该代理的身份纳入缓存键。实际上,这主要限于 Proxy-Authenticate(参见 第 11.7.1 节)、Proxy-Authentication-Info(参见 第 11.7.3 节)和 Proxy-Authorization(参见 第 11.7.2 节)。

缓存 MAY 将尾部字段与头部字段分开存储,也可以丢弃它们。缓存 MUST NOT 将尾部字段与头部字段合并。

3.2. 更新已存储的头字段

缓存在若干情况下需要用另一个(通常是更新的)响应来更新已存储响应的头字段;例如,见第 3.4 节第 4.3.4 节第 4.3.5 节

在这样做时,缓存 MUST 将所提供响应中的每个头字段添加到已存储的响应中,替换已存在的字段值,但有以下例外:

  • 第 3.1 节 (存储头部和尾部字段) 中从存储中例外的头字段,
  • 缓存所依赖的已存响应中的头字段,如下所述,
  • 接收方自动处理并移除的头字段,如下所述,和
  • Content-Length 头字段。

在某些情况下(尤其是在用户代理中),缓存存储的是对接收响应进行处理后的结果,而不是响应本身;更新影响该处理的头字段可能导致不一致行为和安全问题。处于这种情况的缓存 MAY 在例外情况下从更新已存响应中省略这些头字段,但 SHOULD 将此类省略限制为为保证已存响应完整性所必需的字段。

例如,浏览器可能在接收响应时解码内容编码,从而在其已存储的数据与响应的原始元数据之间产生脱节。用不同的 Content-Encoding 头字段更新该已存元数据将是有问题的。同样,浏览器可能存储解析后的 HTML 树而不是响应中接收到的内容;在此情况下更新 Content-Type 头字段也不可行,因为解析时对格式所做的任何假设将不再有效。

此外,一些字段会被 HTTP 实现自动处理并移除,例如 Content-Range 头字段。实现 MAY 在更新时自动省略此类头字段,即使实际未执行该处理。

注意,Content-* 前缀并不表示某个头字段在更新时会被省略;它是 MIME 头字段的约定,而非 HTTP 专用信号。

3.3. 存储不完整的响应

如果请求方法为 GET,响应状态码为 200 (OK),并且已接收了整个响应头部分,则缓存 MAY 存储不完整的响应(见 第 6.1 节,见 [HTTP]),前提是将已存响应记录为不完整。类似地,206 (Partial Content) 响应 MAY 按作不完整的 200 (OK) 响应存储。但如果缓存不支持 RangeContent-Range 头字段,或不理解这些字段中使用的范围单位,则缓存 MUST NOT 存储不完整或部分内容响应。

缓存 MAY 通过发出后续范围请求(见 第 14.2 节,见 [HTTP])并将成功响应与已存响应合并来完成已存的不完整响应,如 第 3.4 节 中定义。除非响应已被补全,或请求是部分请求并指定的范围完全位于不完整响应内,否则缓存 MUST NOT 使用不完整响应来回答请求。缓存 MUST NOT 在未明确使用 206 (Partial Content) 状态码标记的情况下向客户端发送部分响应。

3.4. 合并部分内容

如果连接过早关闭或请求使用了一个或多个 Range 说明符(见 第 14.2 节,见 [HTTP]),响应可能仅传输部分表示。在多次此类传输之后,缓存可能已接收同一表示的若干范围。如果这些部分都共享相同的强验证器并且缓存遵守 第 15.3.7.3 节 中客户端要求,则缓存 MAY 将这些范围合并为单个已存响应,并重用该响应以满足后续请求。

在将新响应与一个或多个已存响应合并时,缓存 MUST 使用新响应中提供的头字段更新已存响应头字段,按 第 3.2 节 的规定。

3.5. 存储对已认证请求的响应

共享缓存 MUST NOT 使用对带有 Authorization 头字段的请求的已缓存响应来满足任何后续请求,除非该响应包含一个 Cache-Control 字段,其带有允许共享缓存存储该响应的响应指令(见 第 5.2.2 节),并且缓存对该响应遵从该指令的要求。

在本规范中,下列响应指令具有此类效果:must-revalidate(参见 第 5.2.2.2 节)、public(参见 第 5.2.2.9 节)和 s-maxage(参见 第 5.2.2.10 节)。

4. 从缓存构建响应

当收到请求时,缓存 MUST NOT 重用已存的响应,除非满足下列条件:

  • 所呈现的目标 URI(第 7.1 节,见 [HTTP])与已存响应的目标 URI 匹配,且

  • 与已存响应关联的请求方法允许将其用于所呈现的请求,且

  • 已存响应指定用于缓存键的请求头字段(如果有)与当前呈现的请求中这些字段相匹配(参见 第 4.1 节),且

  • 已存响应不包含 no-cache 指令(见 第 5.2.2.4 节),除非它已被成功验证(见 第 4.3 节),且

  • 已存响应满足下列之一:

请注意,缓存扩展可以覆盖上述任何要求;见 第 5.2.3 节

当在不进行验证的情况下使用已存响应来满足请求时,缓存 MUST 生成一个 Age 头字段(第 5.1 节),用等于该已存响应 current_age 的值替换响应中任何已存在的 Age 字段;参见 第 4.2.3 节

对于不安全的方法(参见 第 9.2.1 节,见 [HTTP]),缓存 MUST 将请求写通到源服务器;也就是说,缓存不得在将请求转发并收到相应响应之前为此类请求生成回复。

另外,注意不安全的请求可能会使已存响应失效;参见 第 4.4 节

缓存可以使用已存或可存储的响应来满足多个请求,前提是允许将该响应重用于这些请求。这使缓存能够“合并请求”(将多个到达的请求在缓存未命中时合并为单个向源的转发请求),从而减少源服务器和网络的负载。但请注意,如果缓存无法将返回的响应用于某些或全部合并的请求,它仍需要转发这些请求以满足它们,这可能会引入额外的延迟。

当存有多个合适的响应时,缓存 MUST 使用最近的那个(以 Date 头字段确定)。它也可以通过转发带有 "Cache-Control: max-age=0" 或 "Cache-Control: no-cache" 的请求来消除歧义以决定使用哪个响应。

没有时钟的缓存(参见 第 5.6.7 节,见 [HTTP]MUST 在每次使用时重新验证已存响应。

4.1. 使用 Vary 头字段计算缓存键

当缓存收到一个可以由已存响应满足的请求,并且该已存响应包含 Vary 头字段(见 第 12.5.5 节,见 [HTTP])时,除非已呈现请求中由该 Vary 值指定的所有请求头字段与存储该响应时原始请求中的对应字段匹配(即导致缓存该响应的请求),否则缓存 MUST NOT 在不进行重新验证的情况下使用该已存响应。

两个请求的头字段当且仅当可以通过下列任一转换将第一个请求的头字段变换为第二个请求的头字段时,定义为匹配:

  • 添加或移除允许的空白(在头字段语法中允许的情况下);
  • 合并具有相同字段名的多个头字段行(参见 第 5.2 节,见 [HTTP]);
  • 按照头字段规范已知语义等价的方式规范化两个头字段值(例如:在顺序不重要时重新排序字段值;在值被定义为大小写不敏感时进行大小写规范化)。

如果(在可能进行的任何规范化之后)某头字段在请求中缺失,它仅在另一个请求中也缺失时才能匹配。

包含成员 "*" 的 Vary 头字段值的已存响应总是匹配失败。

如果多个已存响应匹配,缓存需要选择一个来使用。当被提名的请求头字段具有已知的偏好排序机制时(例如 Accept 等请求头字段上的 q 值),可以使用该机制来选择首选响应。如果没有此类机制,或导致响应同等优先,则按 Date 头字段选择最近的响应,参见 第 4 节

有些资源在默认响应(即请求不表达任何偏好时发送的响应)中错误地省略了 Vary 头字段,这会导致在后续请求中选择该默认响应,即使存在更优先的响应也会如此。当缓存针对某目标 URI 有多个已存响应且其中一个或多个省略了 Vary 头字段时,缓存 SHOULD 选择最近的、具有有效 Vary 字段值的已存响应(参见 第 4.2.3 节)。

如果没有已存响应匹配,则缓存无法满足该呈现的请求。通常,这个请求会被转发到源服务器,可能在转发时加入用于描述缓存已存哪些响应的前置条件(参见 第 4.3 节)。

4.2. 新鲜度

新鲜的 响应是指其 age 尚未超过其 freshness lifetime 的响应。相反,陈旧的 响应则是已超过该期限的响应。

响应的 freshness lifetime 是其由源服务器生成到其到期时间之间的时长。显式到期时间 是源服务器期望缓存在不进行进一步验证的情况下不再使用已存响应的时间,而 启发式到期时间 则是在没有显式到期时间可用时由缓存分配的时间。

响应的 age 是自响应由源服务器生成或通过与源服务器成功验证以来所经过的时间。

当响应是新鲜的时,可以在不联系源服务器的情况下用于满足后续请求,从而提高效率。

确定新鲜度的主要机制是源服务器在将来的某一时间提供显式到期时间,使用 Expires 头字段(见 第 5.3 节)或 max-age 响应指令(见 第 5.2.2.1 节)。通常,源服务器会为响应分配未来的显式到期时间,认为在到期时间到达之前,表示不太可能在语义上发生显著变化。

如果源服务器希望强制缓存对每个请求都进行验证,它可以将显式到期时间设置为过去的时间以表明响应已经陈旧。合规的缓存通常会在重用陈旧缓存响应之前进行验证(参见 第 4.2.4 节)。

由于源服务器并不总是提供显式到期时间,因此在某些情况下缓存也被允许使用启发式来确定到期时间(参见 第 4.2.2 节)。

确定响应是否新鲜的计算如下:

   response_is_fresh = (freshness_lifetime > current_age)

freshness_lifetime 的定义见 第 4.2.1 节;current_age 的定义见 第 4.2.3 节

客户端可以发送 max-age 或 min-fresh 请求指令(见 第 5.2.1 节)以建议对应响应的新鲜度计算限制。然而,缓存不必强制遵从这些建议。

在计算新鲜度时,为避免常见的日期解析问题:

  • 尽管所有日期格式都被规定为大小写敏感,缓存接收方 SHOULD 以大小写不敏感的方式匹配字段值。
  • 如果缓存接收方的内部时间实现的分辨率低于 HTTP-date 的值,则接收方 MUST 将解析后的 Expires 日期内部表示为小于或等于接收值的最接近时间。
  • 缓存接收方 MUST NOT 允许本地时区影响 age 或过期时间的计算或比较。
  • 缓存接收方 SHOULD 将带有非 "GMT" 时区缩写的日期视为计算过期时无效。

注意,新鲜度仅适用于缓存操作;它不能用来强制用户代理刷新其显示或重新加载资源。有关缓存与历史机制之间差异的解释,见 第 6 节

4.2.1. 计算新鲜度生存期

缓存可以通过评估以下规则并使用第一个匹配项来计算响应的 freshness_lifetime:

  • 如果缓存是共享的并且存在 s-maxage 响应指令(见 第 5.2.2.10 节),则使用其值;或
  • 如果存在 max-age 响应指令(见 第 5.2.2.1 节),则使用其值;或
  • 如果存在 Expires 响应头字段(见 第 5.3 节),则使用其值减去 Date 响应头字段的值(如果不存在 Date,则使用接收消息的时间,参见 第 6.6.1 节);或
  • 否则,响应中没有显式的到期时间。启发式的 freshness lifetime 可能适用;参见 第 4.2.2 节

请注意,此计算旨在通过尽可能使用源服务器提供的时钟信息来减少时钟偏移的影响。

当某指令存在多个值(例如,两行 Expires 头字段或多个 Cache-Control: max-age 指令),应使用第一个出现的值,或将响应视为已陈旧。如果指令相互冲突(例如同时存在 max-age 和 no-cache),应遵从最严格的指令。鼓励缓存将具有无效新鲜度信息的响应(例如 max-age 指令内容非整数)视为已陈旧。

4.2.2. 计算启发式新鲜度

由于源服务器并不总是提供显式到期时间,当未指定显式时间时,缓存 MAY 使用启发式为响应分配到期时间,使用其他字段值(例如 Last-Modified 时间)来估算合理的到期时间。本规范不提供具体算法,但对其结果施加最坏情况约束。

当已存响应中存在显式到期时间时,缓存 MUST NOT 使用启发式来确定新鲜度。由于 第 3 节 中的要求,启发式仅可用于对没有显式新鲜度且其状态码被定义为可启发式缓存的响应(例如见 第 15.1 节)以及对没有显式新鲜度但被明确标记为可缓存(例如带有 public 指令)的响应。

注意,在之前的规范中,可启发式缓存的响应状态码被称为“默认可缓存”。

如果响应具有 Last-Modified 头字段(见 第 8.8.2 节),鼓励缓存使用不超过自该时间以来间隔某一分数的启发式到期值。此分数的典型设置可能是 10%。

4.2.3. 计算 Age

Age 头字段用于在从缓存获取响应消息时传达对其估计的年龄。Age 字段值是缓存估计的自源服务器生成或验证该响应以来经过的秒数。因此,Age 值等于该响应在从源服务器到缓存路径上每个缓存中驻留的时间之和,加上在网络路径上传输的时间。

Age 的计算使用以下数据:

age_value
术语 "age_value" 表示 Age 头字段的值(见 第 5.1 节),以适合算术运算的形式表示;如果不可用则为 0。
date_value
术语 "date_value" 表示 Date 头字段的值,以适合算术运算的形式表示。有关 Date 头字段的定义及对缺失 Date 的响应的要求,请参见 第 6.6.1 节,见 [HTTP]
now
术语 "now" 表示本实现时钟的当前值(参见 第 5.6.7 节,见 [HTTP])。
request_time
发出导致已存响应的请求时的时钟值。
response_time
接收该响应时的时钟值。

响应的年龄可以通过两种完全独立的方式计算:

  1. “apparent_age”:response_time 减去 date_value,如果该实现的时钟与源服务器时钟合理同步;若结果为负,则替换为零。
  2. “corrected_age_value”:如果响应路径上的所有缓存都实现了 HTTP/1.1 或更高版本,则使用该值。缓存 MUST 将此值相对于发起请求的时间解释,而非响应被接收的时间。
  apparent_age = max(0, response_time - date_value);

  response_delay = response_time - request_time;
  corrected_age_value = age_value + response_delay;

在存在可能不会正确插入 Age 的非常老旧缓存实现的情况下,corrected_age_value MAY 被用作 corrected_initial_age。更保守的计算方法为:

  corrected_initial_age = max(apparent_age, corrected_age_value);

已存响应的 current_age 可以通过将自上次由源服务器验证以来经过的时间(以秒为单位)加到 corrected_initial_age 上来计算。

  resident_time = now - response_time;
  current_age = corrected_initial_age + resident_time;

4.2.4. 提供陈旧的响应

“陈旧的”响应是指那些具有显式或可启发式计算的到期信息,但根据 第 4.2 节 的计算并不新鲜的响应。

如果被协议内的显式指令禁止(例如 no-cache 响应指令、must-revalidate 响应指令,或适用的 s-maxage 或 proxy-revalidate 响应指令;参见 第 5.2.2 节),缓存 MUST NOT 生成陈旧响应。

除非缓存处于断开连接状态或客户端或源服务器明确允许(例如通过请求中的 max-stale 指令,见 第 5.2.1 节,或通过如 [RFC5861] 中定义的扩展指令,或根据带外合同的配置),否则缓存 MUST NOT 生成陈旧响应。

4.3. 验证

当缓存对所请求的 URI 存有一个或多个已存响应但不能使用其中任何一个来满足请求(例如因为它们不新鲜或无法选择;见 第 4.1 节)时,它可以在转发请求中使用有条件请求机制(见 第 13 节,见 [HTTP]),以便让下一个接收请求的服务器有机会选择一个可用的已存响应并在此过程中更新存储的元数据,或用新响应替换已存响应。这一过程称为对已存响应进行验证或重新验证。

4.3.1. 发送验证请求

当为验证生成有条件请求时,缓存要么从其正尝试满足的请求开始,要么(如果它独立地发起请求)通过复制已存响应的 method、target URI 和由 Vary 头字段标识的请求头字段来合成一个请求(参见 第 4.1 节)。

然后它用一个或多个前置条件头字段更新该请求。这些字段包含来自具有相同 URI 的已存响应的验证器元数据。通常,这将仅包含具有相同缓存键的已存响应,尽管缓存被允许对它不能选择的响应进行验证(见 第 4.1 节)。

接收方随后比较这些前置条件头字段以确定是否存在与资源的当前表示等价的任何已存响应。

其中一种验证器是 Last-Modified 头字段中给出的时间戳(见 第 8.8.2 节),它可用于 If-Modified-Since 头字段以进行响应验证,或用于 If-Unmodified-SinceIf-Range 头字段以进行表示选择(即客户端特指具有该时间戳的先前获取的表示)。

另一种验证器是 ETag 字段中给出的实体标签(见 第 8.8.3 节)。一个或多个实体标签(表示一个或多个已存响应)可以被用在 If-None-Match 头字段中用于响应验证,或用于 If-MatchIf-Range 头字段用于表示选择(即客户端特指具有所列实体标签的一个或多个先前获取的表示)。

在为验证生成有条件请求时,缓存:

  • MUST 发送相关的实体标签(使用 If-MatchIf-None-MatchIf-Range),如果要验证的已存响应中提供了实体标签。
  • SHOULD 发送 Last-Modified 值(使用 If-Modified-Since),如果请求不是子范围请求、正在验证单个已存响应且该响应包含 Last-Modified 值。
  • MAY 发送 Last-Modified 值(使用 If-Unmodified-SinceIf-Range),如果请求为子范围请求、正在验证单个已存响应,且该响应仅包含 Last-Modified 值(而无实体标签)。

在大多数情况下,即使实体标签明显更优,缓存在验证请求中也会同时生成两种验证器,以允许不理解实体标签前置条件的旧中间件做出适当响应。

4.3.2. 处理收到的验证请求

请求链中的每个客户端可能都有自己的缓存,因此中间缓存常常会收到来自其它(出站)缓存的有条件请求。同样,一些用户代理也使用有条件请求以将数据传输限制为最近修改的表示或完成部分检索的表示的传输。

如果缓存收到的请求可以按 第 4 节 的规定通过重用已存的 200 (OK)206 (Partial Content) 响应来满足,则缓存 SHOULD 使用存储响应内的相应验证器去评估该请求中任何适用的有条件头字段前置条件。

缓存 MUST NOT 评估仅适用于源服务器的有条件头字段、出现在其语义无法用缓存响应满足的请求中,或出现在其目标资源没有已存响应的请求中;此类前置条件很可能是针对其它(入站)服务器的。

缓存正确评估有条件请求取决于接收的前置条件头字段及其优先级。总的来说,If-MatchIf-Unmodified-Since 条件头字段不适用于缓存,而 If-None-Match 优先于 If-Modified-Since。有关前置条件优先级的完整规范,见 第 13.2.2 节

包含 If-None-Match 头字段的请求表明客户端希望将其一个或多个已存响应与缓存选择的已存响应进行比较以验证其有效性(参见 第 4 节)。

如果没有 If-None-Match 头字段,则包含 If-Modified-Since 头字段的请求表明客户端希望按修改日期验证其一个或多个已存响应。

如果请求包含 If-Modified-Since 且已存响应中不存在 Last-Modified 头字段,则缓存 SHOULD 使用已存响应的 Date 字段值(如果没有 Date 字段,则使用接收已存响应的时间)来评估该条件。

实现对范围请求返回部分响应的缓存(见 第 14.2 节)还需要针对缓存所选响应评估接收的 If-Range 头字段(见 第 13.1.5 节)。

当缓存决定为包含 If-None-Match 实体标签列表的请求转发以重新验证其自身已存响应时,缓存 MAY 将接收的列表与自身已存响应(无论新鲜还是陈旧)中的实体标签列表合并,并在转发请求中发送两者的并集作为替换的 If-None-Match 值。如果已存响应仅包含部分内容,则除非请求所需的范围可以被该部分已存响应完全满足,否则缓存 MUST NOT 将其实体标签包含在并集中。如果转发请求的响应为 304 (Not Modified) 且包含一个实体标签,该实体标签不在客户端的列表中,则缓存 MUST 通过重用其相应的已存响应(并用 304 响应的元数据更新之,参见 第 4.3.4 节)为客户端生成一个 200 (OK) 响应。

4.3.3. 处理验证响应

对有条件请求的响应,缓存的处理取决于其状态码:

  • 304 (Not Modified) 表示已存响应可以被更新并重用;见 第 4.3.4 节
  • 完整响应(即包含内容的响应)表明在有条件请求中被提名的已存响应都不合适。缓存 MUST 使用该完整响应来满足请求。缓存 MAY 存储该完整响应,受其约束(见 第 3 节)。
  • 然而,如果缓存在尝试验证响应时收到 5xx (Server Error) 响应,它可以将该响应转发给请求客户端,或视作服务器未响应。在后一种情况下,缓存可以发送先前存储的响应(受其提供陈旧响应的约束,见 第 4.2.4 节),或重试验证请求。

4.3.4. 在验证时刷新已存响应

当缓存收到 304 (Not Modified) 响应时,它需要识别适合用新信息更新的已存响应,然后进行更新。

初始要更新的已存响应集合是那些可被为该请求选用的响应——即符合 第 4 节 中的要求的响应,除最后一项(关于是新鲜的、可作为陈旧提供或刚被验证的要求)之外。

然后,基于下述规则的第一个匹配项进一步过滤该初始集合:

  • 如果新响应包含一个或多个 强验证器(见 第 8.8.1 节),那么每个这些强验证器都标识了要更新的选定表示。初始集合中所有具有相同强验证器的已存响应都被识别为待更新对象。如果初始集合中没有任一项包含至少一个相同的强验证器,则缓存 MUST NOT 使用新响应更新任何已存响应。
  • 如果新响应不包含强验证器但包含一个或多个 弱验证器,且这些验证器与初始集合中的某一已存响应相对应,则选择这些匹配的已存响应中最新的一个进行更新。
  • 如果新响应不包含任何形式的验证器(例如客户端从不是来自 Last-Modified 响应头字段的来源生成了 If-Modified-Since 请求),并且初始集合中只有一个已存响应,且该已存响应也缺乏验证器,则该已存响应被识别为待更新对象。

对于每个被识别的已存响应,缓存 MUST 使用 304 (Not Modified) 响应中提供的头字段来更新其头字段,按 第 3.2 节 的规定。

4.3.5. 使用 HEAD 刷新响应

对 HEAD 方法的响应与等效的 GET 请求的响应相同,但不发送实体主体。HEAD 响应的此属性可用于在更高效的有条件 GET 机制不可用(因为已存响应没有验证器)或即使内容已更改也不希望传输内容时使缓存的 GET 响应失效或更新它。

当缓存为某目标 URI 发起入站 HEAD 请求并收到 200 (OK) 响应时,缓存 SHOULD 更新或使其所有可能被用于该请求的已存 GET 响应失效(见 第 4.1 节)。

对于每个可能被选用的已存响应,如果已存响应与 HEAD 响应在任何接收到的验证器字段(ETagLast-Modified)上具有匹配值,并且如果 HEAD 响应有 Content-Length 头字段,则其值与已存响应的 Content-Length 相匹配,则缓存 SHOULD 按下述方式更新已存响应;否则,缓存 SHOULD 将已存响应视为陈旧。

如果缓存使用 HEAD 响应中提供的元数据更新已存响应,缓存 MUST 使用 HEAD 响应中提供的头字段来更新已存响应(见 第 3.2 节)。

4.4. 使已存响应失效

由于不安全的请求方法(见 第 9.2.1 节)如 PUT、POST 或 DELETE 可能在源服务器上改变状态,中间缓存被要求使已存响应失效以保持其内容最新。

当缓存收到对不安全请求方法的非错误状态码响应(包括安全性未知的方法)时,缓存 MUST 使目标 URI(见 第 7.1 节)失效。

当缓存收到对不安全请求方法的非错误状态码响应时,缓存 MAY 使其它 URI 失效(包括安全性未知的方法)。特别地,响应头字段中的 LocationContent-Location(如果存在)中的 URI 是失效候选;也可能通过本文件未指定的机制发现其它 URI。然而,如果要失效的 URI 的源(见 第 4.3.1 节)与目标 URI 的源不同,则缓存 MUST NOT 在这些条件下触发失效。这有助于防止拒绝服务攻击。

使失效 意味着缓存将删除所有目标 URI 与给定 URI 匹配的已存响应,或将它们标记为“无效”并需要在作为后续请求响应之前进行强制验证。

“非错误响应” 指具有 2xx (Successful)3xx (Redirection) 状态码的响应。

请注意,这并不保证在全局范围内使所有适当的响应失效;一次改变状态的请求仅会使其所经过的缓存中的响应失效。

5. 字段定义

本节定义与缓存相关的 HTTP 字段的语法和语义。

5.1. Age

“Age” 响应头字段传达发送方对响应自源服务器生成或成功验证以来所经过时间的估计。Age 值按 第 4.2.3 节 所述进行计算。

Age 字段值为非负整数,表示以秒为单位的时间(参见 第 1.2.2 节)。

尽管它被定义为单例头字段,但当缓存遇到具有基于列表的 Age 字段值的消息时,SHOULD 使用该字段值的第一个成员,并丢弃后续成员。

如果字段值(按上述丢弃附加成员后)无效(例如包含非非负整数的内容),缓存 SHOULD 忽略该字段。

Age 头字段的存在意味着该响应并非为此请求由源服务器生成或验证。然而,缺少 Age 头字段并不意味着已联系源服务器。

5.2. Cache-Control

“Cache-Control” 头字段用于列出沿请求/响应链的缓存应遵循的指令。缓存指令是单向的:在请求中出现某个指令并不意味着响应中也存在或复制了相同的指令。

关于如何处理在其他地方定义的 Cache-Control 指令的信息,请参见 第 5.2.3 节

无论是否实现缓存,代理在转发消息时 MUST 将缓存指令一并转发,不论这些指令对该应用是否重要,因为这些指令可能适用于请求/响应链上的所有接收者。无法将指令定向到特定缓存。

缓存指令由一个 token 标识,比较时应不区分大小写,并且可以有一个可选参数,该参数可以使用 token 或 quoted-string 语法。对于下文定义且带参数的指令,接收者应接受这两种形式,即使在生成时要求特定形式也应如此。

对于下文定义的缓存指令,除非另有说明,否则不定义(也不允许)参数。

5.2.1. 请求指令

本节定义缓存请求指令。它们是建议性的;缓存 MAY 实现它们,但并非必须。

5.2.1.1. max-age

参数语法:

max-age 请求指令表示客户端偏好响应的 age 小于或等于指定的秒数。除非同时存在 max-stale 请求指令,否则客户端不希望接收陈旧响应。

该指令使用参数语法的 token 形式:例如 'max-age=5' 而不是 'max-age="5"'。发送者 MUST NOT 生成带引号的形式。

5.2.1.2. max-stale

参数语法:

max-stale 请求指令表示客户端将接受已超过其新鲜寿命的响应。如果存在值,则客户端愿意接受超过新鲜寿命不多于指定秒数的响应。如果没有为 max-stale 指定值,则客户端将接受任何年龄的陈旧响应。

该指令使用参数语法的 token 形式:例如 'max-stale=10' 而不是 'max-stale="10"'。发送者 MUST NOT 生成带引号的形式。

5.2.1.3. min-fresh

参数语法:

min-fresh 请求指令表示客户端偏好响应的 freshness lifetime 至少等于其当前 age 加上指定的秒数。也就是说,客户端希望响应在指定秒数内仍然保持新鲜。

该指令使用参数语法的 token 形式:例如 'min-fresh=20' 而不是 'min-fresh="20"'。发送者 MUST NOT 生成带引号的形式。

5.2.1.4. no-cache

no-cache 请求指令表示客户端偏好在未在源服务器上成功验证的情况下不使用已存的响应来满足该请求。

5.2.1.5. no-store

no-store 请求指令表示缓存 MUST NOT 存储该请求的任何部分或其任何响应。该指令适用于私有和共享缓存。“MUST NOT store” 在此上下文中意味着缓存 MUST NOT 故意将信息存入非易失性存储,并且 MUST 在转发后尽最大努力尽快从易失性存储中删除该信息。

该指令不是确保隐私的可靠或充分机制。特别是,恶意或被攻破的缓存可能不会识别或遵守该指令,通信网络也可能易受窃听攻击。

注意,如果包含该指令的请求是从缓存中满足的,则 no-store 请求指令不适用于已存在的已存响应。

5.2.1.6. no-transform

no-transform 请求指令表示客户端请求中间体避免对内容进行转换,按 第 7.7 节 所定义。

5.2.1.7. only-if-cached

only-if-cached 请求指令表示客户端仅希望获得已存响应。遵守此请求指令的缓存 SHOULD 在接收到它时,要么返回与请求其他约束一致的已存响应,要么返回 504 (Gateway Timeout) 状态码。

5.2.2. 响应指令

本节定义缓存响应指令。缓存 MUST 遵守本节定义的 Cache-Control 指令。

5.2.2.1. max-age

参数语法:

max-age 响应指令表示当响应的 age 大于指定的秒数时,该响应被视为陈旧。

该指令使用参数语法的 token 形式:例如 'max-age=5' 而不是 'max-age="5"'。发送者 MUST NOT 生成带引号的形式。

5.2.2.2. must-revalidate

must-revalidate 响应指令表示一旦响应变为陈旧,缓存 MUST NOT 在未被源成功验证之前重用该响应,以满足另一个请求(按 第 4.3 节 的定义)。

must-revalidate 指令对于支持某些协议特性的可靠运行是必要的。在所有情况下,缓存 MUST NOT 忽略 must-revalidate 指令;特别地,如果缓存处于断开状态,缓存 MUST 生成错误响应而不是重用陈旧响应。生成的状态码 SHOULD504 (Gateway Timeout),除非另有更适用的错误状态码。

只有在未能验证请求可能导致不正确操作(例如未显式执行的金融交易)时,服务器才应使用 must-revalidate 指令。

must-revalidate 指令还允许共享缓存在满足带有 Authorization 头字段的请求时重用响应,前提是遵守上述关于重新验证的要求(见 第 3.5 节)。

5.2.2.3. must-understand

must-understand 响应指令将响应的缓存限制为理解并遵从该响应状态码要求的缓存。

包含 must-understand 指令的响应 SHOULD 也包含 no-store 指令。当实现 must-understand 指令的缓存收到包含该指令的响应时,如果该缓存理解并实现该状态码的缓存要求,则该缓存 SHOULD 忽略 no-store 指令。

5.2.2.4. no-cache

参数语法:

未限定形式的 no-cache 响应指令(无参数)表示该响应 MUST NOT 在未转发以进行验证并收到成功响应的情况下被用于满足任何其他请求;参见 第 4.3 节

这允许源服务器阻止缓存在未联系其的情况下使用响应来满足请求,即使是被配置为发送陈旧响应的缓存也无法绕过这一点。

带参数的 no-cache 响应指令(列出一个或多个字段名)表示缓存 MAY 在满足其他缓存限制的前提下,若后续响应中去除了列出的头字段,或后续响应已被源成功重新验证(更新或移除了那些字段),则可以使用该响应来满足后续请求。这允许源服务器阻止响应中特定头字段的重用,同时仍允许缓存响应的其余部分。

指定的字段名不限于本规范定义的头字段集合。字段名不区分大小写。

该指令使用参数语法的 quoted-string 形式。发送者 SHOULD NOT 生成 token 形式(即使在单条目列表看似不需要引用时)。

5.2.2.5. no-store

no-store 响应指令表示缓存 MUST NOT 存储立即请求或响应的任何部分,且 MUST NOT 将该响应用于满足任何其他请求。

该指令适用于私有和共享缓存。“MUST NOT store” 在此上下文中意味着缓存 MUST NOT 故意将信息存入非易失性存储,并且 MUST 在转发后尽最大努力尽快从易失性存储中删除该信息。

该指令不是确保隐私的可靠或充分机制。恶意或被攻破的缓存可能不会识别或遵守该指令,通信网络也可能易受窃听攻击。

注意 must-understand 缓存指令在某些情况下会覆盖 no-store;参见 第 5.2.2.3 节

5.2.2.6. no-transform

no-transform 响应指令表示中间体(无论是否实现缓存) MUST NOT 对内容进行转换,按 第 7.7 节 的定义。

5.2.2.7. private

参数语法:

未限定的 private 响应指令表示共享缓存 MUST NOT 存储该响应(即该响应针对单个用户)。它还表示私有缓存 MAY 存储该响应,受 第 3 节 所定义的约束, 即使该响应否则不会被私有缓存启发式缓存。

如果存在带参数的 private 响应指令,参数列出一个或多个字段名,则只有列出的头字段限制为单个用户:共享缓存 MUST NOT 存储这些在原始响应中存在的列出字段,但在不包含这些头字段的情况下,MAY 存储响应的其余部分,受 第 3 节 中定义的约束。

指定的字段名不限于本规范定义的头字段集合。字段名不区分大小写。

该指令使用参数语法的 quoted-string 形式。发送者 SHOULD NOT 生成 token 形式(即使在单条目列表看似不需要引用时)。

5.2.2.8. proxy-revalidate

proxy-revalidate 响应指令表示一旦响应变为陈旧,共享缓存 MUST NOT 在未被源成功验证之前重用该响应,以满足另一个请求(按 第 4.3 节 的定义)。这类似于 must-revalidate,但 proxy-revalidate 不适用于私有缓存。

注意单独的 proxy-revalidate 并不意味着响应可被缓存。例如,它可能与 public 指令结合使用,允许响应被缓存同时要求共享缓存在陈旧时重新验证。

5.2.2.9. public

public 响应指令表示缓存 MAY 存储该响应,即使按 第 3 节 的规则原本不允许存储,前提是遵守该节定义的约束。换言之,public 明确将响应标记为可缓存。例如,public 允许共享缓存在存在 Authorization 头字段的请求的响应上重用该响应(见 第 3.5 节)。

注意,对于已根据 第 3 节 可缓存的响应,不需要再添加 public 指令。

如果带有 public 指令的响应没有显式的新鲜度信息,则它是可启发式缓存的(见 第 4.2.2 节)。

5.2.2.10. s-maxage

参数语法:

s-maxage 响应指令表示对于共享缓存,该指令指定的最大年龄覆盖由 max-age 指令或 Expires 头字段指定的最大年龄。

s-maxage 指令包含了 proxy-revalidate 指令的语义(见 第 5.2.2.8 节)。共享缓存 MUST NOT 在未被源成功验证之前重用带有 s-maxage 的陈旧响应以满足另一个请求(按 第 4.3 节 的定义)。该指令还允许共享缓存在满足最大年龄和重新验证要求的前提下,重用对带有 Authorization 头字段的请求的响应(见 第 3.5 节)。

该指令使用参数语法的 token 形式:例如 's-maxage=10' 而不是 's-maxage="10"'。发送者 MUST NOT 生成带引号的形式。

5.2.3. 扩展指令

Cache-Control 头字段可以通过使用一个或多个扩展缓存指令来扩展。缓存 MUST 忽略未识别的缓存指令。

信息性扩展(不要求改变缓存行为的)可以在不改变其他指令语义的情况下添加。

行为性扩展旨在通过作为现有缓存指令的修饰符发挥作用。既提供新指令,也提供旧指令,这样不理解新指令的应用将默认采用旧指令指定的行为,而理解新指令的应用将识别其为对旧指令相关要求的修改。通过这种方式,可以在不破坏已部署缓存的情况下对现有缓存指令进行扩展。

例如,考虑一个假设的新响应指令 "community",它作为 private 指令的修饰符:除了私有缓存之外,只有被命名社区的成员共享的缓存才被允许缓存该响应。希望允许 UCI 社区在其共享缓存中使用原本为私有的响应的源服务器可以包含

Cache-Control: private, community="UCI"

识别此类 community 缓存指令的缓存可以根据该扩展扩展其行为。不识别 community 指令的缓存将忽略它并遵守 private 指令。

新的扩展指令应考虑定义:

  • 指令被多次指定时的含义,
  • 当指令不带参数时,如果出现参数其含义,
  • 当指令需要参数时,缺失参数时的含义,和
  • 该指令是特定于请求、特定于响应,还是两者均可使用。

5.2.4. 缓存指令注册表

“超文本传输协议 (HTTP) 缓存指令注册表”定义了缓存指令的命名空间。该注册表已在 https://www.iana.org/assignments/http-cache-directives 创建并维护。

一次注册 MUST 包含下列字段:

  • 缓存指令名称
  • 指向规范文本的指针

要添加到该命名空间的值需要 IETF 审查(参见 [RFC8126]第 4.8 节)。

5.3. Expires

“Expires” 响应头字段给出响应被视为陈旧的日期/时间。有关新鲜度模型的进一步讨论,见 第 4.2 节

Expires 头字段的存在并不意味着原始资源会在该时间点之前、之后或在该时间点发生变化或停止存在。

Expires 字段值为 HTTP-date 时间戳,定义见 第 5.6.7 节。另请参见 第 4.2 节 中针对缓存的解析要求。

例如

Expires: Thu, 01 Dec 1994 16:00:00 GMT

缓存接收者 MUST 将无效的日期格式(尤其是值 "0")解释为表示过去的时间(即“已过期”)。

如果响应包含带有 max-age 指令的 Cache-Control 头字段(见 第 5.2.2.1 节),接收者 MUST 忽略 Expires 头字段。同样地,如果响应包含 s-maxage 指令(见 第 5.2.2.10 节),共享缓存接收者 MUST 忽略 Expires 头字段。在这两种情况下,Expires 中的值仅供尚未实现 Cache-Control 头字段的接收者使用。

没有时钟的源服务器(参见 第 5.6.7 节MUST NOT 生成 Expires 头字段,除非其值表示固定的过去时间(始终已过期),或该值已由具有时钟的系统与资源相关联。

历史上,HTTP 要求 Expires 字段值不超过未来一年。虽然现在不再禁止更长的新鲜寿命,但极大的值已被证明会引起问题(例如使用 32 位整数表示时间时的时钟溢出),且许多缓存会在更早时间逐出响应。

5.4. Pragma

“Pragma” 请求头字段是在 HTTP/1.0 缓存中定义的,以便客户端能够指定 “no-cache” 请求(因为在 HTTP/1.1 之前未定义 Cache-Control)。

然而,Cache-Control 的支持现已广泛。因此,本规范弃用 Pragma。

5.5. Warning

“Warning” 头字段曾用于传达关于消息状态或转换的附加信息,这些信息可能未反映在状态码中。本规范将其作废,因为它并不常被生成或呈现给用户。其所携带的信息可以通过检查其他头字段(例如 Age)来推断。

6. 与应用程序和其他缓存的关系

使用 HTTP 的应用程序常常指定其他形式的缓存。例如,Web 浏览器通常具有历史机制(例如“后退”按钮),可用于重新显示会话中先前检索的表示。

同样,一些 Web 浏览器在页面视图内对图像和其他资源实现缓存;它们可能会也可能不会遵循 HTTP 缓存语义。

本规范中的要求不一定适用于应用程序在从 HTTP 缓存检索数据后如何使用这些数据。例如,历史机制可以显示已过期的先前表示,应用程序也可以在超出其新鲜度生存期后以其他方式使用已缓存的数据。

本规范并不禁止应用程序将 HTTP 缓存纳入考虑;例如,历史机制可能会告知用户某个视图已过期,或可能遵守缓存指令(例如 Cache-Control: no-store)。

但是,当应用程序缓存数据且未向用户显式表明或便于用户控制时,强烈建议其基于 HTTP 缓存指令来定义其操作,以免令期望遵守缓存语义的作者感到意外。例如,定义一个“位于”HTTP 之上的应用程序缓存,以允许对包含 Cache-Control: no-store 的响应在与获取它的请求直接相关的请求(例如在同一页面加载期间创建的那些请求)中重用,可能是合理的;但如果允许在与获取该响应的请求无关的请求中重用,则很可能令用户和作者感到惊讶和困惑。

7. 安全性考虑

本节旨在告知开发者、信息提供者和用户与 HTTP 缓存相关的已知安全问题。更一般的安全性考虑在 “HTTP/1.1” (第 11 节,见 [HTTP/1.1])和 “HTTP Semantics” (第 17 节,见 [HTTP])中讨论。

缓存增加了额外的攻击面,因为缓存内容对恶意利用者来说是有吸引力的目标。由于缓存内容在 HTTP 请求完成后仍然存在,对缓存的攻击可能在用户认为信息已从网络中移除很久之后仍会泄露信息。因此,缓存内容需要被作为敏感信息来保护。

特别地,由于私有缓存仅限于单个用户,它们可以被用来重建用户的活动。因此,用户代理应允许终端用户控制私有缓存,例如允许为某些或所有源服务器移除已存的响应。

7.1. 缓存投毒

在缓存中存储恶意内容可以扩展攻击者影响多个用户的能力。所谓的“缓存投毒”攻击发生在攻击者利用实现缺陷、提升的权限或其它技术将响应插入缓存时。当使用共享缓存来向许多客户端分发恶意内容时,这种攻击尤其有效。

缓存投毒的一个常见攻击向量是利用代理和用户代理在消息解析上的差异;有关 HTTP/1.1 的相关要求,请参见 第 6.3 节(见 [HTTP/1.1])。

7.2. 计时攻击

由于缓存的主要用途之一是优化性能,其使用可以“泄露”有关哪些资源先前被请求的信息。

例如,如果用户访问了某站点且其浏览器对其某些响应进行了缓存,然后导航到第二个站点,该站点可以尝试加载它知道存在于第一个站点的响应。如果这些响应加载很快,则可以推断用户曾访问过该站点,甚至是该站点上的特定页面。

可以通过向缓存键添加更多信息来减轻此类“计时攻击”,例如添加引用站点的身份(以防止上述攻击)。这有时被称为“二重键控”。

7.3. 敏感信息的缓存

实现和部署缺陷(常由对缓存操作的误解引起)可能导致被认为是私密的敏感信息(例如验证凭据)被缓存,从而将其暴露给未授权方。

请注意,Set-Cookie 响应头字段([COOKIE])并不会阻止缓存;带有 Set-Cookie 头字段的可缓存响应可以(并且通常会)用于满足对缓存的后续请求。希望控制这些响应缓存的服务器应当发送适当的 Cache-Control 响应头字段。

8. IANA 注意事项

以下注册的变更控制者为:“IETF (iesg@ietf.org) - Internet Engineering Task Force”。

8.1. 字段名称注册

IANA 已在 https://www.iana.org/assignments/http-fields 更新了“超文本传输协议 (HTTP) 字段名称注册表”,如 第 18.4 节 所述(见 [HTTP]),并在下表中列出了字段名称:

表 1
字段名称 状态 注释
Age permanent 5.1
Cache-Control permanent 5.2
Expires permanent 5.3
Pragma deprecated 5.4
Warning obsoleted 5.5

8.2. 缓存指令注册

IANA 已在 https://www.iana.org/assignments/http-cache-directives 更新了“超文本传输协议 (HTTP) 缓存指令注册表”,并根据 第 5.2.4 节 的注册程序以及下表中汇总的缓存指令名称进行了更新。

表 2
缓存指令
max-age 5.2.1.1, 5.2.2.1
max-stale 5.2.1.2
min-fresh 5.2.1.3
must-revalidate 5.2.2.2
must-understand 5.2.2.3
no-cache 5.2.1.4, 5.2.2.4
no-store 5.2.1.5, 5.2.2.5
no-transform 5.2.1.6, 5.2.2.6
only-if-cached 5.2.1.7
private 5.2.2.7
proxy-revalidate 5.2.2.8
public 5.2.2.9
s-maxage 5.2.2.10

8.3. Warn 代码注册表

IANA 已在 “超文本传输协议 (HTTP) Warn 代码” 注册表(位于 https://www.iana.org/assignments/http-warn-codes)中添加了如下注释,声明 “Warning” 已被作废:

Warning 头字段(以及其使用的 warn 代码)已根据 [RFC9111] 在 HTTP 中被作废。

9. 参考文献

9.1. 规范性参考文献

[HTTP]
Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., “HTTP Semantics”, STD 97, RFC 9110, DOI 10.17487/RFC9110, 2022 年 6 月。
[RFC2119]
Bradner, S., “在 RFC 中指示要求级别的关键词”, BCP 14, RFC 2119, DOI 10.17487/RFC2119, 1997 年 3 月, <https://www.rfc-editor.org/info/rfc2119>。
[RFC5234]
Crocker, D., Ed. and P. Overell, “语法规范的扩展 BNF:ABNF”, STD 68, RFC 5234, DOI 10.17487/RFC5234, 2008 年 1 月, <https://www.rfc-editor.org/info/rfc5234>。
[RFC7405]
Kyzivat, P., “ABNF 中对大小写敏感字符串的支持”, RFC 7405, DOI 10.17487/RFC7405, 2014 年 12 月, <https://www.rfc-editor.org/info/rfc7405>。
[RFC8174]
Leiba, B., “RFC 2119 关键词中大写与小写的歧义”, BCP 14, RFC 8174, DOI 10.17487/RFC8174, 2017 年 5 月, <https://www.rfc-editor.org/info/rfc8174>。

9.2. 参考资料

Barth, A., “HTTP 状态管理机制”, RFC 6265, DOI 10.17487/RFC6265, 2011 年 4 月, <https://www.rfc-editor.org/info/rfc6265>。
[HTTP/1.1]
Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., “HTTP/1.1”, STD 99, RFC 9112, DOI 10.17487/RFC9112, 2022 年 6 月。
[RFC2616]
Fielding, R., Gettys, J., Mogul, J., Frystyk, H., Masinter, L., Leach, P., and T. Berners-Lee, “超文本传输协议 -- HTTP/1.1”, RFC 2616, DOI 10.17487/RFC2616, 1999 年 6 月, <https://www.rfc-editor.org/info/rfc2616>。
[RFC5861]
Nottingham, M., “用于陈旧内容的 HTTP Cache-Control 扩展”, RFC 5861, DOI 10.17487/RFC5861, 2010 年 5 月, <https://www.rfc-editor.org/info/rfc5861>。
[RFC7234]
Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., “超文本传输协议 (HTTP/1.1):缓存”, RFC 7234, DOI 10.17487/RFC7234, 2014 年 6 月, <https://www.rfc-editor.org/info/rfc7234>。
[RFC8126]
Cotton, M., Leiba, B., and T. Narten, “在 RFC 中编写 IANA 注意事项部分的指南”, BCP 26, RFC 8126, DOI 10.17487/RFC8126, 2017 年 6 月, <https://www.rfc-editor.org/info/rfc8126>。

附录 A. 汇总的 ABNF

在下面的汇总 ABNF 中,列表规则按照 第 5.6.1 节 的规定展开,见 [HTTP]

Age = delta-seconds

Cache-Control = [ cache-directive *( OWS "," OWS cache-directive ) ]

Expires = HTTP-date

HTTP-date = <HTTP-date, see [HTTP], Section 5.6.7>

OWS = <OWS, see [HTTP], Section 5.6.3>

cache-directive = token [ "=" ( token / quoted-string ) ]

delta-seconds = 1*DIGIT

field-name = <field-name, see [HTTP], Section 5.1>

quoted-string = <quoted-string, see [HTTP], Section 5.6.4>

token = <token, see [HTTP], Section 5.6.2>

附录 B. 来自 RFC 7234 的变更

对重复和冲突的缓存指令的处理已澄清。(第 4.2.1 节

对 Location 和 Content-Location 头字段中 URI 的缓存失效现在不再是必需的,但仍然允许。(第 4.4 节

当源不同(以前是主机)时,禁止对 Location 和 Content-Location 头字段中 URI 的缓存失效。(第 4.4 节

对无效和多个 Age 头字段值的处理已澄清。(第 5.1 节

本规范定义的一些缓存指令现在更强烈地禁止生成其值的带引号形式,因为发现这会造成互操作性问题。扩展缓存指令的使用者不再被要求接受 token 与 quoted-string 两种形式,但仍需对未知扩展正确解析。(第 5.2 节

public 与 private 缓存指令已澄清,使其不再在任意条件下将响应标记为可重用。(第 5.2.2 节

引入了 must-understand 缓存指令;除非存在该指令,否则缓存不再被要求理解新响应状态码的语义。(第 5.2.2.3 节

Warning 响应头已被作废。通过检查响应本身可以推断出 Warning 支持的大部分信息,而剩余的信息——尽管可能有用——完全是建议性的。实际上,缓存或中间体很少添加 Warning。(第 5.5 节

致谢

参见 “致谢”附录,来源为 [HTTP],同样适用于本文件。

索引

A C E F G H M N O P R S V W

作者地址

Roy T. Fielding (editor)
Adobe
345 Park Ave
San Jose, CA 95110
United States of America
Email: fielding@gbiv.com
URI: https://roy.gbiv.com/
Mark Nottingham (editor)
Fastly
Prahran
Australia
Email: mnot@mnot.net
URI: https://www.mnot.net/
Julian Reschke (editor)
greenbytes GmbH
Hafenweg 16
48155 Münster
Germany
Email: julian.reschke@greenbytes.de
URI: https://greenbytes.de/tech/webdav/