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

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

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

运行后

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

服务端输出如下

客户端请求如下

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

服务端输出如下

客户端请求如下

相关推荐
知否技术几秒前
面试官最爱问的Vue3响应式原理:我给你讲明白了!
前端·vue.js
Bruce_Liuxiaowei18 分钟前
DeepSeek API集成开发指南——Flask示例实践
后端·python·flask·deepseek
钟离墨笺22 分钟前
【网络协议】【http】http 简单介绍
网络·网络协议·http
易元30 分钟前
设计模式-享元模式
后端
一只小鱼儿吖1 小时前
代理IP协议详解HTTP、HTTPS、SOCKS5分别适用于哪些场景
tcp/ip·http·https
小周同学:1 小时前
vue将页面导出成word
前端·vue.js·word
老友@1 小时前
从 Word 到 HTML:使用 Aspose.Words 轻松实现 Word 文档的高保真转换
java·后端·性能优化·html·word·aspose·格式转换
sunbin1 小时前
doc文档转换为html文档
后端
Asthenia04121 小时前
面试复习:游标是什么?什么是深度分页?如何用游标解决深度分页?(以 InnoDB 为例)
后端
阿杰在学习1 小时前
基于OpenGL ES实现的Android人体热力图可视化库
android·前端·opengl