# http与浏览器缓存

缓存是性能优化中惯用的方法,可以减少数据请求,提高页面访问速度。

缓存分为强缓存与协商缓存,那么下面一起学习一下。(文章引用和参考网上文章,如有侵权之处,请及时告知。)

# 强缓存

强缓存是根据这两个字段Expires 和 Cache-Control来判断控制的,若命中这两个字段那么就会直接从浏览器缓存中获取相应的资源,不会再向服务器请求资源了,http状态码会是200。在浏览器中描述可能会是 from dist cache 或者 from memory cache。

在HTTP1.0版本的时候是根据Expires字段来判断的,客户端这边发起请求资源请求的时候会请求头,如果命中就直接从浏览器中获取资源。 但是会存在由于客户端可以修改时间,造成与服务器时间不一致,这样的问题。

在HTTP1.1引入Cache-Control,作为 通用消息头字段,被用于在http请求和响应中,通过指定指令来实现缓存机制。缓存指令是单向的,这意味着在请求中设置的指令,不一定被包含在响应中。

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

# 可缓存指令

  • public

表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存,即使是通常不可缓存的内容。(例如:1.该响应没有max-age指令或Expires消息头;2. 该响应对应的请求方法是 POST 。)

  • private

表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)。私有缓存可以缓存响应内容,比如:对应用户的本地浏览器。

  • no-cache

在发布缓存副本之前,强制要求缓存把请求提交给原始服务器进行验证(协商缓存验证)。

  • no-store

缓存不应存储有关客户端请求或服务器响应的任何内容,即不使用任何缓存。

# 到期指令

  • max-age=

设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)。与Expires相反,时间是相对于请求的时间。

  • s-maxage=

覆盖max-age或者Expires头,但是仅适用于共享缓存(比如各个代理),私有缓存会忽略它。

  • max-stale[=]

表明客户端愿意接收一个已经过期的资源。可以设置一个可选的秒数,表示响应不能已经过时超过该给定的时间。

  • min-fresh=

表示客户端希望获取一个能在指定的秒数内保持其最新状态的响应。

  • stale-while-revalidate=

表明客户端愿意接受陈旧的响应,同时在后台异步检查新的响应。秒值指示客户愿意接受陈旧响应的时间长度。

  • stale-if-error=

表示如果新的检查失败,则客户愿意接受陈旧的响应。秒数值表示客户在初始到期后愿意接受陈旧响应的时间。

# 协商缓存

协商缓存顾名思义就是服务器与客户端协商后是否缓存。

Last-Modified 是一个时间戳,如果我们启用了协商缓存,它会在首次请求时随着 Response Headers

随后我们每次请求时,会带上一个叫 If-Modified-Since 的时间戳字段,它的值正是上一次 response 返回给它的 last-modified 值:

服务器接收到这个时间戳后,会比对该时间戳和资源在服务器上的最后修改时间是否一致,从而判断资源是否发生了变化。如果发生了变化,就会返回一个完整的响应内容,并在 Response Headers 中添加新的 Last-Modified 值;否则,返回如上图的 304 响应,Response Headers 不会再添加 Last-Modified 字段。

  • 不足

服务器并没有正确感知文件的变化。

使用 Last-Modified 存在一些弊端,这其中最常见的就是这样两个场景:

我们编辑了文件,但文件的内容没有改变。服务端并不清楚我们是否真正改变了文件,它仍然通过最后编辑时间进行判断。因此这个资源在再次被请求时,会被当做新资源,进而引发一次完整的响应——不该重新请求的时候,也会重新请求。

当我们修改文件的速度过快时(比如花了 100ms 完成了改动),由于 If-Modified-Since 只能检查到以秒为最小计量单位的时间差,所以它是感知不到这个改动的——该重新请求的时候,反而没有重新请求了。

为了解决这样的问题,Etag 作为 Last-Modified 的补充出现了。

# Etag

Etag 是由服务器为每个资源生成的唯一的标识字符串,这个标识字符串是基于文件内容编码的,只要文件内容不同,它们对应的 Etag 就是不同的,反之亦然。因此 Etag 能够精准地感知文件的变化。

Etag 和 Last-Modified 类似,当首次请求时,我们会在响应头里获取到一个最初的标识符字符串,举个🌰,它可以是这样的:

ETag: W/"2a3b-1602480f459"

那么下一次请求时,请求头里就会带上一个值相同的、名为 if-None-Match 的字符串供服务端比对了:

If-None-Match: W/"2a3b-1602480f459"

  • 不足

Etag 的生成过程需要服务器额外付出开销,会影响服务端的性能,这是它的弊端。因此启用 Etag 需要我们审时度势。正如我们刚刚所提到的——Etag 并不能替代 Last-Modified,它只能作为 Last-Modified 的补充和强化存在。 Etag 在感知文件变化上比 Last-Modified 更加准确,优先级也更高。当 Etag 和 Last-Modified 同时存在时,以 Etag 为准。

# 参考资料

MDN-Cache-Control

Last Updated: 4/26/2020, 12:02:02 PM