4. HTTP 缓存头详解:Cache-Control、Expires、ETag、Last-Modified 在 OpenAPI 中的映射

HTTP 缓存是提升 API 响应速度、降低服务器负载的核心手段。在 OpenAPI 契约中,正确映射缓存头能够使客户端与网关层获得明确的缓存策略,避免因缓存失效导致的接口调用错误或数据不一致。本章将逐一拆解 Cache-Control、Expires、ETag、Last-Modified 四个关键头,并给出它们在 OpenAPI 规范中的标准映射方式。

4.1 缓存头分类与作用域

HTTP 缓存头按作用机制可分为两类:强缓存(无需请求服务器)与 协商缓存(需与服务器验证)。下表列出四类头的归属与核心字段:

缓存类型 响应头 核心作用 优先级
强缓存 Cache-Control 指定缓存策略(max-age、public/private、no-cache 等) 最高
强缓存 Expires 指定绝对过期时间(HTTP/1.0 兼容) 低于 Cache-Control
协商缓存 ETag 资源唯一标识(哈希/版本号),用于条件请求 与 Last-Modified 配合
协商缓存 Last-Modified 资源最后修改时间,用于 If-Modified-Since 验证 低于 ETag
关键原则:Cache-Control 的 max-age 优先级高于 Expires;ETag 的验证精度高于 Last-Modified。在 OpenAPI 中,建议优先使用 Cache-Control 与 ETag 组合。

4.2 Cache-Control 指令详解与 OpenAPI 映射

Cache-Control 是 HTTP/1.1 定义的缓存控制头,支持多个指令组合。在 OpenAPI 中,通过 headers 字段定义响应头,并利用 schema 约束指令值。

4.2.1 常用指令速查表

指令 含义 示例值 适用场景
max-age=<seconds> 资源在缓存中的最大有效时间(秒) max-age=3600 静态资源、不频繁更新的列表
public 允许任何缓存(包括 CDN)存储 public, max-age=86400 公开的静态数据
private 仅允许客户端(浏览器)缓存 private, max-age=600 用户个性化数据
no-cache 强制每次请求都验证资源是否变化 no-cache 动态数据、需实时校验的接口
no-store 禁止任何缓存 no-store 敏感信息、支付令牌
must-revalidate 过期后必须向源服务器验证 must-revalidate 强一致性要求的数据

4.2.2 OpenAPI 映射示例

以下 YAML 片段展示如何在 OpenAPI 3.0 中为 /api/products 响应定义 Cache-Control 头:

paths:
  /api/products:
    get:
      summary: 获取产品列表(可缓存)
      responses:
        '200':
          description: 成功返回产品列表
          headers:
            Cache-Control:
              description: 缓存控制策略
              schema:
                type: string
                example: "public, max-age=3600, must-revalidate"
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Product'
最佳实践:在 OpenAPI 中,Cache-Control 的 schema 应定义为 type: string,并通过 example 或 enum 约束合法指令组合,避免客户端解析歧义。

4.3 Expires 头与兼容性映射

Expires 是 HTTP/1.0 的强缓存头,值为绝对时间(RFC 1123 格式)。当 Cache-Control 存在时,Expires 被忽略。在 OpenAPI 中,Expires 通常作为 后备方案 定义。

4.3.1 OpenAPI 映射示例

headers:
  Expires:
    description: 资源过期时间(HTTP/1.0 兼容)
    schema:
      type: string
      format: date-time-rfc1123
      example: "Wed, 21 Oct 2025 07:28:00 GMT"
注意:Expires 的 format 在 OpenAPI 3.0 中没有标准枚举值,建议使用 date-time 并附加文档说明。更推荐的做法是仅使用 Cache-Control,避免时间同步问题。

4.4 ETag 与 Last-Modified:协商缓存双引擎

协商缓存需要客户端在请求中携带条件头(If-None-Match / If-Modified-Since),服务器返回 304 Not Modified 或完整响应。在 OpenAPI 中,需同时定义响应头与请求头。

