协商缓存的前后端代码实现

协商缓存介绍(知道的可以跳过)

协商缓存是HTTP缓存机制的一种,当强缓存失效时,浏览器会向服务器发送请求验证资源是否更新。以下是协商缓存的核心组件和工作原理:

1. ​Last-Modified 与 If-Modified-Since

  • Last-Modified :服务器在响应头中返回资源的最后修改时间(精确到秒级)。

    示例:Last-Modified: Thu, 27 Feb 2025 18:06:00 GMT

  • If-Modified-Since :浏览器再次请求时,将上次收到的Last-Modified值通过此请求头发送给服务器。服务器对比当前资源修改时间,若未变化则返回304 Not Modified,否则返回新资源 。

局限性

  • 秒级精度可能导致1秒内多次修改无法检测。

  • 分布式系统中需确保多台服务器的文件修改时间一致。


2. ​ETag 与 If-None-Match

  • ETag :服务器生成的资源唯一标识符(如哈希值),优先级高于Last-Modified

    示例:ETag: "d3f4e5kamanotes"

  • If-None-Match :浏览器携带上次的ETag值发送请求。服务器比对当前资源ETag,匹配则返回304,否则返回新资源。

优势

  • 内容变化即触发更新,解决Last-Modified的精度问题。

  • 适用于多语言或动态内容(如Session不同导致相同URL返回不同内容)。

注意

  • 分布式系统中ETag可能因服务器差异导致不一致,需谨慎配置 。

工作流程

  1. 首次请求 :服务器返回资源,并附带Last-ModifiedETag

  2. 再次请求 :浏览器发送If-Modified-SinceIf-None-Match,服务器验证后返回304或新资源。

  3. 优先级 :若两者同时存在,ETag优先 。


应用场景

  • 强缓存失效后 ​(如max-age过期或no-cache)触发协商缓存。

  • 动态资源(如API数据)适合用ETag,静态资源(如CSS/JS)可结合Last-Modified

通过协商缓存,有效减少带宽消耗和服务器压力,提升用户体验

代码实现

看得比较多,但是实际并没有试过实践,所以记录一下协商缓存实践

前端部分:

javascript 复制代码
const getResource=()=>{
  fetch('http://localhost:3000/resource')
  .then(response => {
    console.log('status:',response.status); 
    // 若未修改,返回304
      if (response.status === 200) {
          //一般接口返回304http状态码,会被浏览器自动转成200,document类型通常直接显示304;
        console.log('请求数据');
        return response.json();
      }
      else{
        return '其他状态码';
      }
    })
    .then(data=>{
      console.log('log-zcw=>data',data);
    })
}

服务端部分

javascript 复制代码
import express from 'express';

const app = express();
const cors = require('cors');
const crypto = require('crypto');

// 全局使用 cors 中间件,允许所有来源的跨域请求
app.use(cors());

const mockData = {
    id: 1,
    content: "This is a sample resource",
    updatedAt: new Date().toISOString() // 模拟最后修改时间
};

// 协商缓存(ETag + Last-Modified)
app.get('/resource', (req, res) => {
  // 1. 生成ETag(基于对象内容的哈希)
  const objString = JSON.stringify(mockData);
  const etag = crypto.createHash('md5').update(objString).digest('hex');
  res.setHeader('ETag', etag);//需要设置响应头,否则浏览器不会携带请求头if-none-match和if-modified-since
  console.log('ETag',etag);
  

  // 2. 设置Last-Modified(基于对象的更新时间)
  const lastModified = new Date(mockData.updatedAt).toUTCString();
  res.setHeader('Last-Modified', lastModified);//需要设置响应头,否则浏览器不会携带请求头match和if-modified-since
  console.log('lastModified',lastModified);

  // 检查客户端缓存验证头
  const ifNoneMatch = req.headers['if-none-match'];//是上次返回的ETag值,浏览器会自动携带该请求头。
  console.log('ifNoneMatch',ifNoneMatch);
  
  const ifModifiedSince = req.headers['if-modified-since'];//是上次返回的Last-Modified值,浏览器会自动携带该请求头。
  console.log('ifModifiedSince',ifModifiedSince);
  //浏览器自动携带请求头【if-modified-since】,该行为是浏览器内置的HTTP协议实现,无需开发者干预

  // 如果缓存有效,返回304
  if (
    (ifNoneMatch && ifNoneMatch === etag) ||
    (ifModifiedSince && ifModifiedSince === lastModified)//如果资源的修改时间没修改,则返回304状态码和空响应体。
  ) {
    console.log('缓存数据');
    //HTTP状态码304表示资源未修改
    res.status(304).end();//浏览器会自动转为200状态码,并从缓存中读取数据。
  } else {
    console.log('最新数据');
    res.json(mockData); // 返回最新对象
  }
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

运行后

客户端第一次在浏览器调用接口

服务端输出如下

客户端请求如下

客户端第二次在浏览器调用接口

服务端输出如下

客户端请求如下

相关推荐
BingoGo14 小时前
为什么 PHP 闭包要加 static?
后端
是糖糖啊15 小时前
OpenClaw 从零到一实战指南(飞书接入)
前端·人工智能·后端
百度Geek说15 小时前
基于Spark的配置化离线反作弊系统
后端
Despupilles15 小时前
第三篇、基本骨架结构
前端
swipe15 小时前
从原理到手写:彻底吃透 call / apply / bind 与 arguments 的底层逻辑
前端·javascript·面试
踩着两条虫15 小时前
从设计稿到代码:VTJ.PRO 的 AI 集成系统架构解析
前端·vue.js·人工智能
Mapmost15 小时前
从“雕琢”到“生成”:AIGC正在重塑数字孪生世界
前端
掘金一周15 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了 | 掘金一周 3.5
前端·人工智能·agent
JasonYin15 小时前
ViewModel 知识体系思维导图
前端