HTTP缓存两种机制

174 阅读6分钟

HTTP缓存策略只是为了解决客户端和服务端信息不对称的问题而存在的,客户端为了加快速度会缓存部分资源,但是下次请求时,客户端不知道这个资源有没有更新,服务端也不知道客户端缓存的是哪个版本,不知道该不该再返回资源,其实就是一个信息同步问题,HTTP缓存策略就是来解决这个问题的

HTTP缓存两种机制

一. 协商缓存

ETag和If-None-Match
  • ETag是URL的Entity Tag,就是一个URL资源的标识符,类似于文件的md5,计算方式也类似,当服务器返回时,可以根据返回内容计算一个hash值或者就是一个数字版本号;具体返回什么值要看服务器的计算策略;将它加到response的header里面,可能长这样:
ETag: "33a64df551425fcc55e4d42a148795d9f25f89xasd4"
  • 客户端拿到后会将这个ETag和返回值一起存下来,等下次请求时,使用配套的If-None-Match,将这个放到request的header里面,可能长这样:
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89asxd4"
  • 然后服务端拿到请求里面的If-None-Match跟当前版本的ETag比较下:
  1. 如果是一样的话,直接返回304,语义为Not Modified,不返回内容(body),只返回header,告诉浏览器直接用缓存。
  2. 如果不一样的话,返回200和最新的内容

与ETag配套的还有一个不太常用的request header ----If-Match,这个和前面If-None-Match的语义是相反的。前面If-None-Match的语义是如果不匹配就下载。而If-Match通常用于post或者put请求中,语义为如果匹配才提交,比如你在编辑一个商品,其他人也可能同时在编辑。当你提交编辑时,其他人可能已经先于你提交了,这时候服务端的ETag就已经变了,If-Match就不成立了,这时候服务端会给你返回412错误,也就是Precondition Failed,前提条件失败。如果If-Match成立,就正常返回200。

Last-Modified & If-Modified-Since
  • Last-Modified和If-Modified-Since也是配套使用的,类似于ETag和If-None-Match的关系。只不过ETag放的是一个版本号或者hash值,Last-Modified放的是资源的最后修改时间。Last-Modified是放到response的header里面的,可能长这样:
Last-Modified: Wed, 21 Oct 2000 07:28:00 GMT 
  • 而客户端浏览器在使用时,应该将配套的If-Modified-Since放到request的header里面,长这样:
If-Modified-Since: Wed, 21 Oct 2000 07:28:00 GMT 
  • 服务端拿到这个头后,会跟当前版本的修改时间进行比较:
  1. 当前版本的修改时间比这个晚,也就是这个时间后又改过了,返回200和新的内容
  2. 当前版本的修改时间和这个一样,也就是没有更新,返回304,不返回内容,只返回头,客户端直接使用缓存

与If-Modified-Since对应的还有If-Unmodified-Since,If-Modified-Since可以理解为有更新才下载,那If-Unmodified-Since就是没有更新才下载。如果客户端传了If-Unmodified-Since,像这样:

If-Unmodified-Since: Wed, 21 Oct 2000 07:28:00 GMT 
  • 服务端拿到这个头后,也会跟当前版本的修改时间进行比较:
  1. 如果这个时间后没有更新,服务器返回200,并返回内容。
  2. 如果这个时间后有更新,其实就是这个if不成立,会返回错误代码412,语义为Precondition Failed
ETag和Last-Modified优先级
  • ETag和Last-Modified都是协商缓存,都需要服务器进行计算和比较,那如果这两个都存在,用哪个呢?答案是ETag,ETag的优先级比Last-Modified高。因为Last-Modified在设计上有个问题,那就是Last-Modified的精度只能到秒,如果一个资源频繁修改,在同一秒进行多次修改,你从Last-Modified上是看不出来区别的。但是ETag每次修改都会生成新的,所以他比Last-Modified精度高,更准确。但是ETag也不是完全没问题的,你的ETag如果设计为一个hash值,每次请求都要计算这个值,需要额外耗费服务器资源。具体使用哪一个,需要根据自己的项目情况来进行取舍。

二. 强制缓存

简单来说就是在某个时间段完全不用去问服务端,直接去用缓存就行。

Expires
  • Expires比较简单,就是服务器responseheader带上这个字段:
Expires: Wed, 21 Oct 2000 07:28:00 GMT
  • 然后在这个时间前,客户端浏览器都不会再发起请求,而是直接用缓存资源。
Cache-Control
  • Cache-Control相对比较复杂,可设置属性也比较多,max-age只是其中一个属性,长这样:
Cache-Control: max-age=20000
  • 这表示当前资源在20000秒内都不用再请求了,直接使用缓存。
  • 上面提到的immutable也是Cache-Control的一个属性,但是是个实验性质的,各个浏览器兼容并不好。设置了Cache-control: immutable表示这辈子都用缓存了,再请求是不可能的了。

其他常用属性还有:

  • no-cache:使用缓存前,强制要求把请求提交给服务器进行验证(协商缓存验证)。
  • no-store:不存储有关客户端请求或服务器响应的任何内容,即不使用任何缓存。

另外Cache-Control还有很多属性,大家可以参考MDN的文档

Expires和Cache-Control的优先级
  • 就一句话:如果在Cache-Control响应头设置了 max-age 或者 s-maxage 指令,那么 Expires 头会被忽略。

协商缓存和强制缓存优先级

  • 这个其实很好理解,协商缓存需要发请求跟服务器协商,强制缓存如果生效,根本就不会发请求。所以这个优先级就是:先判断强制缓存,如果强制缓存生效,直接使用缓存;如果强制缓存失效,再发请求跟服务器协商,看要不要使用缓存