淺聊HTTP緩存 (HTTP Cache)

1.引子

HTTP緩存一直是一個老生常談的問題,前端在平常發佈、部署工做中,經常要面對。javascript

其中面對的問題有可能會是:部署的代碼沒法生效css

此次本人所在團隊也遇到了相關問題,這裏簡述一下:html

  • 項目會在靜態資源(如:css,js)使用chunkHash來處理,所以能保證修改後與舊代碼文件名字不會重複。以免沒法更新改動。前端

  • 在該項目中部署後,進行代碼進行一次location.reload,改動便可以生效。java

最後,本人發現是由於該項目部署的服務器上全部靜態資源的response headers的設置以下:git

response headersgithub

  • cache-control: public, max-age=31536000

但致命的是,項目的入口: index.html也是如此。所以實際是由於全部的.html文件命中(cache hit)了強緩存,致使了用戶沒法直接呈現更新後代碼的改動。web

找到了緣由,也想到了以下三個解決方案chrome

  • 跳轉時增長時間戳例如:瀏覽器

    具體爲何能夠這麼作在以後分析查看是否存在緩存步驟時會解析

    location.href = 'https://www.localhost:5000.com/index.html?t=201811141248001';
    複製代碼
  • 修改response headers中的cache-control

    舉例:

    cache-control: public, max-age=0
    複製代碼
  • 使用HTML Meta 標籤

    能夠在html代碼中增長meta標籤:

    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
    <meta http-equiv="Pragma" content="no-cache" />
    <meta http-equiv="Expires" content="0" />
    複製代碼

    上述代碼的做用是告訴瀏覽器當前頁面不被緩存,每次訪問都須要去服務器拉取。使用上很簡單,但只有部分瀏覽器能夠支持,並且全部緩存代理服務器都不支持,由於代理不解析HTML內容自己。

    最好仍是不要指定HTML標籤,經過可能會出現混亂(到底以那端爲主,實際response header的優先級更高)。此外,在HTML5中,這些<meta HTTP等>標籤是無效的。只有HTML5規範中列出的HTTP等效值才被容許。

    可參考:W3 HTML spec chapter 5.2.2

2. HTTP緩存基本概念

既然找到問題了,我以爲那我就順藤摸瓜的總結一下吧。

  • HTTP 緩存:重用已獲取的資源可以有效的提高網站與應用的性能。Web 緩存可以減小延遲與網絡阻塞,進而減小顯示某個資源所用的時間。藉助 HTTP 緩存,Web 站點變得更具備響應性。

  • HTTP 緩存分爲:強緩存和協商緩存

(1) 簡化流程

