浏览器缓存
规则: 所有的缓存都是基于一套规则来决定什么时候使用缓存中的副本提供服务, 新鲜度和校验值两个维度来规定浏览器是否可以直接使用缓存中的副本, 还是需要去源服务器获取更新的版本.
新鲜度 (过期机制): 也就是缓存副本有效期. 一个缓存副本必须满足以下条件, 满足一个条件即可, 浏览器会认为它是有效的, 足够新的:
含有完整的过期时间控制头信息 (HTTP 协议报头), 并且仍在有效期内;
浏览器已经使用过这个缓存副本, 并且在一个会话中已经检查过新鲜度;
校验值 (验证机制): 服务器返回资源的时候有时在控制头信息带上这个资源的实体标签 Etag(Entity Tag) 它可以用来作为浏览器再次请求过程的校验标识. 如过发现校验标识不匹配, 说明资源已经被修改或过期, 浏览器需求重新获取资源内容.
强制缓存
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 内容变了, 还是走的缓存, 没法及时得到更新.
我们可以看到设置的 Cache-Control 和 Expires
10 秒之内重新刷新浏览器, css 走了缓存 from memory caches
对比缓存
根据时间 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);
复制代码
如上图所示, 假如 index.html 没有修改过, 返回 304, 走对比缓存
如上图所以 Last-Modified => If-modified-since 相比没有变化
根据文件内容 (最耗性能) 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);
复制代码
如上图所示, 由于文件内容没有改变, If-none-match 和 Etag 一样, 所以走缓存
来源: https://juejin.im/post/5b728da2e51d45662757d804