http緩存實戰(讓你不再會學過就忘)

「學過就忘」,一個知識點長時間不去讀取,那麼在你腦海中的基因片斷會逐漸模糊,可是基因索引仍是存在的。本篇文章經過實戰理論想結合的方式來幫助你們更深入的記憶經常使用知識點。 若有不足之處,懇請斧正。javascript

什麼是HTTP緩存 ?

http緩存通俗來講: 當客戶端向服務器請求資源時,不會直接向服務器獲取資源(喂:服務器,給我一張最新的自拍圖片)。而是先看下本身的存儲裏有沒有上次保存的還在有效期內的資源。若是有的話,就省下了一筆運費(流量)。這裏舉一個簡單的例子:html

  • 客戶端須要a.js,因而發送一個請求頭(1kb)
  • 服務端響應後,返回a.js(10kb) + 響應頭(1kb)
  • 如此反覆每次就是11kb的流量傳送

可是咱們咱們須要的文件a.js的內容每每並無發生變化,卻仍然須要浪費流量,爲了解決這個問題,因而人們提出了http緩存這個概念。java

HTTP緩存能夠分爲強緩存和協商緩存。node

強緩存

  • Expires
  • Cache-Control
  • Pragma

協商緩存

  • Last-Modified
  • If-Modified-Since
  • ETag
  • If-Not-Match

強緩存與協商緩存的區別:

用戶大人:我如今須要a.js,大家幫我拿回來
強緩存: 稍等,我找下我這裏有沒有關於a.js的備份,找到了。(消耗0kb流量)
協商緩存: 我這裏也有備份,不過我得問下服務端這個備份是否是最新款,發送請求頭(1kb流量)。服務端回覆(1kb流量)響應頭則使用本地備份,如果返回a.js(10kb)和響用頭(1kb)則使用服務器返回的最新數據。webpack

強緩存

Expires

這是 HTTP 1.0 的字段,表示緩存到期時間,是一個絕對的時間 (當前時間 + 緩存時間)。在響應消息頭中,請求資源前瀏覽器會用當前時間與其值對比,如果未過時則不須要再次請求。web

新建cache.html面試

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="icon" href="data:;base64,=">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>cache</title>
  <script src="cache.js"></script>
</head>
<body>
  <h1>cache</h1>
</body>
</html>
複製代碼

新建cache.jsnpm

let http = require('http'),
fs = require('fs'),
path = require('path'),
url = require("url")
http.createServer((req,res)=>{
  let pathname = __dirname + url.parse(req.url).pathname; // 獲取文件路徑
  let statusCode = 200 // 響應頭狀態碼
  let extname = path.extname(pathname) // 獲取文件擴展名
  let headType = 'text/html' // 內容類型
  if( extname){
    switch(extname){
      case '.html':
       headType = 'text/html'
      break;
      case '.js':
        headType = 'text/javascript'
      break;
    }
  }
  fs.readFile(pathname, function (err, data) {
    res.writeHead(statusCode, {
      'Content-Type': headType,
      'Expires':new Date(Date.now() + 10000).toUTCString() // 設置文件過時時間爲10秒後
    });
    res.end(data);
  });
}).listen(3000)

console.log("Server running at http://127.0.0.1:3000/");
複製代碼

啓動json

node cache.js
複製代碼

打開瀏覽器訪問http://127.0.0.1:3000/cache.html 查看network
瀏覽器

  1. 首次訪問所有走網絡請求資源
    cache1.png
  2. 10s內再次刷新從內存中加載(from memory cache)
    cache2.png
  3. 10s內關閉tab,從新打開請求的cache.html,從磁盤加載(from disk cache)
    cache3.png
  4. 10s之後請求,緩存已經失效,重複第1步

缺點:

  1. Expires過時控制不穩定。由於若是咱們修改電腦的本地時間,會致使瀏覽器判斷緩存失效。

Cache-control

這是HTTP1.1的字段,這是一個相對時間'Cache-Control':'max-age=20'意思是20秒內使用緩存資源,不用請求服務器

