深入解析协商缓存(弱缓存)

核心机制:内容验证而非文件验证

协商缓存的核心是验证资源内容是否变化,而不是文件本身是否修改(例如文件元数据变化但内容未变时不应更新)

两种验证机制及优先级

  1. ETag/If-None-Match(优先级更高)

    • 工作原理

      • 服务器生成资源内容的哈希值(如 ETag: "33a64df55142f"

      • 浏览器后续请求携带 If-None-Match: "33a64df55142f"

      • 服务器比较哈希值:

        nginx

        csharp 复制代码
        # Nginx 自动启用 ETag(默认配置)
        etag on;
        
        # 验证逻辑伪代码
        if request.headers['If-None-Match'] == generate_etag(current_content) {
            return 304;
        } else {
            return 200 with new content;
        }
    • 优势

      • 精确感知内容变化(1字节变化也会使哈希值改变)
      • 避免时间同步问题
      • 处理文件重命名但内容不变的情况
  2. Last-Modified/If-Modified-Since

    • 工作原理

      • 服务器返回最后修改时间(如 Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT

      • 浏览器后续请求携带 If-Modified-Since: Wed, 21 Oct 2025 07:28:00 GMT

      • 服务器比较修改时间:

        nginx

        bash 复制代码
        # 自动启用(默认)
        if_modified_since exact;  # 精确模式
        
        # 验证逻辑伪代码
        if request.headers['If-Modified-Since'] >= resource.modified_time {
            return 304;
        } else {
            return 200;
        }
    • 缺陷

      • 最小时间单位为秒,1秒内多次修改无法识别
      • 文件内容未变但修改时间更新(如打包重建)会误判
      • 服务器时间不同步导致问题

优先级规则

当两种机制同时存在时:

原因:ETag 基于内容哈希,比时间戳更可靠(符合 HTTP/1.1 规范 RFC 7232)

关键实践:内容变化检测的陷阱与解决方案

场景:内容未变但文件改变的案例

  1. 文件重建导致时间戳更新

    • 问题:CI/CD 流水线每次构建都更新文件时间戳

    • 解决方案:

      nginx

      csharp 复制代码
      # 禁用 Last-Modified,强制使用 ETag
      add_header Last-Modified "";
      etag on;
  2. ETag 误判问题

    • 问题:分布式服务器使用 inode 生成 ETag(不同节点的 inode 不同)

    • 解决方案(Nginx):

      nginx

      perl 复制代码
      # 关闭默认 ETag(包含 inode)
      etag off;
      
      # 自定义基于内容的 ETag
      add_header ETag "%X-Content-MD5";  # 需要安装 ngx_http_ssi_filter_module

高级内容验证策略

  1. 弱校验 ETag(W/ 前缀)

    • 适用场景:内容语义不变的小修改(如空格调整)
    • 服务器返回:ETag: W/"33a64df55142f"
    • 浏览器行为:接受弱 ETag 匹配的 304 响应
  2. 内容摘要验证

    nginx

    bash 复制代码
    # 生成 SHA-256 内容摘要
    add_header Digest 'sha-256=base64(sha256(content))';
    • 浏览器通过 Want-Digest 头主动请求验证

缓存配置最佳实践

强缓存 + 协商缓存组合策略

nginx

csharp 复制代码
server {
    # 带哈希的静态资源(强缓存)
    location ~* .[a-f0-9]{8}.(js|css)$ {
        add_header Cache-Control "public, max-age=31536000, immutable";
        etag off;  # 不需要协商缓存
    }
    
    # 无哈希资源(协商缓存)
    location ~* .(js|css)$ {
        add_header Cache-Control "public, max-age=0, must-revalidate";
        etag on;
    }
    
    # HTML 文件(禁用缓存)
    location /index.html {
        add_header Cache-Control "no-cache, no-store, must-revalidate";
        etag on;
    }
}

动态内容缓存验证

javascript

javascript 复制代码
// 前端主动验证内容更新
async function checkContentUpdate(url, storedETag) {
  const response = await fetch(url, {
    headers: { 'If-None-Match': storedETag },
    cache: 'no-cache'
  });
  
  if (response.status === 304) {
    console.log('内容未变化');
  } else {
    const newETag = response.headers.get('ETag');
    // 存储新 ETag 并更新内容
  }
}

验证工具与调试技巧

  1. 浏览器网络面板分析

    • 304 响应:协商缓存生效
    • Provisional headers:强缓存生效
  2. CURL 手动验证

    bash

    bash 复制代码
    # 首次获取 ETag
    curl -I https://example.com/app.js
    
    # 发送验证请求
    curl -H 'If-None-Match: "33a64df55142f"' -I https://example.com/app.js
  3. 关键头信息

    http

    arduino 复制代码
    HTTP/1.1 304 Not Modified
    ETag: "33a64df55142f"
    Cache-Control: max-age=0, must-revalidate

特殊场景处理

  1. Service Worker 的缓存验证

    javascript

    csharp 复制代码
    self.addEventListener('fetch', event => {
      event.respondWith(
        caches.match(event.request).then(cached => {
          return fetch(event.request).then(network => {
            // 比较 ETag
            if (cached && 
                cached.headers.get('ETag') === network.headers.get('ETag')) {
              return cached;
            }
            return network;
          }).catch(() => cached);
        })
      );
    });
  2. CDN 边缘计算验证

    js

    csharp 复制代码
    // Cloudflare Worker 示例
    addEventListener('fetch', event => {
      const request = event.request;
      if (request.headers.get('If-None-Match')) {
        // 直接比较 ETag 避免回源
        const etagMatch = /* 从 KV 存储获取 ETag */;
        if (etagMatch) return new Response(null, { status: 304 });
      }
      event.respondWith(handleRequest(request));
    });

通过精确控制 ETag 生成策略和缓存头配置,可在保证缓存效率的同时,实现真正基于内容变化的精准更新检测,避免不必要的资源重新加载。

相关推荐
风度前端6 分钟前
npm 2026安全新规下的免登录发包策略
前端
冴羽21 分钟前
2026 年前端必须掌握的 4 个 CSS 新特性!
前端·javascript·css
rgeshfgreh34 分钟前
Python流程控制:从条件到循环实战
前端·数据库·python
狗头大军之江苏分军41 分钟前
告别旧生态:Ant Design 6 不再支持 IE 与现代前端趋势解读
前端·javascript·后端
C_心欲无痕42 分钟前
nginx - 开启 gzip 压缩
运维·前端·nginx
闲云一鹤1 小时前
2026 最新 ComfyUI 教程 - 本地部署 AI 生图模型 - Z-Image-Turbo
前端·人工智能·ai编程
开开心心_Every1 小时前
安卓后台录像APP:息屏录存片段,行车用
java·服务器·前端·学习·eclipse·edge·powerpoint
狗头大军之江苏分军1 小时前
Ant Design 6.0 正式发布:从 V5 到 V6 有哪些变化?
前端
优弧1 小时前
Claude 终于对普通人下手了!Cowork 发布,你的最强 AI 打工搭子来了!
前端·后端
Zoey的笔记本1 小时前
敏捷与稳定并行:Scrum看板+BPM工具选型指南
大数据·前端·数据库·python·低代码