5分鐘瞭解快取的概念

海明月發表於2019-04-27

目的: 搞懂快取的概念

閱讀時長: 5 分鐘

快取的處理方式一般有以下兩種:

  • 強制快取
  • 比較快取

1. 先來說說強制快取

let http = require('http');
let mime = require('mime');
let url = require('url');
let path = require('path');
let fs = require('fs');

http.createServer(function(req, res) {
  let { pathname } = url.parse(req.url, true);
  res.setHeader('Cache-Control', 'max-age=5');
  res.setHeader('Expires', new Date(Date.now() + 10000).toGMTString());

  let abs = path.join(__dirname, pathname);
  fs.stat(path.join(__dirname, pathname), (err, stat) => {
    if(err) {
      res.sstatusCode = 404;
      res.end('NotFound');
      return;
    }
    if(stat.isFile()) {
      fs.createReadStream(abs).pipe(res);
    }
  })
}).listen(8080);
複製程式碼

上面的程式碼主要是用nodejs開啟了一個服務,然後設定響應頭

  res.setHeader('Cache-Control', 'max-age=10000');
  res.setHeader('Expires', new Date(Date.now() + 10000).toGMTString());
複製程式碼

能看到在響應頭裡面設定的值。

5分鐘瞭解快取的概念

這樣的話,5s內再請求這個資源,就會從快取中取。

5分鐘瞭解快取的概念

這種方式有明顯的缺陷,因為如果5s之內如果檔案改變了呢?

2. 比較快取之last-modify

下面對之前的程式碼進行修改


let http = require('http');
let mime = require('mime');
let url = require('url');
let path = require('path');
let fs = require('fs');

http.createServer(function(req, res) {
  let { pathname } = url.parse(req.url, true);
  /**
  * @注意: 強制快取部分程式碼註釋掉
  */
  
  // res.setHeader('Cache-Control', 'max-age=5');
  // res.setHeader('Expires', new Date(Date.now() + 5000).toGMTString());

  let abs = path.join(__dirname, pathname);
  fs.stat(path.join(__dirname, pathname), (err, stat) => {
    if(err) {
      res.statusCode = 404;
      res.end('NotFound');
      return;
    }
    if(stat.isFile()) {
    /**
    * @ 注意: 以下程式碼為新增程式碼,獲取當前時間,並設定響應頭Last-modified
    * 如果下次請求的is-modified-since 和 當前的ctime相同
    * 說明在此期間,檔案沒有被修改過
    */
      let ctime = stat.ctime.toUTCString()
      res.setHeader('Last-Modified', ctime);
      if(req.headers['if-modified-since'] == ctime) {
        console.log(1)
        res.statusCode = 304;
        res.end();
        return;
      }

      fs.createReadStream(abs).pipe(res);
    }
  })
}).listen(8081);
複製程式碼

5分鐘瞭解快取的概念

這樣的話,只要資源沒有改變,就始終從快取中取。

5分鐘瞭解快取的概念

這種方式同樣有缺陷,last-modified只能精確到秒,如果在1s中多次改變資料呢?

繼續看檔案內容比較方法處理快取e-tag

let http = require('http');
let mime = require('mime');
let url = require('url');
let path = require('path');
let fs = require('fs');
let crypto = require('crypto');

http.createServer(function(req, res) {
  let { pathname } = url.parse(req.url, true);

  let abs = path.join(__dirname, pathname);
  fs.stat(path.join(__dirname, pathname), (err, stat) => {
    if(err) {
      res.statusCode = 404;
      res.end('NotFound');
      return;
    }
    if(stat.isFile()) {
    
      let md5 = crypto.createHash('md5');
      let rs = fs.createReadStream(abs);
      let arr = [];
      rs.on('data', function(data) {
        md5.update(data);
        arr.push(data);
      })
      rs.on('end', function() {
        let etag = md5.digest('base64');
        res.setHeader('Etag', etag);
        res.end(Buffer.concat(arr));
      })
    }
  })
}).listen(8081);
複製程式碼

我們來看看原理是什麼?

if(stat.isFile()) {
      // 通過crypto建立一個md5
      let md5 = crypto.createHash('md5');
      let rs = fs.createReadStream(abs);
      let arr = [];
      rs.on('data', function(data) {
        // 讀取資料的時候,用md5更新
        md5.update(data);
        arr.push(data);
      })
      rs.on('end', function() {
        let etag = md5.digest('base64');
        // 生成base-64,並設定響應頭,Etag
        // 下次請求的時候,用etag和帶過來的if-None-Match進行比較
        if(req.headers['if-none-match'] === etag) {
          res.statusCode = 304;
          res.end();
          return;
        }
        res.setHeader('Etag', etag);
        res.end(Buffer.concat(arr));
      })
    }

複製程式碼

5分鐘瞭解快取的概念
下圖會發現,檔案請求statuscode為304
5分鐘瞭解快取的概念

這種方式是非常安全的,但是,如果檔案太大的話,每次都要讀取檔案內容就會很耗效能。


總結:

現在一般採用的方式是,用last-modified + etag。且e-tag不使用檔案內容去生成base-64,而是使用檔案的大小生成base-64

這裡就不再贅述。

感謝閱讀!

我是海明月,前端小學生。

相關文章