TATECHATLAS
◎ 简体中文
Web 与 API

ETag 与条件请求:验证缓存并保护并发修改

用 If-None-Match 验证缓存,用 If-Match 避免覆盖更新的表示,并正确处理 304 与 412。

本文内容

ETag 是某个资源特定表示的验证器。读取时,客户端可以把收到的标记放入 If-None-Match;修改时,则可以放入 If-Match。对于 GET 或 HEAD,If-None-Match 匹配可能得到 304 Not Modified,从而复用已保存的响应正文。如果 If-Match 前置条件失败,会得到 412 Precondition Failed,此时应重新读取当前表示并协调修改。这两个机制解决不同问题。ETag 不提供访问授权,也不意味着可以把某个用户的私人内容通过共享缓存提供给其他用户。

确认 ETag 对应哪个表示

ETag 对应的是被选中的表示,而不只是 URL。服务器决定它的值,客户端应把它视为不透明的标识符,不自行解释内部含义。它不一定是文件哈希、更新时间或版本号。如果语言或其他请求头会影响响应,表示的选择也必须纳入考虑。实现验证之前,先确认保存了哪份正文、哪个请求选择了它,以及该响应实际携带了哪个验证器。不要只按一个网址推断所有响应都具有相同的内容。

原样保存收到的标记

完整保留收到的值,包括引号和可能存在的 W/ 前缀。教学响应使用 ETag: "version-a"。客户端不应删除引号、根据本地时钟重建标记,也不能根据名字判断版本先后,因为服务器可能采用完全不同的方案。验证器应与对应正文一起保存。不要给所有资源使用一个全局标记,也不要把一种语言的正文与另一种语言的 ETag 配对,否则条件验证即使返回响应,也无法证明保存内容就是正确的表示。

HTTP/1.1 200 OK
ETag: "version-a"
Cache-Control: private, no-cache
Content-Type: application/json

{"title": "Example"}

用 If-None-Match 验证缓存

当缓存响应需要验证时,在 GET 请求的 If-None-Match 中发送它的标记。如果选中表示仍然匹配,服务器可以返回 304;如果表示已经改变,则正常返回所请求的正文,通常是带有新验证器的 200 响应。重新验证可以减少正文的重复传输,但仍然需要向服务器发出请求。根据缓存策略,一份仍然新鲜且允许复用的响应也可能直接从缓存返回,不必每次都进行条件请求。

GET /documents/42 HTTP/1.1
Host: example.com
If-None-Match: "version-a"

处理 304 时保留已有正文

304 响应没有新的表示正文。应保留之前保存的内容,并按照返回的响应头更新相关缓存元数据。不能因为网络响应没有正文,就把已有文档替换为空字符串。如果应用已经丢失对应的缓存正文,只靠 304 无法重新构造资源;需要发出能够取得正文的合适请求,再建立一致的缓存条目。资源地址、表示选择、正文和验证器必须作为关联的一组信息维护,而不是相互独立地随意更新。

HTTP/1.1 304 Not Modified
ETag: "version-a"
Cache-Control: private, no-cache

用 If-Match 保护修改

修改资源时,在 If-Match 中发送用户实际编辑的那份表示的验证器。如果另一个写入者已经更新资源,前置条件可以因不匹配而返回 412。此时应重新获取当前表示,协调双方修改,不能盲目重复同一次写入。服务器也必须将前置条件与更新操作正确地结合执行。仅仅在界面上显示 ETag,并不能防止丢失并发修改;真正保存数据的服务必须提供有效的条件检查。

PUT /documents/42 HTTP/1.1
Host: example.com
If-Match: "version-a"
Content-Type: application/json
Content-Length: 21

{"title": "Updated!"}

区分强验证器与弱验证器

强验证器表达字节级等价;带有 W/ 的弱标记允许更弱的等价关系。If-None-Match 使用弱比较,适合缓存重新验证。If-Match 使用强比较,因此弱 ETag 不能充当匹配的强验证器来保护编辑。手工删除 W/ 不会创造新的保证。如果服务只提供弱标记,应使用它实际支持的修改控制机制,而不是假设任何 ETag 都可以互换使用。先明确服务器提供了哪种语义,再决定客户端怎样处理修改。

同时定义缓存策略

ETag 不替代 Cache-Control 或 Vary。Cache-Control 控制缓存行为;Vary 指出哪些请求头影响表示选择。no-cache 允许存储,但要求复用前进行验证;no-store 则禁止存储响应。针对某个登录用户的内容,还需要适合私有缓存与共享缓存的策略。先确定谁可以保存每个表示、什么时候可以复用,再增加验证器。给未明确的缓存策略补上一个 ETag,本身并不能解决隐私或表示选择的问题。

按步骤诊断条件请求

先检查初始响应的状态、ETag、Cache-Control 和 Vary,再比较资源未变化与表示已变化时的条件读取。遇到修改冲突,检查发送的 If-Match 值和返回状态。日志中记录资源标识与状态,避免不必要的敏感正文。这里的 HTTP 块是教学交换示例,并不是实际执行的网络测试。这样的步骤有助于区分缓存正文缺失、验证器错误,以及服务器没有正确执行前置条件这三类问题。

检查清单

  • 把每个 ETag 与它实际验证的表示一起保存。
  • 收到 304 时保留已有正文,不用空网络正文替换它。
  • 把 412 作为编辑冲突处理,重读之后再决定是否重试。
  • 一起检查 Cache-Control、Vary,以及验证器的强弱类型。

条件请求需要客户端正确保存内容,也需要服务器执行条件。它们不提供授权、机密性或完整的应用冲突解决策略。示例仅说明 HTTP 语义,没有声称测量过流量节约。

参考来源

  1. MDN: HTTP conditional requests ↗
  2. MDN: ETag ↗
  3. RFC 9110: HTTP semantics and conditional requests ↗
  4. RFC 9111: HTTP caching ↗
返回顶部 ↑