利用node.js来实际验证HTTP缓存

144 阅读4分钟

对重复的HTTP请求,我们可以将请求结果缓存在本地,在下次请求的时候直接读取本地数据,而不通过网络获取服务器响应,从而来提高响应速度。

在HTTP协议中支持两种方式的缓存:

  1. 强制缓存
  2. 协商缓存

强制缓存

强制缓存的实现主要靠服务端在返回请求结果时,在响应头中设置Cache-Control或者Expires字段。它用来设置客户端在哪个时间内再次发起请求时使用强制缓存。当使用强制缓存时,浏览器会直接从磁盘中读取之前请求下来的数据作为结果返回,而不会先服务端再次发送请求:

这个字段支持的值有两种形式:

  1. Cache-Control: max-age=3600:使用max-age设置一个相对时间,时间单位是秒。服务器在返回请求结果的时候,响应头中会有Date字段,它表示报文的创建时间,结合max-age就可以得出强制缓存的有效时间了
  2. Expires: Wed, 21 Oct 2015 07:28:00 GMT:填入的则是一个绝对时间,它表示在这个时间之前所发送的相同请求都是使用强制缓存

当响应头中同时设置了Cache-ControlExpires时,Cache-Control的优先级会更高

协商缓存

当浏览器在强制缓存过期后,会再次发送网络请求给服务端,此时服务端可以与通过客户端之前约定的字段来判断是否需要返回资源给客户端:

  1. 如果不需要返回资源给客户端,就设置响应码为304,表示客户端缓存的资源还没有过期,可以继续使用,并会更新强制缓存的有效时间
  2. 如果服务端发现资源发生了改动,则此时需要响应最新的资源给客户端

用于协商缓存的字段也有两种:

  1. 请求头中的If-Modified-Since和响应头中的Last-Modified
  2. 请求头中的If-Not-Match和响应头中的ETag

二者的区别:

  • Last-Modified是基于文件最后修改时间实现的
  • ETag则是服务端约定生成的一种唯一标志
  • ETag的优先级会更高

为什么ETag的优先级会更高呢?

因为使用Last-Modified字段存在以下的问题:

  1. 文件在内容没有更改的情况下,最后修改时间也可能会变化
  2. Last-Modified的粒度是秒级而,而有时候文件的更改会在秒级以内
  3. 有些服务器不能精确获取文件的最后修改时间

注意:

协商缓存需要配和强制缓存一起使用,只有未命中强制缓存时,才会使用协商缓存

工作流程

代码实现

下面我们使用了node.js来实现了一个Web服务器,并给这个服务器接受的HTTP请求设置了强制缓存和协商缓存:

const fs = require("node:fs");
const http = require("node:http");
const path = require("node:path");

const server = http.createServer();
server.on("request", (req, res) => {
  // 允许跨域请求
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader(
    "Access-Control-Allow-Methods",
    "GET, POST, PUT, DELETE, OPTIONS"
  );
  // 设置强制缓存,缓存时间为1小时
  res.setHeader("Cache-Control", "max-age=30");
  // 读取服务器上的资源信息
  const filePath = path.join(__dirname, "test.json");
  // 设置协商缓存(使用ETag)
  fs.stat(filePath, (err, stat) => {
    if (err) {
      console.error(err);
      res.statusCode = 500;
      res.end("Internal Server Error");
      return;
    }
    // 生成etag用于协商缓存
    const etag = stat.size + "-" + Date.parse(stat.mtime);
    res.setHeader("ETag", etag);
    // 对比请求头中的if-none-match和本次的etag是否一致
    console.log(req.headers["if-none-match"]);
    if (req.headers["if-none-match"] === etag) {
      // 一致则说明文件没有变动,返回304走协商缓存
      res.statusCode = 304;
      res.end("");
      return;
    }
    // 不一致则说明服务器资源更新了,需要返回最新资源
    res.setHeader("Content-Type", "application-json");
    fs.createReadStream(filePath).pipe(res);
  });
});
server.listen(3000, () => {
  console.log("server is running on http://localhost:3000");
});

这样我们就可以通过在浏览器中发送网络请求来验证HTTP缓存了:

在浏览器中允许如下的js代码

<script>
  const btnEl = document.querySelector('#btn')
  const xhr = new XMLHttpRequest()
  xhr.onload = () => {
    console.log(xhr.response);
  }
  btnEl.addEventListener('click', () => {
    xhr.open('get', 'http://127.0.0.1:3000')
    // xhr.setRequestHeader('Access-Control-Allow-Origin', "*")
    xhr.send()
  })
</script>

注意:

如果通过浏览器的地址栏直接输入对应的URL发送HTTP请求,我们会发现强制缓存无法生效。这主要是因为浏览器认为用户通过地址栏输入URL发送请求时,更关心获取最新的资源,而不是使用缓存的资源