1. 程式人生 > >node.js中實現http伺服器與瀏覽器之間的內容快取

node.js中實現http伺服器與瀏覽器之間的內容快取

一、快取的作用

1、減少了資料傳輸,節約流量。

2、減少伺服器壓力,提高伺服器效能。

3、加快客戶端載入頁面的速度。

 

二、快取的分類

1、強制快取,如果快取有效,則不需要與伺服器發生互動,直接使用快取。

2、對比快取,每次都需要與伺服器發生互動,對快取進行比較判斷是否可以使用快取。

 

三、通過使用 Last-Modified / If-Modified-Since 來進行快取判斷

1、Last-Modified 是伺服器向客戶端傳送的頭資訊,用於告訴客戶端資源的 最後修改時間,該資訊瀏覽器會儲存起來。

2、If-Modified-Since 是客戶端向伺服器傳送的頭資訊,當客戶端再次請求資源時,瀏覽器會帶上該資訊傳送給伺服器,伺服器通過該資訊來判斷資源是否過期。

3、如果沒有過期,則響應 304 表示 未更新,告訴瀏覽器使用儲存的快取。

4、如果過期了,則響應 200,返回最新的資源。

const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const util = require('util');
const mime = require('mime');

//建立http伺服器並監聽埠
let server = http.createServer();
server.listen(1234, '0.0.0.0', function () {
    console.log('開始監聽');
});

function sendFile(req, res, filePath, stats) {
    //設定檔案內容型別
    res.setHeader('Content-Type', mime.getType(filePath));
    //設定資源最後修改時間頭資訊
    res.setHeader('Last-Modified', stats.ctime.toGMTString());
    //通過管道將檔案資料傳送給客戶端
    fs.createReadStream(filePath).pipe(res);
}

server.on('request', function (req, res) {
    let {pathname} = url.parse(req.url, true);

    //獲取檔案真實路徑
    let filePath = path.join(__dirname, pathname);

    //判斷檔案是否存在
    fs.stat(filePath, function (err, stats) {
        if (err) {
            return res.end(util.inspect(err));
        }
        if (!stats.isFile()) {
            return res.end('is not file');
        }

        //獲取客戶端請求的If-Modified-Since頭資訊
        let ifModifiedSince = req.headers['if-modified-since'];
        if (ifModifiedSince) {
            //如果最後修改時間相同,說明該資源並未修改,直接響應 304,讓瀏覽器從快取中獲取資料。
            if (ifModifiedSince == stats.ctime.toGMTString()) {
                res.statusCode = 304;
                res.end();
            } else {
                sendFile(req, res, filePath, stats);
            }
        } else {
            sendFile(req, res, filePath, stats);
        }
    });
});

通過最後修改時間判斷快取是否可用,並不是很精確,有如下幾個問題:

1、Last-Modified 只精確到秒,秒以下的時間修改,將無法準確判斷。

2、檔案最後修改時間變了,但 內容並沒有發生改變。

3、檔案存在於多個 CDN 上,那該檔案的最後修改時間是不一樣的。

 

四、通過 ETag / If-None-Match 進行判斷

ETag 表示 實體標籤,將內容通過 hash 演算法生成一段字串,用以標識資源,如果資源發生變化,則 ETag 也會變化。

ETag 是伺服器生成的,傳送給客戶端的。

1、客戶端請求資源,伺服器根據資源生成ETag,傳送給客戶端。瀏覽器會儲存該資訊。

2、當客戶端再次請求時,瀏覽器會發送 If-None-Match 給伺服器,值為第1步儲存的資訊,伺服器通過該資訊進行判斷,資源是否修改過。

3、如果沒有修改過,則響應 304 未更新,告訴瀏覽器使用儲存的快取。

4、如果修改過,則響應 200,返回最新資源。

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

//建立http伺服器並監聽埠
let server = http.createServer();
server.listen(1234, '0.0.0.0', function () {
    console.log('開始監聽');
});

function sendFile(req, res, filePath, eTag) {
    //設定檔案內容型別
    res.setHeader('Content-Type', mime.getType(filePath));
    //設定ETag頭資訊
    res.setHeader('ETag', eTag);
    //通過管道將檔案資料傳送給客戶端
    fs.createReadStream(filePath).pipe(res);
}

server.on('request', function (req, res) {
    let {pathname} = url.parse(req.url, true);

    //獲取檔案真實路徑
    let filePath = path.join(__dirname, pathname);

    //判斷檔案是否存在
    fs.stat(filePath, function (err, stats) {
        if (err) {
            return res.end(util.inspect(err));
        }
        if (!stats.isFile()) {
            return res.end('is not file');
        }

        //獲取客戶端請求的If-None-Match頭資訊
        let ifNoneMatch = req.headers['if-none-match'];
        //建立可讀流
        let rs = fs.createReadStream(filePath);
        //建立md5演算法
        let md5 = crypto.createHash('md5');

        rs.on('data', function (data) {
            md5.update(data);
        });
        rs.on('end', function () {
            let eTag = md5.digest('hex');
            if (ifNoneMatch) {
                //判斷eTag與客戶端傳送過來的If-None-Match是否相等
                if (ifNoneMatch == eTag) {
                    res.statusCode = 304;
                    res.end();
                } else {
                    sendFile(req, res, filePath, eTag);
                }
            } else {
                sendFile(req, res, filePath, eTag);
            }
        });
    });
});

  

五、讓瀏覽器在快取有效期內不用發請求

Expires 是http1.0的內容,用於設定快取的有效期,在有效期內瀏覽器直接從瀏覽器快取中獲取資料。

Cache-Control 與Expires作用一樣,是http1.1的內容,用於指明當前資源的有效期,優先順序高於Expires。

Cache-Control可以設定的值 :

1、private 客戶端可以快取

2、public  客戶端和代理伺服器都可以快取

3、max-age=10 快取內容在10秒後失效

4、no-cache 使用對比快取驗證,強制向伺服器驗證

5、no-store 內容都不快取,強制快取和對比快取都不會觸發

const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const util = require('util');
const mime = require('mime');

//建立http伺服器並監聽埠
let server = http.createServer();
server.listen(1234, '0.0.0.0', function () {
    console.log('開始監聽');
});

function sendFile(req, res, filePath, stats) {
    //設定檔案內容型別
    res.setHeader('Content-Type', mime.getType(filePath));

    //設定快取失效時間60秒
    res.setHeader('Expires', new Date(Date.now() + 60 * 1000).toUTCString());
    //設定快取失效時間60秒
    res.setHeader('Cache-Control', 'max-age=60');

    //通過管道將檔案資料傳送給客戶端
    fs.createReadStream(filePath).pipe(res);
}

server.on('request', function (req, res) {
    let {pathname} = url.parse(req.url, true);

    //獲取檔案真實路徑
    let filePath = path.join(__dirname, pathname);

    //判斷檔案是否存在
    fs.stat(filePath, function (err, stats) {
        if (err) {
            return res.end(util.inspect(err));
        }
        if (!stats.isFile()) {
            return res.end('is not file');
        }
        sendFile(req, res, filePath, stats)
    });
});