chm1n9 / blog

0 stars 0 forks source link

浏览器缓存设计 #4

Open chm1n9 opened 3 years ago

chm1n9 commented 3 years ago

无需验证

脚本长期不变,可以做设置文件返回Http头

Cache-Control: max-age=31536000

虽然文件长期不变,但是标准推荐max-age最大不超过一年,以秒为单位的话就是 31536000

如果有修改,更新文件名即可

需要验证

一般文件都不会只请求一次,所以不会设置这个大的值,比如设置2分钟max-age=120

但是两分钟后脚本没有更新又完整下载文件也不合理,所以缓存有验证机制。即协商缓存。如果验证发现有更改,就返回完整内容(http状态码200);否则返回304 Not modified ,浏览器就可以继续使用缓存文件。

技术细节

no-cache, no-store, must-revalidate

Cache-Control 不仅可以设置max-age,还有很多其他只,以及可以组合

Expires VS. max-age

他们都是用于控制缓存的生命周期。

Expires是http/1.0的标准,过期的绝对时间,比较复杂(Sun, 21 Mar 2027 08:52:14 GMT),而且客户端时间和服务端时间不一致可能导致失效

max-age是http/1.1中的标准,是相对时间,设置简单

Etag VS. Last-Modified

都可以用于对资源进行验证,一个是验证时间,一个是验证类似hash值得东西

Etag是强验证,它期望资源字节级别一致,更精确,优先级更高,再次请求资源时使用 If-None-Match 发送服务端

Last-Modified属于弱验证,精度更低,只验证要秒的时间,如果资源可能在毫秒级的时间多次修改,那么设置这个缓存则没有意义。再次请求时使用 If-Modified-SinceIf-Unmodified-Since

max-age=0 VS. no-cache

max-age=0是在告诉浏览器,资源已经过期了,你应该(SHOULD)对资源进行重新验证了;而no-cache则是告诉浏览器在每一次使用缓存之前,你必须(MUST)对资源进行重新验证。

区别在于,SHOULD是非强制性的,而MUST是强制性的。在no-cache的情况下,浏览器在向服务器验证成功之前绝不会使用过期的缓存资源,而max-age=0则不一定了。虽然理论上来说它们的效果应该是一致的。

public VS. private

主要是 Cache-Control 设置为 private 以避免中间服务器缓存。

不要对变化的资源添加较短的max-age

Service Worker 与缓存

它是一种 JavaScript Worker,无法直接访问 DOM。 Service Worker 通过响应 postMessage 接口发送的消息来与其控制的页面通信,页面可在必要时对 DOM 执行操作。

Service Worker 的生命周期完全独立于网页。

注册sw

if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    navigator.serviceWorker.register('/sw.js').then(function(registration) {
      // Registration was successful
      console.log(
        'ServiceWorker registration successful with scope: ',
        registration.scope
      );
    }, function(err) {
      // registration failed :(
      console.log('ServiceWorker registration failed: ', err);
    });
  });
}

安装 Service Worker

  1. 打开缓存。
  2. 缓存文件。
  3. 确认所有需要的资产是否已缓存。
// sw.js
const version = '2';

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

缓存和返回请求

在安装 Service Worker 且用户转至其他页面或刷新当前页面后,Service Worker 将开始接收 fetch 事件。下面提供了一个示例。

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

如果希望连续缓存新请求,可以通过处理 fetch 请求的响应并将其添加到缓存来实现,如下所示。

  1. 在 fetch 请求中添加对 .then() 的回调。
  2. 获得响应后,执行以下检查:
    1. 确保响应有效。
    2. 检查并确保响应的状态为 200
    3. 确保响应类型为 basic,亦即由自身发起的请求。 这意味着,对第三方资产的请求也不会添加到缓存。
  3. 如果通过检查,则克隆响应。 这样做的原因在于,该响应是数据流, 因此主体只能使用一次。 由于我们想要返回能被浏览器使用的响应,并将其传递到缓存以供使用,因此需要克隆一份副本。我们将一份发送给浏览器,另一份则保留在缓存。
self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {
        // Cache hit - return response
        if (response) {
          return response;
        }

        // IMPORTANT:Clone the request. A request is a stream and
        // can only be consumed once. Since we are consuming this
        // once by cache and once by the browser for fetch, we need
        // to clone the response.
        var fetchRequest = event.request.clone();

        return fetch(fetchRequest).then(
          function(response) {
            // Check if we received a valid response
            if(!response || response.status !== 200 || response.type !== 'basic') {
              return response;
            }

            // IMPORTANT:Clone the response. A response is a stream
            // and because we want the browser to consume the response
            // as well as the cache consuming the response, we need
            // to clone it so we have two streams.
            var responseToCache = response.clone();

            caches.open(CACHE_NAME)
              .then(function(cache) {
                cache.put(event.request, responseToCache);
              });

            return response;
          }
        );
      })
    );
});

浏览器的整体缓存机制

除了HTTP标准缓存以外,浏览器还有可能存在标准以外的缓存机制。对于Chrome浏览器而言还存在Memory Cache、Push “Cache”。一个请求在查找资源的过程中经过的缓存顺序是Memory Cache、Service Worker、HTTP Cache、Push “Cache”。HTTP Cache和Service Worker已经介绍过了,接下来简单介绍Memory Cache和Push Cache

Memory Cache

“内存缓存”中主要包含的是当前文档中页面中已经抓取到的资源。例如页面上已经下载的样式、脚本、图片等。我们不排除页面可能会对这些资源再次发出请求,所以这些资源都暂存在内存中,当用户结束浏览网页并且关闭网页时,内存缓存的资源会被释放掉。

这其中最重要的缓存资源其实是preloader相关指令(例如<link rel="prefetch">)下载的资源。总所周知preloader的相关指令已经是页面优化的常见手段之一,而通过这些指令下载的资源也都会暂存到内存中。根据一些材料,如果资源已经存在于缓存中,则可能不会再进行preload。

需要注意的事情是,内存缓存在缓存资源时并不关心返回资源的HTTP缓存头Cache-Control是什么值,同时资源的匹配也并非仅仅是对URL做匹配,还可能会对Content-Type,CORS等其他特征做校验

Push “Cache”

“推送缓存”是针对HTTP/2标准下的推送资源设定的。推送缓存是session级别的,如果用户的session结束则资源被释放;即使URL相同但处于不同的session中也不会发生匹配。推送缓存的存储时间较短,在Chromium浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令

chm1n9 commented 3 years ago

test: 什么叫缓存