前端进阶:你需要掌握的 http缓存
http 缓存的分类
强缓存
强缓存是指:当浏览器请求的资源命中强缓存时,直接从内存中读取对应的资源,不和服务器进行交互。
强缓存可以通过两种方式来实现:
Expires
Cache-Control
Expires 强缓存
我们先开启一个 nodejs 本地服务器
//server.js
const http = require('http');
const fs = require('fs');
const path = require('path');
http.createServer((request, response) => {
// 设置 expires 强缓存
response.writeHead(200, {
Expires: new Date("2023-5-20 23:59:59").toUTCString()
})
// 读取资源
const data = fs.readFileSync(path.join(__dirname, '图片.png'))
response.end(data)
}).listen(8080, () => {
console.log('server listening on port 8080');
});
其中,Expires 字段可以设置一个时间段,当我们在这个时间范围内请求该资源时
,直接读取缓存的资源,而不用再次与服务器交互。
比如上面的 Expires: new Date("2023-5-20 23:59:59").toUTCString()
就表示 在 2023-5-20 23:59:59 前,请求该资源时,都走强缓存,去本地缓存服务器读取该资源,不与服务器交互。
但是,Expires 已经被废弃
,且 Expires 的判断机制存在问题。
Expires 的判断机制是:当客户端请求资源时,会获取本地时间戳,然后
拿本地时间戳与 Expires 设置的时间做对比
,如果对比成功,走强缓存,对比失败,则对服务器发起请求。
这里有一个严重的问题就是:如果本地时间不准,就会造成资源永久缓存或者不能缓存的问题。
所以对于强缓存,我们使用 Cache-Control 代替 Expires
Cache-Control 强缓存
在 http1.1 中,新增了 Cache-Control 响应头字段,它的使用如下:
//server.js
const http = require('http');
const fs = require('fs');
const path = require('path');
http.createServer((request, response) => {
// cache-control 强缓存
response.writeHead(200, {
'Cache-Control':'max-age=3600'
})
const data = fs.readFileSync(path.join(__dirname, '图片.png'))
response.end(data)
}).listen(8080, () => {
console.log('server listening on port 8080');
});
上面的代码中,'Cache-Control':'max-age=3600'
代表的意思就是,某个资源第一次返回后,从这个时候开始的 3600 秒内,如果这个资源被再次请求,则读取缓存的数据
,而不与服务器交互。
也就是说【Cache-Control: X 】:
X 就代表资源第一次成功请求后的 X 秒内
,若资源再次被请求,则走缓存,不与服务器交互。
Cache-Control 的值:
Cache-Control 的值如下:
max-age
:浏览器资源缓存的时长。s-maxage
:代理服务器缓存资源的时长。no-cache
:不走强缓存,走协商缓存。no-store
:禁止任何缓存策略。public
:资源即可以被浏览器缓存也可以被代理服务器缓存。private
:资源只能被浏览器缓存。
- no-cache 和 no-store 互斥,不能同时出现。
- public 和 private 互斥,不能同时出现。
- max-age 和 s-maxage 可以一起使用,但是 s-maxge 必须和 public 一起使用。
其中,public 和 private 就是设置,代理服务器是否能缓存资源。如果不设置,默认情况下是 private,此时资源只能被浏览器缓存。
协商缓存
协商缓存也分为两种
last-modified ➕ if-modified-since
etag ➕ if-none-match
last-modified ➕ if-modified-since
这种方式的实现如下:
//server.js
const http = require('http');
const fs = require('fs');
const path = require('path');
// last-modified 协商缓存
http.createServer((request, response) => {
// 读取资源
const data = fs.readFileSync(path.join(__dirname, '图片.png'))
// 读取资源修改的时间
const { mtime } = fs.statSync(path.join(__dirname, '图片.png'))
// 读取上一次返给浏览器的文件修改时间,也就是上一次读取的 mtime
const ifModifiedSince = request.headers['if-modified-since']
// 判断文件修改时间是否改变
if (ifModifiedSince === mtime.toUTCString()) {
// 若不变,则状态码 304,直接 return
response.statusCode = 304
response.end()
return
}
// 若改变,更新 last-modified 字段
response.setHeader('last-modified', mtime.toUTCString())
// 设置 cache-control 为 no-cache:走协商缓存,不走强缓存
response.setHeader('Cache-Control', 'no-cache')
response.end(data)
}).listen(8080, () => {
console.log('server listening on port 8080');
});
它的流程是:
- 第一次时,读取文件修改的时间 mtime。
将 mtime 的值设置给响应头的 last-modified
。- 浏览器读取到 响应头返回的 last-modified 时,
会在下一次请求时,在请求头中带上 if-modified-since 字段
,该字段的值就是第一次响应头的 last-modified 的值,也就是第一次服务器读取的资源的修改时间。
- 第二次请求该资源时,就会
读取请求头中的 if-modified-since 字段,且服务器再次读取该资源的修改时间
。 - 判断第一次资源的修改时间和第二次资源的修改时间是否相等。
- 若相等,则说明资源没有更改,返回 304。
- 若不相等,则重新设置响应头的 last-modified 字段。
- 重复以上步骤。
图解如下:
而 last-modified ➕ if-modified-since 这种方式有两个严重的问题:
- 在文件内容不被修改的前提下,修改文件名再改回去,缓存失效。
- 文件在很短时间内改变,比如几百毫秒,文件的修改记录最小单位是秒,此时文件修改时间不会改变,此时内容变了,但不会返回新的资源。
Etag ➕ if-none-match
这种方式是是根据文件的指纹,也就是 hash值
来命中协商缓存的。
//server.js
const http = require('http');
const fs = require('fs');
const path = require('path');
const etag = require('etag');
http.createServer((request, response) => {
// etag 协商缓存
// 读取资源
const data = fs.readFileSync(path.join(__dirname, '图片.png'));
// 获取资源 hash 值
const etag = etag(data);
// 获取上一次返回给浏览器的文件指纹
const ifNoneMatch = request.headers['if-none-match'];
// 对比hash值
if (ifNoneMatch === etag) {
response.statusCode = 304;
response.end();
return;
}
// 设置 etag
response.setHeader('etag', etag);
response.setHeader('cache-control', no-cache);
response.end(data)
}).listen(8080, () => {
console.log('server listening on port 8080');
});
它的流程与 last-modified 方式类似,这里不再赘述。
图解如下:
结语
以上内容如有错误,欢迎留言指出,一起进步💪,也欢迎大家一起讨论。
转载自:https://juejin.cn/post/7231434092230803516