浏览器缓存
浏览器缓存分类
先说一下两个概念,浏览器分为:强制缓存和协商缓存(官网没有这么定义,民间说法便于理解)
强制缓存
代表强制缓存的header
Pragma
Pragma是HTTP/1.0的缓存协议,它用来向后1兼容HTTP/1.0协议的缓存服务器。由于 Pragma 在 HTTP 响应中的行为没有确切规范,所以建议只有在兼容HTTP/1.0客户端场合下应用Pragma首部
<!-- 强制要求缓存服务返回缓存版本之前跟服务器进行验证 -->
Pragma: no-cache
Expires
Expires(HTTP/1.1)响应头包含日期/时间,在此之后资源过期。
如果写成0则为无效日期代表该资源过期。
<!-- GMT格林乔治时间,该含义代表该资源在Wed, 21 Oct 2015 07:28:00这个时间后过期,在请求需要去源服务获取 -->
Expires: Wed, 21 Oct 2015 07:28:00 GMT
缺点:响应报文中Expires所定义的缓存时间是相对服务器上的时间而言的,如果客户端上的时间跟服务器上的时间不一致(特别是用户修改了自己电脑的系统时间),那缓存时间可能就没啥意义了。
注: Pragma优先级要高于Expires,也就是说对于一个资源,设置如下:
Pragma: no-cache
Expires: Wed, 21 Oct 2030 07:28:00 GMT
该资源仍然会去和服务器协商该资源,而不会走缓存。
Cache-Control
被用于在http请求和响应中,通过指定指令来实现缓存机制。
指令不区分大小写,指令与指令之间逗号分隔
<!-- request header -->
Cache-Control: max-age=<seconds>
Cache-Control: max-stale[=<seconds>]
Cache-Control: min-fresh=<seconds>
Cache-control: no-cache
Cache-control: no-store
Cache-control: no-transform
Cache-control: only-if-cached
<!-- reponse header -->
Cache-control: must-revalidate
Cache-control: no-cache
Cache-control: no-store
Cache-control: no-transform
Cache-control: public
Cache-control: private
Cache-control: proxy-revalidate
Cache-Control: max-age=<seconds>
Cache-control: s-maxage=<seconds>
上面记录了很多,我们说一些常用的方便记忆。
Cache-control: private; 表明只能被用户缓存,不能被代理服务器等缓存。
Cache-control: public; 表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存,即使是通常不可缓存的内容
Cache-control: max-age=28800; 表示首次加载后28800s(8天)后过期。
Cache-Control: no-store; 禁止缓存,必须从源服务器拉取新资源。
Cache-Control:public, max-age=31536000;发起客户端以及代理服务都可以缓存,首次加载后31536000s过期。
Cache-Control: no-cache,max-age=0; 指定 no-cache 或 max-age=0 表示客户端可以缓存资源,每次使用缓存资源前都必须重新验证其有效性。这意味着每次都会发起 HTTP 请求,但当缓存内容仍有效时可以跳过 HTTP 响应体的下载。
以上请求头即为强制缓存。
协商缓存
就是当强制缓存失效后,仍需与服务端校验相关的缓存。
引用来解释协商缓存:
上述的首部字段均能让客户端决定是否向服务器发送请求,比如设置的缓存时间未过期,那么自然直接从本地缓存取数据即可(在chrome下表现为200 from cache),若缓存时间过期了或资源不该直接走缓存,则会发请求到服务器去。
我们现在要说的问题是,如果客户端向服务器发了请求,那么是否意味着一定要读取回该资源的整个实体内容呢?
我们试着这么想——客户端上某个资源保存的缓存时间过期了,但这时候其实服务器并没有更新过这个资源,如果这个资源数据量很大,客户端要求服务器再把这个东西重新发一遍过来,是否非常浪费带宽和时间呢?
答案是肯定的,那么是否有办法让服务器知道客户端现在存有的缓存文件,其实跟自己所有的文件是一致的,然后直接告诉客户端说“这东西你直接用缓存里的就可以了,我这边没更新过呢,就不再传一次过去了”。
为了让客户端与服务器之间能实现缓存文件是否更新的验证、提升缓存的复用率,Http1.1新增了几个首部字段来做这件事情。
Last-Modified
当客户端访问页面时,服务器会将页面最后修改时间通过 Last-Modified 标识由服务器发往客户端,客户端记录修改时间,再次请求本地存在的cache页面时,客户端会通过 If-Modified-Since(只可以用在GET或者HEAD请求中)或者If-Unmodified-Since头将先前服务器端发过来的最后修改时间戳发送回去,服务器端通过这个时间戳判断客户端的页面是否是最新的,如果不是最新的,则返回新的内容,如果是最新的,则 返回 304 告诉客户端其本地 cache 的页面是最新的,于是客户端就可以直接从本地加载页面了,这样在网络上传输的数据就会大大减少,同时也减轻了服务器的负担。
If-Modified-Since: Last-Modified-value
<!-- response header -->
....
Last-Modified: Thu, 27 Oct 2020 04:11:11 GMT
....
<!-- request header -->
....
If-Modified-Since: Thu, 27 Oct 2020 04:11:11 GMT
....
如果请求时间戳一直则直接返回304,表示使用缓存。
If-Unmodified-Since: Last-Modified-value
告诉服务器,若Last-Modified没有匹配上(资源在服务端的最后更新时间改变了),则应当返回412(Precondition Failed) 状态码给客户端。
Last-Modified 说好却也不是特别好,因为如果在服务器上,一个资源被修改了,但其实际内容根本没发生改变,会因为Last-Modified时间匹配不上而返回了整个实体给客户端(即使客户端缓存里有个一模一样的资源)。
ETag
ETagHTTP响应头是资源的特定版本的标识符。这可以让缓存更高效,并节省带宽,因为如果内容没有改变,Web服务器不需要发送完整的响应。而如果内容发生了变化,使用ETag有助于防止资源的同时更新相互覆盖(“空中碰撞”)。
If-None-Match: ETag-value
告诉服务端如果 ETag 没匹配上需要重发资源数据,否则直接回送304 和响应报头即可。
下面是一个例子,假设请求第一次请求后server通过对资源的计算出ETag为:3f80f-1b6-3e1cb03b,然后下次请求的时候会把这个ETag值放在If-None-Match里面。如果两个值相匹配返回给前端304,客户端直接从缓存中取值,否则返回客户端200,刷新资源。
<!-- response header -->
ETag: 3f80f-1b6-3e1cb03b
<!-- request header -->
If-None-Match: 3f80f-1b6-3e1cb03b
分析
当放弃一个请求的时候首先判断强制缓存(Cache-Control,Expires,Pragma)标识,如果命中强制缓存则直接取出缓存中输入,如果没有命中则走入协商缓存(ETag, Expires)逻辑,如果协商缓存命中则返回304使用缓存中输入,如果没有命中则server端返回200加载新资源。
我们看到的缓存包含:
-
Service Worker
-
Memory Cache
-
Disk Cache
-
Push Cache
分别是来自Serivce Worker、内存、硬盘和push cache。相区分四者不同参考四种缓存区别
小实例:
-
chrome中选中disable cache你会发现每次请求,浏览器都会给header中加入Cache-Control:no-cache和pragma: no-cache来保证每次都是最新资源。
-
我们可以获得response所有header并根据Age和Date来判断该资源是不是最新资源还是缓存资源