一、http缓存分类
分为2类,一类是强缓存,一类是协商缓存。
实现强缓存的方式有2种:一种是在响应头里设置expires字段,一种是在响应头里设置Cache-Control字段。
相应的,实现协商缓存的方式也有2种:一种是在响应头里设置last-modified字段,一种是在响应头里设置tag字段。
接下来我们进行代码实操。
二、搭建node项目环境
按次序执行下面命令:
1、随便建一个文件夹(命名:koa-project),在文件夹下输入:npm init -y
2、npm install koa koa-router nodemon
在该文件夹下建立相应文件与文件夹,使得项目结构与下面的保持一致:
| - koa-project
| - node_modules
| - src
| - file
| - a.txt
| - b.txt
| - main.js
| - package-lock.json
| - package.json
在package.json文件里编写启动命令:
{
// 省略默认配置...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon ./src/main.js"
}
// 省略默认配置...
}
main.js文件编写如下:
let Koa = require('koa');
let Router = require('koa-router');
let crypto = require('crypto');
let fs = require('fs');
let app = new Koa();
let KoaRouter = new Router();
KoaRouter.get('/', function (ctx, next){
ctx.body = 'hello home';
});
app.use(KoaRouter.routes()).use(KoaRouter.allowedMethods());
app.listen(3000,()=>{
console.log('我们启动了一个3000端口的服务')
});
在命令行里输入:npm run dev,然后在浏览器端访问:localhost:3000,如果出现浏览器端出现 'hello home'字样,那么就代表我们的服务已经起来了。
三、强缓存实操
编写a.txt文件,文件内容越多越好,比如我的文件内容就是如下:
a-强缓存1
a-强缓存2
a-强缓存3
a-强缓存4
a-强缓存5
a-强缓存6
a-强缓存7
a-强缓存8
a-强缓存9
a-强缓存10
a-强缓存11
a-强缓存12
a-强缓存13
此时,我们修改一下main.js文件:
// 其他代码都不变...
// 强缓存路由 +++++++++++
KoaRouter.get('/getFile', async function (ctx, next){
const getFileResult = () => {
return new Promise((resolve, reject) => {
fs.readFile('src/file/a.txt', 'utf-8',(err, data) => {
if (err){
resolve(err)
} else {
resolve(data)
}
});
});
}
let result = await getFileResult(); // 读取文件内容
ctx.body = result;
ctx.set('Cache-Control', 'max-age=180'); // 设置浏览器缓存时间为180s,也就是3分钟
});
// 在app.listen代码前添加...
此时我们再访问一下:http://localhost:3000/getFile,
从上面的图里,我们可以重点关注一下响应时间(13ms)、响应头里的Cache-Control字段、size字段。
然后我们重新打开一个页签,再次访问该地址,返回情况如下图:
我们会发现,时间极大的减小了,响应时间(0ms)、size字段(disk cache)。
我们的设置起了作用,从上述代码来看,我们发现强缓存的流程如下:
- 第一次向后端发送请求时,后端会在响应头里设置
expires或者Cache-Control字段。 - 因为上一次请求对应的响应里存在
expires或者Cache-Control字段,所以在n s以内,如果再次请求的话,浏览器会从本地的磁盘里读取相应的结果。
四、协商缓存实操
4.1、last-modified
继续编辑main.js文件:
// 协商缓存路由-Last-Modified ++++++++++
KoaRouter.get('/getFileConsult', async function (ctx, next){
// 读取文件信息
const getFileInformation = () => {
return new Promise((resolve, reject) => {
fs.stat('src/file/b.txt', function (err, stat){
if (err){
return resolve('读取文件失败');
} else {
return resolve(stat);
}
});
})
}
// 读取文件内容
const getFileContent = () => {
return new Promise((resolve, reject) => {
fs.readFile('src/file/b.txt', 'utf-8', (err, data) => {
if (err){
resolve(err)
} else {
resolve(data)
}
});
});
}
let ifModifiedSince = ctx.header['if-modified-since']; // 从浏览器端发送的请求中获取上一次的文件修改时间
if (ifModifiedSince){
console.log('协商缓存里的标识:', ctx.header);
}
let fileInformation = await getFileInformation();
ctx.set('Cache-Control', 'no-cache');
// 开始对比时间
if (ifModifiedSince === fileInformation.mtime.toGMTString()){
// 如果时间一致,返回304
ctx.status = 304;
} else {
ctx.set('Last-Modified', fileInformation.mtime.toGMTString());
let fileContent = await getFileContent();
ctx.body = fileContent;
}
});
验证方式跟 强缓存 一样。
4.2、etag
继续编辑main.js如下:
// 协商缓存路由-tag
KoaRouter.get('/getFileConsultTag', async function (ctx, next){
// 读取文件信息
const getFileInformation = () => {
return new Promise((resolve, reject) => {
fs.stat('src/file/b.txt', function (err, stat){
if (err){
return resolve('读取文件失败');
} else {
return resolve(stat);
}
});
})
}
// 读取文件内容
const getFileContent = () => {
return new Promise((resolve, reject) => {
fs.readFile('src/file/b.txt', 'utf-8', (err, data) => {
if (err){
resolve(err)
} else {
resolve(data)
}
});
});
}
let ifNoneMatch = ctx.header['if-none-match']; // 从浏览器端发送的请求中获取上一次的文件对应的hash
if (ifNoneMatch){
console.log('协商缓存里的标识:', ctx.header);
}
let fileContent = await getFileContent();
// 创建基于md5算法的hash对象
let md5HashObject = crypto.createHash('md5');
md5HashObject.update(fileContent);
let etag = `${md5HashObject.digest('hex')}`;
ctx.set('etag', etag);
ctx.set('Cache-Control', 'no-cache');
// 开始对比时间
if (ifNoneMatch === etag){
// 如果时间一致,返回304
ctx.status = 304;
} else {
ctx.body = fileContent;
}
});
验证方式跟 强缓存 一样。
现在对协商缓存的流程总结如下:
- 第一次向后端发送请求时,后端会在响应头里设置
last-modified或者etag字段。 - 因为在上一次的响应里存在
last-modified或者etag字段,所以发送下一次请求时,浏览器会自动带上if-modified-since(取值为上一次响应里的last-modified字段对应的值)或者if-none-match(取值为上一次响应里的etag字段对应的值)字段。 - 后端再次收到请求,会拿
if-modified-since字段 与 对应请求资源的最后修改时间进行比较,或者拿if-none-match字段与 对应请求资源的hash值进行对比,如果不相等,则返回最新资源;如果相等,则直接返回304,浏览器拿到304状态码后,从本地缓存里取出相应资源。 协商缓存命中后,不会再返回报文主体,只返回header部分,这是导致它响应时间缩短的原由。
五、最后
本次文章到这里就结束啦,下次再见......