目的: 搞懂快取的概念
閱讀時長: 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());
複製程式碼
能看到在響應頭裡面設定的值。
這樣的話,5s內再請求這個資源,就會從快取中取。
這種方式有明顯的缺陷,因為如果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);
複製程式碼
這樣的話,只要資源沒有改變,就始終從快取中取。
這種方式同樣有缺陷,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));
})
}
複製程式碼
下圖會發現,檔案請求statuscode為304
這種方式是非常安全的,但是,如果檔案太大的話,每次都要讀取檔案內容就會很耗效能。
總結:
現在一般採用的方式是,用last-modified + etag。且e-tag不使用檔案內容去生成base-64,而是使用檔案的大小生成base-64
這裡就不再贅述。
感謝閱讀!
我是海明月,前端小學生。