浏览器缓存实践篇

1,748 阅读2分钟

纸上得来终觉浅,绝知此事要躬行。

最近在复习浏览器缓存,在复习过程中产生了很多的疑问,决定来实际使用一下测试真实情况。理论知识参考的 (1.6w字)浏览器灵魂之问,请问你能接得住几个? 这篇文章。

强缓存

  • Expires
  • Cache-Control

Expires

首先测试下 Expires,首先使用 Express 搭建一个本地服务器。使用 static 访问本地的 public 文件夹,通过配置 etag, cacheControl, lastModified 为 false,禁用所有的缓存。

const express = require('express')
const app = express()
const port = 3000

app.get('/hello', (req, res) => res.send('Hello World!'))
app.use('/static', express.static('public', {
    etag: false,
    cacheControl: false,
    lastModified: false,
}))

app.listen(port, () => console.log(`Example app listening on port ${port}!`))

启动起来后可以在本地访问多次看下状态都是 200,没有缓存,Response Headers 里面没有缓存相关的字段。

然后我们添加上 Expires 字段测试一下。我们给所有请求的 Header 加上 Expires 字段,设置过期时间为请求后的 60 秒后。

app.use('/static', express.static('public', {
    etag: false,
    cacheControl: false,
    lastModified: false,
    setHeaders: function (res, path, stat) {
      res.append('Expires', new Date(new Date().getTime() + 60 * 1000));
    }
}))

可以看到第一次请求 index.html 和 test.js 的 Response Headers 里面都有 Expires 字段,时间为发起请求 + 60 秒。

然后我们再次刷新,可以看到 test.js 直接从 memory cache 里面取的,看下后端的日志,也没有打印出来 /static/test.js 的请求 url,说明 Expires 生效了,但是对于 index.html 确没有生效,这里没有搞清楚原因,希望知道的指点一下。 60 秒后再次刷新,可以看到又是从后端后去资源了。

另外,Expires 是跟本地时间有关系的,我们也可以测试下。因为不好截图显示我修改了本地时间,在此就不截图了,具体操作是:先请求,然后修改本地时间到一分钟后,刷新,可以看到并没有缓存。


Cache-Control

然后测试 Cache-Control,文档里说到 Cache-Control 和 Expires 同时存在时,优先考虑 Cache-Control,所以我们直接加上 Cache-Control 字段测试一下。

app.use('/static', express.static('public', {
    etag: false,
    // 去掉 cacheControl 的 false 设置
    // cacheControl: false,
    lastModified: false,
    setHeaders: function (res, path, stat) {
      res.append('Expires', new Date(new Date().getTime() + 60 * 1000));
    }
}))

继续刷新看结果,可以看到 Cache-Control 和 Expires 同时存在,但是 Expires 不生效了,每次都是 200 并且没有取缓存。

因为没有设置 max-age,Cache-Control 的默认值为 public, max-age=0,所以没有缓存效果,现在设置下 max-age 为 10 秒。

app.use('/static', express.static('public', {
    etag: false,
    // 去掉 cacheControl 的 false 设置
    // cacheControl: false,
   	// 这里的 maxAge 是毫秒,但是 Response Header 中的 max-age 是秒单位
    maxAge: 10000,
    lastModified: false,
    setHeaders: function (res, path, stat) {
      res.append('Expires', new Date(new Date().getTime() + 60 * 1000));
    }
}))

刷新看结果,10 秒内直接取缓存。

然后测试下 Cache-Control 的其他设置值。

  1. no-cache 跳过强缓存,直接进入协商缓存
app.use('/static', express.static('public', {
    etag: false,
    // 去掉 cacheControl 的 false 设置
    // cacheControl: false,
   	// 这里的 maxAge 是毫秒,但是 Response Header 中的 max-age 是秒单位
    maxAge: 10000,
    lastModified: false,
    setHeaders: function (res, path, stat) {
      res.append('Expires', new Date(new Date().getTime() + 60 * 1000));
      res.append('Cache-Control', 'no-cache');
    }
}))

刷新可以看没有强缓存,无 memory cache

  1. no-store 不进行任何缓存
