HTTP/1.x 及 Service Worker 缓存实践小结

栏目: 后端 · 前端 · 发布时间: 5年前

内容简介:根据 MDN 定义可知道,缓存是对已获取资源的重新利用,是提升 WEB 性能的重要指标。根据是否和 Server 进行交互,HTTP 缓存分为两类:强制缓存是无需和 Server 进行交互,直接在 Client 进行缓存。 而协商缓存需要和 Server 交互来判断是否重用缓存。HTTP 缓存首部有以下几种:
The performance of web sites and applications can be significantly improved by reusing previously fetched resources. Web caches reduce latency and network traffic and thus lessen the time needed to display a representation of a resource. By making use of HTTP caching, Web sites become more responsive.

根据 MDN 定义可知道,缓存是对已获取资源的重新利用,是提升 WEB 性能的重要指标。根据是否和 Server 进行交互,HTTP 缓存分为两类:

  • 强制缓存
  • 协商缓存

强制缓存是无需和 Server 进行交互,直接在 Client 进行缓存。 而协商缓存需要和 Server 交互来判断是否重用缓存。

HTTP 缓存首部有以下几种:

Expires
Cache-Control
ETag/If-None-Match
Last-Modified/If-Modified-Since

Expires

语法: Expires: <http-date>

Expires 通过设置一个时间戳,控制缓存的过期时间点。但缺点是客户端时间和服务器时间可能不一致,无法保证缓存的同步性。

此外,如果存在 Cache-Control 首部并设置了 max-age 指令, Expires 首部将被忽略。

Cache-Control

