HTTP headers 分类整理

123 阅读10分钟

缓存相关

缓存控制

请求头

  1. Cache-Control:
    • no-cache:强制向服务器验证缓存有效期(协商缓存验证)
    • o-store:禁止任何缓存
    • max-age=0:缓存已经过期需重新验证
    • max-stale=300:允许使用过期资源但不能超过300秒的缓存
  2. Pragma:HTTP/1.0 遗留头(兼容用) Pragma: no-cache 等效于 Cache-Control: no-cache

响应头

  1. Cache-Control
    • no-store:禁止任何缓存
    • no-cache:协商缓存
    • private:仅允许浏览器缓存
    • public:允许所有缓存(浏览器/CDN)
    • max-age=3600:资源有效期3600秒
    • immutable:资源永远不会过期
    • must-revalidate:一旦资源过期(比如已经超过max-age),在成功向原始服务器验证之前,缓存不能用该资源响应后续请求。
  2. Expires:资源的绝对过期时间(HTTP/1.0 遗留)
Expires: Wed, 21 Oct 2026 07:28:00 GMT
  1. Age:资源在缓存中已存储的时间(秒),在代理缓存中停留的时间,以秒为单位;通常接近于 0。如果显示为 Age: 0,则表示该内容可能是从源服务器上获取的;否则,它通常是通过代理服务器当前日期与 HTTP 响应中包含的 Date 通用标头之间的差值来计算得出的。
Age: 120(由代理服务器添加)

缓存验证

请求头

  1. If-None-Match
  • 携带缓存的 ETag 值
  • 匹配时服务器返回 304 Not Modified
  • 示例:If-None-Match: "33a64df551425fcc55e4d42a148795d9"
  1. If-Modified-Since
  • 携带缓存的 Last-Modified 时间
  • 资源未修改时返回 304
  • 示例:If-Modified-Since: Mon, 08 Jul 2025 14:30:00 GMT

响应头

  1. Etag 资源版本标识符(如哈希值)ETag: "33a64df551425fcc55e4d42a148795d9"
  2. Last-Modified 资源最后修改时间 Last-Modified: Mon, 08 Jul 2025 14:30:00 GMT

辅助缓存类头部

影响缓存键和存储策略

  1. Vary:响应头 指定区分缓存版本的请求头

示例:

  • Vary: User-Agent:为不同浏览器存储独立缓存
  • Vary: Accept-Encoding:区分压缩/未压缩版本
  1. Pragma 请求头 旧版等效于 Cache-Control: no-cache(现代浏览器已弃用)

缓存 HTTP 头部交互

前端缓存的核心是请求头和响应头的协同工作,下面我将通过流程和交互示例说明这些头部如何协同实现缓存控制:

缓存控制流程交互


sequenceDiagram

participant Client as 浏览器

participant Cache as 浏览器缓存

participant Server as 服务器

Note over Client: 首次请求资源

Client->>Server: GET /app.js<br>无缓存相关头部

Server-->>Client: HTTP 200 OK<br>Cache-Control: public, max-age=3600<br>ETag: "abc123"<br>Last-Modified: Wed, 21 Oct 2026 07:28:00 GMT<br>Content-Type: application/javascript

Client->>Cache: 存储响应和缓存头部

Note over Client: 60分钟后再次请求

Client->>Cache: 检查缓存

Cache-->>Client: 缓存已过期(max-age=3600)

Client->>Server: GET /app.js<br>If-None-Match: "abc123"<br>If-Modified-Since: Wed, 21 Oct 2026 07:28:00 GMT

alt 资源未修改

Server-->>Client: HTTP 304 Not Modified<br>Cache-Control: public, max-age=3600<br>(空响应体)

Client->>Cache: 更新缓存过期时间

Cache-->>Client: 返回缓存的资源

else 资源已修改

Server-->>Client: HTTP 200 OK<br>Cache-Control: public, max-age=3600<br>ETag: "def456"<br>新资源内容

Client->>Cache: 更新缓存

end

关键头部交互关系

1. 缓存存储阶段(首次请求)

请求头:无特殊缓存头

响应头

  • Cache-Control: public, max-age=3600 → 指示缓存存储策略
  • ETag: "abc123" → 资源版本标识符
  • Last-Modified: [date] → 资源最后修改时间

浏览器接收到这些头部后,会将资源存入缓存并记录这些元数据。

