聊一下浏览器 HTTP 协议缓存

聊一下浏览器 HTTP 协议缓存

平常上网打开一个网页,第一次很慢,第二次再打开就发现很快,后面是HTTP缓存机制产生的效果。工作中也经常被小伙伴问到缓存的一些问题,比如“为什么更新了服务端文件,我本地浏览器打开还是旧的”,“为什么浏览器强制刷新一下就能看到新更新的文件”等各种问题。对这块的知识有一个轮廓了解,这里知道一点那里知道一点,就是无法串起来形成一个HTTP缓存机制知识平面网。静下心来总结一下,留存一个记录。

参考:

周志明的软件架构课

浏览器缓存知识小结及应用

浏览器的请求流程

浏览器第一次请求流程图
浏览器再次请求时

HTTP协议缓存分类

HTTP从1.0 到 1.1、再到 2.0 版本的演进中,URL获取单个资源的场景逐步形成了下面三种HTTP缓存机制:

  1. 状态缓存
    • 不经过服务器,客户端直接根据缓存信息来判断目标网站的状态
    • 301跳转和HSTS(HTTP Strict Transport Security)状态缓存
  2. 强制缓存
    • 假设某个时间点内资源的内容和状态一定不改变
    • 在 HTTP 协议中,设置了两类可以实现强制缓存的 Headers(标头):Expires 和 Cache-Control。
  3. 协商缓存
    • 当浏览器对某个资源的请求没有命中强缓存,就会发一个请求到服务器,验证协商缓存是否命中
    • 协商缓存有两种变动检查机制,一种是根据资源的修改时间进行检查(Last-Modified 和 If-Modified-Since),另一种是根据资源唯一标识是否发生变化来进行检查(Etag 和 If-None-Match)

强制缓存

基于时效性,某项资源什么时候变化不会特别准确

第一类:Expires

Expires 是 HTTP/1.0 协议中开始提供的 Header,后面跟随了一个截止时间参数。当服务器返回某个资源时,如果带有该 Header 的话,就意味着服务器承诺在截止时间之前,资源不会发生变动,浏览器可直接缓存该数据,不再重新发请求。

缺点:

  1. 受限于客户端的本地时间,客户端调整本地时间缓存就失效了
  2. 无法描述“不缓存”的语义,导致需要在资源后面加个版本号或者时间戳来控制

第二类:Cache-Control

Cache-Control 是 HTTP/1.1 协议中定义的强制缓存 Header,它的语义比起 Expires 来说就丰富了很多。而如果 Cache-Control 和 Expires 同时存在,并且语义存在冲突(比如 Expires 与 max-age / s-maxage 冲突)的话,IETF 规定必须以 Cache-Control 为准。

在客户端的请求 Header 或服务器的响应 Header 中,Cache-Control 都可以存在,它定义了一系列的参数,并且允许自行扩展。Cache-Control 标准的参数主要包括 6 种:

  1. max-age 和 s-maxage
    • max-age 后面跟随了一个数字,它是以秒为单位的,表明相对于请求时间(在 Date Header 中会注明请求时间)多少秒以内,缓存是有效的,资源不需要重新从服务器中获取。这个相对时间,就避免了 Expires 中,采用的绝对时间可能受客户端时钟影响的问题。
    • s-maxage,其中的“s”是“Share”的缩写,意味着“共享缓存”的有效时间,即允许被 CDN、代理等持有的缓存有效时间,这个参数主要是用来提示 CDN 这类服务器如何对缓存进行失效。
  2. public 和 private
    • public意味着资源可以被代理、CDN 等缓存;
    • private意味着只能由用户的客户端进行私有缓存
  3. no-cache 和 no-store
    • no-cache 表明该资源不应该被缓存,哪怕是同一个会话中对同一个 URL 地址的请求,也必须从服务端获取,从而令强制缓存完全失效(但此时的协商缓存机制依然是生效的)
    • no-store 不强制会话中是否重复获取相同的 URL 资源,但它禁止浏览器、CDN 等以任何形式保存该资源。
  4. no-transform
    • no-transform 禁止资源以任何形式被修改
  5. min-fresh 和 only-if-cached(仅用于客户端的请求 Header)
    • min-fresh 后续跟随了一个以秒为单位的数字,用于建议服务器能返回一个不少于该时间的缓存资源(即包含 max-age 且不少于 min-fresh 的数字)
    • only-if-cached 表示服务器希望客户端不要发送请求,只使用缓存来进行响应,若缓存不能命中,就直接返回 503/Service Unavailable 错误。
  6. must-revalidate 和 proxy-revalidate
    • must-revalidate 表示在资源过期后,一定要从服务器中进行获取,即超过了 max-age 的时间后,就等同于 no-cache 的行为
    • proxy-revalidate 用于提示代理、CDN 等设备资源过期后的缓存行为,除对象不同外,语义与 must-revalidate 完全一致

