瀏覽器快取你瞭解麼?

Jeffywin發表於2019-02-28

瀏覽器快取

1. 為什麼瀏覽器需要快取

  • 節約網路資源
  • 加快頁面訪問速度

2. 快取規則:

所有的快取都是基於一套規則來決定什麼時候使用快取中的副本提供服務, 新鮮度和校驗值兩個維度來規定瀏覽器是否可以直接使用快取中的副本,還是需要去源伺服器獲取更新的版本。

  • 新鮮度(過期機制):也就是快取副本有效期。一個快取副本必須滿足以下條件,滿足一個條件即可,瀏覽器會認為它是有效的,足夠新的:

    1. 含有完整的過期時間控制頭資訊(HTTP協議報頭),並且仍在有效期內;
    2. 瀏覽器已經使用過這個快取副本,並且在一個會話中已經檢查過新鮮度;
  • 校驗值(驗證機制): 伺服器返回資源的時候有時在控制頭資訊帶上這個資源的實體標籤Etag(Entity Tag) 它可以用來作為瀏覽器再次請求過程的校驗標識。如過發現校驗標識不匹配,說明資源已經被修改或過期,瀏覽器需求重新獲取資源內容。

3.1 強制快取

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

3.2 對比快取(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 相比沒有變化

3.3 對比快取(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 一樣,所以走快取

4 不走快取

  • 瀏覽器快取的請求基本是get方法,post很少用快取
  • 設定了Cache-Control:no-cache, pragma:no-cache, Cache-Control:max-age=0

相關文章