← 中文资源

简体中文版本

为只读接口增加 ETag 协商缓存

生成稳定的实体标签,用 If-None-Match 返回 304,并在压缩或 CDN 改写字节时仍保持校验器语义正确。

阅读英文原文

概述

ETag 是某个具体表述的不透明校验器。客户端带着 If-None-Match 再次请求时,服务端只需返回 304 Not Modified 与响应头,一次 80 KB 的 JSON 下载就变成几百字节,同时客户端仍能立刻感知内容变化。与设置较长的 max-age 不同,协商缓存不会推迟更新。

步骤

  1. 明确校验器强度:只有字节完全一致才用强校验器;动态压缩或经过 CDN 改写时应使用弱校验器,If-None-Match 本身也按弱比较处理。
  2. 基于规范化后的内容生成标签,排除 updatedAt、trace ID 等易变字段,并按主键稳定排序。
  3. 设置 Cache-Control: no-cache 与 ETag,用 Express 的 req.fresh 判断是否返回 304:
app.get('/api/orders', async (req, res) => {
  const rows = await listOrdersCanonical();
  res.set('Cache-Control', 'no-cache');
  res.set('ETag', weakTag(rows));
  if (req.fresh) return res.status(304).end();
  res.json(rows);
});
  1. 若用数据库版本列派生标签,必须保证每次写入都递增版本,否则客户端会在合法 304 后面缓存到旧数据。

验证

先请求一次拿到 ETag,再带上 If-None-Match 请求,应得到 304 且 size_download 为 0。检查 304 响应仍然重复 ETag、Cache-Control 与 Vary,且没有响应体。连续请求同一份未变化的数据,重启进程后标签也应保持一致。

注意事项

  • 服务端必须先构造完整表述才能计算哈希,因此 ETag 无法降低 CPU 密集型接口的开销。
  • 标签只对应一个 URL,查询参数变化必须改变响应内容,从而改变标签。
  • 序列化格式变更会让所有标签同时失效,这可以接受,但部署后应观察 304 比例确认缓存正常再验证。
  • 304 若错误地带上响应体,会同时浪费带宽并让部分客户端解析失败。