缓存

Cache-Control 请求与响应指令全集、Expires/Age 与新鲜度计算公式。

作者 David更新于 第 17 篇(共 22 篇)

规范基准是 RFC 9111。缓存键默认是「请求方法 + 目标 URI」,再由 Vary 扩展。

可存储的前提

缓存 MUST NOT 存储响应,除非全部满足:

  1. 请求方法可缓存(GET/HEAD,或带显式新鲜度的 POST)。
  2. 响应状态码可缓存(见状态码的默认可缓存列表),或有显式指令允许。
  3. 请求与响应中都无 no-store。
  4. 若是共享缓存:响应无 private;且请求无 Authorization(除非响应带 public/s-maxage/must-revalidate)。
  5. 响应有以下之一:Expires、max-age、s-maxage(共享缓存)、可用的启发式依据、public。

Cache-Control 语法

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

指令名大小写不敏感。不认识的指令 MUST 被忽略(不影响其他指令)。delta-seconds 是非负十进制整数,溢出实现上限时按上限处理。

响应指令

指令语义
max-age=N响应在生成后 N 秒内新鲜
s-maxage=N同上,仅对共享缓存,且覆盖 max-age 与 Expires
no-cache可以存储,但复用前 MUST 向源站成功验证
no-storeMUST NOT 存储本请求或响应的任何部分
private仅私有缓存可存储;共享缓存禁止
public即使通常不可缓存(如带 Authorization)也可被共享缓存存储
must-revalidate一旦陈旧,MUST NOT 在未成功验证前使用;源站不可达时 MUST 返回 504
proxy-revalidate同上,仅对共享缓存
must-understand缓存仅在理解该状态码的缓存需求时才可存储;应与 no-store 连用作降级
no-transform中间件 MUST NOT 改动体或 Content-Encoding/Content-Type/Content-Range

no-cache 与 no-store 的区别

高频混淆:

  • no-cache:存,但每次用前必须验证(可命中 304,省带宽)。
  • no-store:根本不存,磁盘上不留痕迹。用于敏感数据。

要「绝对不缓存」应发 Cache-Control: no-store,单发 no-cache 达不到目的。

带参数的限定形式

Cache-Control: no-cache="Set-Cookie"
Cache-Control: private="X-Internal-Id"

仅对列出的字段生效:可以复用缓存,但复用前必须移除这些字段。仅响应指令可带此参数形式;共享缓存必须支持,否则应按无参数形式(更严格)处理。

扩展指令

指令RFC语义
immutable8246新鲜期内即使用户强制刷新也不发验证请求。用于带哈希文件名的静态资源
stale-while-revalidate=N5861陈旧后 N 秒内可先返回旧副本,后台异步验证
stale-if-error=N5861源站返回 5xx/不可达时,N 秒内可继续用旧副本

请求指令

指令语义
max-age=N只接受年龄 ≤ N 秒的副本
max-stale[=N]接受已陈旧的副本;带 N 则限制超期不超过 N 秒;不带值表示接受任意陈旧程度
min-fresh=N要求副本在 N 秒内仍保持新鲜
no-cache强制向源站验证后再返回(浏览器 Ctrl+F5 的行为)
no-store不得存储本次请求与响应
no-transform禁止中间件改动
only-if-cached仅返回已缓存副本;无可用副本时 MUST 返回 504

Expires

Expires = HTTP-date

绝对过期时刻。优先级低于 max-age/s-maxage——两者同时存在时 Cache-Control 胜出(因其不受时钟偏差影响)。

  • 无效日期(含 Expires: 0)MUST 被解析为「已过期」。
  • 早于 Date 表示已过期。
  • 建议值不超过一年后。

Age

Age = delta-seconds

响应字段,由缓存添加,表示该副本自源站生成以来经过的秒数估计值。源站不发 Age;Age: 0 表示刚从源站取回。

新鲜度计算

freshness_lifetime 的取值顺序

1. s-maxage        (仅共享缓存)
2. max-age
3. Expires - Date
4. 启发式估算

命中即停。

current_age

apparent_age          = max(0, response_time - date_value)
corrected_age_value   = age_value + (response_time - request_time)
corrected_initial_age = max(apparent_age, corrected_age_value)
current_age           = corrected_initial_age + (now - response_time)

取 max 的目的是抵消时钟偏差:无论本机时钟快还是源站时钟慢,都取更保守(更老)的估计。

判定

响应新鲜  ⟺  freshness_lifetime > current_age

陈旧后并非不可用——除非有 must-revalidate/no-cache,缓存 MAY 在客户端允许(max-stale)或源站不可达(stale-if-error)时继续返回陈旧副本。

启发式新鲜度

无任何显式过期信息时,缓存 MAY 自行估算。常见做法是取 (Date − Last-Modified) × 10%,并设上限(如 24 小时)。

  • 仅可用于默认可缓存的状态码。
  • RFC 9111 移除了 Warning 字段(原用于标注启发式/陈旧响应),不应再发送或依赖。

验证与更新

副本陈旧后,缓存发条件请求(If-None-Match 优先于 If-Modified-Since):

  • 收到 304 → 用响应头更新已存副本,重置年龄,返回给客户端。
  • 收到 200 → 用新响应替换旧副本。
  • 收到 5xx → 按 stale-if-error 决定返回旧副本还是透传错误;有 must-revalidate 时 MUST 返回 504。

失效(invalidation)

缓存 MUST 在收到非安全方法(POST/PUT/DELETE/PATCH)的成功响应后,失效以下 URI 的已存副本:

  1. 请求的目标 URI。
  2. 响应中 Location 与 Content-Location 所指 URI——仅当其 origin 与目标 URI 相同(防止跨站投毒)。

非成功响应(4xx/5xx)不触发失效。

与 Authorization 的交互

带 Authorization 的请求,其响应默认不可被共享缓存存储。仅当响应显式带 public、s-maxage 或 must-revalidate 时才允许——这是防止把 A 用户的私有数据发给 B 用户的关键防线。

下一篇:认证——挑战语法与 Authorization 字段。

本页目录