針對web開發者的瀏覽器快取指南(譯)

下半身要幸福發表於2019-01-17

原文連結

個人文章彙總在blog

概要

快取是一個非常重要同樣也非常複雜的瀏覽器特性

在這篇文章中,我們將解釋瀏覽器是如何利用快取來使載入頁面更快,哪些因素決定了快取的週期,在必要的時候如何去避開快取。

為什麼快取會如此重要呢?

所有的瀏覽器都會嘗試去快取靜態資源的本地副本,以此去降低頁面的載入時間以及體積最小的網路傳輸

不論服務是在相同的網路環境下還是在世界上其他的網路環境下,從本地快取中取資源一定會比通過網路請求的方式要快

如何讓瀏覽器快取生效呢?

case1:使用者之前沒有訪問過這個站點

瀏覽器針對此站點沒有任何的快取檔案,所以瀏覽器會向站點服務去請求所有所需的資源

image

下面是一張首次訪問維基百科首頁的資源下載完之後的截圖,底部狀態列顯示出有265kb的資料傳輸到了瀏覽器中

image

case2:使用者之前訪問過站點

瀏覽器會去請求站點伺服器取到html頁面,然後會檢視是否有對應靜態資源的快取(js, css, images)

image

當我們重新重新整理維基百科的頁面時,就能看出因為快取的原因與之前的請求狀況不同的地方

image

此時資料的傳輸量已經降到了928bytes,相當於首頁訪問頁面時的0.3%,Size這一列就顯示出了我們的大多數的資源都來自於快取

Chrome即會在memory cache中去拉取內容,也會在disk cache中去拉取內容,因為在case1和case2中,我們還沒有關閉瀏覽器,所以資料依然會儲存在memory cache中

顯示瀏覽器快取

在chrome中,我們可以在位址列中輸入chrome://cache去檢視快取的內容,對於每一個已經快取的檔案,這裡都將會顯示一個頁面連結,頁面連結的內容包含一個更加詳細的檢視說明。

image

瀏覽器是如何知道什麼該快取的

瀏覽器先去檢查伺服器所生成的http響應頭資訊,一般用於快取相關的頭資訊有4個:

  • ETag
  • Cache-Control
  • Expires
  • Last-Modified

ETag

ETag(Entity Tag)是一個用於快取校驗token的字串,它通常以檔案的雜湊值來表示

伺服器可以新增ETag到它的響應裡,瀏覽器接收到這個響應,那在未來的請求中(在檔案過期之後),瀏覽器就可以根據這個欄位去判斷快取中是否保留著一份過期的副本

如果通過對比hash值是相同的,那麼就說明這個資源沒有變化,伺服器就會返回一個304狀態碼(Not Modified),瀏覽器就知道當前快取的副本是安全的

iamge

注意:只有當快取中的檔案過期了,ETag才會被用在請求中

Cache-Control

Cache-Control頭擁有許多指令,利用這些指令我們可以設定快取的行為,過期時間,驗證等,同時這些也可以組合起來一起進行設定。

Cache Behavior(快取行為)
  Cache-Control: public
複製程式碼

public意味著資源可以被任意快取(瀏覽器,CDN等)

  Cache-Control: private
複製程式碼

private意味著資源只能被瀏覽器快取

  Cache-Control: no-store
複製程式碼

no-store意味著讓瀏覽器總是去請求伺服器以獲取資源

  Cache-Control: no-cache
複製程式碼

no-cache有一點誤導性,它並不是說“不要快取”

這是在告訴瀏覽器去快取這個檔案,但在和伺服器確認最新版本之前不要去使用它。這個驗證的過程是通過ETag來完成的

這個行為一般會用在html檔案中,因為瀏覽器總會去檢查最新的html檔案標識,所以這一點是講得通的

Expiration
  Cache-Control: max-age=60
複製程式碼

這個指令指定了資源應該被快取多少秒,所以max-age=60就意味著資源應該被快取一分鐘,RFC 2616推薦這裡的最大值不要超過一年(max-age=31536000)

  Cache-Control: s-max-age=60
複製程式碼

這個指令僅僅是被用在中間快取(比如CDN)

Validation
  Cache-Control: must-revalidate
複製程式碼

這個指令告訴快取在使用它之前必須去驗證資源的過期狀態,如果已經過期就不應該被使用

Expires

Expires頭部來源於http1.0版本,但是在當今許多站點中依然保留著

這個頭部欄位指定了一個日期,超過這個日期就代表資源是無效的

  Expires: Wed, 25 Jul 2018 21:00:00 GMT
複製程式碼

注意:如果已經指定了Cache-Control中的max-age指令,那瀏覽器就會忽略expires

Last-Modified

Last-Modified頭部也是來自http1.0版本

  Last-Modified: Mon, 12 Dec 2016 14:45:00 GMT
複製程式碼

這個欄位包含了資源最後修改的日期和時間

HTML Meta Tag

