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

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

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

运行后

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

服务端输出如下

客户端请求如下

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

服务端输出如下

客户端请求如下

相关推荐
童先生27 分钟前
Nginx + Vue/React 前端 + API:防止路径混淆漏洞与跨域问题实战分享
前端·vue.js·nginx
蝎子莱莱爱打怪2 小时前
Hadoop3.3.5、Hbase2.6.1 集群搭建&Phoenix使用记录
大数据·后端·hbase
David爱编程2 小时前
并发编程三大特性全解析:原子性、可见性、有序性,一文讲透!
java·后端
Stringzhua3 小时前
Vue数据的变更操作与表单数据的收集【6】
前端·javascript·vue.js
万少3 小时前
可可图片编辑 HarmonyOS 上架应用分享
前端·harmonyos
你的人类朋友3 小时前
git常见操作整理(持续更新)
前端·git·后端
无羡仙3 小时前
Webpack 核心实战:从零搭建支持热更新与 Babel 转译的现代前端环境
前端·webpack·前端框架
你的人类朋友3 小时前
git中的Fast-Forward是什么?
前端·git·后端
初遇你时动了情4 小时前
uniapp vue3 ts自定义底部 tabbar菜单
前端·javascript·uni-app
JarvanMo4 小时前
天塌了?Flutter工程总监跑去苹果了?
前端