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

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

协商缓存是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');
});

运行后

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

服务端输出如下

客户端请求如下

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

服务端输出如下

客户端请求如下

相关推荐
yolo_Yang3 分钟前
【Spring Boot】Spring Boot解决循环依赖
java·spring boot·后端
wdfk_prog19 分钟前
结合QBoot与HPatchLite实现高效差分升级(FOTA)
java·后端·struts
舒一笑32 分钟前
用数据照亮成长之路:PandaCoder Git 统计工具窗口
git·后端·intellij idea
yinuo1 小时前
Git Submodule 与 Subtree 全方位对比:使用方式与场景选择
前端
yinuo1 小时前
深入理解与实战 Git Subtree
前端
向上的车轮1 小时前
Actix Web 不是 Nginx:解析 Rust 应用服务器与传统 Web 服务器的本质区别
前端·nginx·rust·tomcat·appche
Liudef062 小时前
基于LLM的智能数据查询与分析系统:实现思路与完整方案
前端·javascript·人工智能·easyui
潘小安2 小时前
跟着 AI 学(三)- spec-kit +claude code 从入门到出门
前端·ai编程·claude
金梦人生2 小时前
让 CLI 更友好:在 npm 包里同时支持“命令行传参”与“交互式对话传参”
前端·npm
Mintopia3 小时前
🐋 用 Docker 驯服 Next.js —— 一场前端与底层的浪漫邂逅
前端·javascript·全栈