-
Notifications
You must be signed in to change notification settings - Fork 0
Description
缓存是一种保存资源副本并在下次请求时直接使用该副本的技术,对于提高前端响应速度,缩短页面加载有十分显著的效果。前端缓存技术也有非常多,例如 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 资源文件被缓存:
并且 max-age 设置也完全正确。
接着我们来看 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 状态码。
// 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,也成功显示缓存。
(本文完)



