HTTP快取瞭解一下

acdseen發表於2018-04-24

快取

快取作用

  • 減少了冗餘的資料傳輸,節省了網費。
  • 減少了伺服器的負擔, 大大提高了網站的效能
  • 加快了客戶端載入網頁的速度

快取分類

強制快取

強制快取:說白了就是第一次請求資料時,服務端將資料和快取規則一併返回,下一次請求時瀏覽器直接根據快取規則進行判斷,有就直接讀快取資料庫,不用連線伺服器;沒有,再去找伺服器。

HTTP快取瞭解一下

對比快取

  • 對比快取,顧名思義,需要進行比較判斷是否可以使用快取。
  • 瀏覽器第一次請求資料時,伺服器會將快取標識與資料一起返回給客戶端,客戶端將二者備份至快取資料庫中。
  • 再次請求資料時,客戶端將備份的快取標識傳送給伺服器,伺服器根據快取標識進行判斷,判斷成功後,返回304狀態碼,通知* 客戶端比較成功,可以使用快取資料。

HTTP快取瞭解一下

請求流程

第一次請求,此時沒有快取

HTTP快取瞭解一下

第二次請求

HTTP快取瞭解一下

從上張圖我們可以看到,判斷快取是否可用,有兩種方式

  • ETag是實體標籤的縮寫,根據實體內容生成的一段hash字串,可以標識資源的狀態。當資源發生改變時,ETag也隨之發生變化。ETag是Web服務端產生的,然後發給瀏覽器客戶端。
  • Last-Modified是此資源的最後修改時間,
    • 如果客戶端在請求到的資源中發現實體首部裡有Last-Modified宣告,再次請求就會在頭裡帶上if-Modified-Since欄位
    • 服務端收到請求後發現if-Modified-Since欄位則與被請求資源的最後修改時間進行對比

說了這麼多,不如直接來實現一下快取

快取的實現

通過最後修改時間來判斷快取是否可用

let http = require('http');
let url = require('url');
let path = require('path');
let fs = require('fs');
let mime = require('mime');//用來生成MimeType
let app = http.createServer((req, res) => {
    // 根據url獲取客戶端要請求的檔案路徑
    let { parsename } = url.parse(req.url);
    let p = path.join(__dirname, 'public', '.' + pathname);
    // fs.stat()用來讀取檔案資訊,檔案最後修改時間就是stat.ctime
    fs.stat(p, (err, stat) => {
        if (!err) {
            let since = req.headers['if-modified-since'];//客戶端發來的檔案最後修改時間
            if (since) {
                if (since === stat.ctime.toUTCString()) {//最後修改時間相等,讀快取
                    res.statusCode = 304;
                    res.end();
                } else {
                    sendFile(req, res, p, stat);//最後修改時間不相等,返回新內容
                }
            } else {
                sendError(res);
            }
        }
    })
})
function sendError(res) {
    res.statusCode = 404;
    res.end();
}
function sendFile(req, res, p, stat) {
    res.setHeader('Cache-Control', 'no-cache');// 設定通用首部欄位 控制快取行為
    res.setHeader('Last-Modified', stat.ctime.toUTCString());// 實體首部欄位 資源最後修改時間
    res.setHeader('Content-Type', mime.getType(p) + ';charset=utf8')
    fs.createReadStream(p).pipe(res);
}
app.listen(3000, () => {
    console.log('server is starting on port 3000');
});
複製程式碼

最後修改時間存在問題:
1. 某些伺服器不能精確得到檔案的最後修改時間, 這樣就無法通過最後修改時間來判斷檔案是否更新了。
2. 某些檔案的修改非常頻繁,在秒以下的時間內進行修改. Last-Modified只能精確到秒。
3. 一些檔案的最後修改時間改變了,但是內容並未改變。 我們不希望客戶端認為這個檔案修改了。
4. 如果同樣的一個檔案位於多個CDN伺服器上的時候內容雖然一樣,修改時間不一樣。

通過ETag來判斷快取是否可用

ETag就是根據檔案內容來判斷,說白了就是採用MD5(md5並不叫加密演算法,它不可逆,應該叫摘要演算法)產生資訊摘要,用摘要來進行比對。

let http = require('http');
let url = require('url');
let path = require('path');
let fs = require('fs');
let mime = require('mime');
// crypto是node.js中實現加密和解密的模組 具體詳解請自行了解
let crypto = require('crypto');
let app = http.createServer((req, res) => {
    // 根據url獲取客戶端要請求的檔案路徑
    let { parsename } = url.parse(req.url);
    let p = path.join(__dirname, 'public', '.' + pathname);
    // fs.stat()用來讀取檔案資訊,檔案最後修改時間就是stat.ctime
    fs.stat(p, (err, stat) => {
        let md5 = crypto.createHash('md5');//建立md5物件
        let rs = fs.createReadStream(p);
        rs.on('data', function (data) {
            md5.update(data);
        });
        rs.on('end', () => {
            let r = md5.digest('hex'); // 對檔案進行md5加密
            // 下次就拿最新檔案的加密值 和客戶端請求來比較
            let ifNoneMatch = req.headers['if-none-match'];
            if (ifNoneMatch) {
                if (ifNoneMatch === r) {
                    res.statusCode = 304;
                    res.end();
                } else {
                    sendFile(req, res, p, r);
                }
            } else {
                sendFile(req, res, p, r);
            }
        });
    })
});
function sendError(res) {
    res.statusCode = 404;
    res.end();
}
function sendFile(req, res, p, stat) {
    res.setHeader('Cache-Control', 'no-cache');// 設定通用首部欄位 控制快取行為
    res.setHeader('Etag', r);// 響應首部欄位 資源的匹配資訊
    res.setHeader('Content-Type', mime.getType(p) + ';charset=utf8')
    fs.createReadStream(p).pipe(res);
}
app.listen(3000, () => {
    console.log('server is starting on port 3000');
});
複製程式碼

最後

本人水平有限,有不足之處,望大家指出改正。

相關文章