W

http缓存

2024-08-09 · 8min

demo

什么是缓存

一种常见的网络优化手段,减少重复请求
缓存:保存资源副本数据,并在下次请求中直接使用该副本数据

分类

强缓存

浏览器会根据请求的响应头中的 Expires 和 Cache-Control 字段判断是否命中缓存

Expires

app.use('/', (req, res) => {
  const { pathname } = url.parse(req.url);
  if (pathname === '/') {
    const html = fs.readFileSync(
      path.resolve(__dirname, './public/index.html')
    );
    res.end(html);
  } else if (pathname === '/1.jpeg') {
    const img = fs.readFileSync('./public/1.jpeg');
    // 强缓存  expires
    res.set('Expires', new Date('2024-07-10 11:15:00').toUTCString());
    res.end(img);
  } else {
    res.end('404');
  }
});

第一次请求,响应数据,状态码 200 expires第一次请求

第二次请求,取缓存的数据 expires第二次请求

Expires 设置的是一个具体的时间,在该时间之前浏览器都会使用该缓存

:::tip 缺点:expires 时间依赖于客户端的时间,可能存在时间不准导致缓存过期 :::

Cache-Control

expires 是 http1.0 的参数,为了解决 expires 时间不准的问题,http1.1 引入了 Cache-Control,使用时长来判断缓存是否过期,但是 http1.1 还是兼容 expires 参数的

app.use('/', (req, res) => {
  const { pathname } = url.parse(req.url);
  if (pathname === '/') {
    const html = fs.readFileSync(
      path.resolve(__dirname, './public/index.html')
    );
    res.end(html);
  } else if (pathname === '/1.jpeg') {
    const img = fs.readFileSync('./public/1.jpeg');
    // 强缓存 cache-control
    res.set('Cache-Control', 'max-age=10');
    res.end(img);
  } else {
    res.end('404');
  }
});

第一次请求,响应数据,状态码 200 cache-control第一次请求

第二次请求,取缓存的数据 cache-control第二次请求

:::tip 如果同时存在 expires 和 Cache-Control,Cache-Control 优先级更高 :::

协商缓存

第一次请求,服务器会在响应头中添加 last-modified、etag 等字段,后续请求时浏览器会在请求头中用 If-Modified-Since、If-None-Match 带上 ast-modified、etag 字段的值,服务器会根据请求头中的 If-Modified-Since、If-None-Match 字段判断资源是否更新,如果有更新,则返回最新资源,否则直接使用缓存

last-modified/If-Modified-Since

app.use('/', (req, res) => {
  const { pathname } = url.parse(req.url);
  if (pathname === '/') {
    const html = fs.readFileSync(
      path.resolve(__dirname, './public/index.html')
    );
    res.end(html);
  } else if (pathname === '/2.jpg') {
    const ifModifiedSince = req.headers['if-modified-since'];
    // 文件时上一次修改的时间
    const { mtime } = fs.statSync('./public/2.jpg');
    // 判断文件是否更新
    if (ifModifiedSince === mtime.toUTCString()) {
      res.statusCode = 304;
      res.end();
      return;
    }
    const img = fs.readFileSync('./public/2.jpg');
    res.set('Cache-Control', 'no-cache');
    res.set('last-modified', mtime.toUTCString());
    res.end(img);
  } else {
    res.end('404');
  }
});

第一次请求数据,服务器获取文件上一次更新的时间并在响应头 last-modified 字段中返回给客户端,状态码 200 last-modified第一次请求

第二次请求数据,客户端在 If-Modified-Since 字段中带上上一次请求的 last-modified 的值,服务器判断两个值是否相同,如果相同,则返回 304 状态码,否则返回最新资源,状态码 200 last-modified第二次请求

:::tip缺点:

Etag/If-None-Match

app.use('/', (req, res) => {
  const { pathname } = url.parse(req.url);
  if (pathname === '/') {
    const html = fs.readFileSync(
      path.resolve(__dirname, './public/index.html')
    );
    res.end(html);
  } else if (pathname === '/2.jpg') {
    const ifNoneMatch = req.headers['if-none-match'];
    const img = fs.readFileSync('./public/2.jpg');
    const hash = etag(img);
    // 判断文件是否更新
    if (ifNoneMatch === hash) {
      res.statusCode = 304;
      res.end();
      return;
    }
    res.set('Cache-Control', 'no-cache');
    res.set('Etag', hash);
    res.end(img);
  } else {
    res.end('404');
  }
});

第一次请求数据,服务器获取文件内容的 hash 值并在响应头 Etag 字段中返回给客户端,状态码 200 etag第一次请求

第二次请求数据,客户端在请求头 If-None-Match 字段中带上上一次请求的 Etag 的值,服务器判断两个值是否相同,如果相同,则返回 304 状态码,否则返回最新资源,状态码 200 etag第二次请求

总结