http-cache

http cache

http 协议与浏览器的缓存机制

(简易)浏览器执行步骤:

浏览器 -> 发请求 -> 响应请求 -> 解析资源 -> 显示

如果每次对于页面的静态资源都跑以上步骤,会影响页面加载渲染速度,使得用户体验被大打折扣。

(缓存优化)浏览器执行步骤:

浏览器 -> 发请求 -> 从缓存读取 -> 解析资源 -> 显示

直接到缓存读取比起从发请求到服务器响应省去了网络传输等途径,加快了浏览器进入解析的进度。

在前端茫茫的优化道路上,对于静态资源(html/js/css/img/webfont)的请求上有几个理想的指标:

  • 页面能够以最快的速度获取到其所有的静态资源
  • 当服务器静态资源未更新时,再次访问不请求服务器,从而是读取浏览器缓存
  • 当服务器静态资源更新时,请求服务器是最新静态资源。

其实无非就是该请求的时候快速响应下载,不该请求的时候读取缓存资源

http 头部信息

http header 包含了缓存相关的规则信息

强制/指定 缓存(Expires/Cache-Control)

指资源缓存的方式及有效期

Expires

是 http 1.0 的产物,在 http 1.x 后使用 Cache-Control 代替,http 1.x 是没实现 Cache-Control。
Expires 的值为服务端返回的到期时间,在响应 http 请求时告诉浏览器在过期时间前浏览器获取静态资源的方式是从浏览器缓存取数据,而无需再次请求。
Expires 返回的是服务端的时间,与之比较的是客户端本地设置的时间,有可能存在导致差错。

Cache-Control

定义请求缓存指示

  • public:响应被缓存,并且在多用户间共享。
  • private:默认值,响应只能够作为私有的缓存,不能再用户间共享;
  • no-cache:响应不会被缓存,而是实时向服务器端请求资源。
  • max-age:数值,单位是秒,从请求时间开始到过期时间之间的秒数。基于请求时间(Date 字段)的相对时间间隔,而不是绝对过期时间;

Cache-Control 中设定的时间会覆盖 Expires 中指定的时间。若 Cache-Control 的值为ax-age=0,则立即过期重新请求。

Pragma

该字段是兼容 http 1.0 没有 Cache-Control 出现的。

  • no-cache,只有唯一一个选项,效果作用和Cache-Control:no-cache一样

对比/条件 缓存

第一次请求的时,服务端会响应缓存的标识和数据回到客户端,下次请求时,客户端会被该标识带到服务器,服务器根据缓存标识进行判断,若成功返回 304,则客户端使用缓存资源

HTTP 304: Not Modified
标准解释:Not Modified 客户端有缓冲的文档并发出了一个条件性的请求(一般是提供 If-Modified-Since 头表示客户只想比指定日期更新的文档)。服务器告诉客户,原来缓冲的文档还可以继续使用。

Last-Modified/If-Modified-Since

Last-Modified:
在响应请求的头部里,用于告知浏览器资源的最后修改时间

If-Modified-Since:
再次请求时,通过此字段通知服务器上次请求时,服务器返回的资源最后修改时间。服务器通过此If-Modified-Since与被请求资源的修改时间mtime进行对比。
mtime 大于 If-Modified-Since,响应资源,状态码 200。
mtime 小于或等于 If-Modified-Since,则资源无修改,返回状态码 304 通知浏览器读取对应的 cache 资源。

mtime,此处是举例说明,该字段用的是 node file.stats 类的属性

example

首次请求时,请求头部没带If-Modified-Since,需要获取资源修改时间,并响应Last-Modified字段。

1
2
ctx.set('Last-Modified', stats.mtime.toUTCString());
ctx.body = fs.createReadStream(rootPath);

再次请求时,浏览器发出的请求会带上If-Modified-Since字段,服务器获取资源修改时间与带上来的If-Modified-Since进行对比。

1
2
3
4
5
6
7
8
9
10
if (
ctx.get('if-modified-since') &&
ctx.get('if-modified-since') === stats.mtime.toUTCString()
) {
ctx.status = 304;
return;
} else {
ctx.set('Last-Modified', stats.mtime.toUTCString());
ctx.body = fs.createReadStream(rootPath);
}

Etag/If-None-Match

Etag:
服务对资源的唯一标示

If-None-Match:
再次请求时,通过此字段通知服务器上次请求时,服务器返回的资源的唯一标示。服务器通过此If-None-Match与被请求资源在服务器上资源的标示进行对比。匹配成功,响应资源,状态码 200。匹配不成功,则资源无修改,返回状态码 304 通知浏览器读取对应的 cache 资源。

优先级高于 Last-Modified/If-Modified-Since,比其处理更加细腻精准,Etag 主要为了解决 Last-Modified 无法解决的一些问题。

eg:

  • 如资源在短时间内被修改多次,但由于Last-Modified对比只精确到秒级别,故请求依旧走缓存,从而没法获取最新的资源。
  • 资源定期生成,但内容不变,Last-Modified切被改变,从而引发请求会获取资源,而不是去走缓存。

example

首次请求时,请求头部没带If-None-Match,需要获取资源然后根据 file 内容生产 hash,并响应Etag字段。

1
2
3
4
5
6
7
8
9
10
11
function getHash(str: any) {
const chash = crypto.createHash('sha1');
return chash.update(str).digest('base64');
}

const file = await fs.readFile(rootPath);
const hash = getHash(file);
...
ctx.set('Etag', hash);
ctx.body = fs.createReadStream(rootPath);
...

再次请求时,浏览器发出的请求会带上If-None-Match字段,服务器获取资源计算 hash 与带上来的If-None-Match的 hash 值进行对比。

1
2
3
4
if (ctx.get('if-none-match') && ctx.get('if-none-match') === hash) {
ctx.status = 304;
return;
}

项目 demo 地址,yarn run httpCache

备注:开发时注意关闭浏览器在打开开发者工具时关闭缓存的设置

end

http 协议的缓存机制是 web 静态资源缓存及优化的另一种方式,再进一步优化,前端可以采取preloading(预加载)service work(浏览器新功能)等方式加快页面解析速度。