瀏覽器強快取與協商快取

2023-04-20 06:01:08

一、強快取

強制快取的思想是,在瀏覽器內建資料庫中快取每次請求中 「可以被快取」 (受到一些關鍵字的管控)的靜態資源如 image, css, js 檔案, 當第二次請求被快取過的資源時候,會通過校驗兩個欄位 Expires 和 Cache-Control 的max-age欄位(注意,Expires 是 http1.0 的產物, Cache-Control 則是 http1.1 的產物。 兩者同時存在, 或者只存在其中之一, 都可以觸發強制快取

  • Expires:響應過期的日期和時間。
  • Cache-Control:指定請求和響應遵循的快取機制。

  當滿足欄位約束的情況下, 瀏覽器就不會向伺服器傳送請求而是直接從伺服器返回資料, 同時其狀態碼為 200

  當不滿足欄位約束的情況下, 瀏覽器則會向伺服器正常傳送請求

  強制快取主要取決於兩個欄位 Expires  Cache-Control 中的 max-age 欄位, 在兩個響應頭都存在的情況下, 其流程如圖 

 當兩個欄位同時存在得到時候, Cache-Control 中的 max-age 欄位欄位優先順序會稍微高一點, 當 Cache-Control 中的 max-age 欄位校驗成功,會直接返回瀏覽器內建資料庫的快取, 失效時才會將決策權傳遞給 Expires 欄位判斷。

這樣設計的原因,大概是因為 Expires 欄位在設計時存在了這麼一個缺陷——Expires欄位返回的是伺服器的時間, 而非使用者端的本機時間。 當存在時差, 或者客戶修改本地時間的情況下 Expires 欄位會存在失效的可能性,比如 當同一時刻下的伺服器時間為 2022/4/26 06:00:00 使用者端時間為 2022/4/26 12:00:00 過期時間為兩個小時之後, 則伺服器會返回 2022/4/26 08:00:00 這個時間對應的值。由於瀏覽器執行在客戶環境下,對於客戶而言, 這個快取已經過期了,雖然快取確實有效, 但是對於瀏覽器而言這個快取確確實實是 「過期了」, 這會導致強制快取永遠不會生效!

那麼為了解決Expires 欄位這個問題, http 1.1 協定中新增了 Cache-Control 中的 max-age, 他是一個相對值, 即使用者端獲取到這個檔案多少秒後失效, 其判別權力全權交由瀏覽器, 這會相對更準確些。

二、協商快取

協商快取主要由 ETag Last-Modified 兩個欄位來實現

  • ETag 是一個用於對映 web 資源的對映 token,這個 token 應該滿足唯一對應到一 個web伺服器上的靜態資源(具體實現通常是提取檔案相關資訊進行hash和base64編碼等操作)
  • Last-Modified 則通常是檔案最後更新的日期時間戳

(通過上述兩個欄位就可以判斷當前檔案是否是最新的資料)


與上述兩個欄位配對的分別是 If-None-Match  If-Modified-Since 這兩個欄位,

 

瀏覽器首次向伺服器請求資料 A, 伺服器正常返回資料,同時在響應頭中放入 ETag Last-Modified 兩個新欄位。

當瀏覽器第二次向伺服器請求資料 A 時, 瀏覽器會自動地在請求頭附上 If-None-Match 和 If-Modified-Since 兩個欄位(分別對應的是 ETag 和 Last-Modified 的值,兩兩相等), 然後由伺服器端進行校驗, 校驗通過的話(表明資料有效), 伺服器會直接返回 狀態碼 304 ,且不攜帶響應體的報文段, 這相當於告訴瀏覽器:當前快取有效, 可以直接使用! 校驗失敗則會和首次請求一樣, 返回狀態碼為200且攜帶資料響應體的報文段, 同時這個響應頭會帶上新的ETag 和Last-Modified, 為下一次協商快取做好鋪墊 。

需要注意的是, 在不用框架的情況下, 協商快取需要由後端開發人員手動實現,因此 ETag 和 Last-Modified 兩個欄位的優先順序取決於開發者, 但是 Last-Modified 這個欄位可以記錄的時間戳精確度是有一定限制的,如果連續多次資料更新在精確度範圍外, 會產生精確度丟失, 因此通常會讓ETag 的優先順序高於 Last-Modified 欄位(類似於Cache-control中max-age一樣, 屬於是後續改進協定的一個新欄位, 因此優先順序一般會高點)

三、強快取協商快取並存的情況

預設情況下, 瀏覽器會優先考量強制快取的情況, 當強制快取生效的情況下, 請求並不會到達伺服器, 因此也就不會觸發協商快取。 當強制快取失效的時候, 瀏覽器便會將請求傳遞到伺服器, 於是伺服器又會開始校驗 If-Modified-Since 和 If-None-math 兩個欄位, 重複上述協商快取的一個執行流程

乍一看,兩者並存的情況, 有點像是兩個協定的簡單疊加,此時的協商快取更像是強制快取的兜底策略, 很可能協商快取很長一段時間都不會生效(強制快取過期時間設定過長的情況下), 因為強制快取的優先順序是要高於協商快取的。 當然這並不是我們想看到的, 比方說當後端資料確實變更了, 而此時的瀏覽器由於使用了強制快取,則會出現資料不一致的情況, 因此在這裡引入了請求頭中的兩個欄位 no-cache, 當使用了 no-cache 欄位的時候, 瀏覽器將不再使用強制快取, 而是直接去請求伺服器, 這個時候就會用到協商快取了(順帶一提的是, 還有一個 no-store 欄位, 用了這個欄位瀏覽器則不會在使用快取的資料也不快取資料,即強制快取和協商快取都失效了)

四、快取機制之間的一些區別

  1. 強制快取在快取有效的情況下不會去請求伺服器, 其資料來源則是瀏覽快取的本地磁碟。而協商快取會向伺服器請求,但是在協商快取成功的情況下, 伺服器只會返回一個不帶響應體的報文,結合開頭的背景來說 強制快取選擇「減少過橋次數」的策略, 而協商快取則是採用 ‘減少過橋人數’的策略
  2. 強制快取在瀏覽器強制重新整理的情況下不會生效, 而協商快取則不受影響。(偵錯程式碼測試時候,要注意)
  3. 強制快取返回的報文狀態碼為 200, 協商快取返回的報文狀態碼為 304 (前端使用fetch請求的情況, 協商快取的 狀態碼304 會轉成 200)
  4. 強制快取發生在瀏覽器端, 協商快取發生在伺服器端

五、使用小結

  1. 強制快取和協商快取需要具體條件下來用, 下邊是筆者總結的幾個小點
  2. 強制快取存在一個瓶頸, 當瀏覽器使用者強重新整理時,瀏覽器會直接跳過強制快取, 這點不注意很容易會被忽視掉。
  3. 強制快取不適合 SPA 應用的入口檔案, 因為重新部署後, 使用者如果沒有強制重新整理, 則無法在第一時間內看到新的網頁內容。
  4. 作為一個前端開發者可以通過設定請求頭中的 no-cache 和 no-store 欄位選擇使用協商快取或者不使用快取!!!