通过express框架简单实践几种设置HTTP对缓存的控制

0.096字数 1680阅读 2906

传统客户端在安装后的应用过程中仅仅需要传输数据,而Web应用还需要传输构成界面的组件(HTML、JavaScript、CSS文件等)。
有些内容在很多场景下不会怎么变动,如果每次请求都向客户端传递那些一段时间内不会变动的内容数据,会造成不必要的带宽浪费。
有时候网络速度比较差时,请求这些内容就需要花费很多时间来打开页面。
因此通过浏览器的缓存机制,协同服务器让浏览器缓存那些不怎么变动的资源就可以有效地降低互联网流量和用户响应时间。

首先我们要知道,与缓存有关的HTTP首部有:

通用首部字段

字段 说明
Cache-Control 控制缓存的指令
Pragma HTTP/1.1 之前的历史遗留字段,仅作为与HTTP/1.0的向后兼容而定义

请求首部字段

字段 说明
If-Match 与Etag比较是否一致,若是告知服务器处理请求
If-None-Match 与Etag比较是否不一致,若是告知服务器处理请求
If-Modified-Since 与资源最后更新时间比较
If-Unmodified-Since 与资源最后更新时间比较,作用与if-Modified-Since相反

响应首部字段

字段 说明
ETag 将资源以字符串形式做唯一性标识的方式

实体首部字段

字段 说明
Expires 告知客户端资源实效日期
Last-Modified 指明资源最后一次修改的时间

下面,我们就通过设置

  • Expires
  • Cache-Control
  • Last-modified
  • Etag
    这四种HTTP首部来简单实现一下缓存控制

首先通过npm i express安装express
基于express的简易后台可以是这样的:)


pic1

然后通过浏览器多次请求,浏览器显示的内容及控制台NetWork中的情况应该会是这样。


pic2

好了,那我们就实践一下缓存style.css
Cache-Control
在这里,我们可以设置响应报文中的Cache-Control首部为如下的指令。要快速了解更多Cache-Control相关的各种指令可以查阅《图解HTTP》。

pic3

接着先清除一下缓存
pic4

再通过浏览器请求两次,第一次我们看到:

pic5

第二次请求则是:

pic6

注意看请求的style.css来自disk cache(本地缓存文件)。这里,我们设置max-age=600使得10min中内。请求style.css的返回资源都来自本地。

另外,浏览器对于第一个请求是肯定不会缓存的,比如

pic11

express会针对请求根目录下index.html的响应配置了Etag(后面会提到),所以响应状态码出现304。即便为该响应也设置一个Cache-Control首部,并设置max-age指令一个时间段,再次请求该index.html也不会获取缓存的内容。

pic12

Expires
通过设置Expires首部的值为一个GMT(格林尼治时间)时刻,比如Mon, 22 Jul 2002 11:12:01 GMT,就可以告诉浏览器资源缓存过期时间,在这个时刻之前的请求,资源都来自本地,而不是服务器;这个时刻之后才是向服务器请求资源。例子:

pic13
pic14

在'Sat, 07 Oct 2017 07:39:42 GMT'这个时刻前请求style.css都会从本地拿资源。

而加上query string去改变请求的url,才能在重新获取更新。

但是使用Expires有一个问题,那就是设置的失效时间是跟用户电脑系统的时间进行比较的。假设用户把系统时钟设成了2019年,那么设置一个2018年的失效时刻毫无意义。

另外定义缓存过期时间的优先级是Cache-Control -> Expires。除非要去兼容HTTP1.0的时候需要使用Expires,不然一般都使用Cache-Control。

Last-Modified
设置Last-Modified指明资源最后修改的时刻。
浏览器受到响应后,会在下一次请求时带上If-Modified-Since的首部字段,值就是之前响应的Last-Modified首部字段的值。
通过比较请求的if-Modified-Since首部的值和服务器上该资源的最后修改时间是否一致,若是,则不再下载该资源;反之,下载最新的资源。
具体可以这样设置:

pic15

多次请求,请求头与响应头会是如下

pic16

  1. ETag
    有时候设置last-modified总有些不准确的情况,所以Http1.1还推出了 ETag 实体首部字段。 服务器会通过某种算法,给资源计算得出一个唯一标志符(比如md5标志),在把资源响应给浏览器的时候,会在实体首部加上“ETag: 唯一标识符”。
    浏览器在下一次请求时会带上一个If-None-Match请求首部,值则是之前响应的Etag首部的值。
    服务器通过比较请求的If-None-Match首部的值与服务器上的资源的ETag值,如果匹配上了,则不用再下载资源了。反之重新下载资源。
    比如:
pic17

可以用这张熟悉的图总结以上四种缓存控制

缓存机制.jpg

了解完四种HTTP缓存控制的机制后,该如何选择使用它们呢?
对于Expires和Cache-Control,使用上来说Cache-Control不会有服务器和客户端时间不能同步的问题,还能设置public、private等更精细的指令,应该是更优的选择。不过需要兼容HTTP1.0的时候需要使用Expires。不过,同时设置这俩时浏览器都会解析,并且优先使用Cache-Control,所以一起设置也无妨。

对于Last-Modified和ETag,ETag解决了Last-Modified使用时可能出现的资源的时间戳变了但内容没变及如果再一秒钟以内资源变化但Last-Modified没变的问题。除了多了些计算ETag多了点消耗外。那么怎么选择看场景,感觉上ETag更加稳妥。

最后还有一个重要的问题是,设置了缓存之后,当需要更新资源时,如何消除缓存?其实浏览器是根据url决定是否获取缓存还是从服务器更新资源,那么通过设置url的query string就可以消除缓存了。
一般设置有两种方式:

  1. 使用版本号,比如:http://url.com/?version=20171006
  2. 使用一个hash值,比如:http://url.com/?hash=sdfkljlkjasdklxclkv13
    后者其实是把服务器ETag的那一套理论搬到了前端来使用,也可以给query string的参数带上一串md5。

更多参考可以看看这篇详细介绍缓存控制的文章HTTP缓存控制小结
虽然后台框架都有更简单地设置,但通过手动地实践一遍通过HTTP对缓存进行控制,多少能帮助理解。

推荐阅读更多精彩内容