浏览器缓存
1. 为什么浏览器需要缓存
- 节约网络资源
- 加快页面访问速度
2. 缓存规则:
所有的缓存都是基于一套规则来决定什么时候使用缓存中的副本提供服务, 新鲜度和校验值两个维度来规定浏览器是否可以直接使用缓存中的副本,还是需要去源服务器获取更新的版本。
-
新鲜度(过期机制):也就是缓存副本有效期。一个缓存副本必须满足以下条件,满足一个条件即可,浏览器会认为它是有效的,足够新的:
- 含有完整的过期时间控制头信息(HTTP协议报头),并且仍在有效期内;
- 浏览器已经使用过这个缓存副本,并且在一个会话中已经检查过新鲜度;
-
校验值(验证机制): 服务器返回资源的时候有时在控制头信息带上这个资源的实体标签Etag(Entity Tag) 它可以用来作为浏览器再次请求过程的校验标识。如过发现校验标识不匹配,说明资源已经被修改或过期,浏览器需求重新获取资源内容。
3.1 强制缓存
Cache-Control(1.1) 和 Expires(1.0)
let http = require('http');
let path = require('path');
let fs = require('fs');
let { promisify} = require('util');
let stat = promisify(fs.stat);//获取文件状态信息
// 静态服务器
let url = require('url'); // 专门用来处理url路径的
let server = http.createServer(async function (req,res) {
let { pathname,query} = url.parse(req.url,true); // 就是将query转化成对象
let readPath = path.join(__dirname, 'public', pathname);//文件绝对路径
try {
let statObj = await stat(readPath);
// 和客户端说 10m内走缓存
res.setHeader('Cache-Control','max-age=10'); //1.1
res.setHeader('Expires',new Date(Date.now()+10*1000).toGMTString()); //1.0
if (statObj.isDirectory()) {
let p = path.join(readPath, 'index.html');
await stat(p);//判断有没有这文件,如果读不到,就报错,就被catch到
// 如果当前目录下有html那么就返回这个文件
fs.createReadStream(p).pipe(res);
} else {
// 是文件 读取对应的文件直接返回即可
fs.createReadStream(readPath).pipe(res);
}
}catch(e){
res.statusCode = 404;
res.end(`Not found`);
}
}).listen(3000);
复制代码
缺点:假如10s内我们的index或者css内容变了,还是走的缓存,没法及时得到更新。
3.2 对比缓存(Last-Modified => if-modified-since)
let server = http.createServer(async function (req,res) {
let { pathname,query} = url.parse(req.url,true);
let readPath = path.join(__dirname, 'public', pathname);
try {
let statObj = await stat(readPath);
res.setHeader('Cache-Control','no-cache');
if (statObj.isDirectory()) {
let p = path.join(readPath, 'index.html');
let statObj = await stat(p);
res.setHeader('Last-Modified', statObj.ctime.toGMTString());//服务器设置文件最后修改时间
if (req.headers['if-modified-since'] === statObj.ctime.toGMTString()){//客户端请求的时间
res.statusCode = 304;
res.end();
return; // 走缓存
}
fs.createReadStream(p).pipe(res);
} else {
res.setHeader('Last-Modified', statObj.ctime.toGMTString());
if (req.headers['if-modified-since'] === statObj.ctime.toGMTString()) {
res.statusCode = 304;
res.end();
return; // 走缓存
}
fs.createReadStream(readPath).pipe(res);
}
}catch(e){
res.statusCode = 404;
res.end(`Not found`);
}
}).listen(3000);
复制代码
3.3 对比缓存(Etag => if-none-match)
let http = require('http');
let path = require('path');
let fs = require('fs');
let { promisify} = require('util');
let stat = promisify(fs.stat);
let url = require('url');
let crypto = require('crypto');
let server = http.createServer(async function (req,res) {
let { pathname,query} = url.parse(req.url,true);
let readPath = path.join(__dirname, 'public', pathname);
try {
let statObj = await stat(readPath);
res.setHeader('Cache-Control','no-cache');
if (statObj.isDirectory()) {
let p = path.join(readPath, 'index.html');
let statObj = await stat(p);
// 我要根据文件内容 生成一个md5的摘要 最耗性能 ,给实体加一个标签
let rs = fs.createReadStream(p);//读流
let md5 = crypto.createHash('md5'); // 不能写完相应体在写头
let arr = [];
rs.on('data',function (data) {
md5.update(data);//读一点加密一点
arr.push(data);//不能res.write(),下面setHeader还没完成
});
rs.on('end',function () {
let r = md5.digest('base64');
res.setHeader('Etag', r);//服务器设置Etag 和 客户端 if-none-match最对比
if (req.headers['if-none-match'] === r ){
res.statusCode = 304;
res.end();
return;
}
res.end(Buffer.concat(arr));
})
} else {
let rs = fs.createReadStream(readPath);
let md5 = crypto.createHash('md5'); // 不能写完相应体在写头
let arr = [];
rs.on('data', function (data) {
md5.update(data);
arr.push(data);
});
rs.on('end', function () {
let r = md5.digest('base64');
res.setHeader('Etag', r);
if (req.headers['if-none-match'] === r) {
res.statusCode = 304;
res.end();
return;
}
res.end(Buffer.concat(arr));
})
}
}catch(e){
res.statusCode = 404;
res.end(`Not found`);
}
}).listen(3000);
复制代码
4 不走缓存
- 浏览器缓存的请求基本是get方法,post很少用缓存
- 设置了Cache-Control:no-cache, pragma:no-cache, Cache-Control:max-age=0