2. 缓存基础理论:HTTP缓存机制、Cache-Control指令、ETag与Last-Modified
HTTP缓存是提升API响应速度、降低服务器负载、改善用户体验的核心手段。在OpenAPI契约审计中,缓存策略的正确声明与实现一致性是基线检查的关键项。本节从缓存机制、核心指令、验证令牌三个维度建立理论基础。
2.1 HTTP缓存机制:请求-响应链中的缓存决策
HTTP缓存分为私有缓存(浏览器/客户端)和共享缓存(代理/CDN/网关)。缓存决策由响应头中的指令组合驱动,核心流程如下:
- 客户端发起请求:检查本地缓存是否有效(新鲜度判断)。
- 新鲜缓存命中:直接返回缓存副本,不发起网络请求(状态码200 from cache)。
- 缓存过期/缺失:向服务器发送请求,携带验证令牌(ETag/Last-Modified)。
- 服务器验证:资源未变更返回304 Not Modified,客户端续用缓存;资源变更返回200及新资源。
- 响应存储:根据响应头中的Cache-Control、ETag、Last-Modified更新本地缓存。
审计要点:OpenAPI规范中若未声明缓存策略,客户端和中间件可能采用默认行为(如浏览器强缓存200状态码资源),导致API消费者获取过期数据。契约审计必须检查每个响应是否显式定义了缓存指令。
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的格式与行为一致性。
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?