3. HTTP 缓存机制:核心头与验证流程

在 OpenAPI 契约的缓存与权限验证场景中,HTTP 缓存机制是减少重复请求、降低后端负载、提升响应速度的关键技术。本节将系统讲解 Cache-Control、Expires、ETag / If-None-Match、Last-Modified / If-Modified-Since 四组核心头部的定义、优先级与协作流程。

3.1 缓存头部概览与优先级

HTTP 缓存头部按控制粒度分为“绝对过期”与“条件验证”两类。下表列出各头部的作用与优先级关系:

头部名称 方向 作用 优先级
Cache-Control 请求 / 响应 指令式控制缓存行为(max-age、no-cache、no-store 等) 最高
Expires 响应 指定绝对过期时间(HTTP/1.0 遗留) 低于 Cache-Control
ETag 响应 资源唯一标识(哈希或版本号) 条件验证
If-None-Match 请求 携带 ETag 值,询问资源是否变更 与 ETag 配对
Last-Modified 响应 资源最后修改时间 条件验证
If-Modified-Since 请求 携带时间戳,询问资源是否修改 与 Last-Modified 配对
优先级规则:Cache-Control 的 max-age 指令会覆盖 Expires 头部;ETag 的验证优先级高于 Last-Modified。当同时存在时,缓存代理优先使用 ETag 做条件请求。

3.2 Cache-Control 与 Expires:过期控制

Cache-Control 是 HTTP/1.1 定义的缓存指令头部,支持多种指令组合:

  • max-age=<seconds>:资源从生成时刻起可缓存的最大秒数。
  • no-cache:每次使用前必须向源服务器验证资源是否过期。
  • no-store:禁止任何缓存(包括代理和浏览器)。
  • public / private:允许公共缓存或仅私有缓存。
  • must-revalidate:过期后必须重新验证,不可直接使用。

Expires 是 HTTP/1.0 的绝对时间戳头部,格式为 Expires: Wed, 21 Oct 2025 07:28:00 GMT。当 Cache-Control 存在时,Expires 被忽略。

OpenAPI 契约缓存建议:对不频繁变更的契约文档(如 OpenAPI 规范 JSON),设置 Cache-Control: public, max-age=3600;对包含权限信息的端点,使用 no-cache 确保每次请求都验证权限。

3.3 ETag 与 If-None-Match:强验证器

ETag(Entity Tag)是服务器为资源生成的唯一标识,通常为哈希值或版本号。客户端在后续请求中通过 If-None-Match 头部携带该值,服务器比较后返回:

  • 304 Not Modified:资源未变更,客户端可继续使用本地缓存。
  • 200 OK:资源已变更,返回新资源及新 ETag。
# 首次响应
HTTP/1.1 200 OK
ETag: "abc123def456"
Content-Type: application/json

# 后续请求
GET /openapi/v3/spec HTTP/1.1
If-None-Match: "abc123def456"

# 未变更响应
HTTP/1.1 304 Not Modified
ETag: "abc123def456"
安全注意:ETag 不应包含敏感信息(如用户 ID、权限哈希)。若 ETag 可预测,攻击者可能通过构造 If-None-Match 探测资源是否存在。建议使用内容哈希(如 SHA-256)作为 ETag 值。

3.4 Last-Modified 与 If-Modified-Since:弱验证器

Last-Modified 记录资源的最后修改时间,精度为秒。客户端通过 If-Modified-Since 携带该时间戳,服务器判断是否返回 304 或 200。

# 首次响应
HTTP/1.1 200 OK
Last-Modified: Tue, 15 Oct 2025 12:00:00 GMT

# 后续请求
GET /openapi/v3/spec HTTP/1.1
If-Modified-Since: Tue, 15 Oct 2025 12:00:00 GMT

# 未修改响应
HTTP/1.1 304 Not Modified

Last-Modified 的局限性:

  • 精度仅到秒,毫秒级变更无法区分。
  • 无法检测内容是否真正变化(如元数据修改但内容未变)。
  • 建议与 ETag 配合使用,ETag 优先。

3.5 缓存验证流程(SVG 图)

下图展示客户端与服务器之间的完整缓存验证流程,涵盖强缓存与条件请求两条路径:

HTTP 缓存验证流程 客户端 缓存代理 源服务器 请求 转发请求 检查 Cache-Control / Expires 强缓存命中(max-age 有效) 强缓存过期 / no-cache 条件验证:ETag / Last-Modified If-None-Match / If-Modified-Since 比较 ETag / Last-Modified 304 Not Modified 200 OK + 新资源 响应(304 或 200) 更新本地缓存

3.6 缓存头部在 OpenAPI 契约中的典型配置

以下是一个针对 OpenAPI 规范文档的缓存响应示例,展示如何组合使用各头部:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: public, max-age=3600, must-revalidate
ETag: "d5e8c2a1b3f4"
Last-Modified: Mon, 14 Oct 2025 08:30:00 GMT
Expires: Mon, 14 Oct 2025 09:30:00 GMT

{
  "openapi": "3.1.0",
  "info": { "title": "用户服务 API", "version": "2.4.1" },
  "paths": { ... }
}
配置要点:
  • public 允许 CDN 或网关缓存契约文档。
  • max-age=3600 设置 1 小时强缓存有效期。
  • must-revalidate 确保过期后必须回源验证。
  • ETag 使用内容哈希,Last-Modified 作为辅助验证。
  • Expires 作为向后兼容的备选。

3.7 权限验证场景下的缓存策略

当 OpenAPI 端点需要权限验证时,缓存策略需谨慎设计:

场景 推荐缓存策略 说明
公开契约文档 public, max-age=3600 允许公共缓存,减少重复请求
用户专属权限数据 private, no-cache 仅私有缓存,每次验证权限
动态权限策略 no-store 禁止任何缓存,确保实时性
带 ETag 的权限资源 private, max-age=0, must-revalidate 结合 ETag 做条件请求,减少带宽
安全警告:切勿将包含权限信息的响应设置为 public 缓存。攻击者可能通过共享缓存获取其他用户的权限数据。始终使用 private 或 no-cache 保护敏感端点。

3.8 小结