Sitelet https://github.com/lilins/Blog/issues/4
Skip to content

HTTP 缓存(附 Express 实现代码) #4

Description

@lilins

缓存是一种保存资源副本并在下次请求时直接使用该副本的技术,对于提高前端响应速度,缩短页面加载有十分显著的效果。前端缓存技术也有非常多,例如 CDN 缓存、DNS 缓存、NGINX 缓存、反向代理缓存和浏览器缓存等。今天要说明的是 HTTP 缓存是浏览器缓存中的一种。

常见的 HTTP 缓存只能缓存 GET 响应,一般会通过在请求 (Request) 和响应 (Response) 的 HTTP 报文 Header 上添加不同的字段以达到不同的缓存策略。

Header 上和缓存有关的字段

以下是 RFC2616 规定的 47 种 HTTP 报文首部字段中与缓存相关的字段:

字段名 Request/Response 说明
Cache-Control Request/Response 可设置不同的缓存策略
Pragma Request/Response HTTP/1.0 的字段,可忽略
If-Match Request 与 ETag 一起使用,服务器会比较 ETag 是否一致
If-None-Match Request 同上,结果取反
If-Modified-Since Request 与 Last-Modified 一起使用,服务器会比较最后更新的时间是否一致
If-Unmodified-Since Request 同上,结果取反
ETag Response 资源的标识符
Expires Response 值为时间,在此时间之后 Response 过期,可忽略
Last-Modified Response 资源最后更新的时间

Cache-Control

最为丰富的是 Cache-Control 的使用,所使用的值可参考语法。这里列举一下例子:

完全禁用缓存

Cache-Control: no-cache, no-store, must-revalidate

no-cache 表示不直接使用缓存,而是先向服务器进行验证(是否过期)

no-store 表示所有的内容都不允许缓存

must-revalidate 表示必须与服务器进行验证,若请求失败返回 504

缓存静态资源

Cache-Control:public, max-age=3600

public 表示可以被任何人缓存

max-age=3600 表示缓存最大周期为 3600 秒(1小时),即从接受到资源后的 3600 秒之后才会过期

但 Cache-Control 也有一些问题,在上例中,如果超过1小时后,这个请求将会失效,那么浏览器会从服务器强制更新,而此时很可能这个静态资源没有任何改动,这就浪费了时间和带宽。

为了解决这个问题,在 Cache-Control: max-age:0 的基础上,出现了新的 Header 字段来处理这件事,即 Last-Modified 和 ETag。

Last-Modified

Last-Modified 的格式为 <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT

在服务器的 Response Header 中设置 Last-Modified 后,浏览器会自动在下次发送 Request 附带 If-Modified-Since 和 If-Unmodified-Since 。

服务器接收到请求后,就会根据 Header 判断是否符合资源最后更新的时间,如果符合的话,会返回 304 状态码。此时,并不会发送完整的资源 Response,浏览器根据返回的 304 状态码从缓存中返回资源以达到节省带宽、加快页面响应速度的效果。

ETag

ETag 的格式为 W/<etag_value> 。

W/ 是可选项,表示使用弱验证器。弱验证器容易生成,但不利于比较。<etag_value> 则通常使用时间戳的哈希值。ETag 配合 If-Match 或 If-Not-Match 可以同样作为判断资源是否需要缓存的依据。

服务器会在 Response Header 中设置 ETag ,浏览器会在下次发送 Request 附带 If-Match 或 If-Not-Match 进行匹配,如果匹配成功则返回 304 状态码告诉浏览器资源依然可以用。

优先级

在上面提到的三种缓存策略,也可以分为两种:强缓存(expires, Cache-Control)和对比缓存(也被称为协商缓存,Last-Modified 和 ETag)。同时存在各种缓存时,各缓存优先级:

1、强缓存和对比缓存同时存在,如果强缓存还在生效期则强制缓存覆盖对比缓存,对比缓存不生效;如果强缓存不在有效期,对比缓存生效。即:强缓存优先级 > 对比缓存优先级

2、强缓存 expires 和 Cache-control 同时存在时,则 Cache-control 会覆盖 Expires, Expires 无论有没有过期,都无效。 即:Cache-control 优先级 > Expires 优先级。

3、对比缓存 Etag 和 Last-Modified 同时存在时,则 Etag 会覆盖 Last-Modified, Last-Modified 不会生效。即:ETag 优先级 > Last-Modified 优先级。

补充:Pragma 优先级 > cache-control 优先级,Pragma 通常不使用,有限的使用场景是在 Chrome 的 devtools 中启用 disable cache 时,会在所有的 Request Header 上添加 Pragma: no-cache。

在 Express 上的实践

我们可以通过 Express 来进行 Cache 的试验,以下是文件结构:

/static
--/index.html
--/cache.js
index.js

代码文件可见 Example

index.js 是用来启动 Express 的,static 文件夹中存放着启动页面和一个用来测试 Cache 的 JS 资源文件,启动页面中引入了 JS 资源文件。

首先是 Cache-Control,为了实现效果,我们需要暂时把 Last-Modified 和 ETag 停用:

// index.js
const express = require('express')
const path = require('path')
const app = express()

const opts = {
  etag: false,
  lastModified: false,
  maxAge: 86400000
}
app.use('/static', express.static(path.join(__dirname, 'static'), opts))

在浏览器中输入 localhost:3000/static/index.html,在第一次获取中可以看到返回 200,刷新页面发现 JS 资源文件被缓存:

1555772893898

并且 max-age 设置也完全正确。

1555772931069

接着我们来看 Last-Modified ,要注意的是,这里必须设置 maxAge: 0 ,因为 Last-Modified 和 ETag 是在 Cache-Control: max-age=0 的基础上生效的。

// index.js
const express = require('express')
const path = require('path')
const app = express()

const opts = {
  etag: false,
-  lastModified: false,
-  maxAge: 86400000
+  lastModified: true,
+  maxAge: 0
}
app.use('/static', express.static(path.join(__dirname, 'static'), opts))

使用 Ctrl+F5 强制刷新后,再普通刷新一次,我们可以发现,JS 资源已经被缓存,显示 304 状态码。

1555773340933

// index.js
const express = require('express')
const path = require('path')
const app = express()

const opts = {
-  etag: false,
+  etag: true,
   lastModified: false,
-  maxAge: 86400000
+  maxAge: 0
}
app.use('/static', express.static(path.join(__dirname, 'static'), opts))

同样状态下修改 etag,也成功显示缓存。

1555822403717

(本文完)

参考

No activity

Activity on this issue will appear here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions