nodejs篇-http緩存

緩存是一種保存資源副本並在下次請求時直接使用該副本的技術,經過複用之前獲取的資源,能夠顯着提升網站性能,下降服務器處理壓力,減小了等待時間和網絡流量。經過使用 HTTP緩存,變得更加響應性。

這篇文章會介紹三種緩存機制在nodejs中的實現,分別是:html

  • 強制緩存 Cache-Control/Expires
  • 對比緩存前端

    • Last-Modified/If-Modified-Since
    • Etag/If-None-Match

兩類緩存規則的不一樣

  • 強制緩存若是生效,不須要再和服務器發生交互,對比緩存無論是否生效,都須要與服務端發生交互。
  • 強制緩存優先級高於對比緩存強制緩存生效時,再也不執行對比緩存規則。

新建http服務

爲了方便測試,新建一個簡單的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

Expires是HTTP1.0的東西,如今默認瀏覽器均默認使用HTTP 1.1,因此它的做用基本忽略,咱們在響應頭header設置Expires,瀏覽器根據它的到期時間來決定是否使用緩存數據:緩存

res.setHeader("Expries",new Date(Date.now()+5*1000).toUTCString());

Cache-Control

Cache-Control是最重要的規則。常見的取值有private、public、no-cache、max-age,no-store,默認爲private。服務器

  • private 客戶端能夠緩存
  • public 能夠被任何中間人(好比中間代理、CDN等)緩存
  • max-age=xxx 緩存的內容將在 xxx 秒後失效(單位是秒)
  • no-cache 須要使用對比緩存來驗證緩存數據
  • no-store 全部內容都不會緩存,強制緩存對比緩存都不會觸發

能夠在handleRequest方法中添加給響應頭設置Cache-Control,在瀏覽器刷新查看效果:網絡

function handleRequest(err, statObj) {
    ...
    res.setHeader("Cache-Control","max-age=10");
    sendFile()
}

若是常常調試前端項目的開發人員,常常會把控制檯Disable cache給勾上,這裏記得必定要關掉它:性能

image.png

不出意外的話,能夠在Network的請求中看到信息:測試

Status Code: 200 OK (from disk cache)

對比緩存

對比緩存,服務器將文件的修改信息發送給客戶端(瀏覽器),客戶端在下次請求的時候順便帶上,而後服務端就能夠拿到上一次的修改信息,跟本地的文件修改信息作比較,告訴客戶端是否用緩存數據仍是用最新數據了網站

Last-Modified/If-Modified-Since 對比時間

經過statObjctime屬性能夠獲取文件的修改時間,將這個修改信息經過請求頭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/If-None-Match方式解決,也就是內容對比,不過Etag生成有必定的開銷,若是文件頻繁變化對服務器有額外壓力。

固然咱們不可能將內容都存在header裏面,這裏能夠經過crypto將文件內容加密成一串祕鑰,寫在headerEtag屬性中:

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)
  }

參考文章:
完全弄懂HTTP緩存機制及原理
HTTP 緩存
協商緩存

相關文章
相關標籤/搜索