app.use('/static', express.static('public', {
    etag: false,
    // 去掉 cacheControl 的 false 设置
    // cacheControl: false,
   	// 这里的 maxAge 是毫秒,但是 Response Header 中的 max-age 是秒单位
    maxAge: 10000,
    lastModified: false,
    setHeaders: function (res, path, stat) {
      res.append('Expires', new Date(new Date().getTime() + 60 * 1000));
      res.append('Cache-Control', 'no-store');
    }
}))

刷新可以看没有强缓存,无 memory cache

  1. private/s-maxage 这两个字段用于代理服务器,没搞明白如何测试代理服务器,求助大家。

协商缓存

当强缓存失效后,会根据是否存在协商缓存字段进行协商缓存阶段

  • Last-Modified
  • ETag
Last-Modified

默认 lastModified 为 true,lastModified 设置为 true 时,会在 Response Headers 中添加 Last-Modified 字段,然后在下次请求该资源时把该字段放入 If-Modified-Since 中,服务器会根据资源的最近修改时间和 If-Modified-Since 进行对比,如果发现文件更新了,则返回 200,把最新更新时间放入 Last-Modified 中,如果没有更新,则返回 304,告诉浏览器直接用缓存。

app.use('/static', express.static('public', {
    etag: false,
    // 去掉 cacheControl 的 false 设置
    // cacheControl: false,
   	// 这里的 maxAge 是毫秒,但是 Response Header 中的 max-age 是秒单位
    maxAge: 10000,
    // lastModified: false,
    setHeaders: function (res, path, stat) {
      res.append('Expires', new Date(new Date().getTime() + 60 * 1000));
      res.append('Cache-Control', 'no-cache');
    }
}))

第一次请求后再次直接请求 ,可以看到 index.html 和 test.js 都返回了 304,当我修改了 test.js 并保存后,可以看到 test.js 请求返回了 200。

可以看到返回了 Last-Modified 为最新的修改时间,比 If-Modified-Since 时间晚。

文章里说到 Last-Modified 能够感知的单位时间是秒,如果文件在 1 秒内改变了多次,那么这时候的 Last-Modified 并没有体现出修改了。这里我测试了一下确实是这样的。 我写了个 js 去修改 index.html

const fs = require('fs');

const res = fs.readFileSync('./public/index.html');

function sleep(time) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, time)
  })
}

(async function() {
  await sleep(10000);
  for (let i = 0; i < 60; i++) {
    await sleep(10);
    fs.writeFileSync('./public/index.html', `${res} ${i}`);
  }  
})()

在 600 ms 内每 10 ms 去修改一次 index.html,可以看到最后获取到的 index.html里面的数字是 43,而实际上我的文件里内容是 59.

E-Tag

打开静态文件的 E-Tag 只需要去掉 etag: false,默认 etag 为 true

app.use('/static', express.static('public', {
    // etag: false,
    // 去掉 cacheControl 的 false 设置
    // cacheControl: false,
    // 这里的 maxAge 是毫秒,但是 Response Header 中的 max-age 是秒单位
    // maxAge: 10000,
    // lastModified: false,
    setHeaders: function (res, path, stat) {
      res.append('Expires', new Date(new Date().getTime() + 60 * 1000));
      res.append('Cache-Control', 'no-cache');
    }
}))

可以看到设置了 etag 之后就不会有 “Last-Modified 能够感知的单位时间是秒” 的问题了。每次文件有修改都会更新到 ETag

总结

到这里文章就结束了,总结一下:

  1. Cache-Control 的 max-age 和 Expires 控制强缓存,有 Cache-Control,则 Expires 失效。
  2. 强缓存失效,Last-Modified 和 ETag 控制协商缓存。ETag 优先级比 Last-Modified 优先级高。
  3. 另外需要注意,因为我使用的 express.static 支持缓存,如果是 Get 或者 Head 请求,可以自己实现或者使用中间件实现缓存效果。

另外有几个问题没有解决。

1.index.html 不会从 memory-cache 取,我猜测可能是浏览器认为是入口页面不会 memory-cache,而 index.html 里面引入的 test.js 则可以 memory-cache。
2.private 和 s-maxage 没有进行测试。