协商缓存

协商缓存:资源过期后带验证信息去问服务器,没变返回 304,变了返回新内容。

#tech / dev / frontend #type / concept #status / evergreen #platform / browser #tech / network

[!info] related notes

协商缓存

协商缓存:资源过期后,带验证信息去问服务器是否还能用。

触发条件

强缓存不命中(max-age 过期)后,进入协商缓存流程。

两种验证方式

1. ETag / If-None-Match

# 服务器首次响应
ETag: "abc123"

# 浏览器下次请求
If-None-Match: "abc123"

# 服务器响应
304 Not Modified   # ETag 匹配,资源没变

2. Last-Modified / If-Modified-Since

# 服务器首次响应
Last-Modified: Tue, 07 Apr 2026 10:00:00 GMT

# 浏览器下次请求
If-Modified-Since: Tue, 07 Apr 2026 10:00:00 GMT

# 服务器响应
304 Not Modified   # 时间匹配,资源没变

Strong vs Weak ETag

ETag 有两种强度:

Strong ETag

ETag: "abc123"
  • 保证逐字节完全一致
  • 可用于 If-None-Match(缓存验证)和 If-Range(断点续传)
  • 服务器只有在资源内容完全不变时才应返回相同的 strong ETag

Weak ETag

ETag: W/"abc123"
  • 保证语义等价,但不一定逐字节相同
  • 只能用于 If-None-Match不能用于 If-Range
  • 适用场景:服务器对响应做了 gzip 压缩、去除了空白字符等不影响语义的变换

选择建议

场景用哪种
静态文件哈希Strong ETag
动态生成内容Weak ETag(语义等价即可)
断点续传 / Range 请求必须 Strong ETag
内容可能有微小格式差异Weak ETag

ETag vs Last-Modified

对比ETagLast-Modified
精度基于内容 hash,更精确精确到秒
成本服务器需维护/计算
优先级更高(同时存在时)-

ETag 更精确:因为是版本语义,不只是时间语义。

条件请求:PUT / DELETE 中的验证

条件请求头不仅用于 GET 缓存验证,还用于乐观并发控制

If-Match(PUT / DELETE)

PUT /api/resource/123
If-Match: "abc123"
  • 含义:只有当资源当前 ETag 是 "abc123" 时才执行更新
  • 如果 ETag 不匹配 → 服务器返回 412 Precondition Failed
  • 用途:防止”最后写入者覆盖”问题(Lost Update)

If-None-Match: *(PUT)

PUT /api/resource/new
If-None-Match: *
  • 含义:只有当资源不存在时才创建
  • 如果已存在 → 412 Precondition Failed
  • 用途:REST API 的”仅创建不覆盖”语义

对比

头部方法语义
If-None-MatchGET资源变了才发给我(缓存验证)
If-MatchPUT/DELETE资源没变才能改(并发控制)
If-None-Match: *PUT资源不存在才能创建

边界情况

If-Modified-Since 不能用于 Range 请求

RFC 9110 规定:If-Modified-Since 不得用于字节范围请求。断点续传应使用 If-Range 头部配合 strong ETag。

秒级精度缺失

Last-Modified 精确到秒。如果同一秒内修改了两次,第二次修改无法被检测到。If-Unmodified-Since 也有同样的限制。

Last-Modified 缺失

如果服务器从未返回 Last-Modified,浏览器无法使用 If-Modified-Since。此时只能依赖 ETag。

304 vs 200 决策

服务器何时返回 304,何时返回 200:

条件响应
验证器匹配 + 缓存可用304 Not Modified
验证器不匹配200 OK + 新内容
无法确定等价性200 OK + 新内容
响应头变化影响缓存200 OK + 新内容

304 可以更新缓存头

RFC 9111 Section 4.3.4:304 响应可以携带新的 Cache-ControlExpiresSet-Cookie 等头部来更新已缓存的元数据,无需重新传输资源体。

# 304 响应可以包含
304 Not Modified
Cache-Control: max-age=7200    # 更新缓存有效期
ETag: "def456"                 # 更新 ETag

完整流程

浏览器请求资源

检查强缓存(max-age)

[过期]

带 ETag/Last-Modified 发协商请求

服务器判断

304 → 继续用本地副本(省流量)
200 → 下载新内容,更新缓存

性能价值

虽然发了请求,但:

  • 省流量:304 不带响应体
  • 省带宽:只传验证信息
  • 比直接请求快:服务器处理比完整响应快
创建于 2026/4/7 更新于 2026/5/27