浏览器缓存
# 前言
Web 缓存分为浏览器缓存和服务端缓存,本文主要描述的是浏览器缓存中的 HTTP 缓存
# 查看缓存文件
chrome 可以通过 chrome://view-http-cache
查看浏览器的缓存文件
chrome 66 后就用不了了
pc 上的实际存储位置
\User\{user}\AppData\Local\google\Chrome\User Data\Default\Cache\index 的文件内容
火狐可以通过 about:cache
查看缓存文件列表
进入缓存文件详情页,可以看到缓存文件内容由三部分组成
- 资源请求地址
- 响应头
- 原文件的二进制内容
而实际的缓存文件也是包含这些部分的,我们可以通过 ChromeCacheView 工具查看内部内容
通过下面几个问题来学习 pc 上浏览器缓存如何设计的
是不是应该有一个列表文件记录所有缓存文件的基本信息?
不能直接拿到缓存源文件,每次命中缓存都需要解码文件?
从如果删了缓存文件,是删了什么东西?列表文件内容会相应的修改不?
不会出现列表文件内容还在但是对应缓存原文件被删除的情况?
TODO
# 强缓存
# Cache-Control 响应头
优先级比 Expires
高,且用的是相对时间,不需要担心客户端与服务端时间不一致带来的缓存失效问题
# max-age
指定缓存的有效期
# s-maxage
指定代理缓存(如cdn缓存)的有效期,优先级比 max-age 高
# no-cache
不使用本地缓存和代理缓存
# no-store
禁用缓存
# public
可以被所有用户缓存,包括终端用户以及 CDN 等中间代理服务器
默认值
# private
仅能被终端用户缓存
# stale-while-revalidate
- 非缓存标准,属于缓存拓展,实验性质,仅部分浏览器可用。
- 和
max-age
配合使用。若max-age
过期,而stale-while-revalidate
还在有效期内,仅继续使用强缓存,并额外发送一个请求去刷新缓存;而如果stale-while-revalidate
也过期,则正常重新刷新。
举例:max-age=60, stale-while-revalidate=10;
- 在 60s 内访问请求都将命中强缓存
- 在 60~70s 内访问请求,先使用强缓存,并额外请求一次去更新缓存(包括有效期)
- 60~70s内有请求的情况下,在 80s 时请求,将命中强缓存
- 60~70s内没有请求的情况下,在 80s 时请求,无缓存可用,发起新的请求
实际可用的场景较少,了解就好。大部分需求场景可以通过 js 解决,参考 js 的 swr 库,其实就是利用了 stale-while-revalidate 的思想。
更多资料详见:
# Expires 响应头
表示资源过期时间,以服务端时间为准
# 协商缓存
# Last-modified 响应头
表示服务端文件的最后修改时间
当缓存过期时,再次发起请求,会带个 If-Modified-Since
请求头,其值为之前返回的 Last-modified
响应头的值
服务端会去查询在该时间点后文件是否被修改,若被修改,返回新的资源, 200 响应以及 Last-modified
的新值;否则返回 304 响应,根据 Cache-Control 或 Expires 重新设置过期时间
# ETag 响应头
由于 Last-modified
只精确到秒,且如果内容没变但是最后修改时间变了,还是会当新资源请求
于是 http1.1 加了一个 ETag
响应头
其值为根据文件内容生成的 hash
当缓存过期时,再次发起请求,会带上 If-None-Match
请求头,其值为之前返回的 ETag
响应头的值
服务器会去该值和服务器文件的 hash 进行比对,若不同,返回新的资源和 200 响应以及 ETag
的新值;否则返回 304 响应,根据 Cache-Control 或 Expires 重新设置过期时间
优先级比 Last-modified
高
当两个同时存在时,先判断 ETag ,如果 hash 值没有变化,再去判断 Last-modified
,最终决定是否返回 304
# 总流程
借用 [浅谈 Web 缓存] 中的图
# 用户行为
用户操作 | 强缓存 | 协商缓存 |
---|---|---|
前进后退 | 有效 | 有效 |
地址栏回车 | ? | 有效 |
按刷新按钮 | 无效 | 有效 |
F5 | 无效 | 有效 |
ctrl + F5 | 无效 | 无效 |
Disable cache | 无效 | 无效 |
前四种针对的是文档请求,后两者针对的是所有资源的请求
强缓存失效的原因在于浏览器会在请求中带上cache-control: max-age=0
协商缓存失效的原因在于本该带上 If-Modified-Since
或 If-None-Match
请求头,但是浏览器没有带上,并带上以下两个请求头避免强缓存,因此会重新加载资源
Cache-Control: no-cache
Pragma: no-cache
2
前进后退是浏览器直接拿缓存页面,即使强缓存已经过期,所以即使断网了,后退还是能访问原来页面文档,参考自:https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.13
会发现此时网络请求头显示 Provisional headers are shown
地址栏回车之所以不固定,在于当前文档链接与要回车的链接是否相同。如果相同,浏览器会认为这是打算 f5 刷新,强缓存无效,否则强缓存有效
最后两种的区别在于,前者会走内存缓存(相同请求命中内存缓存,不会再发一条请求),后者什么缓存都不走;
放入内存缓存的是页面扫描加载的,脚步异步请求的不放内存中(没做完整的测试,不确定)
# 常见问题
# 请求头出现 Provisional headers are shown 的原因
- 插件拦截
- 命中本地缓存
- 服务器超时
- 跨域拦截,最经常出现的情况,非同源的资源请求都会显示这个
# 响应头未设置 cache-control 和 expires 还会命中强缓存么?
对于前进后退的场景来说,有没有这些响应头,都会命中强缓存
其他场景则不会命中
# 如何设置才能不缓存资源
# 通过 Expires 判断缓存过期,而本地时间比服务器时间快的话,会出现什么问题?
如果通过 Expires 判断缓存过期,而本地时间又比服务端快资源有效期以上,就会出现始终不能命中强缓存的情况
# 浏览器缓存中的其他缓存
读取某个 url 的资源,按照下面的过程
(需要做个试验)
内存缓存
preload 预加载的资源会存储在此处 当前页面已经加载过的资源会放在内存中,方便读取 但注意一点,如果资源设置了 no-store ,前面即使加载过这个资源,还是会继续加载(待确定)
Service Worker 缓存
有一套自己的缓存API,更加可控,不受浏览器影响
HTTP 缓存
上文提到的那个,根据请求响应等头部信息确定是否走缓存
HTTP2 Push 缓存
有一定的时效,chromium 中是五分钟 在命中缓存后,可能会在 HTTP 缓存中增加拷贝以及内存缓存增加引用