2. 缓存验证阶段(后续请求)

当缓存过期或需要验证时:

请求头

  • If-None-Match: "abc123" → 携带上次响应的 ETag 值
  • If-Modified-Since: [date] → 携带上次响应的 Last-Modified 值

服务器处理

  1. 比较当前资源的 ETag 与请求中的 If-None-Match
  2. 比较当前修改时间与 If-Modified-Since
  3. 如果未修改 → 返回 304 Not Modified
  4. 如果已修改 → 返回 200 和新资源
3. 缓存更新阶段

304响应时

  • 浏览器使用缓存资源
  • 根据响应中的 Cache-Control 更新缓存过期时间

200响应时

  • 浏览器使用新资源
  • 根据响应头部更新缓存内容和元数据

常见缓存控制场景

场景1:强制缓存验证
# 请求头
Cache-Control: no-cache

# 响应头(无论资源是否修改)
Cache-Control: max-age=600
ETag: "xyz789"

交互:no-cache 强制跳过缓存直接向服务器验证,但服务器仍可返回缓存指令

场景2:版本化资源永久缓存
# 响应头
Cache-Control: public, max-age=31536000, immutable
ETag: "v1.0-sha256"

交互:immutable 告诉浏览器在 max-age 期间不会修改,无需验证

场景3:Vary头部控制缓存版本
# 响应头
Vary: Accept-Encoding, User-Agent

# 后续请求头
Accept-Encoding: gzip
User-Agent: Mobile

交互:浏览器会根据 Accept-Encoding 和 User-Agent 的不同值存储多个缓存版本

常见组合

  1. 强缓存+协商缓存组合
Cache-Control: public, max-age=604800
ETag: "sha256-abc123"
  1. 静态资源永久缓存
Cache-Control: public, max-age=31536000, immutable
  1. 动态内容不缓存
Cache-Control: no-store
  1. 用户相关内容
Cache-Control: private, max-age=600
Vary: Cookie

Expiresmax-age 的区别与优先级

核心区别

特性ExpiresCache-Control: max-age
标准版本HTTP/1.0HTTP/1.1
时间类型绝对时间 (GMT 格式)相对时间 (秒数)
示例Expires: Wed, 21 Oct 2026 07:28:00 GMTCache-Control: max-age=3600
时钟依赖依赖客户端/服务器时间同步不依赖时钟,从响应接收时刻开始计算
优先级
精度问题时区转换可能导致误差精确到秒

同时设置时的处理规则

当响应头中同时包含 ExpiresCache-Control: max-age 时:

  1. max-age 优先级更高
  • 浏览器会完全忽略 Expires
  • 缓存时间以 max-age 值为准
  1. 计算方式差异
// max-age 计算方式 (推荐)
const expirationTime = Date.now() + (maxAgeValue * 1000);

// Expires 计算方式 (不推荐)
const expirationTime = new Date(expiresValue).getTime();
  1. 现代浏览器行为
  • 所有主流浏览器(Chrome/Firefox/Safari/Edge)都优先采用 max-age
  • 仅当缺少 Cache-Control 时才会回退到 Expires

实际场景分析

场景1:两者设置一致
Expires: Wed, 10 Jul 2025 08:00:00 GMT
Cache-Control: max-age=86400 // 24小时
  • 结果:浏览器使用 max-age=86400 (忽略 Expires)
  • 缓存有效期 = 响应接收时间 + 86400秒
场景2:两者冲突
Expires: Wed, 10 Jul 2025 08:00:00 GMT // 未来时间
Cache-Control: max-age=0 // 立即过期
  • 结果:资源被视为立即过期,下次请求必须验证
  • 浏览器完全忽略 Expires 的未来值
场景3:仅设置 Expires
Expires: Wed, 10 Jul 2025 08:00:00 GMT
  • 结果:浏览器使用 Expires 时间
  • 风险:如果客户端时钟错误,缓存可能提前失效或过久保留

为什么 max-age 更优秀?

  1. 时钟无关性
  • 不依赖客户端/服务器时间同步
  • 避免时区转换错误
  1. 精确控制
// 精确控制到秒级
Cache-Control: max-age=31536000 // 精确1年

// 对比 Expires 需要计算日期
Expires: Wed, 10 Jul 2026 08:15:22 GMT
  1. 组合指令