4.4.1 ETag 映射

ETag 是资源内容的强校验器,通常为哈希值。OpenAPI 定义如下:

# 响应头定义
headers:
  ETag:
    description: 资源唯一标识(强校验)
    schema:
      type: string
      example: "686897696a7c876b7e"

# 请求头定义(客户端条件请求)
parameters:
  - name: If-None-Match
    in: header
    description: 客户端缓存的 ETag 值
    schema:
      type: string
    required: false

4.4.2 Last-Modified 映射

Last-Modified 是弱校验器,精度为秒级。OpenAPI 定义:

# 响应头定义
headers:
  Last-Modified:
    description: 资源最后修改时间
    schema:
      type: string
      format: date-time
      example: "2025-03-15T14:30:00Z"

# 请求头定义
parameters:
  - name: If-Modified-Since
    in: header
    description: 客户端缓存的最后修改时间
    schema:
      type: string
      format: date-time
    required: false

4.5 缓存头在 OpenAPI 中的完整映射流程

下图展示了一个典型 GET 请求的缓存协商流程,以及 OpenAPI 中各头的对应位置:

HTTP 缓存协商流程与 OpenAPI 映射 客户端 缓存层 / CDN 源服务器 GET /api/products If-None-Match / If-Modified-Since 200 OK + ETag / Last-Modified 缓存响应 Cache-Control: max-age=3600 OpenAPI 映射要点 1. 响应头定义:在 responses → headers 中声明 Cache-Control、ETag、Last-Modified 2. 请求头定义:在 parameters 中声明 If-None-Match、If-Modified-Since 3. 304 响应:需在 responses 中定义 304 Not Modified 状态码 4. 缓存键:建议在 OpenAPI 中标注 Vary 头(如 Accept-Encoding) 5. 安全约束:敏感接口必须使用 no-store,避免数据泄露 推荐组合:Cache-Control (public/private) + ETag (强校验) + Last-Modified (后备)

4.6 缓存头组合策略与 OpenAPI 契约设计

根据 API 的数据特性,推荐以下四种缓存头组合策略,并在 OpenAPI 中通过 description 或 x-cache-policy 扩展字段标注:

策略 Cache-Control ETag Last-Modified 适用场景
静态资源 public, max-age=86400, immutable 可选 可选 图片、CSS、版本化 JS
列表数据 public, max-age=300, must-revalidate 强 ETag 推荐 产品列表、文章目录
用户数据 private, max-age=60 强 ETag 推荐 用户信息、订单状态
敏感数据 no-store 不适用 不适用 令牌、密码、支付信息
OpenAPI 扩展实践:使用 x-cache-policy 自定义字段在操作级别标注缓存策略,便于工具链自动生成缓存配置。例如:
x-cache-policy: "static-asset" 或 x-cache-policy: "user-private"。

4.7 常见避坑指南

  • Expires 与 Cache-Control 冲突:当两者同时存在时,Cache-Control 的 max-age 覆盖 Expires。OpenAPI 中建议只定义 Cache-Control。
  • ETag 弱校验器:ETag 值前加 W/ 前缀表示弱校验(如 W/"686897"),OpenAPI 中需在文档中说明校验强度。
  • Vary 头缺失:如果响应内容因 Accept-Encoding 或 Accept-Language 变化,必须在 OpenAPI 中定义 Vary 头,否则缓存层可能返回错误版本。
  • 304 响应体为空:OpenAPI 中 304 响应应定义 content 为 null 或省略,避免客户端解析错误。
# 304 响应在 OpenAPI 中的正确定义
responses:
  '304':
    description: Not Modified - 使用缓存
    headers:
      ETag:
        schema:
          type: string
    # 不包含 content 字段
总结:在 OpenAPI 契约中映射 HTTP 缓存头,本质是将运行时行为文档化。通过精确的 schema 定义、示例值和扩展字段,可以使客户端、网关和缓存中间件获得一致的缓存策略,从而构建高效、可预测的 API 缓存体系。