HTTP 缓存介绍
Web缓存指的是将所请求的文档保存下来,并且在随后的请求中复用这个响应的副本的技术,可以用来提高请求的响应速度,降低网络带宽的消耗并且减轻服务器的压力。
Web缓存分为公有缓存和私有缓存,公有缓存指的是在客户端和服务器之间存储响应文档并且可以供许多用户分享的缓存,比如缓存代理服务器;而私有缓存指的是服务于单个用户的,可以为用户存储个性化内容的缓存服务,浏览器缓存就是典型的私有缓存。
默认情况下,以 GET , HEAD 方法请求的响应可以被缓存,而 POST 请求的响应不会被缓存。
HTTP 缓存相关首部字段
Cache-Control
Cache-Control 用于设置缓存的使用方式,比如文档可以被哪些用户缓存,缓存的有效时间等。
Cache-Control 字段在请求报文和响应报文中拥有不同的含义:
响应报文中的 Cache-Control 字段:
-
可以用 Cache-Control 控制当前缓存可以被公有服务器缓存或只能被私有缓存例如浏览器所缓存:
Cache-Control: public 指的是当前响应可以被公有缓存和私有缓存所缓存。
Cache-Control: private 默认值 , 指的是当前响应只能被私有缓存所缓存。 -
Cache-Control 还可以指定当前响应可以在缓存中使用的方式,比如是否可以缓存以及可以缓存多久:
Cache-Control: max-age=604800 max-age旨在告知浏览器或缓存服务器在指定时间内的对于当前资源的后续请求都可以直接使用缓存,这是一个相对于响应在源服务器创建时间的秒数,而响应的创建时间会在响应报文的Date字段中指出。很多博文都认为max-age是相对于请求时间的,但是 MDN 英文版中指明:
Cache-Control: no-cache 指明此响应可以被缓存,但是在每次使用缓存的时候必须重新向服务器发送条件请求验证缓存是新鲜可用还是已经过期的,条件请求指的是在请求首部加上 If- 开头的字段比如: If-Modified-Since 和 If-None-Match , 如果验证结果为缓存可用,则服务器返回 304 Not Modified 并且不在响应报文实体部分携带数据,如果验证结果为缓存过期,则服务器直接返回 200 OK 并且携带新数据。
Cache-Control: must-revalidate 指明在 max-age 过期前可以不用验证,但是一旦 max-age过期就要向服务器发送条件请求验证缓存是否新鲜 ,所以 must-revalidate 经常和 max-age 同时使用。
Cache-Control: no-store 指明此响应不可以被缓存。
请求报文中的 Cache-Control 字段:
-
Cache-Control: max-age=6000 在请求报文的 Cache-Control 设置 max-age ,含义是浏览器不会直接使用从响应报文创建时间起最多经过了 max-age 秒的缓存文件,即使之前收到的响应报文设置了一个更大的 max-age 并且尚未到时,缓存是否有效也要以这次请求报文设置的 max-age 为准。
-
一般浏览器会在页面刷新(Reloading)时,会为 HTML 文档的请求报文在首部加上 Cache-Control: max-age=0 , 同时加上条件请求首部例如 If-Modified-Since ,含义是暂不使用缓存,并且向服务器验证文档是否有过更新,如果服务器返回 304 Not Modified 才会使用缓存,如果服务器返回 200 OK 并且携带新文档则使用新文档。
-
Cache-Control: no-cache 请求报文中携带 no-cache 意味着浏览器希望服务器返回最新的文档,即使缓存中的文档并未过期。通常,浏览器会在强制刷新(Force Reloading)时为请求加上 no-cache。
-
在 Cache-Control:max-age 有效期内的缓存不用再向服务器验证缓存有效性,这样的缓存被称为强缓存。
Expires
可以在响应报文中设置 Expires 指出改响应的缓存在某个时刻之前都是可以直接使用的,和 Cache-Control 一样都属于强缓存。如果响应报文在 Cache-Control 中设置了 max-age ,那么 Expires 会被直接忽略。
示例: Expires: Wed, 21 Oct 2015 07:28:00 GMT
Last-Modified
Last Modified 响应首部字段记录了响应报文生成时当前资源在原始服务器最后一次被修改的精准到秒的时间。
这个字段为条件请求首部 If-Modified-Since 提供了使用环境,即若服务器向客户端发送 Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT 来说明文件最后一次被修改的时间 , 那么暗示了客户端可以在下一次请求的请求头部加上 If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT(响应报文中Last-Modified的值) 来验证资源在这个时刻之后是否发生过更新,如果没有更新,服务器返回 304 Nod Modified , 表示客户端可以使用缓存内容,如果发生了更新服务器返回 200 OK 并且携带新的资源,这就是由 Last-Modified 控制的协商缓存。
If-Modified-Since
此请求首部一般用来配合 Last-Modified 完成对缓存对应的原始服务器资源是否在某一时刻后被更新过的验证。
示例:If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
Etag
Etag 响应首部是资源不同版本对应的唯一标识符,用来表示资源是否被更新过,比 Last-Modified 精确到秒级的时间更精准,可以在每一次资源被更新时生成新的 Etag ,服务器向客户端发送带有 Etag 的响应报文暗示客户端之后的请求可以在请求首部携带 If-None-Match: Etag值 来验证资源是否已经更新过,同样如果资源没有更新则返回 304。
Etag 可以在前缀中带有 W/ 代表资源只在出现重要更新时生成新的 Etag , 而不是每次更新都生成新的 Etag , 这样做的结果是可以使用“足够好”的缓存,这样的 Etag 被称为弱验证器,弱验证器的 Etag 更容易生成,但是弱验证禁止了对于资源部分请求(带有 Range 字段)的缓存。
强验证器示例:ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
弱验证器示例 :ETag: W/"0815"
If-None-Match
用于在请求报文配合 Etag 完成对缓存是否被更新过的验证。
示例:If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
浏览器缓存
浏览器缓存机制介绍
浏览器缓存机制是HTTP缓存机制的具体实现,其运行是由HTTP缓存相关首部字段决定的。
浏览器缓存过程如下:
-
在浏览器发送请求时,会检查是否已经有对应资源的缓存,如果是第一次发送请求或者其他没有缓存的情况,浏览器回在接收响应时,根据响应报文的头部决定是否缓存资源及资源缓存的方式。例如响应报文头部中设置 Cache-Control: no-store 就不对当前资源进行缓存;如果设置 Cache-Control: max-age=600 或者 Expires: time 指示浏览器可以在指定时间之内认定缓存有效,直接使用缓存 (强缓存);如果设置了 Last-Modified 或者 Etag 字段意味着浏览器可以在后续的请求中附带对应的请求首部来向服务器验证缓存是否可用 (协商缓存)。
-
在浏览器发送请求时,如果发现有对应资源的缓存标识与缓存结果,则会查看当前缓存是否有效,流程如下:
-
- 首先查看由 Cache-Control 和 Expires 控制的强缓存是否生效,如果在缓存仍然在强缓存有效期内,那么直接使用缓存。
- 如果 Cache-Control 和 Expires 控制的强缓存失效,那么浏览器会根据缓存的响应报文头部给出的 Etag 和 Last-Modified 在当前请求报文头部加入对应的 If-None-Match 和 If-Modified-Since 首部字段来使用协商缓存验证缓存是否新鲜,如果验证成功浏览器会返回 304 Not Modified ,通知浏览器缓存仍然可用,或者资源已经被更新则返回 200 OK 并携带新的资源供浏览器使用。
一图以蔽之:
图片来自:heyingye大神的博客
浏览器缓存机制实践
以 MDN 首页为例查看 Chrome v111 缓存行为:
-
首次加载网站:
-
- 没有任何资源被缓存,所有的请求响应状态码均为 200 OK:
- 并且其中的 developer.mozilla.org/en-US/ 路径返回了一个HTML文档, 并在响应报文头部中给出了 Cache-Control , Expires , Etag , Last-Modified 首部:
- ga.js 同样在响应报文给出了这几个字段:
- 没有任何资源被缓存,所有的请求响应状态码均为 200 OK:
-
正常刷新页面: 现在我们刷新页面,再次请求资源,查看 en-US 和 ga.js 的缓存是否生效 , 按照浏览器缓存机制,两个资源都应该因为由 Cache-Control 控制的强缓存仍在有效期内而复用缓存。
-
- 刷新结果如下:
- 可以看到,ga.js 返回了状态码 200 并且标出了 memory cache 证明浏览器使用了位于内存中的对于 ga.js 文件的缓存,同时响应首部中的 Date 字段和上一次的取值相同,证明了两份响应报文生成的时间是一样的, 侧面证明了是同一份报文:
- 而 en-US/ 返回的 HTML 文档,即网页本体文件却返回了 304 Not Modified , 证明强缓存并未生效,反而是协商缓存生效了,这是因为 Chrome v111 浏览器在刷新页面时会对当前的 HTML 文档请求报文首部中加入 Cache-Control: max-age=0 代表不想直接使用缓存,而是通过协商缓存向服务器验证一下缓存的新鲜程度:
- 刷新结果如下:
-
强制刷新页面:现在我们强制刷新页面,查看浏览器的行为:
-
- 可以看到,所有的请求均返回 200 OK , 这是因为浏览器在所有的请求头部中加上了 Cache-Control: no-cache , 代表浏览器想要服务器返回最新的文档,无论目前的缓存是否过期:
- 可以看到,所有的请求均返回 200 OK , 这是因为浏览器在所有的请求头部中加上了 Cache-Control: no-cache , 代表浏览器想要服务器返回最新的文档,无论目前的缓存是否过期: