功能说明
这组函数覆盖 HTTP 缓存的两个方向:cacheHeaders 根据响应体和策略生成 Cache-Control、ETag 和可选的 Last-Modified;evaluateConditionalRequest 读取请求中的条件头,判断是否可以返回 304,还是需要发送完整响应。它们适合静态资源服务、API 响应和任何需要减少重复传输的 Node.js 服务。
ETag 使用 SHA-256 摘要并截取为较短的 base64url 字符串,支持弱验证器。Cache-Control 生成器校验 max-age、s-maxage、stale-while-revalidate 和 immutable 的组合,避免产生语义矛盾的响应头。条件评估遵循 ETag 优先于修改时间的原则,并且只对 GET 和 HEAD 返回 304。
关键代码
条件请求的核心是先做弱比较,再回退到秒级修改时间比较:
if (ifNoneMatch !== undefined && etag !== undefined) {
const candidates = ifNoneMatch.split(",").map((tag) => tag.trim());
notModified =
candidates.includes("*") ||
candidates.some((tag) => opaqueTag(tag) === opaqueTag(etag));
}
opaqueTag 去掉 W/ 前缀,因此强弱 ETag 在 If-None-Match 的弱比较中都能匹配;返回 304 时只保留 ETag、Cache-Control 和 Last-Modified 等缓存相关头。
使用注意
ETag 基于完整响应体计算,大文件或流式响应会带来额外 CPU 和内存成本,生产环境可改用文件元数据或版本号生成。Cache-Control 的 immutable 要求 max-age 大于零,否则函数会抛错。stale-while-revalidate 只应配合可缓存响应使用,并且需要上游缓存支持。
Last-Modified 的时间比较按秒向下取整,避免毫秒差异导致永远判定为已修改。若请求同时带 If-None-Match 和 If-Modified-Since,且响应有 ETag,则只按 ETag 判断,这是 RFC 规定的优先级。304 响应不应包含响应体;本示例只返回状态和头,调用方需要自行确保不写 body。非 GET/HEAD 请求一律返回 200,不参与条件缓存。
验证方式
示例先为一个 JSON 响应生成缓存头,打印出 public, max-age=60, stale-while-revalidate=30 和 ETag。随后分别用空请求头、匹配的 If-None-Match、匹配的 Last-Modified 以及 POST 请求调用评估函数,状态应依次为 200、304、304、200。可以修改响应体后复用旧 ETag,确认返回 200 而不是错误地命中缓存。