概述
本文从 HTTP缓存策略 为入口,讲解HTTP缓存在浏览器的应用。
文章按 强缓存 、 协商缓存 和 启发式缓存 三个类别,进行深入剖析。
HTTP缓存策略
HTTP缓存分为三大策略:
- 存储策略
- 过期策略
- 对比策略(也叫协商策略)
存储策略
存储策略,用于决定HTTP响应内容,是否可以缓存到客户端。
Cache-Control 头的 max-age 、 no-cache 、 no-store 、 public 、 private 、 s-maxage ,使用存储策略,来指明资源文件是否可以被缓存。
过期策略
过期策略,用于决定客户端是否可以直接从本地缓存中读取文件数据,而不需要发起HTTP请求。
响应头包含 Cache-Control: public 的文件,虽然会被缓存,但不能明确当前文件是否在有效期内,所以需要其他字段做好“过期策略”。
强缓存的 Expires 字段,就是用 “过期策略” 定义缓存文件的有效期。借此,浏览器可判断是否需要发起HTTP请求。
响应头包含 Cache-Control: max-age=<seconds> 的文件,则包含存储策略和过期策略。
具体的策略应用,可以详细查阅下文的 Cache-Control 章节。
对比策略
将本地缓存文件的数据标识,发送到服务端进行验证,判断文件是否有效。这种策略,就叫对比策略,也叫协商策略。
对比策略,用于协商缓存场景,对应的字段是:
- Last-Modified 和 If-Modified-Since
- ETag 和 If-None-Match
例如:
ETag 用于存储缓存文件的哈希值。
浏览器需要判断当前缓存文件是否有效时,需要将 ETag 的值放入请求头 If-None-Match 字段,发送到服务端。
服务端接收到请求后,对比 If-None-Match 中的值与最新文件的值是否一致,来决定是否使用缓存。
当两个值一致时,则返回HTTP状态码304,告知浏览器,可使用本地缓存文件。
当两个值不一致时,则返回HTTP状态码200,并携带最新的文件返回给浏览器。
具体的策略应用,可以详细查阅下文的 协商缓存 章节。
小结
强缓存
强缓存通过字段 Expires 和 Cache-Control 来控制本地缓存文件的有效期。
如果本地缓存有效,则浏览器不会发起HTTP请求。
在浏览器控制台 NetWork 中的体现为:
200 OK (from disk cache) 或者 200 OK (from memory cache)
释义
200 OK (from disk cache)
200 OK (from memory cache)
强缓存的字段
字段 | 协议版本 | 缓存类型 | 响应头 | 请求头 |
Expires | HTTP1.0 | 强缓存 | :o:️ | :x: |
Cache-Control | HTTP1.1 | 强缓存 | :o:️ | :o:️ |
HTTP1.1字段 优先级比 HTTP1.0字段高。
Expires
Expires 表示缓存的过期时间,时间代表的是 服务端的时间 。
如果本地时间小于 Expires 的时间,则在有效期内。浏览器会直接读取缓存,不会发起HTTP请求。
Expires: Sun, 14 Jun 2020 02:50:57 GMT
缺点
Expires 受限于本地时间,如果本地时间修改,则可能会导致缓存失效。
Cache-Control
Cache- Control 比较特殊,可以在 响应头 和 请求头 中使用。它通过提供不同的值,来定义缓存策略。
Cache-Control是所有缓存定义字段中,优先级最高的。
Cache-Control 字段取值 | 含义 | 存储策略 | 过期策略 | 响应头 | 请求头 |
max-age | 缓存资源, 但是在指定时间(单位为秒)后缓存过期 | :o:️ | :o:️ | :o:️ | :o:️ |
no-cache | 相当于 max-age:0,must-revalidate 即资源被缓存, 但是缓存立刻过期, 同时下次访问时强制验证资源有效性 | :o:️ | :o:️ | :o:️ | :o:️ |
no-store | 请求和响应都不缓存 | :o:️ | :x: | :o:️ | :o:️ |
public | 资源将被客户端和代理服务器缓存 | :o:️ | :x: | :o:️ | :x: |
private | 资源仅被客户端缓存, 代理服务器不缓存 | :o:️ | :x: | :o:️ | :x: |
s-maxage | 依赖public设置, 覆盖max-age, 且只在代理服务器上有效 | :o:️ | :o:️ | :o:️ | :x: |
must-revalidation / proxy-revalidation | 如果缓存失效, 强制重新向服务器(或代理)发起验证(因为max-stale等字段可能改变缓存的失效时间) | :x: | :o:️ | :o:️ | :x: |
max-stale | 指定时间内, 即使缓存过时, 资源依然有效 | :x: | :o:️ | :x: | :o:️ |
min-fresh | 缓存的资源至少要保持指定时间的新鲜期 | :x: | :o:️ | :x: | :o:️ |
only-if-cached | 仅仅返回已经缓存的资源, 不访问网络, 若无缓存则返回504 | :x: | :x: | :x: | :o:️ |
no-transform | 强制要求代理服务器不要对资源进行转换, 禁止代理服务器对 Content-Encoding , Content-Range , Content-Type 字段的修改(因此代理的gzip压缩将不被允许) | :x: | :x: | :o:️ | :o:️ |
释义
- Cache-Control:max-age=31536000 距离请求发起的时间 + 31536000 秒之后,才会过期
- Cache-Control: must-revalidate 缓存过期的任何情况下,都必须发起请求重新验证
- Cache-Control: s-maxage=60 同max-age作用一样,距离请求发起的时间 + 60 秒之后,才会过期。
只在代理服务器中生效(比如 CDN 缓存), s-maxage优先级高于max-age,只对 public 缓存有效 。设置了 s-maxage,没设置 public,代理服务器也可以缓存这个资源。 - Cache-Control: no-store 所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存。
- Cache-Control: no-cache 是否使用本地缓存都需要经过协商缓存来验证决定。使用 Etag 或者 Last-Modified 字段来控制缓存。
- Cache-Control:max-age=31536000,max-stale=60 距离请求发起的时间 + 31536000 秒 + 60 秒之后,缓存才会失效。
max-stale 表示最大容忍的过期时间,单位是秒。 - Cache-Control:max-age=31536000, min-fresh=60 距离请求发起的时间 + 31536000 秒 – 60 秒之后,缓存才会失效。
min-fresh 表示最小要留有N秒的新鲜度,单位是秒。
当max-age 与 max-stale 和 min-fresh 同时使用时, 它们的设置相互之间独立生效, 最为保守的 缓存 策略总是有效。
即哪个过期时间最早,就在这个过期时间后,发起资源请求,重新向服务端做验证。
协商缓存
当浏览器对某个资源的请求,没有命中强缓存,并在本地查找到缓存文件,则会发一个请求到服务器,验证本地缓存是否有效。
如果本地缓存文件有效,服务端响应请求,返回HTTP状态为: 304(Not Modified) , 不带消息主体。
如果本地缓存文件过期,服务端响应请求,返回HTTP状态为: 200 ,并携带资源实体数据。
协商缓存的字段
协商缓存字段分为两种:
- Last-Modified 和 If-Modified-Since
- ETag 和 If-None-Match
字段 | Header 类型 | 协议版本 | 缓存类型 |
Last-Modified | Response(响应头) | HTTP1.0 | 协商缓存 |
If-Modified-Since | Request (请求头) | HTTP1.0 | 协商缓存 |
ETag | Response(响应头) | HTTP1.1 | 协商缓存 |
If-None-Match | Resquest(请求头) | HTTP1.1 | 协商缓存 |
HTTP1.1字段 优先级比 HTTP1.0字段高。
Last-Modified 和 If-Modified-Since
Last-Modified 表示本地文件的最后修改日期(精确到秒级)。
当浏览器发起资源请求时,会将文件的 Last-Modified 值,放入 If-Modified-Since 中,发送给服务端,询问该文件在该日期后,是否有更新。
如果在本地打开并修改缓存文件,则会导致Last-Modified日期被修改。
以下是例子:
- 响应头
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
- 请求头
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
缺点
If-Modified-Since
注意点
- Last-Modified 是服务端响应头 Response Headers 中的字段。
- If-Modified-Since 是客户端请求头 Request Headers 中的字段。
- If-Modified-Since 只可以用在 GET 或 HEAD 请求中 。
- 当与 If-None-Match 一同出现时, If-None-Match 的优先级更高。 If-Modified-Since 会被忽略掉,除非服务器不支持 If-None-Match 。
ETag 和 If-None-Match
ETag 像文件的指纹一样,每次内容一更改, ETag 值都会发生变化。
当浏览器发起资源请求时,会将上一次文件的 ETag 值,放入 If-None-Match 中,发送给服务端,询问该文件是否有更新。
ETag 值之间的比较采用的是 弱比较算法 ,即两个文件除了每个字节都相同外,内容一致也可以认为是相同的。例如,如果两个页面仅仅在页脚的生成时间有所不同,就可以认为二者是相同的。
举个例子:
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
ETag: W/"0815"
其中, ‘W/’ (大小写敏感) 表示使用 弱验证器 。
注意点
ETag 是服务端响应头 Response Headers 中的字段。
If-None-Match 是客户端请求头 Request Headers 中的字段。
避免“空中碰撞”与 HTTP状态码412
空中碰撞,是指同时有多个人修改同个文件,产生竞态。
如果服务端接收每个人的保存请求,则会出现相互覆盖的状态。
所以,需要依靠ETag值,在保存前,做一些校验,以避免这种情况。
当需要修改或上传文件时,包含有 If-Match 头( ETag 值)的 POST 请求,会发送给服务端。
If-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
如果服务端的文件哈希值与 If-Match 头的值不相等,则证明文件已经修改过。
这种情况下,服务端可以返回 HTTP状态码412 Precondition Failed (先决条件失败)表示客户端错误,拒绝处理请求。
启发式缓存
在资源请求的响应头中没有出现 Expires , Cache-Control: max-age , 或 Cache-Control:s-maxage 字段, 并且设置了 Last-Modified , 那么浏览器默认会采用一个 启发式的算法 。
启发式缓存的算法
取响应头的Date值 – Last-Modified值的结果的10%作为缓存时间。
详细可查看Caching in HTTP 中的介绍,笔者截取部分原文如下:
If none of Expires, Cache-Control: max-age, or Cache-Control: s- maxage (see section 14.9.3) appears in the response, and the response does not include other restrictions on caching, the cache MAY compute a freshness lifetime using a heuristic. The cache MUST attach Warning 113 to any response whose age is more than 24 hours if such warning has not already been added.
Also, if the response does have a Last-Modified time, the heuristic expiration value SHOULD be no more than some fraction of the interval since that time. A typical setting of this fraction might be 10%.
The calculation to determine if a response has expired is quite simple:
response_is_fresh = (freshness_lifetime > current_age)
巩固练习题
怎么让浏览器不缓存静态资源
Cache-Control: no-cache, no-store, must-revalidate
<link rel="stylesheet" type="text/css" href="../css/style.css?version=1.8.9"/>
- 部分浏览器支持在 HTML 中禁用缓存
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"/>
相同作用的请求头
- Cache-Control: no-cache 与 Cache-Control: max-age=0
max-age=0 表示该资源已在0秒后过期,需要使用协商策略。因此,和 Cache-Control: no-cache 一样。