一、強快取
強制快取的思想是,在瀏覽器內建資料庫中快取每次請求中 “可以被快取” (受到一些關鍵字的管控)的靜態資源如 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 欄位, 用了這個欄位瀏覽器則不會在使用快取的資料也不快取資料,即強制快取和協商快取都失效了)
四、快取機制之間的一些區別
- 強制快取在快取有效的情況下不會去請求伺服器, 其資料來源則是瀏覽快取的本地磁碟。而協商快取會向伺服器請求,但是在協商快取成功的情況下, 伺服器只會返回一個不帶響應體的報文,結合開頭的背景來說 強制快取選擇“減少過橋次數”的策略, 而協商快取則是採用 ‘減少過橋人數’的策略
- 強制快取在瀏覽器強制重新整理的情況下不會生效, 而協商快取則不受影響。(除錯程式碼測試時候,要注意)
- 強制快取返回的報文狀態碼為 200, 協商快取返回的報文狀態碼為 304 (前端使用fetch請求的情況, 協商快取的 狀態碼304 會轉成 200)
- 強制快取發生在瀏覽器端, 協商快取發生在伺服器端
五、使用小結
- 強制快取和協商快取需要具體條件下來用, 下邊是筆者總結的幾個小點
- 強制快取存在一個瓶頸, 當瀏覽器使用者強重新整理時,瀏覽器會直接跳過強制快取, 這點不注意很容易會被忽視掉。
- 強制快取不適合 SPA 應用的入口檔案, 因為重新部署後, 使用者如果沒有強制重新整理, 則無法在第一時間內看到新的網頁內容。
- 作為一個前端開發者可以透過設定請求頭中的 no-cache 和 no-store 欄位選擇使用協商快取或者不使用快取!!!