在HTML5版本之前,在html中使用元標籤(meta tag)制定cache-control是一個有效的方式

  <meta http-equiv="Cache-control" content="no-cache">
複製程式碼

但在html5中已經不推薦這麼做了,為什麼?因為只有瀏覽器可以識別這個標籤,而中間快取(CDN)是識別不了的。

所以最好是都通過http頭的方式去傳送快取指令

HTTP Response

讓我們來看一個簡單的http響應

  Accept-Ranges: bytes
  Cache-Control: max-age=3600
  Connection: Keep-Alive
  Content-Length: 4361
  Content-Type: image/png
  Date: Tue, 25 Jul 2017 17:26:16 GMT
  ETag: "1109-554221c5c8540"
  Expires: Tue, 25 Jul 2017 18:26:16 GMT
  Keep-Alive: timeout=5, max=93
  Last-Modified: Wed, 12 Jul 2017 17:26:05 GMT
  Server: Apache
複製程式碼
  • 第2行告訴我們max-age為1個小時
  • 第5行告訴我們這是一個png圖片資源
  • 第7行告訴我們ETag將在1個小時之後去驗證資源是否改變過
  • 第8行,Expires頭會被忽略,因為已經設定了Cache-Control: max-age=3600
  • 第10行,Last-Modified頭展示了圖片資源的最後修改時間

快取的誤區

所以我們已經意識到快取是個好東西,我們應該積極利用它

但是我們希望的是,不需要讓使用者每次都去重新整理(Ctrl + F5)或者清空快取才能看到我們頁面的最新內容

這類快取問題經常困擾者開發者以及使用者,使用者可能會看到一個錯亂的頁面或者是一個行為怪異的按鈕,因為他們當前使用的已經是過期的靜態資原始檔

過期的檔案(Stale Files)

下面這張截圖描述了一個銀行網站的使用者與Chase Support之間的交流,可以看到是有關登陸表單出了問題

image

該使用者很有可能還在使用老的js檔案,這就是導致點選登陸按鈕是表單重置操作而不是表單提交操作

讓我們來探索過期檔案影響我們的另外一種情況。

假設我們在一個叫做app.min.js的檔案中修復了一個bug,並且把它推送到了生產環境

在HTML中,該指令碼是這個樣子

  <script src="assets/js/app.min.js">
複製程式碼

我們的web伺服器給這個js檔案設定了max-age為1周(604800秒)的過期時間

  Cache-Control: private, max-age=604800
複製程式碼

在更新完該js檔案之後,一些使用者仍在反饋說那個bug依然存在。 那這裡到底發生了什麼呢?

  • Bob兩個星期以前訪問了這個網站並且快取一個有問題的app.min.js檔案的副本,由於他的副本已經過期,所以瀏覽器回去請求伺服器,去拿最新的已經修復了bug的版本檔案。
  • Mary在兩天之前訪問了該網站,同樣快取了一個有問題的app.min.js檔案的副本,她的副本由於還沒過期,所以瀏覽器還是會去使用快取中的那個副本

在下一個章節中,我們將會利用一個叫做"cache busting"的技術來解決這些問題和情況

Cache Busting

Cache busting會使一個資原始檔失效,強制瀏覽器去伺服器端重新獲取資料。

我們可以通過改變檔名來命令瀏覽器去避開快取,對於瀏覽器來說,這是一份完全新的資源,所以瀏覽器會去服務端請求最新的資料

Cache busting同樣允許我們設定一個比較長的max-age值針對頻繁改動的資源。Google推薦max-age被設為1年(source

版本號

我們可以給檔名新增一個版本號

  assets/js/app-v2.min.js
複製程式碼

指紋(finerprinting)

我們可以基於檔案內容新增一個指紋值

  assets/js/app-d41d8cd98f00b204e9800998ecf8427e.min.js
複製程式碼

拼接查詢引數

我們可以在檔名的末尾拼接一個查詢引數

  assets/js/app.min.js?version=2
複製程式碼

拼接查詢引數的方式在和代理伺服器互動時有明顯的錯誤(known issues),所以這種方式一般不推薦使用

最佳實踐

推薦

  • 對於靜態資源來說,使用Cache-Control和ETag頭部來控制快取的行為
  • 設定一個比較長的max-age值,以此來獲得瀏覽器快取帶來的好處
  • 針對Cache busting,使用指紋值(或hash值)和版本號的方式

不推薦

  • 使用html元標籤去指定快取的行為
  • 使用查詢引數的方式實現Cache busting

FAQ

如何知道這個檔案時來自於快取中

開啟web開發者工具,在chrome中,這個資訊會顯示在network皮膚中的Size列

如何阻止去快取一個檔案

使用下面的響應頭

  Cache-Control: no-cache, no-store, must-revalidate
複製程式碼

注意

從chrome 66版本開始,chrome已經刪除了chrome://cache,原因可以參考reason1, reason2

相關文章