其中關鍵步驟是:

  • 判斷是否存在緩存
  • 判斷緩存是否有效(即強緩存是否命中)
  • 請求服務端,判斷服務端資源是否更新(即協商緩存是否命中
  • 返回資源(若服務端返回的資源,本地保存請求,包括請求頭信息)

(2) 查看是否存在緩存

瀏覽器怎麼斷定是否存在本地緩存,這個步驟在此能夠理解爲瀏覽器去查找本地是否存在該響應請求的文件存在,查找是不是否有該對應的請求,不一樣瀏覽器緩存文件的地址也不盡相同。

以firefox舉例,能夠在地址欄輸入:about:cache

P.S: chrome://cache 在 chrome66版本後已廢棄。

如圖上所示,這裏咱們能夠看到瀏覽器關於網絡請求緩存的一些信息。咱們以本地磁盤中的爲例子。

如圖上所示,在此咱們能夠查看到一些關於緩存在磁盤內的信息,包括實際本地緩存所在的位置等。

如上圖,這就是一次響應請求的文件,而且他會記錄完整的url包括:query string。

如上兩圖所見,咱們改變了t參數的值,實際咱們在url後打時間戳來規避命中緩存,實際就是在此改變了查詢URL,讓瀏覽器沒法查詢到與以前請求相同的本地緩存。

最後能夠看到,咱們的本地緩存文件內,會包含response-head的信息,以後的緩存策略和流程都須要依賴此處的信息。

總結一下,查看是否存在緩存的過程實際就是查找響應請求文件是否存在

(3) 強緩存

[1] 強緩存概念

強制緩存就是向瀏覽器緩存查找該請求結果,並根據該結果的緩存規則來決定是否使用該緩存結果的過程。

實際就是咱們總體流程內的,查看是否存在緩存以及,查看緩存是否有效。

[2] 如何實現強緩存

  • 實現強緩存,主要是根據客戶端保留的一個服務器端的response header中的兩個字段:expirescache-control

  • cache-control優先級比expires

如圖:

圖中可知二者的區別

  • HTTP響應報文中expires的時間值,是一個絕對值。

  • HTTP響應報文中Cache-Control爲max-age=31536000,是相對值。

在沒法肯定客戶端的時間是否與服務端的時間同步的狀況下,Cache-Control相比於expires是更好的選擇,因此同時存在時,只有Cache-Control生效。

Expires

  • Expires是HTTP/1.0控制網頁緩存的字段,其值爲服務器返回該請求結果緩存的到期時間,即再次發起該請求時,若是客戶端的時間小於Expires的值時,直接使用緩存結果。

    Expires: Wed, 21 Oct 2015 07:28:00 GMT
    複製代碼

Cache-Control

在HTTP/1.1中,Cache-Control是最重要的規則,主要用於控制網頁緩存,列幾個常見的值:

  • public:全部內容都將被緩存(客戶端和代理服務器均可緩存)

  • private:全部內容只有客戶端能夠緩存,Cache-Control的默認取值

  • no-cache:客戶端緩存內容,可是是否使用緩存則須要通過協商緩存來驗證決定

  • no-store:全部內容都不會被緩存,即不使用強制緩存,也不使用協商緩存

  • max-age=xxx (xxx is numeric):緩存內容將在xxx秒後失效

    Cache-Control:public, max-age=31536000
    複製代碼

判斷緩存是否過時的流程的流程:

緩存失效時間計算公式以下:

expirationTime = responseTime + freshnessLifetime - currentAge
複製代碼

在上面這個公式裏,responseTime 表示瀏覽器接收到此響應的那個時間點。

[3] 如何判斷強緩存是否命中

狀態碼爲灰色的請求則表明使用了強制緩存,請求對應的Size值則表明該緩存存放的位置

至於from memory cache 和 from disk cache相關的以後講解。

(4) 協商緩存

[1] 協商緩存概念

協商緩存就是強制緩存失效後,瀏覽器攜帶緩存標識向服務器發起請求,由服務器根據緩存標識決定是否使用緩存的過程。

[2] 如何實現協商緩存

  • 協商緩存的標識也是在響應報文的HTTP頭中和請求結果一塊兒返回給瀏覽器的,控制協商緩存的字段分別有:Last-Modified / If-Modified-SinceEtag / If-None-Match

  • Etag / If-None-Match 優先級比 Last-Modified / If-Modified-Since 高。

Last-modified:

Last-Modified是服務器響應請求時,返回該資源文件在服務器最後被修改的時間

Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
複製代碼

If-Modified-Since:

If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT 
複製代碼

Etag:

Etag是服務器響應請求時,返回當前資源文件的一個惟一標識(由服務器生成)

ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
ETag: W/"0815"
複製代碼

If-None-Match:

If-None-Match是客戶端再次發起該請求時,攜帶上次請求返回的惟一標識Etag值,經過此字段值告訴服務器該資源上次請求返回的惟一標識值。服務器收到該請求後,發現該請求頭中含有If-None-Match,則會根據If-None-Match的字段值與該資源在服務器的Etag值作對比,一致則返回304,表明資源無更新,繼續使用緩存文件;不一致則從新返回資源文件,狀態碼爲200

If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
複製代碼

而後咱們看一下具體流程:

[4] 如何判斷協商緩存是否命中

若是協商緩存命中,請求響應返回的http狀態爲304而且會顯示一個Not Modified的字符串。

(5) 二者異同

  • 二者的共同點是:若是命中,都是從客戶端緩存中加載資源,而不是從服務器加載資源數據;

  • 二者的區別是:強緩存不發請求到服務器,協商緩存會發請求到服務器。

4.相關瀏覽操操做

其實瀏覽器的相關操做,會對開發人員理解瀏覽器HTTP緩存產生一些影響,所以咱們詳細來分析一下:

在Alloy Team的Web緩存機制系列中有總結:

Web緩存機制系列2 – Web瀏覽器的緩存機制 - Alloy Team

瀏覽器相關操做 Expires/Cache-Control Last-Modified / Etag
地址欄回車 有效 有效
頁面連接跳轉 有效 有效
新開窗口 有效 有效
前進、後退 有效 有效
刷新 無效 有效
強制刷新 無效 無效

這塊我想梳理一下,與你們分享以及驗證一下:

測試前提:

  • 服務端設置相應的response header,
  • 相應資源都已經加載完畢第一次(若是測試結果相同,測試結果的圖片就複用了):

測試的瀏覽器爲:

  • Chrome 70
  • Firefox 63.0.1
  • Opera 56.0

測試影響的文件:

  • index.html (主頁面)
  • index.js (js資源)
  • index.css (樣式文件)
  • doge.jpeg (圖片文件)
  • favicon.ico (圖標文件)
  • temp.html(跳轉輔助頁面,不設置response header且,不在統計範圍內)

測試使用的response header的設置爲:

  • Cache-Control: max-age=300 // 緩存5分鐘
  • ETag: 33a64df551425fcc55e4d42a148795d9f25f89d4 // 服務端固定返回
  • Expires: Fri Nov 16 2018 09:33:01 GMT+0800 (CST) // 緩存5分鐘
  • Last-Modified: Wed, 21 Oct 2018 07:28:00 GMT // 服務端固定返回

(1) 頁面連接跳轉

Chrome 70測試結果:

如圖:

結果:

  • index.html:命中強緩存
  • index.js: 命中強緩存
  • index.css:命中強緩存
  • doge.jpeg:命中強緩存
  • favicon.ico:未顯示(使用抓包工具抓包,未發出請求)

Firefox 63.0.1 測試結果:

如圖:

結果:

  • index.html:命中強緩存
  • index.js:命中強緩存
  • index.css:命中強緩存
  • doge.jpeg:命中強緩存
  • favicon.ico:命中強緩存(使用抓包工具抓包,未發出請求)

Opera 56.0 測試結果:

如圖:

結果:

  • index.html:命中強緩存
  • index.js:命中強緩存
  • index.css:命中強緩存
  • doge.jpeg:命中強緩存
  • favicon.ico:未發出請求(使用抓包工具抓包,無請求)

(2) 新開窗口

Chrome 70測試(因爲默認爲google頁,採用了隱私模式測試)結果:

如圖:

結果:

  • index.html:命中強緩存
  • index.js:命中強緩存
  • index.css:命中強緩存
  • doge.jpeg:命中強緩存
  • favicon.ico:未發出請求(使用抓包工具抓包,無請求)

Firefox 63.0.1 測試結果:

如圖:

結果:

  • index.html:命中強緩存
  • index.js:命中強緩存
  • index.css:命中強緩存
  • doge.jpeg:命中強緩存
  • favicon.ico:命中強緩存(使用抓包工具抓包,未發出請求)

Opera 56.0(隱私模式) 測試結果:

如圖:

結果:

  • index.html:命中強緩存
  • index.js:命中強緩存
  • index.css:命中強緩存
  • doge.jpeg:命中強緩存
  • favicon.ico:未發出請求(使用抓包工具抓包,無請求)

(4) 前進、後退

Chrome 70測試結果:

如圖:

結果:

  • index.html:命中強緩存
  • index.js:命中強緩存
  • index.css:命中強緩存
  • doge.jpeg:命中強緩存
  • favicon.ico:未發出請求(使用抓包工具抓包,無請求)

Firefox 63.0.1 測試結果:

如圖:

結果:

  • index.html:命中強緩存
  • index.js:命中強緩存
  • index.css:命中強緩存
  • doge.jpeg:命中強緩存
  • favicon.ico:命中強緩存(使用抓包工具抓包,未發出請求)

Opera 56.0 測試結果:

如圖:

結果:

  • index.html:命中強緩存
  • index.js:命中強緩存
  • index.css:命中強緩存
  • doge.jpeg:命中強緩存
  • favicon.ico:未發出請求(使用抓包工具抓包,無請求)

(5) 刷新

刷新這一塊是測試的重點(以前正由於)

Chrome 70測試結果:

如圖:

結果:

  • index.html:命中協商緩存
  • index.js:命中強緩存
  • index.css:命中強緩存
  • doge.jpeg:命中強緩存
  • favicon.ico:沒有命中緩存,服務端獲取資源

Firefox 63.0.1 測試結果:

如圖:

結果:

  • index.html:命中協商緩存
  • index.js:命中協商緩存
  • index.css:命中協商緩存
  • doge.jpeg:命中協商緩存
  • favicon.ico:命中協商緩存

Opera 56.0 測試結果:

如圖:

結果:

  • index.html:命中協商緩存
  • index.js:命中強緩存
  • index.css:命中強緩存
  • doge.jpeg:命中強緩存
  • favicon.ico:未發出請求(使用抓包工具抓包,無請求)

(6) 地址欄回車

這個部分還得分紅從當前tab回車和從另外一url

  • Chrome 70測試結果:

[1] 從另外一url跳轉

至關於頁面連接跳轉

[2] 當前url回車

至關於刷新

Firefox 63.0.1 測試結果:

[1] 從另外一url跳轉

至關於頁面連接跳轉

[2] 當前url回車

至關於頁面連接跳轉

Opera 56.0 測試結果:

[1] 從另外一url跳轉

[2] 當前url回車

至關於刷新

(6) 強制刷新

Chrome 70測試結果:

如圖:

結果:

  • index.html:從服務器端獲取資源
  • index.js:從服務器端獲取資源
  • index.css:從服務器端獲取資源
  • doge.jpeg:從服務器端獲取資源
  • favicon.ico:從服務器端獲取資源

Firefox 63.0.1 測試結果:

如圖:

結果:

  • index.html:從服務器端獲取資源

  • index.js:從服務器端獲取資源

  • index.css:從服務器端獲取資源

  • doge.jpeg:從服務器端獲取資源

  • favicon.ico:從服務器端獲取資源

  • Opera 56.0 測試結果:

如圖:

結果:

  • index.html:從服務器端獲取資源
  • index.js:從服務器端獲取資源
  • index.css:從服務器端獲取資源
  • doge.jpeg:從服務器端獲取資源
  • favicon.ico:從服務器端獲取資源

(7)最終總結:

雖然費了那麼大力氣測試,最終結論只是稍微調整了一下:

瀏覽器相關操做 Expires/Cache-Control Last-Modified / Etag
頁面連接跳轉 有效 有效
新開窗口 有效 有效
前進、後退 有效 有效
刷新 chrome opera html無效 ico文件無效,
ff有效
chrome opera ico文件無效,
ff有效
地址欄回車 當前URL回車 - chrome opera同刷新
當前URL回車 - ff同刷新
其餘URL回車 - 同頁面連接跳轉
當前URL回車 - chrome opera同刷新
當前URL回車 - ff同刷新
其餘URL回車 - 同頁面連接跳轉
強制刷新 無效 無效

6. 相關HTTP相關的頭字段總結

圖片引用自:Web緩存機制系列2 – Web瀏覽器的緩存機制

7.完整流程圖

總結了一個相對完成的流程圖:

8. 本文一些不足之處

(1) 分佈式系統相關

這部分沒有實際實踐過,只是摘選了部分文章的觀點:

  • 分佈式系統裏多臺服務器間的文件的Last-Modified必須保持一致,以避免負載均衡到不一樣服務器致使對比結果不一致。

  • 分佈式系統儘可能關閉掉ETag(每臺機器生成的ETag都會不同,淘寶頁面中的靜態資源response headers中都沒有ETag)。

(2) 緩存的不一樣來源相關

這個部分目前暫時沒有找到十分的標準答案或文檔,目前我僅將本身梳理過的部分知識記錄在案:

其實webkit緩存機制還有一個叫 pageCache 這裏暫不討論:WebKit Page Cache I – The Basics

Chrome使用兩個緩存: disk cachememory cache

如下例子都僅針對Chrome

[1] disk cache

從磁盤中獲取緩存資源,等待下次訪問時不須要從新下載資源,而直接從磁盤中獲取。

[2] memory cache

從內存中獲取資源,等待下次訪問時不須要從新下載資源,而直接從內存中獲取。Webkit早已支持memoryCache。

[3]瀏覽器如何區分使用二者呢?

測試條件與上文其餘測試相同:

a. 當前tabs生命週期未結束

b. 當前tabs生命週期結束

能夠得出一個基本**「現象」**:

memory cache的生命週期於tabs的選項卡大體對應。

The lifetime of an in-memory cache is attached to the lifetime of a render process, which roughly corresponds to a tab.

能夠參考:developer.chrome - webRequest

c. 有疑問之處

有見過一種論點:

目前Webkit資源分紅兩類:

  1. 主資源

    主資源: 經過MainResourceLoader加載,如HTML頁面,或者下載項等

  2. 派生資源

    派生資源:,經過 SubresourceLoader加載,好比HTML頁面中內嵌的圖片或者腳本連接

雖然Webkit支持memoryCache,可是也只是針對派生資源,它對應的類爲CachedResource,用於保存原始數據(好比CSS,JS等),以及解碼過的圖片數據。

此圖所示:

好像並不適用,徹底適用css第一次並無,從 memory cache 加載,

可是通過幾回,後退從新跳轉後(不定次數):

到此,以本人的能力可能暫時,沒法做出一個比較好的解答了,但願以後有大佬能夠給到一個解答。

9. 供實踐的Demo

Demo倉庫地址

參考文獻:

相關文章
相關標籤/搜索