HTTP缓存 && Service Worker

温馨提示
本文只是下面两篇文章
HTTP缓存
Caching best practices & max-age gotchas
的阅读理解。如有错误请不吝赐教!

前段时间看了Service Worker,今天看了HTTP缓存,既然都是缓存,那么它们之间必定就有联系。
HTTP缓存的内容不多,总结成了一张思维导图,具体可以看这篇文章:
HTTP缓存

HTTP缓存 && Service Worker_第1张图片
HTTP缓存

还有文章里一张很干货的图,什么时候用什么缓存策略:
HTTP缓存 && Service Worker_第2张图片
HTTP缓存策略

关于缓存策略还可以参考这篇文章:
Caching best practices & max-age gotchas
文章主要思想如下:

  1. 不变的内容,用大的max-age,比如Cache-Control: max-age=31536000,当文件发生修改,可以通过同步更新文件名(比如,style-v1.css , style-v2.css)的方式在强制更新缓存。
    这种方法不适用于博客类的网页,因为博文的url无法版本化。
  2. 经常改变的内容,每次请求都要向服务器验证其内容没变,Cache-Control: no-cache
  3. 适当使用max-age会有效减轻服务器压力,但有时把max-age用在经常改变的内容上可能会有很严重的后果。
    举个例子,现在HTTP缓存了index.html, style.css, script.js,在新鲜期内,缓存器丢失了style.css,而服务器刚好又更新了这三个文件。
    现在我请求这三个文件,缓存器会返回缓存的index.html, script.js,因为style.css丢了,所以他要向服务器请求新的css文件。
    那么,现在,我得到了旧的html,js文件,新的css文件,会出现什么情况呢?
    整张页面的样式可能全乱掉了。
    更可怕的是,因为三者的max-age是一样的,那么缓存器请求了新的css文件,css文件的到期时间和html,js文件就变得不同步了!这样的连锁反应导致的后果就是,以后缓存器中的style.cssindex.html, script.js可能会一直保持早不兼容的状态。
  4. 如何解决这个问题呢?一种方法是用户刷新页面,因为刷新页面会绕过max-age,向服务器重新验证内容是否为最新。但是这么做会降低用户对网站的好感度。
  5. 第二种方法是和Service Worker合作。

终于进入正题了~~!
我们可能会想要用SW来代替HTTP缓存,比如现在写一个这样的sw.js

const version = '2';

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(`static-${version}`)
      .then(cache => cache.addAll([
        '/styles.css',
        '/script.js'
      ]))
  );
});

self.addEventListener('activate', event => {
  // …delete old caches…
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => response || fetch(event.request))
  );
});

这段代码说,每次更新SW,SW在install阶段中,会缓存/styles.css, /script.js两个文件,之后会捕获用户的网络请求,先去找缓存里有没有对象的响应,没有的话再走正常的网络流程。

但是很尴尬的是,也许你已经从前面的“正常的网络流程”的字里行间看出来了,如果SW发送了一个网络请求,请求也会经过缓存服务器,缓存服务器很可能会返回缓存的文件。也就是说,我们还是会得到不兼容的版本。

同样的道理,SW在install阶段缓存的这两个文件,也很有可能是不兼容的旧版本。

那我们要怎么避免这种情况?一个办法是让SW每次发送的url都不一样,这样就绕过了HTTP缓存了。

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(`static-${version}`)
      .then(cache => Promise.all(
        [
          '/styles.css',
          '/script.js'
        ].map(url => {
          // url中添加一个随机数
          return fetch(`${url}?${Math.random()}`).then(response => {
            // fail on 404, 500 etc
            if (!response.ok) throw Error('Not ok');
            return cache.put(url, response);
          })
        })
      ))
  );
});

有一些浏览器已经支持了Request接口的cache选项,可以直接这么写:

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(`static-${version}`)
      .then(cache => cache.addAll([
        new Request('/styles.css', { cache: 'no-cache' }),
        new Request('/script.js', { cache: 'no-cache' })
      ]))
  );
});

我们似乎已经解决了问题,但是,用SW代替HTTP缓存可行吗?‘
看一下SW的兼容性,是不是泪流满面?因为这种方案只能解决支持SW的浏览器的问题。

更好的方法是两者结合。

在HTTP缓存中,把根页面的缓存机制设为:no-cache,css,js文件用版本号控制变更,然后使用max-age=...的缓存策略。每次我们更新css或者js文件,文件名也会改变。

使用下面的sw.js代码:

const version = '23';

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(`static-${version}`)
      .then(cache => cache.addAll([
        '/',
        '/script-f93bca2c.js',
        '/styles-a837cb1e.css',
        '/cats-0e9a2ef4.jpg'
      ]))
  );
});

每次SW更新时,都会触发一次对根页面的请求。而css,js文件只有在文件名改变时,才会打扰远程服务器。

如此,我们避免了版本不兼容的问题,也有效的节省了带宽。

你可能感兴趣的:(HTTP缓存 && Service Worker)