浏览器缓存机制

浏览器缓存机制_第1张图片

 浏览器的存储和缓存机制就是为了减少网格请求,提高用户体验。

其中缓存位置上来说分为四种:Memory Cache,Service Worker Cache,HTTP Cache, Push Cache.并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络。浏览器缓存机制_第2张图片

1、Memory Cache

Memory Cache 也就是内存中的缓存,其优点是读取速度快,但是一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。

当我们访问过页面以后,再次刷新页面,可以发现很多数据都来自于内存缓存

浏览器缓存机制_第3张图片

浏览器秉承的是“节约原则”。我们发现,Base64 格式的图片,几乎永远可以被塞进 memory cache。此外,体积不大的 JS、CSS 文件,也有较大地被写入内存的几率——相比之下,较大的 JS、CSS 文件由于内存资源是有限的,它们往往被直接甩进磁盘。

2、Service Worker Cache

Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。 但是传输协议必须为 https。

Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。我们不常用到。

3、Push Cache

Push Cache 是缓存的最后一道防线。浏览器只有在 Memory Cache、HTTP Cache 和 Service Worker Cache 均未命中的情况下才会去询问 Push Cache。详情请查看HTTP/2 push is tougher than I thought。

4、HTTP Cache

HTTP 缓存是我们日常开发中最为熟悉的一种缓存机制。它又分为强缓存协商缓存。优先级较高的是强缓存,在命中强缓存失败的情况下,才会走协商缓存。

强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control

// 处理图片,强制性缓存
router.get(/\S*\.(jpe?g|png)$/, async (ctx, next) => {
  const { path } = ctx;
  ctx.type = mime.getType(path);
  // Expires:缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。
  ctx.set('expires', new Date(Date.now() + 30000).toUTCString())
  /* Cache-Control
     public:表示响应可以被客户端和代理服务器缓存
     private:表示响应可以被客户端缓存、
     max-age=1000:缓存30秒后就过期,需要重新请求。
     s-max-age=1000:覆盖max-age,作用一样,只是在代理服务器生效
     no-store:不缓存任何响应
     no-cache: 资源被缓存,但是立即失效,下次会发起请求验证资源是否过期。
     max-stale=1000:1000秒内,即使缓存过期,也使用该缓存
     max-fresh=1000:希望在1000秒内获取最新的响应
   */
  ctx.set('cache-control', 'no-cache,public,max-age=1000');
  const imageBuffer = await fs.readFile(Path.resolve(__dirname, `.${path}`));
  ctx.body = imageBuffer;

  await next();
});

浏览器缓存机制_第4张图片

协商缓存依赖于服务端与浏览器之间的通信。

协商缓存机制下,浏览器需要向服务器去询问缓存的相关信息,进而判断是重新发起请求、下载完整的响应,还是从本地获取缓存的资源

如果服务端提示缓存资源未改动(Not Modified),资源会被重定向到浏览器缓存,这种情况下网络请求对应的状态码是 304(如下图)。

浏览器缓存机制_第5张图片

协商缓存中那几个首部字段是配对使用的,即:

  • 请求头 if-modified-since 和响应头 last-modified
  • 请求头 if-none-match 和响应头 etag

if-modified-since 和 last-modified

它俩的值都是 GMT 格式的精确到秒的时间值。从字面上就很好理解它们的含义:自从某某时间有没有修改过?最后一次修改时间为某某时间

他俩有啥关系呢?其实本次请求头 if-modified-since的值应该为上一次请求该资源的响应头中 last-modified 的值

当浏览器发起资源请求并携带 if-modified-since 字段,服务器会将请求头中的 if-modified-since 值和请求资源的 最后修改时间进行比较,如果资源最后修改时间比 if-modified-since 时间晚,那么资源过期,状态码为 200,响应体为请求资源,响应头中加入最新的 last-modified 的值。没过期就返回状态码 304,命中协商缓存,响应体为空,响应头不需要 last-modified 值。

// 处理 css 文件 last-modified 配置协商缓存
router.get(/\S*\.css$/, async (ctx, next) => {
  const { request, response, path } = ctx;
  response.set('pragma', 'no-cache');
  const cssPath = Path.resolve(__dirname, `.${path}`);
  const ifModifiedSince = request.headers['if-modified-since'];
  const cssStatus = await fs.stat(cssPath);
  const lastModified = cssStatus.mtime.toGMTString();
  if (ifModifiedSince === lastModified) {
    response.status = 304;
  } else {
    response.lastModified = lastModified;
    await responseFile(cssPath, ctx);
  }
  await next();
});

浏览器缓存机制_第6张图片

if-none-match 和响应头 etag 

Etag就像个key值,资源变化都会导致ETag变化,ETag可以保证每一个资源是唯一的key。

MDN 上对 etag 的描述是:

它们是位于双引号之间的ASCII字符串(如“675af34563dc-tr34”)。 没有明确指定生成ETag值的方法。 通常,使用内容的散列,最后修改时间戳的哈希值,或简单地使用版本号。 例如,MDN使用wiki内容的十六进制数字的哈希值。

为什么有了 last-modified 还需要 etag ?

  • 资源在 1 秒内更新,并且在该一秒内访问,使用 last-modified 处理协商缓存无法获取最新资源。本质上的原因还是因为 last-modified 是精确到秒的,无法反映在 1 秒内的变化。
  • 当资源多次被修改后内容不变,使用 last-modified 来处理有点浪费。多次修改资源,其 last-modified 值肯定是会变的,但是如果内容不变我们其实不需要服务器返回最新资源,直接使用本地缓存。使用 etag 就没这个问题,因为同一个资源多次修改,内容一样, hash 值也一样。
  • 使用 etag 更加灵活,因为 etag 并不一定是我说的就用 hash 值,etag 采用的是弱比较算法,即两个文件除了每个比特都相同外,内容一致也可以认为是相同的。例如,如果两个页面仅仅在页脚的生成时间有所不同,就可以认为二者是相同的。

使用 etag 配置协商缓存 

/ 处理 js 文件 使用 etag 配置协商缓存
router.get(/\S*\.js$/, async (ctx, next) => {
  const { request, response, path } = ctx;
  ctx.type = mime.getType(path);
  response.set('pragma', 'no-cache');

  const ifNoneMatch = request.headers['if-none-match'];
  const imagePath = Path.resolve(__dirname, `.${path}`);
  const hash = crypto.createHash('md5');
  const imageBuffer = await fs.readFile(imagePath);
  hash.update(imageBuffer);
  const etag = `"${hash.digest('hex')}"`;
  if (ifNoneMatch === etag) {
    response.status = 304;
  } else {
    response.set('etag', etag);
    ctx.body = imageBuffer;
  }

  await next();
});

浏览器缓存机制_第7张图片

5、如何选择适合的缓存

  • Cache-Control —— 请求服务器之前
  • Expires —— 请求服务器之前
  • If-None-Match (Etag) —— 请求服务器
  • If-Modified-Since (Last-Modified) —— 请求服务器

 整体流程图(来源网上)

浏览器缓存机制_第8张图片

参考资源

1、浏览器缓存:memory cache、disk cache、强缓存协商缓存等概念

2、前端性能优化原理与实践

3、理解http浏览器的协商缓存和强制缓存

4、缓存(二)——浏览器缓存机制:强缓存、协商缓存

github

你可能感兴趣的:(网络/网络安全,前端进阶系列,缓存)