聊聊web快取那些事!

xlei1123發表於2018-07-23

在說快取那些事之前,必須對http常用的狀態碼有一些簡單的瞭解:

  • 200 請求已成功,請求所希望的響應頭或資料體將隨此響應返回。出現此狀態碼是表示正常狀態
  • 304 Not Modified 強制快取
  • 當然還有很多其他的狀態碼,以上200 和 304是本篇文章重點講到的。

為什麼要快取?

  1. 減少響應延遲:當伺服器發現檔案沒有更新直接返回304狀態碼,告訴瀏覽器直接讀快取就好了,不需要傳輸資原始檔;
  2. 減少網路頻寬消耗:當副本被重用時會減低客戶端的頻寬消耗;客戶可以節省頻寬費用,控制頻寬的需求的增長並更易於管理。
  3. 如果設定了Cache-Control,expires這樣的之類,在有效時間之內不需要再次向伺服器傳送請求而是直接讀取快取(注意這樣情況的狀態碼是200 也是強制快取的一種

怎麼快取

其實就是通過設定http的頭資訊來進行設定各種快取的

  1. Cache-Control
//HTTP 1.1 引入的
//Cache-Control: no-cache/max-age=600
let http = require('http')
let url = require('url')
let util = require('util')
let fs = require('mz/fs')
let stat = util.promisify(fs.stat);
let path = require('path');
let p = path.resolve(__dirname);
http.createServer(async function(req, res) {
    let {pathname} = url.parse(req.url);
    let realPath = path.join(p, pathname);
    console.log(realPath)
    try{
        let statObj = await fs.stat(realPath);
        console.log(statObj)
        res.setHeader('Cache-Control','max-age=10')  //強制快取 10s內不需要再次請求伺服器
        //res.setHeader('Cache-Control','no-cache')
        res.setHeader('Content-Type',require('mime').getType(realPath)+';charset=utf8')
       fs.createReadStream(realPath).pipe(res)
    }catch(e) {
        res.statusCode = 404;
        res.end('404')
    }
}).listen(3000)

我們請求一個本地的檔案
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    
    <img src="/agree.png"/>
</body>
</html>
//在瀏覽器開啟localhost:3000/index.html
//可以看到狀態碼是200,這個200有兩種情況一種是直接讀取快取的 
//1. (from memory cache) 快取在記憶體當中;(from disk cache) 快取在硬碟中
//2. 真的是從伺服器中拉取過來的

複製程式碼
  1. expires
//Expires:Sun, 22 Jul 2018 02:43:42 GMT
//備註:如果Cache-Control和Expires同時存在,Cache-Control說了算
res.setHeader('Expires', new Date(Date.now() + 10*1000).toGMTString()) //強制快取的另一種方式,另外強調一點:主網頁只有對比快取沒有強制快取
複製程式碼
  1. Last-Modified
//對比快取 為了更加明顯的看到對比快取,我們將在以下的程式碼中都將強制快取關閉
//res.setHeader('Cache-Control','no-cache')

//響應頭設定了res.setHeader('Last-Modified',statObj.ctime.toGMTString())
//請求頭就會帶上req.headers['if-modified-since']

let http = require('http')
let url = require('url')
let util = require('util')
let fs = require('mz/fs')
let stat = util.promisify(fs.stat);
let path = require('path');
let p = path.resolve(__dirname);
http.createServer(async function(req, res) {
    let {pathname} = url.parse(req.url);
    let realPath = path.join(p, pathname);
    console.log(realPath)
    try{
        let statObj = await fs.stat(realPath);
        console.log(statObj)
        // res.setHeader('Cache-Control','max-age=10')  //強制快取  10s內不需要再次請求伺服器
        res.setHeader('Cache-Control','no-cache')
        res.setHeader('Content-Type',require('mime').getType(realPath)+';charset=utf8')
        res.setHeader('Expires', new Date(Date.now() + 10*1000).toGMTString()) //強制快取 因為上面設定了no-cache,所以這裡的設定其實無效
        let since = req.headers['if-modified-since'];
        if (since === statObj.ctime.toGMTString()) {
            res.statusCode = 304                      //伺服器的快取
            res.end();
        } else {
            res.setHeader('Last-Modified',statObj.ctime.toGMTString())
            fs.createReadStream(realPath).pipe(res)
        }
    }catch(e) {
        res.statusCode = 404;
        res.end('404')
    }
}).listen(3000)
//在瀏覽器開啟localhost:3000/index.html  重新整理看到就是304,我們返回狀態碼304,瀏覽器就乖乖地去讀快取中的檔案了。
//我們稍微改動一下index.html就可以看到 200 
複製程式碼
  1. Etag
//對比快取
//Etag內容的標識 
// 響應頭設定了res.setHeader('Etag',statObj.size.toString());  這裡設定的是檔案大小
//請求頭就會帶上req.headers['if-none-match'];
//
let http = require('http');
let util = require('util');
let fs = require('fs');
let stat = util.promisify(fs.stat);
let url = require('url');
let path = require('path');
let p = path.resolve(__dirname);
// 比較內容 stat.size (不靠譜)
// 第一次請求Etag:內容的標識  
// 第二次在請求我的時候 if-none-match 
http.createServer(async function(req,res){
    let {pathname} = url.parse(req.url);
    let realPath = path.join(p,pathname);
    try{
        let statObj = await stat(realPath);
        console.log(realPath) 
        res.setHeader('Cache-Control','no-cache');
        let match = req.headers['if-none-match'];
        if(match){
            if(match === statObj.size.toString()){
                res.statusCode = 304;
                res.end();
            }else{
                res.setHeader('Etag',statObj.size.toString());
                fs.createReadStream(realPath).pipe(res);
            }
        }else{
            res.setHeader('Etag',statObj.size.toString());
            fs.createReadStream(realPath).pipe(res);
        }
       
    }catch(e){
        res.statusCode = 404;
        res.end(`not found`);
    }
}).listen(3000);

複製程式碼

兩種快取的區別

  1. 強制快取
  • 設定強制快取的方式就是 res.setHeader('Cache-Control','max-age=10')
  • res.setHeader('Expires', new Date(Date.now() + 10*1000).toGMTString())
  • 以上兩種以第一種方式取決定作用
  1. 對比快取
  • 通過時間對比 Last-Modified ---- if-modified-since
  • 通過標識對比 Etag ---- if-none-match

以上就是web快取的部分內容,不足之處歡迎各位提出寶貴的意見或建議,也希望能幫助到你從中獲得一些知識,謝謝大家的關注!

相關文章