根据请求是否达到服务端,可以将缓存分为 强缓存协商缓存

强缓存

只在客户端即可判断是否命中的缓存

常用响应头Pragma, Expires

请求头Cache-Control (也可能存在于响应头中)

这些字段配置后不到期的请求不会到达服务端

Pragma

HTTP/1.0 协议中规定的通用首部,其效果依赖不同的实现,在请求到响应时可能会有不同的效果

现在只用来兼容只支持 HTTP/1.0 协议的缓存服务器

该头并没有一个明确的规范,所以可靠性不行,除非要兼容 HTTP/1.0 客户端,不然不建议使用

语法很简单且只有如下一种指令

1
Pragma: no-cache

Cache-Control: no-cache 效果一致,强制要求服务器验证缓存

Expires

该响应头存放着一个 HTTP标准时间戳,用于设置资源的过期时间,格式如下

1
Expires: <http-date>

浏览器在请求一个资源的时候,会拿当前时间与该资源上次请求时的 Expires 对比,如果没有超过则读取本地缓存,否则向服务器请求资源

例如我们用拉起一个简单的 http 服务器:

1
2
3
4
5
6
7
app.use(async (ctx, next) => {
console.log('request');
ctx.response.body = 'RESPONSE DATA';
// 设置过期时间为请求时间 + 三秒
ctx.set('Expires', new Date(Date.now() + 1000 * 3).toUTCString());
await next();
});

我们在三秒内请求该资源多次,观察浏览器调试器可以发现除首次请求外都使用了磁盘缓存

三秒内请求同一资源多次

且服务端控制台只打印了一次 request

三秒后再次请求一次,会发现没有走缓存

该响应头返回的是绝对时间,因此客户端与服务端之间的时间差如果过大,会出现缓存失效、更新不及时的问题

下文的 Cache-Control 更加完善且优先级比 Expires

Cache-Control

该指令请求头和响应头中都可能存在,该指令是单向的,这意味着响应中不一定会有

语法规则如下:

  • 不区分大小写,建议小写

  • 多个指令逗号分隔

  • 有可选参数,可以用令牌或者带引号的字符串语法

请求指令

客户端在请求时的指令

1
2
3
4
5
6
7
Cache-Control: max-age=<seconds> // 必填参数例子(尖括号)
Cache-Control: max-stale[=<seconds>] // 可选参数例子(中括号+尖括号)
Cache-Control: min-fresh=<seconds>
Cache-control: no-cache
Cache-control: no-store
Cache-control: no-transform
Cache-control: only-if-cached

响应指令

服务端在响应时的指令

1
2
3
4
5
6
7
8
9
Cache-control: must-revalidate
Cache-control: no-cache
Cache-control: no-store
Cache-control: no-transform
Cache-control: public
Cache-control: private
Cache-control: proxy-revalidate
Cache-Control: max-age=<seconds>
Cache-control: s-maxage=<seconds>

指令作用

这边只记录些常用的,其它的查文档不香吗

  • public

    表示响应可以被任何对象缓存(客户端、代理服务器、等等),即使是一个通常来说不能被缓存的内容:

    • POST 请求

    • 没有 max-age 或者 Expires

  • no-cache

    强制走协商缓存(先向服务器确认后才决定是否用本地缓存)

  • no-store

    不使用任何缓存

  • max-age=<seconds>

    缓存最大周期,设置一个相对时间,超过该相对时间的缓存被判定过期

  • only-if-cached

    只接受已缓存的响应,并且不向服务器进行任何请求以检查更新

协商缓存

顾名思义,需要和服务器“协商”的缓存

常用响应头Last-Modified, ETag

常用请求头If-Modified-Since, If-None-Match,一般与上面的响应头成对使用

协商缓存会向服务器询问资源是否需要更新,若无需则使用缓存,否则服务器返回新资源

Last-Modified

是一个响应头部,包含了该响应资源的最后修改时间,用于判断资源的一致性

语法:

1
Last-Modified: <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT

具体格式看文档

If-Modified-Since

是一个请求头部,服务器只会在资源最后修改时间大于该值时返回最新的资源(资源在这之后修改了,需要更新),状态码 200

如果请求的资源在这之后都没有修改,那么会在响应头的 Last-Modified 中存放资源上次修改时间,并且返回一个不带主体的 304 响应,客户端收到响应后会使用本地缓存

客户端一般会在再次请求同一资源时将上次 Last-Modified 的值放在该次请求头部 If-Modified-Since

If-Modified-Since 只能用在 GET,HEAD 请求中

该请求头一般用在响应中无 ETag 的资源上,是其备选方案

ETag

是响应头,记录了资源特定版本的标识符,用于判断资源是否变动

ETag 的生成方法没有指定,一般计算哈希值

语法:

1
2
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4" // 强验证
ETag: W/"0815" // 在前面加上 W/ 表示弱验证

If-Math

是请求头,配合 ETag 使用

再次请求某资源时,带上上次响应的 ETag 并放在 If-Math 中发送

如果客户端发送的标识与服务端不匹配,则返回最新的资源和最新的 ETag

如果匹配则返回不带内容的 304 响应,客户端读取本地缓存

避免数据冲突

除了缓存外,还能解决一些冲突问题

具体可以看MDN 的例子

参考

Pragma | MDN
Expires | MDN
HTTP 日期/时间标准 | 简书
Cache-Control | MDN
Last-Modified | MDN
If-Modified-Since | MDN
ETag | MDN