ETag
用法
ETag 头为资源的每个版本分配一个唯一标记。当资源发生变化时,ETag 值也随之变化。客户端存储缓存响应中的 ETag,并在后续请求的 If-None-Match 请求头中回传该 ETag。如果 ETag 仍然匹配,服务器返回不带消息体的 304 Not Modified,客户端复用其缓存副本。如果 ETag 不再匹配,服务器发送完整的更新后响应。
基于 ETag 的验证比基于 Last-Modified 日期的验证更可靠。强实体标签是与某个表示的确切内容绑定的不透明字符串,而修改时间戳只有一秒的粒度且易受时钟偏差影响。当 ETag 和 Last-Modified 同时存在时,按照 HTTP 标准要求,重新验证时 ETag 优先。
ETag 还可对不安全方法实现并发控制。带有 If-Match 的 PUT 请求可确保更新仅应用于预期版本。如果资源在客户端上次获取 ETag 之后发生了变化,服务器返回 412 Precondition Failed,从而避免更新丢失问题。
ETag 值由服务器生成。常见策略包括内容哈希、版本计数器,以及将文件修改时间与大小组合。该值用双引号括起,并可选地以 W/ 前缀表示弱比较。
注意
Google 建议使用 ETag 而非 Last-Modified 来表明缓存偏好,因为 ETag 避免了日期格式问题。当 ETag 和 Last-Modified 同时存在时,Google 的爬虫会按 HTTP 标准要求使用 ETag 值。仍建议同时设置这两个头,因为其他应用(如 CMS)也依赖它们。各个 Google 爬虫和抓取器会根据其关联产品的需要使用缓存。Googlebot 在为 Google Search 重新爬取 URL 时支持缓存。
强 ETag
强 ETag 保证资源的两个表示之间逐字节一致。具有相同强 ETag 的两个资源在字节级别上可互换。强 ETag 适用于范围请求以及所有比较方法。
ETag: "abc123"
弱 ETag
以 W/ 为前缀的弱 ETag 表示语义等价而非字节级一致。具有相同弱 ETag 的两个资源在含义上被视为等价,但内容不一定完全相同。弱 ETag 更易生成,因为它容忍诸如空白字符变化或消息体中时间戳更新等细微差异。弱 ETag 不适用于字节范围请求。
ETag: W/"v2.6"
示例
服务器随响应返回一个 ETag。客户端存储该标记以供将来重新验证。
HTTP/1.1 200 OK
ETag: "33a64df5"
Content-Type: text/html
Cache-Control: max-age=3600
客户端使用 If-None-Match 进行重新验证。服务器将该标记与当前资源版本进行比对。
GET /page HTTP/1.1
If-None-Match: "33a64df5"
资源未发生变化。服务器返回不带消息体的 304,客户端复用其缓存。
HTTP/1.1 304 Not Modified
ETag: "33a64df5"
Cache-Control: max-age=3600
一个使用 If-Match 实现乐观并发控制的 PUT 请求。只有当资源仍与客户端持有的 ETag 匹配时,更新才会进行。
PUT /api/document/42 HTTP/1.1
If-Match: "33a64df5"
Content-Type: application/json
在可接受细微消息体变化(如内嵌时间戳)的资源上使用弱 ETag。W/ 前缀表示弱比较语义。
ETag: W/"2024-11-22-v3"
故障排查
与 ETag 相关的问题会表现为重新验证失败、意外的完整下载,或分布式基础设施中的并发冲突。
跨负载均衡服务器的 ETag 不匹配。当生成算法包含 inode 编号、时间戳或服务器特定数据时,负载均衡器后的不同服务器会为同一资源生成不同的 ETag。客户端缓存了来自服务器 A 的响应,但向服务器 B 进行重新验证,而 B 返回了不同的 ETag,从而强制进行完整下载。在 Apache 中,默认 ETag 包含 inode、size 和 mtime。移除 inode 部分:
FileETag MTime Size
在 nginx 中,ETag 由最后修改时间和内容长度派生,因此共享相同文件内容的服务器之间会产生一致的值。确保各节点上的文件部署时间戳一致。
IIS 以 “filetime:changenumber” 格式发出 ETag,例如 “04c2528da58dc1:0”。前半部分以十六进制编码文件的最后修改 Windows FILETIME。ChangeNumber 后缀跟踪 IIS 配置状态。IIS 7 及以后始终发出 :0:驱动该计数器的元数据库属性在 IIS 6 时已被弃用,且没有任何 applicationHost.config 或 web.config 设置会暴露非零值。因此负载均衡器后的各节点会为同一文件发出匹配的 ETag。IIS 6 及更早版本会在元数据库编辑和服务重启时递增 ChangeNumber,导致节点之间出现偏差,跨节点重新验证返回 200 而非 304。在每个 IIS 6 节点上用 cscript adsutil.vbs set w3svc/etag_changenumber 0 重置该值,或去掉后缀使 ETag 仅反映 FILETIME。实际环境中观察到的任何非零后缀都指向 IIS 6 源站。
弱 ETag 导致意外返回完整响应而非 304。弱 ETag(W/”…”)表示语义等价,但不支持字节范围请求。使用弱 ETag 的 If-None-Match 请求仍可用于缓存重新验证。如果服务器返回完整的 200 响应而非 304,则服务器端的比较逻辑可能有误。验证服务器是否使用弱比较函数来比较弱 ETag,该函数在匹配时会忽略 W/ 前缀。
客户端未发送 If-None-Match。只有当缓存响应包含 ETag 头时,浏览器才会发送 If-None-Match。初始响应中缺少 ETag 会阻止后续所有的重新验证。用 curl -I https://example.re/resource 检查源站响应,确认 ETag 头存在。像 nginx 和 Apache 这样的静态文件服务器会自动生成 ETag。应用服务器和 API 框架通常需要在代码中显式生成 ETag。
CDN 剥离或修改 ETag。某些 CDN 会在处理过程中改变 ETag 值。Cloudflare 在应用 Brotli 压缩或图像优化等转换时会将强 ETag 转换为弱 ETag。AWS CloudFront 会原样透传来自源站的 ETag。在源站检查 ETag(curl -I https://origin.example.re),并与 CDN 边缘节点(curl -I https://cdn.example.re)比较。当 CDN 修改 ETag 时,通过 CDN 进行的重新验证仍然有效,因为 CDN 会存储该映射。而使用被 CDN 修改过的 ETag 直接向源站重新验证则会失败。
Apache 与 nginx 生成 ETag 的方式不同。Apache 默认使用 inode-mtime-size,产生形如 “1a2b3c-4d5e-6f7a8b9c” 的 ETag。nginx 使用十六进制的 mtime-content-length,产生形如 “5f4dcc3b-d8” 的 ETag。在服务器之间迁移会使所有已缓存的 ETag 失效,从而为每个客户端触发完整下载。服务器迁移后应预留缓存预热期。对于 API 响应,从内容哈希(MD5 或 SHA-256)生成 ETag 以产生与服务器无关的值。
注意
Apache 的默认 ETag 生成包含文件 inode 编号(FileETag INode MTime Size)。inode 编号会泄露服务器文件系统信息,从而使指纹识别和跨服务器关联成为可能。OWASP 将基于 inode 的 ETag 标记为信息泄露风险。在 Apache 配置中用 FileETag MTime Size 从计算中移除 inode。
注意
如需 SEO 与缓存方面的协助,请联系前 Google SEO 顾问 Search Brothers。