// 可与其他指令组合
Cache-Control: max-age=604800, must-revalidate, public
  1. 解决「时间陷阱」
  • Expires 设置为过去时间时,浏览器会立即过期资源
  • max-age=0 表达更清晰

Vary 响应头 是啥?

它解决了现代 Web 开发中资源多样性的核心问题:同一 URL 可能对应多个不同版本的资源。

核心作用机制


graph TB

A[客户端请求] --> B{服务器检查 Vary 指定头部}

B -->|Accept-Encoding: gzip| C[缓存版本A]

B -->|Accept-Encoding: br| D[缓存版本B]

B -->|Accept-Language: fr| E[缓存版本C]

C & D & E --> F[返回匹配的缓存副本]

Vary 的工作方式:

  1. 服务器声明响应内容依赖哪些请求头
  2. 缓存系统(浏览器/CDN)将这些头组合作为缓存键
  3. 后续请求必须完全匹配这些头的值才能使用缓存

关键使用场景

1. 内容压缩协商(最常用)

问题:同一资源有 gzip/brotli 等压缩版本

解决方案

Vary: Accept-Encoding

缓存效果

请求1: Accept-Encoding: gzip → 缓存版本A
请求2: Accept-Encoding: br → 缓存版本B
请求3: Accept-Encoding: gzip → 命中版本A
2. 多语言站点

问题:根据用户语言返回不同内容

解决方案

Vary: Accept-Language

实际案例

#请求头
Accept-Language: fr-FR

#响应头
Vary: Accept-Language
Content-Language: fr
3. 响应式图像服务

问题:根据设备 DPR 返回不同分辨率图像

解决方案

Vary: DPR, Viewport-Width

现代替代方案

<!-- 使用srcset避免Vary碎片化 -->
<img srcset="img-1x.jpg 1x, img-2x.jpg 2x">
4. 用户个性化内容

问题:登录用户看到不同内容

解决方案

Vary: Cookie

风险警告

  • 这会使缓存完全失效(因为Cookie值唯一)
  • 更好的替代方案:
Cache-Control: private
5. API 版本控制

问题:根据客户端版本返回不同数据结构

解决方案

Vary: X-API-Version

请求示例

GET /api/users
X-API-Version: 2.1
6. 客户端能力适配

问题:根据客户端支持特性返回不同资源

解决方案

Vary: Save-Data, Device-Memory

现代实践

// 通过客户端提示(Client Hints)优化
res.setHeader('Accept-CH', 'Device-Memory, DPR')

结论:何时使用 Vary

场景推荐方案缓存效率
内容压缩Vary: Accept-Encoding
多语言⚠️ 使用不同URL更好
用户个性化❌ 避免使用Vary
响应式图像✅ 客户端提示+srcset
API版本控制✅ URL版本化 /v2/resource极高

黄金法则

只在真正影响资源内容取值有限(如压缩格式、语言)时使用 Vary。对于用户级个性化,采用 Cache-Control: private 更有效。现代 Web 开发趋势是通过 URL 设计消除 Vary 需求,最大化缓存效率。

跨域资源共享 (CORS)

sequenceDiagram
    participant Client as 浏览器
    participant Server as 服务器

    Note over Client: 1. 发起跨域请求
    alt 简单请求(GET/POST/HEAD)
        Client->>Server: GET /api/data<br>Origin: https://client.com
        Server-->>Client: HTTP 200 OK<br>Access-Control-Allow-Origin: https://client.com<br>Data: {...}
    else 复杂请求(PUT/DELETE等)
        Note over Client: 2. 发送预检请求(OPTIONS)
        Client->>Server: OPTIONS /api/data<br>Origin: https://client.com<br>Access-Control-Request-Method: DELETE<br>Access-Control-Request-Headers: X-API-Key
        
        alt 预检通过
            Server-->>Client: HTTP 204 No Content<br>Access-Control-Allow-Origin: https://client.com<br>Access-Control-Allow-Methods: DELETE<br>Access-Control-Allow-Headers: X-API-Key<br>Access-Control-Max-Age: 3600
            Note over Client: 3. 发送实际请求
            Client->>Server: DELETE /api/data<br>Origin: https://client.com<br>X-API-Key: 123abc
            Server-->>Client: HTTP 200 OK<br>Access-Control-Allow-Origin: https://client.com<br>Data: {status: "deleted"}
        else 预检失败
            Server-->>Client: HTTP 403 Forbidden<br>(缺少必要CORS头)
            Note over Client: 浏览器阻止实际请求
        end
    end
    
    alt 需要携带凭证
        Note over Client: 4. 客户端设置withCredentials
        Client->>Server: GET /user<br>Origin: https://client.com<br>Cookie: sessionid=xyz
        Server-->>Client: HTTP 200 OK<br>Access-Control-Allow-Origin: https://client.com<br>Access-Control-Allow-Credentials: true<br>Data: {user: "John"}
    end

