Nodejs ·

http请求头中缓存的实现

什么是http缓存呢,当我们使用chrome浏览器,按F12打开控制台,在网络请求中有时候看到状态码是200,有时候状态码是304,当我们去看这种请求的时候,我们会发现状态码为304的状态结果是:Status Code: 304 Not Modified,而状态码为200的时候一般会有四种情况,一种是直接返回200,没有任何其他的标志,另一种是Status Code: 200 OK (from memory cache),还有一种是Status Code: 200 (from disk cache)。最后一种不是太常见,Status Code: 200 (from Service Worker).后面这三种状态码看到的效果是灰色的,其实从给出的信息也能看出来是从缓存中获取上数据。下面我们来详细介绍一下他们都分别是什么时候出现的。

其实我们可以按状态码来区分其为两大类,分别是写上缓存--304和强制缓存--200

协商缓存(304)

这种方式使用到了headers请求头里的两个字段,Last-Modified & If-Modified-Since 。服务器通过响应头Last-Modified告知浏览器,资源最后被修改的时间:

Last-Modified: Thu, 20 Jun 2019 15:58:05 GMT

当再次请求该资源时,浏览器需要再次向服务器确认,资源是否过期,其中的凭证就是请求头If-Modified-Since字段,值为上次请求中响应头Last-Modified字段的值:

If-Modified-Since: Thu, 20 Jun 2019 15:58:05 GMT

浏览器在发送请求的时候服务器会检查请求头request header里面的If-modified-Since,如果最后修改时间相同则返回304,否则给返回头(response header)添加last-Modified并且返回数据(response body)。

另外,浏览器在发送请求的时候服务器会检查请求头(request header)里面的if-none-match的值与当前文件的内容通过hash算法(例如 nodejs: cryto.createHash('sha1'))生成的内容摘要字符对比,相同则直接返回304,否则给返回头(response header)添加etag属性为当前的内容摘要字符,并且返回内容。

综上总结为:

  • 请求头last-modified的日期与响应头的last-modified一致
  • 请求头if-none-match的hash与响应头的etag一致
    这两种情况会返回Status Code: 304

强制缓存(200(from ...))

这里我们使用到了Cache-Control和Expires这两个字段来进行控制,Cache-Control里面可以有多个属性,可以组合设置,其属性有如下几种:

  • max-age=xxx,最大的有效时间 单位秒,是一个相对时间
  • must-revalidate,如果超过了max-age的时间,必须向服务器发送请求,验证资源的有效性
  • no-cache,基本等价于max-age=0,由协商缓存来决定是否缓存资源
  • no-store,真正意义上的不缓存
  • public,代表 http 请求返回的内容所经过的任何路径当中(包括中间一些http代理服务器以及发出请求的客户端浏览器),都可以对返回内容进行缓存操作
  • private,代表只有发起请求的浏览器才可以进行缓存。默认值

比如我们设置

Catch-Control:public,max-age=360000

我们在之前说了强制缓存有三种情况,上面说返回200有四种,第一种其实是不缓存,正常服务器返回响应。

Service Worker

这个东西其实本质上时服务器和客户端之间的代理服务器,一般我们在使用react开发的时候,会发现在根目录出现了一个server-worker.js文件,这个东西就是和service Worker缓存相关的,他会根据网络的状态做出不同的缓存策略,有时候断网了,之前访问过的接口有可能依然会返回数据,其数据来源就是从其缓存中读取。

memory cache

这个是将资源缓存在了内存中。事实上,所有的网络请求都会被浏览器缓存到内存中,当然,内存容量有限,缓存不能无限存放在内存中,因此,注定是个短期缓存。而其控制权在于浏览器。

disk cache

与内存缓存相对的,这个是将资源缓存在硬盘中。虽然相比于内存,硬盘的读取速度要慢很多,但总比没有强。硬盘缓存的控制权在后端,通过什么控制呢?通过HTTP响应头控制,也就是我们在上面说到的catche-control和expires

Expires设置的过期时间是一个绝对的GMT时间,例如:Expires:Thu,20 Jun 2019 20:00:00 GMT; 他告诉浏览器缓存有效性持续到2019年6月20日为止,一直都使用缓存来处理。

Expires有一个非常大的缺陷,它使用一个固定的时间,要求服务器与客户端的时钟保持严格的同步,并且这一天到来后,服务器还得重新设定新的时间。

HTTP1.1引入了Cathe-Control,它使用max-age指定组件被缓存多久,从请求开始在max-age时间内浏览器使用缓存,之外的使用请求,这样就可以消除Expires的限制,

如果对浏览器兼容性要求很高的话,可以两个都使用。

其实在上面说到的Last-Modified对比最后修改时间与Expires一样是有缺陷的,如果,资源的变化的时间间隔小于秒级,比如说是毫秒级的,或者说资源直接是动态生成的,那根据Last-Modified判断,资源就是每时每刻都最新的,即被修改过!

所以,Etag & If-Node-Match 就是来解决这个问题的。
Etag字段的值为文件的特殊标识,一般都是hash生成的,服务器存储着资源的Etag值。接下来的流程都与Lst-Modified & If-Modified-Since一致,只不过,比较的值从最后修改时间变成了Etag值。

Etag的优点在于,对于动态资源或者现在流行的Restful API返回的JSON数据,这些是没有修改时间这一说法的,但是Http标准并没有规定Etag值如何生成,因此我们通过代码自己生成Etag值。当然,计算Etag值会消耗服务器性能。

Cache-Control+Last-Modified+ETag 的优先级会如何?

因为http1.1>http1.0,所以Cache-Control>Expires,ETag>Last-Modified。依照就近原则,先找本地缓存,没有再向服务器发请求,所以Expires>Last-Modified,Cache-Control>ETag,

如果浏览器只支持http1.0,那么浏览器只会携带Last-Modified发送给后台,
如果服务器只支持http1.0,那么服务器会以Last-Modified为标准。
如果浏览器支持http1.1,那么浏览器会携带Cache-Control+Last-Modified+ETag发送给后台,
如果服务器支持http1.1,那么服务器会以Cache-Control+ETag为标准。

200状态码和304状态码何时出现

在没有设置Cache-Contral的情况下,设置Last-Modified和ETag缓存,会出现200(from cache)和304 交替出现的情况。

设置Cache-Contral的情况下,过期刷新会出现304(如果有更新内容,则是200),之后再过期之前刷新都是200(from cache)。如果要确保要向服务端确认,可以将Cache-Contral的max-age设置为0。

通过下图我们可以清晰的明白200和304
http请求头中缓存的实现

参与评论