// 省略其餘代碼
  fs.readFile(pathname, function (err, data) {
    res.writeHead(statusCode, {
      'Content-Type': headType,
      'Cache-Control':'max-age=20'
    });
複製代碼

cache5.png

Cache-Control 除了max-age 相對過時時間之外,還有不少屬性

  1. no-store:全部內容都不緩存
  2. no-cache:緩存,可是瀏覽器使用緩存前,都會請求服務器判斷緩存資源是不是最新,它是個比較高貴的存在,由於它只用不過時的緩存。
  3. public 客戶端和代理服務器(CDN)均可緩存
  4. private 只有客戶端能夠緩存

更多屬性可參考 Cache-Control

Tips:也許你們在這裏有一些小疑問,爲何對cache.html設置ExpiresCache-Control在谷歌瀏覽器裏不生效。這時候查看request header 發現 Cache-Control: max-age=0,瀏覽器強制不用緩存。

cache6.png
這是由於瀏覽器會針對的用戶不一樣行爲採用不一樣的緩存策略,這樣會致使在不一樣的瀏覽器會產生不一樣的現象:

Chrome does something quite different: 'Cache-Control' is always set to 'max-age=0′, no matter if you press enter, f5 or ctrl+f5. Except if you start Chrome and enter the url and press enter.

pragma

pragma是http/1.1以前版本的歷史遺留字段,僅做爲與http的向後兼容而定義。這裏不作討論。感興趣的朋友能夠點擊瞭解 Pragma

node 熱更新

這裏若是你們以爲每次修改cache.js文件都須要從新執行node run cache.js,這裏咱們能夠配置node 熱更新

npm init
npm i -D nodemon
複製代碼

npm i -Dnpm install --save-dev的縮寫

修改package.json

"scripts": {
    "dev": "nodemon ./bin/www"
  },
複製代碼

下面咱們只須要執行一次npm run dev便可

對比緩存(協商緩存)

上面的強緩存依舊存在着很大的缺陷。當設置的時間過時後,無論文件內容有沒有變化,咱們不得再也不次向服務器請求資源。這時候咱們就須要用到協商緩存了。

Last-Modified

響應值,由服務器返回給客戶端關於請求資源的最近修改時間 (GMT標準格式)

If-Modified-Since

請求值 , 由客戶端發送給服務端上次返回的資源修改時間

首次請求時服務端會攜帶Last-Modified返回給客戶端,客戶端將其數值保存起來,並從新命名爲If-Modified-Since 再次請求時,客戶端會先發送一個攜帶If-Modified-Since的請求頭髮送到服務端,服務端會比較請求頭的If-Modified-Since和服務器請求資源上次的修改時間(Last-Modified).

  1. 若是資源已經被修改:那麼返回響應資源a.js(10kb) + 響應頭(1kb),狀態碼:200 OK
  2. 若是沒有被修改:那麼只返回響應頭(1kb),狀態碼:304 Not Modified
// 省略其餘代碼
let stat = fs.statSync(pathname);
  fs.readFile(pathname, function (err, data) {
    // 判斷請求頭的文件修改時間是否等於服務端的文件修改時間
    if(req.headers['if-modified-since'] === stat.mtime.toUTCString()) { // mtime爲文件內容改變的時間戳
      statusCode = 304;
    }
    res.writeHead(statusCode, {
      'Content-Type': headType,
      'Last-Modified':stat.mtime.toUTCString()
    });
    res.end(data);    
  });
複製代碼

cache7.png
但使用Last-Modified一樣存在缺陷

  1. last-modified是以秒爲單位的,若是資源在1s內修改屢次,因爲1s內last-modified並未改變,客戶端仍然會使用緩存。
  2. 若是在服務器上請求的資源(a.js)被修改了,但其實際內容根本沒發生改變,會由於 Last-Modified 時間匹配不上而從新返回 a.js 給瀏覽器(舉例:服務器動態生成文件)

爲了解決上述問題,使用新的字段 ETag 和 If-None-Match

ETag

響應值,由服務器返回給客戶端根據文件內容,算出的一個惟一的值 (GMT標準格式)

If-Not-Match

請求值 , 由客戶端發送給服務端上次返回的資源惟一值

請求流程與Last-Modified一致

上面咱們所述last_modified 通常由 mtime 組成,而 ETag 通常由 last_modified content_length 組成

// 省略其餘代碼
  fs.readFile(pathname, function (err, data) {
    let Etag = `${data.length.toString(16)}${stat.mtime.toString(16)}`
    if((req.headers['if-modified-since'] === stat.mtime.toUTCString()) || (req.headers['if-none-match'] === Etag)) {
      statusCode = 304;
    }
    res.writeHead(statusCode, {
      'Content-Type': headType,
      Etag
    });
    res.end(data);    
  });
複製代碼

cache8.png

緩存優先級

當多種緩存同時存在時,瀏覽器改使用哪一種緩存方式呢?這裏就有了一個優先級關係

  • 強緩存與協商緩存同時存在時,若是強緩存還在生效期則強制緩存,不然使用協商緩存。(強緩存優先級 > 對比緩存優先級)
  • 強緩存expirescache-control同時存在時,cache-control會覆蓋expires (cache-control優先級 > expires優先級。)
  • 對比緩存EtagLast-Modified同時存在時,Etag會覆蓋Last-Modified效。(ETag優先級 > Last-Modified)優先級。

最佳實踐

上面只是讓你們更好的瞭解了http緩存的大概知識點,那麼在實際開發中咱們是如何如何利用緩存實現更好的用戶體驗的呢?
相信webpack你們已經並不陌生,若是有過實際配置經驗的同窗必定記得咱們在配置出口文件output或者打包圖片的filename時每每會加上hash || chunkhash || contenthash這些字段

module.exports = {
     output:{
        path:path.resolve(__dirname,'../dist'),
        filename:'js/[name].[contenthash:8].js',
        chunkFilename:'js/[name].[chunkhash:8].js'
    },
    loader:{
        rules:[
             {
                test:/\.(jep?g|png|gif)$/,
                use:{
                  loader:'url-loader',
                  options:{
                    limit:10240,
                    fallback:{
                      loader:'file-loader',
                      options:{
                        name:'img/[name].[hash:8].[ext]'
                      }
                    }
                  }
                }
            }
        ]
    }
}
複製代碼

這樣打包出來的文件名稱每每以下

這樣若是每次文件有變化,那麼文件名稱隨即變化。瀏覽器則會從新請求這些文件資源。若是文件名稱與上次一致,那麼則會使用到http緩存策略。

最後附上用戶行爲對瀏覽器緩存的影響

拓展閱讀

面試精選之http緩存

相關文章
相關標籤/搜索