客户端请求头

  • Origin:发起请求的源(协议+域名+端口) Origin: https://www.client.com
  • Access-Control-Request-Method预检请求中声名实际请求的HTTP方法 Access-Control-Request-Method: DELETE
  • Access-Control-Request-Headers预检请求中声名实际请求的自定义头 Access-Control-Request-Headers: X-API-Key, Content-Type

服务端响应头

  • Access-Control-Allow-Methods:允许的HTTP方法(预检响应Access-Control-Allow-Methods: GET, POST, PUT, DELETE
  • Access-Control-Allow-Origin:允许访问的源(必需)Access-Control-Allow-Origin: https://www.client.com*(禁止和凭证同用)
  • Access-Control-Allow-Headers:允许的自定义请求头(预检响应Access-Control-Allow-Headers: X-API-Key, Content-Type
  • Access-Control-Allow-Credentials:是否允许发送凭证(cookies/HTTP认证)Access-Control-Allow-Credentials: true
  • Access-Control-Max-Age
  • Access-Control-Expose-Headers:允许客户端访问的响应头 Access-Control-Expose-Headers: X-Total-Count

完整交互流程

1. 简单请求流程(无需预检)


sequenceDiagram

participant Client as 浏览器

participant Server as 服务器

Client->>Server: GET /api/data<br>Origin: https://www.client.com

alt 允许跨域

Server-->>Client: HTTP 200 OK<br>Access-Control-Allow-Origin: https://www.client.com<br>Data: [...]

else 拒绝跨域

Server-->>Client: HTTP 200 OK (无CORS头)

Client-->>Error: 拦截响应

end

简单请求条件

  • 方法为:GET、HEAD、POST
  • 头为:Accept、Accept-Language、Content-Language、Content-Type(仅限 application/x-www-form-urlencoded, multipart/form-data, text/plain)

2. 预检请求流程(复杂请求)


sequenceDiagram

participant Client as 浏览器

participant Server as 服务器

Note over Client: 检测到复杂请求

Client->>Server: OPTIONS /api/data<br>Origin: https://www.client.com<br>Access-Control-Request-Method: DELETE<br>Access-Control-Request-Headers: X-API-Key

alt 预检通过

Server-->>Client: HTTP 204 No Content<br>Access-Control-Allow-Origin: https://www.client.com<br>Access-Control-Allow-Methods: DELETE<br>Access-Control-Allow-Headers: X-API-Key<br>Access-Control-Max-Age: 3600

Client->>Server: DELETE /api/data<br>Origin: https://www.client.com<br>X-API-Key: 12345

Server-->>Client: HTTP 200 OK<br>Access-Control-Allow-Origin: https://www.client.com<br>Data: {...}

else 预检失败

Server-->>Client: HTTP 403 Forbidden (无CORS头或配置错误)

Client-->>Error: 阻止实际请求

end

触发预检的常见场景

  • 方法:PUT、DELETE、PATCH
  • 自定义头:X-API-Key, Authorization
  • Content-Type:application/json

凭证请求规则

  1. 客户端设置:fetch(url, { credentials: 'include' })
  2. 服务端必须:
    • 设置 Access-Control-Allow-Credentials: true
    • 服务器不能将 Access-Control-Allow-Origin 的值设为通配符“*”,而应将其设置为特定的域,如:Access-Control-Allow-Origin: example.com。
    • 服务器不能将 Access-Control-Allow-Headers 的值设为通配符“*”,而应将其设置为标头名称的列表,如:Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
    • 服务器不能将 Access-Control-Allow-Methods 的值设为通配符“*”,而应将其设置为特定请求方法名称的列表,如:Access-Control-Allow-Methods: POST, GET
    • 如果需要设置Cookie,必须添加 SameSite=None; Secure