目的: 搞懂缓存的概念
阅读时长: 5 分钟
缓存的处理方式一般有以下两种:
- 强制缓存
- 比较缓存
1. 先来说说强制缓存
let http = require('http');
let mime = require('mime');
let url = require('url');
let path = require('path');
let fs = require('fs');
http.createServer(function(req, res) {
let { pathname } = url.parse(req.url, true);
res.setHeader('Cache-Control', 'max-age=5');
res.setHeader('Expires', new Date(Date.now() + 10000).toGMTString());
let abs = path.join(__dirname, pathname);
fs.stat(path.join(__dirname, pathname), (err, stat) => {
if(err) {
res.sstatusCode = 404;
res.end('NotFound');
return;
}
if(stat.isFile()) {
fs.createReadStream(abs).pipe(res);
}
})
}).listen(8080);
复制代码
上面的代码主要是用nodejs开启了一个服务,然后设置响应头
res.setHeader('Cache-Control', 'max-age=10000');
res.setHeader('Expires', new Date(Date.now() + 10000).toGMTString());
复制代码
能看到在响应头里面设置的值。
这样的话,5s内再请求这个资源,就会从缓存中取。
这种方式有明显的缺陷,因为如果5s之内如果文件改变了呢?
2. 比较缓存之last-modify
下面对之前的代码进行修改
let http = require('http');
let mime = require('mime');
let url = require('url');
let path = require('path');
let fs = require('fs');
http.createServer(function(req, res) {
let { pathname } = url.parse(req.url, true);
/**
* @注意: 强制缓存部分代码注释掉
*/
// res.setHeader('Cache-Control', 'max-age=5');
// res.setHeader('Expires', new Date(Date.now() + 5000).toGMTString());
let abs = path.join(__dirname, pathname);
fs.stat(path.join(__dirname, pathname), (err, stat) => {
if(err) {
res.statusCode = 404;
res.end('NotFound');
return;
}
if(stat.isFile()) {
/**
* @ 注意: 以下代码为新增代码,获取当前时间,并设置响应头Last-modified
* 如果下次请求的is-modified-since 和 当前的ctime相同
* 说明在此期间,文件没有被修改过
*/
let ctime = stat.ctime.toUTCString()
res.setHeader('Last-Modified', ctime);
if(req.headers['if-modified-since'] == ctime) {
console.log(1)
res.statusCode = 304;
res.end();
return;
}
fs.createReadStream(abs).pipe(res);
}
})
}).listen(8081);
复制代码
这样的话,只要资源没有改变,就始终从缓存中取。
这种方式同样有缺陷,last-modified只能精确到秒,如果在1s中多次改变数据呢?
继续看文件内容比较方法处理缓存e-tag
let http = require('http');
let mime = require('mime');
let url = require('url');
let path = require('path');
let fs = require('fs');
let crypto = require('crypto');
http.createServer(function(req, res) {
let { pathname } = url.parse(req.url, true);
let abs = path.join(__dirname, pathname);
fs.stat(path.join(__dirname, pathname), (err, stat) => {
if(err) {
res.statusCode = 404;
res.end('NotFound');
return;
}
if(stat.isFile()) {
let md5 = crypto.createHash('md5');
let rs = fs.createReadStream(abs);
let arr = [];
rs.on('data', function(data) {
md5.update(data);
arr.push(data);
})
rs.on('end', function() {
let etag = md5.digest('base64');
res.setHeader('Etag', etag);
res.end(Buffer.concat(arr));
})
}
})
}).listen(8081);
复制代码
我们来看看原理是什么?
if(stat.isFile()) {
// 通过crypto创建一个md5
let md5 = crypto.createHash('md5');
let rs = fs.createReadStream(abs);
let arr = [];
rs.on('data', function(data) {
// 读取数据的时候,用md5更新
md5.update(data);
arr.push(data);
})
rs.on('end', function() {
let etag = md5.digest('base64');
// 生成base-64,并设置响应头,Etag
// 下次请求的时候,用etag和带过来的if-None-Match进行比较
if(req.headers['if-none-match'] === etag) {
res.statusCode = 304;
res.end();
return;
}
res.setHeader('Etag', etag);
res.end(Buffer.concat(arr));
})
}
复制代码
这种方式是非常安全的,但是,如果文件太大的话,每次都要读取文件内容就会很耗性能。
总结:
现在一般采用的方式是,用last-modified + etag。且e-tag不使用文件内容去生成base-64,而是使用文件的大小生成base-64
这里就不再赘述。
感谢阅读!
我是海明月,前端小学生。