语法:`Cache-Control: [public | private | no-cache | only-if-cached],max-age=|s-maxage=|max-stale[=]|min-fresh=][,must-revalidate|proxy-revalidate|immutable][,no-store|no-transform]

具体配置细节见MDN,属于强制缓存,不再赘述。这里只讲下自己实践所用到的设置项。

public | private
max-age=<seconds>
no-cache | no-store | must-revalidate

publicprivate 定义了缓存的共享性,分为共享(public)与私有(private)缓存。共享缓存存储的响应能够被多个用户使用,私有缓存只能用于单独用户。 共享缓存可存在于 ISP、网关或 CDN 的节点上,能很大程度缓存热门资源,减少网络拥堵与延迟,但存在中间人攻击的风险,故存在 private 缓存 —— 只缓存在用户的浏览器端,不会被共享。可根据自己的业务需求,选择是私有还是共享的。

max-age=<seconds> 规定了缓存时长,以秒为单位。从开始接收到资源为时间点,在接下来的 max-age 时间内使用缓存。理论上来说可以长期缓存,但带来的问题是浏览器缓存的臃肿,根据RFC2616 最长时常设为一年较为合适,即 Cache-Control: max-age=31536000

no-cacheno-storemust-revalidateno-cache 规定使用缓存之前时一定要经过验证,比如验证 ETag/ Last-Modified 等; no-store 直接禁止浏览器以及所有中间缓存存储任何版本的返回响应,每次用户请求该资产时,都会向服务器发送请求,并下载完整的响应; must-revalidate 缓存必须在使用之前验证旧资源的状态,并且不可使用过期资源。

ETag/If-None-Match

ETag 是对资源的一个特殊标志符,能唯一确定资源。语法:

ETag: [W/]"<etag_value>"
复制代码

W/ 表明了资源是否采用弱类型验证器进行比较,其较为容易生成但不利于比较。 "<etag_value>" 是对资源的唯一标志符,其值是一串 ASCII 字符串。生成规则没有一定的要求,但常采用的生成算法是内容的 hash 值加上内容的最后修改时间。

当响应头部包含 ETag 时,下次请求时浏览器会自动带上 If-None-Match: <last_etag_value> 首部,用来验证资源是否过期。 如果已过期,则以 HTTP 200 返回新的内容响应并带上新的 ETag 。如果资源未过期,则返回 HTTP 304 告知浏览器资源未过期可以继续使用。

Last-Modified/If-Modified-Since

语法: Last-Modified: <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT

顾名思义, Last-Modified/If-Modified-Since 是根据内容最后的修改时间来判断是否采用缓存的方法。但由于最小时间单位为秒,对于要求时间比较精细的资源可能不太适用。

缓存优先级

HTTP/1.x 缓存首部的优先级: Cache-Control > Expires > ETag/If-None-Match > Last-Modified/If-Modified-Since , 即在同时设定了上述首部时 Cache-Control 最高,可根据业务需求设定。

以上,便是 HTTP/1.x 缓存设置的首部解释,可以通过 Browser Caching Checker 对浏览器缓存进行检查。

HTTP/1.x 及 Service Worker 缓存实践小结

Server Worker 缓存

当下时间点,Service Worker 在浏览器上的支持度已高达86.16%, 所以是时候考虑开启 Service Worker 来加速你的网站了。不仅可以利用 Service Worker 所带来的缓存好处,还能很容易迁移到 PWA,更大程度发掘 Web App 的能力。

不同于 HTTP 缓存,Server Worker 不仅能动态缓存资源,而且还能提供 offline 模式,对弱网络环境的用户极为友好。开启 Service Worker 大概需要注册、安装、缓存资源、更新和注销等过程。

HTTP/1.x 及 Service Worker 缓存实践小结

接下来以一个小 Demo 为例,简单介绍如何开启一个 Service Worker 服务。源码见 sw-cache-example

注册

注册流程很简单,只需要判断浏览器是否支持 Service Worker 特性,并在页面 Load 之后,注册 Service Worker 服务,关键代码:

// sw-reg.js

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)
      }
    )
  })
}
复制代码

安装

安装过程需要做的有:监听 install 事件,并在其回调事件内缓存资源。

var CACHE_NAME = 'cache-v1'
var urlsToCache = ['/', '/styles/main.css', '/script/main.js']

self.addEventListener('install', function(event) {
  // Perform install steps
  event.waitUntil(
    caches.open(CACHE_NAME).then(function(cache) {
      console.log('Opened cache')
      return cache.addAll(urlsToCache)
    })
  )
})
复制代码

响应缓存

最重要的一步,就是在资源被缓存后利用缓存了。需要做的也很简单:监听 fetch 事件 -> 对已缓存的资源进行响应。

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request).then(function(response) {
      if (response) {
        return response
      }
      return fetch(event.request)
    })
  )
})
复制代码

更新

更新也是 Service Worker 很重要的一步,其过程也很易懂:验证资源是否过期 -> 对过期的资源进行删除并缓存新的资源。

self.addEventListener('activate', function(event) {
  var cacheWhitelist = ['pages-cache-v1', 'blog-posts-cache-v1']

  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cacheName) {
          if (cacheWhitelist.indexOf(cacheName) === -1) {
            return caches.delete(cacheName)
          }
        })
      )
    })
  )
})
复制代码

注销

注销只需要拿到 Service Worker 实例,调用 unregister 即可。

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.ready.then(registration => {
    registration.unregister()
  })
}
复制代码

至此,基本完成了 Service Worker 的基本部署,开启其提供的缓存能力。

:mask: 实践过程中遇到的坑

  1. 迁移 HTTP 请求方法为 fetch

由于在响应缓存时,需要通过监听 fetch 事件来响应缓存,故需要更改 HTTP 请求方法为 fetch ,其 API 参见MDN。 对于不支持 fetch 的浏览器,可以使用这个 fetch 进行打补丁。

  1. 取消 fetch 请求

由于 fetch 没有提供原生的取消方法,故需要使用 signal 来取消 fetch 请求。

const controller = new AbortController()
const signal = controller.signal

fetch('/some/url', { signal })
  .then(res => res.json())
  .then(data => {
    // do something with "data"
  })
  .catch(err => {
    if (err.name == 'AbortError') {
      return
    }
  })

// controller.abort(); // can be called at any time
复制代码

Polyfill 参照 abortcontroller-polyfill

  1. 增加 Service Worker 开关 Service Worker 提供的缓存虽然好用,但有时候需要根据业务注销 Service Worker, 这时就需要一个开关来控制。而且应该在第一次部署的时候就增加开关,对于缓存进行控制。
fetch(API.switch)
  .then(res => {
    const isOn = res.status
    if (isOn) {
      sw.register()
    } else {
      sw.unregister()
    }
  })
  .catch(err => {
    console.error('fetch sw status error', err)
  })
复制代码
  1. 对入口文件取消缓存 对于一般的 SPA,是通过入口文件进行资源的索引,所以对入口文件应该不予缓存,并要求其强制更新。在使用 sw-precache-webpack-plugin 应排除入口文件:
new SWPrecacheWebpackPlugin(
    {
      cacheId: 'my-project-name',
      dontCacheBustUrlsMatching: /\.\w{8}\./,
      filename: 'service-worker.js',
      minify: true,
      navigateFallback: PUBLIC_PATH + 'index.html',
      staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/, /index\.html$/],
    }
  ),
复制代码

对入口文件可以设置 HTTP 响应首部:

Cache-Control: no-cache, no-store, must-revalidate
复制代码

其含义是不使用本地及任何中间存储缓存,必须和服务器取得验证才能拿到新的内容。

  1. 如果不想自己编写 Service Worker, 可以参照网上的模板或插件 :

以上所述就是小编给大家介绍的《HTTP/1.x 及 Service Worker 缓存实践小结》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

How to Design Programs

How to Design Programs

Matthias Felleisen、Robert Bruce Findler、Matthew Flatt、Shriram Krishnamurthi / The MIT Press / 2001-2-12 / 71.00美元

This introduction to programming places computer science in the core of a liberal arts education. Unlike other introductory books, it focuses on the program design process. This approach fosters a var......一起来看看 《How to Design Programs》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

MD5 加密
MD5 加密

MD5 加密工具

SHA 加密
SHA 加密

SHA 加密工具