淺聊HTTP快取 (HTTP Cache)
1.引子
HTTP快取一直是一個老生常談的問題,前端在日常釋出、部署工作中,常常要面對。
其中面對的問題有可能會是:部署的程式碼無法生效
這次本人所在團隊也遇到了相關問題,這裡簡述一下:
-
專案會在靜態資源(如:css,js)使用chunkHash來處理,因此能保證修改後與舊程式碼檔名字不會重複。以避免無法更新改動。
-
在該專案中部署後,進行程式碼進行一次
location.reload
,改動即可以生效。
最後,本人發現是因為該專案部署的伺服器上所有靜態資源的response headers的設定如下:
response headers:
- cache-control
但致命的是,專案的入口: index.html也是如此。因此實際是因為所有的.html檔案命中(cache hit)了強快取,導致了使用者無法直接呈現更新後代碼的改動。
找到了原因,也想到了如下三個解決方案
-
跳轉時增加時間戳例如:
具體為什麼可以這麼做在之後分析檢視是否存在快取步驟時會解析
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等效值才被允許。
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中的兩個欄位:
expires
、cache-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-Since
和Etag / 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 cache
和 memory 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資源分成兩類:
-
主資源
主資源: 通過
MainResourceLoader
載入,如HTML頁面,或者下載項等 -
派生資源
派生資源:,通過
SubresourceLoader
載入,比如HTML頁面中內嵌的圖片或者指令碼連結
雖然Webkit支援memoryCache,但是也只是針對派生資源,它對應的類為CachedResource,用於儲存原始資料(比如CSS,JS等),以及解碼過的圖片資料。
此圖所示:
好像並不適用,完全適用css第一次並沒有,從 memory cache 載入,
但是經過幾次,後退重新跳轉後(不定次數):
到此,以本人的能力可能暫時,無法作出一個比較好的解答了,希望之後有大佬可以給到一個解答。
9. 供實踐的Demo
參考文獻:
-
MDN HTTP 快取:
-
Web快取機制系列2 Web瀏覽器的快取機制:
-
W3 HTML spec chapter 5.2.2:
-
Stackoverflow how-to-control-web-page-caching-across-all-browsers:
-
Stackoverflow how-chrome-browser-determine-memory-cache-and-disk-cache:
-
developer.chrome.com webRequest: