“我正在参加「掘金·启航计划」”
很久以前我还没有出生,当时服务器需要处理太多的请求,经常抱怨自己压力特别大,快吃不消了。于是就呼叫 HTTP,设计一个缓存类的东西,来做服务器的助手,于是我诞生了,我的使命是使用网页打开速度更快,使服务器压力减小。自从有了我,我为服务器扛下了太多的请求。
服务器在私底下经常夸我是他的得力干将。这样持续了一段时间,我为服务器扛下了越来越多的请求。但是也发现了问题。当服务器资源更新后,我无法及时拿到服务器新的资源。
于是我就建议,要不给我设置个过期时间吧。这样既不影响我的作用,又不影响我从服务器拿到新资源。于是第一员大将 expires 就诞生了。
Expires
expires 的作用非常简单,它的值就是一个过期时间,浏览器拿到这个时间值以后,就可以决定缓存有没有过期。
那以后的请求流程就变成了,浏览器发出请求,然后服务器在返回资源的时候,在相应头里添加 expires 这个字段,值就是过期时间,然后我拿到这个值以后,就可以判断缓存有没有过期,如果没有过期,我就直接跟浏览器打交道。
但是有一天我们接到用户的一个咨询,说 expires 时间是服务器返回的,但比对的却是客户端时间,服务端与客户端时间不一致怎么办?回顾过去的确发现了很多类似的问题,如果客户端与服务器端时间不一致的话,那这个缓存就不准确了,会出现很多问题。于是我们就请来了第二元大将叫 Cache-Control。
Cache-Control
准确的说是在 http1.1 的时候引入的。但是不同的是,他采用的是过期时长,那表达方式就是用 max-age 值,单位是秒。相比过期时间用过期时长就准确多了,
Cache-control 除了 max-age 外,还有如下的常用指令。
| 常用指令 | 描述 | MDN |
|---|---|---|
| max-age | 单位是秒 | 设置缓存存储的最大周期,超过这个时间缓存被认为过期 (单位秒)。与Expires相反,时间是相对于请求的时间。 |
| no-cache | 不使用强缓存 | 在发布缓存副本之前,强制要求缓存把请求提交给原始服务器进行验证 (协商缓存验证)。 |
| no-store | 禁止缓存 | 缓存不应存储有关客户端请求或服务器响应的任何内容,即不使用任何缓存。 |
| private | 只有浏览器可以缓存 | 表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)。私有缓存可以缓存响应内容,比如:对应用户的本地浏览器。 |
| public | 浏览器、服务器、代理服务器都可以缓存 | 表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存,即使是通常不可缓存的内容。 |
示例 - 禁止缓存
发送如下响应头可以关闭缓存。
Cache-Control: no-store
示例 - 缓存静态资源
对于应用程序中不会改变的文件,你通常可以在发送响应头前添加积极缓存。这包括例如由应用程序提供的静态文件,例如图像,CSS 文件和 JavaScript 文件。
Cache-Control:public, max-age=31536000
示例 - 需要重新验证
指定 no-cache 或 max-age=0, must-revalidate 表示客户端可以缓存资源,每次使用缓存资源前都必须重新验证其有效性。这意味着每次都会发起 HTTP 请求,但当缓存内容仍有效时可以跳过 HTTP 响应体的下载。
Cache-Control: no-cache
Cache-Control: max-age=0, must-revalidate
注意: 如果服务器关闭或失去连接,Cache-Control: no-cache 指令可能会造成使用缓存。
于是往后的请求流程就变成了,浏览器发送请求,服务器在返回资源的时候,在响应头里增加 Cache-Control 这个字段值,包含过期时长,我拿到这个时长就可以决定缓存有没有过期,如果没有过期我就直接跟浏览器打交道。
强缓存和协商缓存
但是有一天服务器又扛不住了,很多请求又发往服务器,服务器感知到了是缓存过期了,然后又把我叫来说兄弟想个办法吧,于是我就在内部做了一定的分隔,把缓存分为了强缓存和协商缓存。
强缓存就是在缓存没有过期的时候,浏览器可以直接决定使用缓存,就叫做强缓存。
协商缓存顾名思义就是有个协商的过程。就是当缓存过期以后,然后浏览器需要咨询服务器是否可以继续使用缓存,服务器返回可以使用或者不可以使用,因为有个协商,所以叫做协商缓存。
如何实现协商的过程呢?这样就要请来第三员大将叫 Last-Modified。
Last-Modified
最后修改时间。
往后的请求流程就变成了,浏览器发送请求,然后服务器在返回资源的时候,在相应头里增加 Last-Modified 的这个字段值,值是服务器资源的最后修改时间。浏览器以后在每次请求的时候,都带上 If-Modified-Since,值就是 Last-Modified 的值,交给服务器,服务器拿到这个值去跟服务器端资源最后修改时间去做比对,如果比对的结果是没有变化,就告诉浏览器可以使用缓存返回 304 状态,比对的结果如果说资源已经更新了,那就给浏览器正常返回资源,返回 200 状态。
后来浏览器接到了很多用户的投诉,然后就对服务器抱怨说,你太不靠谱了,有新的资源却说没有,还返回 304 状态,然后服务器说都怪 Last-Modified 他是以秒级别记录的,那以秒级别记录就有问题,如果资源在 1 秒内发生改变的话,Last-Modified 是无感知的,他认为没有变化,这样的话就出现问题了。意识到问题后,我们坐下来一起讨论接下来该怎么办?我们的最终结论是换将,我们准备请来第四员大将叫 Etag。
ETag
浏览器发出请求,服务器根据文件内容生成唯一标识,然后通过这个标识 ETag 响应头字段值传送给浏览器,浏览器以后在每次请求的时候,都会增加一个字段值叫 If-None-Match,值就是 ETag 的值,然后服务器拿到这个值去跟服务器当前的 ETag 做比对,比对结果如果没有变化,就是没有过时,告诉浏览器可以使用缓存返回 304 状态,或者对比的结果是资源已经更新了,那就会把新的资源返回给浏览器。