2. 缓存基础理论:HTTP缓存机制、Cache-Control指令、ETag与Last-Modified

HTTP缓存是提升API响应速度、降低服务器负载、改善用户体验的核心手段。在OpenAPI契约审计中,缓存策略的正确声明与实现一致性是基线检查的关键项。本节从缓存机制、核心指令、验证令牌三个维度建立理论基础。

2.1 HTTP缓存机制:请求-响应链中的缓存决策

HTTP缓存分为私有缓存(浏览器/客户端)和共享缓存(代理/CDN/网关)。缓存决策由响应头中的指令组合驱动,核心流程如下:

  1. 客户端发起请求:检查本地缓存是否有效(新鲜度判断)。
  2. 新鲜缓存命中:直接返回缓存副本,不发起网络请求(状态码200 from cache)。
  3. 缓存过期/缺失:向服务器发送请求,携带验证令牌(ETag/Last-Modified)。
  4. 服务器验证:资源未变更返回304 Not Modified,客户端续用缓存;资源变更返回200及新资源。
  5. 响应存储:根据响应头中的Cache-Control、ETag、Last-Modified更新本地缓存。
审计要点:OpenAPI规范中若未声明缓存策略,客户端和中间件可能采用默认行为(如浏览器强缓存200状态码资源),导致API消费者获取过期数据。契约审计必须检查每个响应是否显式定义了缓存指令。
HTTP缓存决策流程 HTTP缓存决策流程 客户端发起请求 检查本地缓存新鲜度 缓存命中 → 返回200 (from cache) 缓存过期 → 发起验证请求 服务器验证 (ETag/Last-Modified) 304 Not Modified → 续用缓存 200 OK → 返回新资源并更新缓存 缓存决策由响应头指令驱动

2.2 Cache-Control指令:细粒度缓存策略

Cache-Control是HTTP/1.1定义的缓存控制头,取代了Expires。指令分为请求指令和响应指令,在OpenAPI契约中主要关注响应指令。下表列出核心指令及其审计含义:

指令 类型 语义 审计关注点
max-age=<seconds> 响应 资源被视为新鲜的最大秒数 值是否过短(频繁回源)或过长(数据滞后)
s-maxage=<seconds> 响应 共享缓存(CDN/代理)专用新鲜时间,覆盖max-age 是否与max-age一致;共享缓存场景必须声明
private 响应 仅允许私有缓存(浏览器)存储 含用户敏感数据的API必须使用
public 响应 允许任何缓存(包括共享缓存)存储 公开API默认值,但需确认无敏感数据泄露
no-cache 响应 每次使用前必须向服务器验证资源是否变更 高频验证场景使用,避免强缓存导致数据陈旧
no-store 响应 禁止任何缓存存储响应 金融、医疗等敏感API必须声明
must-revalidate 响应 缓存过期后必须验证,不可使用过期资源 与max-age配合,防止缓存提供陈旧数据
proxy-revalidate 响应 共享缓存过期后必须验证 CDN场景下替代must-revalidate
最佳实践:OpenAPI规范中建议为每个响应显式声明Cache-Control。对于GET/HEAD方法,推荐组合 public, max-age=3600, must-revalidate;对于包含敏感数据的响应,使用 private, no-cache;对于绝对不应缓存的响应(如实时交易状态),使用 no-store。

2.3 ETag与Last-Modified:条件请求验证令牌

ETag(实体标签)和Last-Modified(最后修改时间)是HTTP条件请求的核心验证机制,用于判断缓存资源是否与服务器一致。

2.3.1 ETag:强验证器

  • 定义:服务器为资源版本生成的唯一标识符(通常为哈希值或版本号)。
  • 类型:强ETag(资源字节级变更即变化) vs 弱ETag(前缀 W/,语义等价即可)。
  • 请求头:If-None-Match: "etag-value"(GET/HEAD)或 If-Match(PUT/PATCH)。
  • 响应状态:匹配返回304 Not Modified;不匹配返回200及新ETag。

2.3.2 Last-Modified:弱验证器

  • 定义:资源最后修改的HTTP日期时间戳。
  • 精度:秒级,无法检测毫秒级变更。
  • 请求头:If-Modified-Since: "date"。
  • 响应状态:未修改返回304;已修改返回200及新Last-Modified。
特性 ETag Last-Modified
精度 字节级/语义级 秒级
唯一性 强(资源内容唯一标识) 弱(不同资源可能同时间戳)
性能开销 需计算哈希或维护版本号 仅需读取文件修改时间
并发安全 支持If-Match防止更新冲突 不支持并发控制
OpenAPI声明 响应头 ETag 需在契约中定义 响应头 Last-Modified 需在契约中定义
审计风险:若OpenAPI规范中声明了ETag但实际响应未返回,或返回格式与契约不一致(如缺少引号、弱ETag前缀错误),会导致客户端条件请求失效,引发缓存击穿。契约测试必须验证ETag和Last-Modified的格式与行为一致性。
ETag与Last-Modified验证流程对比 ETag与Last-Modified验证流程对比 ETag 验证流程 服务器生成强/弱ETag 客户端缓存ETag值 请求头: If-None-Match: "etag" 匹配→304 / 不匹配→200+新ETag Last-Modified 验证流程 服务器记录最后修改时间 客户端缓存Last-Modified时间 请求头: If-Modified-Since: date 未修改→304 / 已修改→200+新时间 ETag精度更高,Last-Modified性能更优,建议同时使用

2.4 缓存策略组合与契约声明示例

在OpenAPI 3.0规范中,缓存相关头应在响应对象的headers字段中声明。以下是一个完整的缓存策略声明示例:

paths:
  /api/users/{id}:
    get:
      summary: 获取用户信息
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: 成功返回用户信息
          headers:
            Cache-Control:
              schema:
                type: string
                example: "private, max-age=300, must-revalidate"
            ETag:
              schema:
                type: string
                example: "33a64df551425fcc55e4d42a148795d9f25f89d4"
            Last-Modified:
              schema:
                type: string
                format: date-time
                example: "Wed, 21 Oct 2025 07:28:00 GMT"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '304':
          description: 资源未修改,使用缓存
          headers:
            ETag:
              schema:
                type: string
            Last-Modified:
              schema:
                type: string
                format: date-time
审计基线检查项:
  • 每个GET/HEAD响应是否声明了Cache-Control?
  • Cache-Control指令是否与API安全等级匹配(敏感数据→private/no-store)?
  • ETag格式是否为双引号包裹的字符串(强ETag)或 W/"value"(弱ETag)?
  • Last-Modified格式是否符合RFC 1123日期格式?
  • 304响应是否复用了200响应的头定义?
  • 共享缓存场景是否声明了s-maxage?