全部的緩存都是基於一套規則來決定何時使用緩存中的副本提供服務, 新鮮度和校驗值兩個維度來規定瀏覽器是否能夠直接使用緩存中的副本,仍是須要去源服務器獲取更新的版本。css
新鮮度(過時機制):也就是緩存副本有效期。一個緩存副本必須知足如下條件,知足一個條件便可,瀏覽器會認爲它是有效的,足夠新的:html
校驗值(驗證機制): 服務器返回資源的時候有時在控制頭信息帶上這個資源的實體標籤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內容變了,仍是走的緩存,無法及時獲得更新。 bash
咱們能夠看到設置的Cache-Control 和 Expires 10秒以內從新刷新瀏覽器,css走了緩存 from memory cacheslet 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 相比沒有變化
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 同樣,因此走緩存