「查缺补漏」HTTP 缓存

437 阅读7分钟

前言

HTTP 缓存作为前端工程师必备是知识点之一,相信在座的各位都已经掌握了。本文将对 HTTP 缓存知识进行查缺补漏,首先抛出几个问题:

  1. 当你想对某个文件进行缓存时,缓存是由客户端还是服务端来设置,或者都可以?
  2. 客户端和服务端都设置了 Cache-Control 时,这时缓存会以哪个为准?
  3. Cache-Control 的两个值 max-age=0 和 no-cache 效果是一样的吗?

当你看到 Cache-Control 是通用首部字段(请求和响应报文都会使用的首部)的时候,你是不是会有些困惑请求头设置 Cache-Control 的作用,那就对了,本文将带你对 HTTP 缓存知识进行差缺补漏。

实践出真知,相信很多人对 HTTP 缓存也仅是书面上掌握,并未实践过,故本文将使用 koa 搭建服务进行验证。

HTTP 缓存知识回顾

使用 HTTP 缓存可以减少带宽,缓解服务器压力,提高网站性能。与之有关的三个首部字段:Cache-Control, Expires, Pragma。

Pragma

Pragma 是 HTTP/1.1 之前版本的历史遗留字段, 仅作为与 HTTP/1.0的向后兼容而定义。Pragma 只有一个属性值 no-cache,只用在客户端发送的请求中,效果和 Cache-Control 的 no-cache 相同。

Pragma 的优先级高于 Cache-Control,我们看下面两个图:

2022.01.14e532b1c191283454c88009daa5938366.png

图 1:仅设置 Cache-Control: max-age=15

2022.01.14&03a1c5881bd9b2ee7dfe94b068ac223f.png

图 2:设置 Cache-Control: max-age=15 和 Pragma: no-cache

从图 1 和图 2 的结果来看,两个属性都存在时是不使用缓存的,故Pragma 的优先级是高于 Cache-Control 的。

Expires

首部字段 Expires 会将资源失效的日期告知客户端,它的值是一个 HTTP 日期,例如Expires: Wed, 04 Jul 2012 08:26:05 GMT,请求时间不超过这个值的时候,浏览器直接使用缓存。其优先级低于 Cache-Control,我们通过下面两个图来验证。

2022.01.14&3922ada6fe4a308389c056d52a113db3.png

图 3:仅设置 Expires

2022.01.14&d3beb7955379bf52ea498cf3d727a9a1.png

图 4:同时设置 Expires 和 Cache-Control

从图 3 和图 4 可以看到,在同时设置 Expires 和 Cache-Control 的情况下,请求不走缓存,故 Expires 优先级低于 Cache-Control。

三个字段的优先级在此就出来: Pragma > Cache-Control > Expires

Cache-Control

Cache-Control 是 HTTP1.1 新增的通用首部字段,我们来看下它的指令:

2022.01.14&cfd017698b27091b4d52d950f2f6dc8e.png

图 5:请求指令(图片来源《图解 HTTP》)

2022.01.14&895e54e70f57f47a42cdf01e351e94e0.png

图 6:响应指令(图片来源《图解 HTTP》)

请求首部中需要重点关注的有: no-cache, max-age。通常我们会使用 no-cache 或 max-age=0 来禁用缓存。

响应首部中需要重点关注的有: public, private, no-cache, no-store, max-age, s-maxage。no-store 表示禁用缓存,max-age 表示强缓存,no-cache 表示协商缓存,其他的看图中说明。

强缓存和协商缓存

当响应首部中出现 max-age 并且值大于 0 时,浏览器就会启用强缓存,意味着从现在开始,在 max-age 秒内,浏览器不会向服务端发起请求,会直接使用本地缓存。只有在超过 max-age 秒后,浏览器才会重新向服务端发起请求。

max-age.png

图 7:强缓存

我们看图 7:

  1. 浏览器第一次发起请求。
  2. 服务端返回 200,并且设置 Cache-Control: max-age=3600,意味着在接下来的 3600 秒内,浏览器可以直接使用本地缓存,不用向服务端发起请求。
  3. 浏览器再一次发起请求。
  4. 直接读取缓存。

no-cache.png

图 8:协商缓存