协商缓存

在 HTTP 中,协商缓存与强制缓存并没有互斥性,这两套机制是并行工作的

第一类:根据资源的修改时间进行检查

Last-Modified 是服务器的响应 Header,用来告诉客户端这个资源的最后修改时间

而对于带有这个 Header 的资源,当客户端需要再次请求时,会通过 If-Modified-Since,把之前收到的资源最后修改时间发送回服务端。服务端发现资源在该时间后没有被修改过,就只要返回一个 304/Not Modified 的响应。

第二类:根据资源唯一标识是否发生变化来进行检查

Etag 是服务器的响应 Header,用于告诉客户端这个资源的唯一标识。对于带有这个 Header 的资源,当客户端需要再次请求时,就会通过 If-None-Match,把之前收到的资源唯一标识发送回服务端。服务端计算后发现资源的唯一标识与上传回来的一致,就说明资源没有被修改过,同样也只需要返回一个 304/Not Modified 的响应。

Etag 是 HTTP 中一致性最强的缓存机制(Last-Modified标注的时间只能精确到秒级,或者被定期生成但是内容不变),Etag 又是 HTTP 中性能最差的缓存机制(每次请求时,服务端都必须对资源进行哈希计算)。

HTTP 的内容协商机制

在 HTTP 协议的设计中,一个 URL 地址是有可能提供多份不同版本的资源的,比如说,一段文字的不同语言版本,一个文件的不同编码格式版本,一份数据的不同压缩方式版本,等等。因此针对请求的缓存机制,也必须能够提供对应的支持。

针对这种情况,HTTP 协议设计了以Accept*(Accept、Accept-Language、Accept-Charset、Accept-Encoding)开头的一套请求 Header,以及对应的以Content-*(Content-Language、Content-Type、Content-Encoding)开头的响应 Header。这些 Headers 被称为 HTTP 的内容协商机制。

Vary Header 的作用,根据什么内容来对同一个 URL 返回给用户正确的资源,Vary 后面应该跟随一组其他 Header 的名字。

用户行为与缓存

  1. 根据约定,在浏览器的地址输入、页面链接跳转、新开窗口、前进和后退中,强制缓存都可以生效,但在用户主动刷新页面时应当自动失效。
  2. 根据约定,协商缓存不仅可以在浏览器的地址输入、页面链接跳转、新开窗口、前进、后退中生效,而且在用户主动刷新页面(F5)时也同样是生效的。
  3. 只有用户强制刷新(Ctrl+F5)或者明确禁用缓存(比如在 DevTools 中设定)时才会失效,此时客户端向服务端发出的请求会自动带有“Cache-Control: no-cache”。
用户操作强制缓存 Expires/Cache-Control协商缓存 Last-Modified/Etag
地址栏回车有效有效
页面链接跳转有效有效
新开窗口有效有效
前进、后退有效有效
F5/ 按钮刷新无效 (BR 重置 max-age=0)有效
Ctrl+F5 刷新无效(重置 CC=no-cache)无效(请求头丢弃该选项)

写在最后

以上更多的是提炼和归纳总结(农夫山泉式),实际工作中是很少直接面对服务端server资源的,通常情况下中间会有一层CDN,请求的大部分静态资源都是CDN缓存的结果,关于CDN这部分的缓存留待下一篇讲一下。

留下回复

error: Content is protected !!