緩存是一種保存資源副本並在下次請求時直接使用該副本的技術,經過複用之前獲取的資源,能夠顯着提升網站性能,下降服務器處理壓力,減小了等待時間和網絡流量。經過使用 HTTP緩存,變得更加響應性。
這篇文章會介紹三種緩存機制在nodejs
中的實現,分別是:html
Cache-Control/Expires
對比緩存前端
Last-Modified/If-Modified-Since
Etag/If-None-Match
強制緩存
若是生效,不須要再和服務器發生交互,對比緩存
無論是否生效,都須要與服務端發生交互。強制緩存
優先級高於對比緩存
,強制緩存
生效時,再也不執行對比緩存
規則。爲了方便測試,新建一個簡單的http服務進行調試:node
const http = require("http") const url = require("url") const mime = require("mime") const fs = require("fs") const server = http.createServer((req, res) => { const { pathname } = url.parse(req.url, true) const abspath = process.cwd() + pathname fs.stat(abspath, handleRequest) // 判斷是文件仍是文件夾 function handleRequest(err, statObj) { if (err || statObj.isDirectory()) return sendError(err) sendFile() } // 響應錯誤請求 function sendError(error) { res.statusCode = 404 res.end(`Not Found \r\n ${error.toString()}`) } // 響應文件請求 function sendFile() { res.setHeader("Content-Type", mime.getType(abspath) + ";charset=utf-8") fs.createReadStream(abspath).pipe(res) } }) server.listen(3000, () => console.log("serving http://127.0.0.1:3000"))
強制緩存指的是在緩存數據未失效的狀況下,能夠直接使用緩存數據,瀏覽器經過服務器響應的header
獲取緩存規則信息。對於強制緩存,響應頭header使用Cache-Control/Expires
來標明失效規則。瀏覽器
Expires
是HTTP1.0的東西,如今默認瀏覽器均默認使用HTTP 1.1,因此它的做用基本忽略,咱們在響應頭header設置Expires
,瀏覽器根據它的到期時間來決定是否使用緩存數據:緩存
res.setHeader("Expries",new Date(Date.now()+5*1000).toUTCString());
Cache-Control
是最重要的規則。常見的取值有private、public、no-cache、max-age,no-store,默認爲private。服務器
對比緩存
來驗證緩存數據強制緩存
,對比緩存
都不會觸發能夠在handleRequest
方法中添加給響應頭設置Cache-Control
,在瀏覽器刷新查看效果:網絡
function handleRequest(err, statObj) { ... res.setHeader("Cache-Control","max-age=10"); sendFile() }
若是常常調試前端項目的開發人員,常常會把控制檯Disable cache
給勾上,這裏記得必定要關掉它:性能
不出意外的話,能夠在Network
的請求中看到信息:測試
Status Code: 200 OK (from disk cache)
對比緩存,服務器將文件的修改信息發送給客戶端(瀏覽器),客戶端在下次請求的時候順便帶上,而後服務端就能夠拿到上一次的修改信息,跟本地的文件修改信息作比較,告訴客戶端是否用緩存數據仍是用最新數據了網站
經過statObj
的ctime
屬性能夠獲取文件的修改時間,將這個修改信息經過請求頭Last-Modified
屬性發送給瀏覽器:
const serverTime = statObj.ctime.toUTCString() res.setHeader("Last-Modified", serverTime)
下次客戶端請求的時候,也會在請求頭經過if-modified-since
帶上:
const clientTime = req.headers["if-modified-since"]
修改handleRequest
方法以下:
function handleRequest(err, statObj) { if (err || statObj.isDirectory()) return sendError(err) const clientTime = req.headers["if-modified-since"] const serverTime = statObj.ctime.toUTCString() // 若是本地的文件修改時間和瀏覽器返回的修改時間相同,則使用緩存的數據,返回304 if (clientTime === serverTime) { res.statusCode = 304 return res.end() } res.setHeader("Last-Modified", serverTime) res.setHeader("Cache-Control", "no-cache") // 對比緩存驗證緩存數據 sendFile() }
不過這種方式有兩個弊端:
上面提到的兩個弊端,能夠經過Etag/If-None-Match
方式解決,也就是內容對比,不過Etag
生成有必定的開銷,若是文件頻繁變化對服務器有額外壓力。
固然咱們不可能將內容都存在header
裏面,這裏能夠經過crypto
將文件內容加密成一串祕鑰,寫在header
的Etag
屬性中:
const crypto = require("crypto"); ... const md5 = crypto.createHash("md5"); // md5加密 const rs = fs.createReadStream(abspath); // 建立可讀流 const data = []; rs.on("data",(buffer)=>{ md5.update(buffer); // 讀取文件內容過程當中加密 data.push(buffer); }); rs.on("end",()=>{ const serverMatch = md5.digest("base64"); // 加密後的文件 const clientMatch = req.headers["if-none-match"]; // 客戶端下次請求會帶上serverMatch if(clientMatch === serverMatch){ // 對比文件內容是否相同 res.statusCode = 304; return res.end(null); } // 設置 ETag res.setHeader("ETag", serverMatch) res.end(Buffer.concat(data)); })
咱們能夠在業務中根據本身的須要,設置對應的緩存方式,這裏經過寫個通用方法,將這三種模式整合起來:
function cache(statObj) { // 強制緩存 res.setHeader("Cache-Control", "max-age=60") // 時間對比 let lastModified = statObj.ctime.toUTCString() let modifiedSince = req.headers["if-modified-since"] res.setHeader("Last-Modified", lastModified) if (modifiedSince !== lastModified) return false // 內容對比 let etag = statObj.size + "" let noneMatch = req.headers["if-none-match"] res.setHeader("ETag", etag) if (etag !== noneMatch) return false return true } ... if(cache(statObj)){ res.statusCode = 304 return res.end(null) }