接着从图 8 来看协商缓存:

  1. 浏览器第一次发起请求
  2. 服务端返回 200,设置 Cache-Control: no-cache 和 etag 值
  3. 浏览器再一次发起请求会带上之前返回的 etag 值,设置 If-None-Match=etag
  4. 此时服务端判断最新的 etag 值是否和请求头的 etag 值相同,相同就返回 304,不同就返回 200 和最新的内容。
  5. 接收到 304 状态码,从缓存读取内容
  6. 缓存返回内容

协商缓存除了 Etag 和 If-None-Match 这对组合外,还有 Last-Modified 和 If-Modified-Since,一个是根据文件的内容是否有修改,一个是根据文件的更新时间。

缓存整体流程

有了以上的知识作为基础,我们就可以知道 HTTP 缓存的整体运作流程,如下图:

cache-control.png

图 9:HTTP 缓存整体流程

HTTP 缓存实践

缓存的设置

我们将单独在请求首部和响应首部设置缓存,看下什么情况下缓存会生效。

2022.01.14&18d899d8fc4f181583eaf9470d4fb40f.png 图 10:响应首部设置缓存,第二次发起请求

2022.01.14&76d149841c2a5590056b04620d9124c5.png 图 11:请求首部设置缓存,第二次发起请求

从图 10 和图 11 来看,我们就知道了,缓存只有在服务端设置才会生效。

请求首部和响应首部都设置了 Cache-Control

第一种情况,都设置了 max-age。

2022.01.14&6a2b0cc48484df3438ff6c4c8681fd3f.png

图 12:请求首部设置 max-age=10,响应首部设置 max-age=60

2022.01.14&2fcc89ac5efb93b850ff09d4357d24f7.png

图 13:请求首部设置 max-age=0,响应首部设置 max-age=60

从图 12 中可以看到时间已经过去 50 多秒,浏览器依旧读取缓存,故是同时设置 max-age 情况下是响应首部在起效。

有一种例外情况,从图 13 可以看到,请求首部设置 max-age=0,此时是走协商缓存而不是强缓存。

第二种情况,响应首部设置 max-age,请求首部设置 no-cache

2022.01.14&fb09ac36d7c1fe73f370681551f6a980.png

图 14:响应首部设置 max-age,请求首部设置 no-cache

多次发起情况的结果都同图 14,可以看到此时缓存不会启用,同时注意多次发起请求,都不会走协商缓存,直接都是不使用缓存。

有兴趣的同学可以看下 Chrome DevTool 的 Disable Cache 功能就是在请求首部就 no-cache

第三种情况,请求首部设置 max-age,响应首部设置 no-cache

2022.01.14&d301025044dc61a7341909b2ef82216d.png

图 15:请求首部设置 max-age,响应首部设置 no-cache

很明显,这个情况是走协商缓存,即请求首部的 max-age 只有在等于 0 的情况下才会有效果。

Cache-Control 的两个值 max-age=0 和 no-cache 效果是一样的吗

这个分请求首部和响应首部来看。从上面的例子,我们可以看到请求首部下,这两个效果是不一样的。no-cache 是禁用缓存,max-age=0 会走协商缓存。接下来我们看下响应首部情况。

2022.01.149941b765c93c723474b8d8d82e3d8da2.png

图 16:响应首部设置 no-cache

2022.01.14&4324a147fc4f46e676aebba33cee900b.png

图 17:响应首部设置 max-age=0

从上面两个图可以看出,max-age=0 和 no-cache 在响应头部效果是一样的。

这边顺便提一下,请求头部设置 no-cache 是禁用缓存,那请求头部的 no-store 是什么用,看 MDN 的介绍

2022.01.14&32bccf4c3dbbfde0b850814dafe82f4f.png

图 18:请求头部的 no-store

可以看到大部分浏览器不会支持这个属性。

总结

对于开头的三个问题,做个总结:

  1. 服务端设置缓存才会有效果。
  2. 缓存通常是以服务端为准,但是客户端设置 Cache-Control 为 no-cache 或者 max-age=0 会改变缓存机制的决策。
  3. 客户端设置时效果不同,no-cache 是禁用缓存,max-age=0 是协商缓存。服务端设置时效果一致,都为协商缓存。

通过这次的查缺补漏,相信你对 HTTP 缓存有了更深的了解。

最后实践才是真道理,这次实践的源码,有兴趣的同学可以自己去尝试一番。

实践基于 Koa 和 Chrome 浏览器,如有错误,欢迎指正。