1. 程式人生 > >http快取,強快取和協商快取

http快取,強快取和協商快取

原文連結:http://caibaojian.com/http-cache-3.html

下面我貼出2道題,大家可以嘗試解答下:·

以下為 page.html 內容:

<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml"><head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>page頁</title></head><body>
    <img src="images/head.png" />
    <a href="page.html">重新訪問page頁</a></body></html>

首次訪問該頁面,頁面中 head.png 響應頭資訊如下:

HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Type: image/png
Last-Modified: Tue, 08 Nov 2016 06:59:00 GMT
Accept-Ranges: bytes
Date: Thu, 10 Nov 2016 02:48:50 GMT
Content-Length: 3534

 

  • 問題1:請問當點選“重新訪問 page 頁”連結重新載入該頁面後, head.png 如何二次載入?
  • 問題2:如果將上述資訊中的 Cache-Control 設定為 private,那麼結果又會如何呢?

HTTP 快取體系

首先我將 Http 快取體系分為以下三個部分:

640wx_fmtpngamptpwebpampwxfrom5ampwx_lazy1

1. 快取儲存策略

用來確定 Http 響應內容是否可以被客戶端快取,以及可以被哪些客戶端快取·

這個策略的作用只有一個,用於決定 Http 響應內容是否可快取到客戶端

對於 Cache-Control 頭裡的 Public、Private、no-cache、max-age 、no-store 他們都是用來指明響應內容是否可以被客戶端儲存的,其中前4個都會快取檔案資料(關於 no-cache 應理解為“不建議使用本地快取”,其仍然會快取資料到本地),後者 no-store 則不會在客戶端快取任何響應資料。另關於 no-cache 和 max-age 有點特別,我認為它是一種混合體,下面我會講到。

通過 Cache-Control:Public 設定我們可以將 Http 響應資料儲存到本地,但此時並不意味著後續瀏覽器會直接從快取中讀取資料並使用,為啥?因為它無法確定本地快取的資料是否可用(可能已經失效),還必須藉助一套鑑別機制來確認才行, 這就是我們下面要講到的“快取過期策略”。

2. 快取過期策略

客戶端用來確認儲存在本地的快取資料是否已過期,進而決定是否要發請求到服務端獲取資料

這個策略的作用也只有一個,那就是決定客戶端是否可直接從本地快取資料中載入資料並展示(否則就發請求到服務端獲取)

剛上面我們已經闡述了資料快取到了本地後還需要經過判斷才能使用,那麼瀏覽器通過什麼條件來判斷呢? 答案是:Expires,Expires 指名了快取資料有效的絕對時間,告訴客戶端到了這個時間點(比照客戶端時間點)後本地快取就作廢了,在這個時間點內客戶端可以認為快取資料有效,可直接從快取中載入展示。

不過 Http 快取頭設計並沒有想象的那麼規矩,像上面提到的 Cache-Control(這個頭是在Http1.1里加進來的)頭裡的 no-cache 和 max-age 就是特例,它們既包含快取儲存策略也包含快取過期策略,以 max-age 為例,他實際上相當於:

Cache-Control:public/private(這裡不太確定具體哪個)
Expires:當前客戶端時間 + maxAge 。

而 Cache-Control:no-cache 和 Cache-Control:max-age=0 (單位是秒)相當·

這裡需要注意的是:

Cache-Control 中指定的快取過期策略優先順序高於 Expires,當它們同時存在的時候,後者會被覆蓋掉。 快取資料標記為已過期只是告訴客戶端不能再直接從本地讀取快取了,需要再發一次請求到伺服器去確認,並不等同於本地快取資料從此就沒用了,有些情況下即使過期了還是會被再次用到,具體下面會講到。

3. 快取對比策略

將快取在客戶端的資料標識發往服務端,服務端通過標識來判斷客戶端 快取資料是否仍有效,進而決定是否要重發資料。

客戶端檢測到資料過期或瀏覽器重新整理後,往往會重新發起一個 http 請求到伺服器,伺服器此時並不急於返回資料,而是看請求頭有沒有帶標識( If-Modified-Since、If-None-Match)過來,如果判斷標識仍然有效,則返回304告訴客戶端取本地快取資料來用即可(這裡要注意的是你必須要在首次響應時輸出相應的頭資訊(Last-Modified、ETags)到客戶端)。至此我們就明白了上面所說的本地快取資料即使被認為過期,並不等於資料從此就沒用了的道理了。

關於 Last-Modified,這個響應頭使用要注意,可能會影響到快取過期策略,具體原因,後面我會通過解答開篇提到的2道題來作說明。

以上就是我所認識的快取策略,下面我將快取策略三要素和常用的幾個快取頭(項)結合一起,讓大家更清晰的認識到它們之間的關係:

640wx_fmtpngamptpwebpampwxfrom5ampwx_lazy3

通過上圖我可以清晰的看到各快取項分別屬於哪個快取策略範疇,這其中有部分重疊,它表明這些快取項具有多重快取策略,所以實際在分析快取頭的時候,除了常規的頭外,我們還需要將這些具有雙重快取策略的項分解開來。·

最後我們回到最開始提到的2道題目,我們來一起分解下:

第一道題:

HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Type: image/png
Last-Modified: Tue, 08 Nov 2016 06:59:00 GMT
Accept-Ranges: bytes
Date: Thu, 10 Nov 2016 02:48:50 GMT
Content-Length: 3534

分析上述 Http 響應頭髮現有以下兩項與快取相關:

Cache-Control: no-cache 
Last-Modified: Tue, 08 Nov 2016 06:59:00 GMT

我們上面講到了 Cache-Control: no-cache 相當於 Cache-Control: max-age=0,且他們都是多重策略頭,我們需將其分解:·

Cache-Control: no-cache 等於 Cache-Control: max-age=0,
接著 Cache-Control: max-age=0 又可分解成:

Cache-Control: public/private (不確定是二者中的哪一個)
Expires: 當前時間

最終我們得到了以下完整的快取策略三要素:

640wx_fmtpngamptpwebpampwxfrom5ampwx_lazy2

所以最終結果是:瀏覽器會再次請求服務端,並攜帶上 Last-Modified 指定的時間去伺服器對比:·

  • a)對比失敗:伺服器返回200並重發資料,客戶端接收到資料後展示,並重新整理本地快取。
  • b)對比成功:伺服器返回304且不重發資料,客戶端收到304狀態碼後從本地讀取快取資料。以下為模擬此種情況下請求後的抓包情況:

原文來自http://caibaojian.com/http-cache-3.html

這道題本身不難,但若認為 no-cache 不會快取資料到本地,那麼你理解起來就會很矛盾,因為如果檔案資料沒有被本地快取,伺服器返回304後將會無法展示出圖片內容,但實際上它是能正常展示的。這道題很好的證明了 no-cache 也會快取資料到本地這一說法。

第二道題:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: image/png
Last-Modified: Tue, 08 Nov 2016 06:59:00 GMT
Accept-Ranges: bytes
Date: Thu, 10 Nov 2016 02:48:50 GMT
Content-Length: 3534

解題思路和上題一樣,首先先找到快取相關項:

Cache-Control: private     
Last-Modified: Tue, 08 Nov 2016 06:59:00 GMT

這時我們會發現根本找不到快取過期策略項,那答案會不會和上面一樣? 一時半會也分析不出答案,那隻能實際測試下了:·

原文來自http://caibaojian.com/http-cache-3.html

再看看 Chrome 瀏覽器下抓包:

770wx_fmtpngamptpwebpampwxfrom7ampwx_lazy7

可以看到,瀏覽器後續請求都直接取的本地快取,看來的確存在某種快取過期策略(根據我上面的快取過期策略理論,瀏覽器如果直接從本地載入快取資料,說明它相信本地快取資料有效,那一定存在某種快取過期判斷條件)。這個問題百思不得其解,困擾了我好久,直到一次偶然的機會我在 Fiddler 響應資訊面板裡的 Caching 選項卡中找到了答案:

660wx_fmtpngamptpwebpampwxfrom6ampwx_lazy6

原來,在沒有提供任何瀏覽器快取過期策略的情況下,瀏覽器遵循一個啟發式快取過期策略:

根據響應頭中2個時間欄位 Date 和 Last-Modified 之間的時間差值,取其值的10%作為快取時間週期。

貼一下Caching面板裡的描述,英語好的同學可以精準翻譯下:

HTTP/1.1 Cache-Control Header is present: private
HTTP Last-Modified Header is present: Tue, 08 Nov 2016 06:59:00 GMT
No explicit HTTP Cache Lifetime information was provided.
Heuristic expiration policies suggest defaulting to: 10% of the delta between Last-Modified and Date.
That's '05:15:02' so this response will heuristically expire 2016/11/11 0:46:01.

最終我們得到了以下完整的快取策略三要素:·

原文來自http://caibaojian.com/http-cache-3.html

最終結果

瀏覽器會根據 Date 和 Last-Modified 之間的時間差值快取一段時間,這段時間內會直接使用本地快取資料而不會再去請求伺服器(強制請求除外),快取過期後,會再次請求服務端,並攜帶上 Last-Modified 指定的時間去伺服器對比並根據服務端的響應狀態決定是否要從本地載入快取資料。

瀏覽器快取主要有兩類:快取協商和徹底快取,也有稱之為協商快取和強快取。

1.強快取:不會向伺服器傳送請求,直接從快取中讀取資源,在chrome控制檯的network選項中可以看到該請求返回200的狀態碼;

2.協商快取:向伺服器傳送請求,伺服器會根據這個請求的request header的一些引數來判斷是否命中協商快取,如果命中,則返回304狀態碼並帶上新的response header通知瀏覽器從快取中讀取資源;

兩者的共同點是,都是從客戶端快取中讀取資源;區別是強快取不會發請求,協商快取會發請求。

快取中header的引數:

強制快取

Expires:response header裡的過期時間,瀏覽器再次載入資源時,如果在這個過期時間內,則命中強快取。

Cache-Control:當值設為max-age=300時,則代表在這個請求正確返回時間(瀏覽器也會記錄下來)的5分鐘內再次載入資源,就會命中強快取。

cache-control除了該欄位外,還有下面幾個比較常用的設定值:

-no-cache:不使用本地快取。需要使用快取協商,先與伺服器確認返回的響應是否被更改,如果之前的響應中存在ETag,那麼請求的時候會與服務端驗證,如果資源未被更改,則可以避免重新下載。

-no-store:直接禁止瀏覽器快取資料,每次使用者請求該資源,都會向伺服器傳送一個請求,每次都會下載完整的資源。

-public:可以被所有的使用者快取,包括終端使用者和CDN等中間代理伺服器。

-private:只能被終端使用者的瀏覽器快取,不允許CDN等中繼快取伺服器對其快取。

協商快取

Last-Modify/If-Modify-Since:瀏覽器第一次請求一個資源的時候,伺服器返回的header中會加上Last-Modify,Last-modify是一個時間標識該資源的最後修改時間;當瀏覽器再次請求該資源時,request的請求頭中會包含If-Modify-Since,該值為快取之前返回的Last-Modify。伺服器收到If-Modify-Since後,根據資源的最後修改時間判斷是否命中快取

Etag:web伺服器響應請求時,告訴瀏覽器當前資源在伺服器的唯一標識(生成規則由伺服器決定)。

If-None-Match:當資源過期時(使用Cache-Control標識的max-age),發現資源具有Etage宣告,則再次向web伺服器請求時帶上頭If-None-Match (Etag的值)。web伺服器收到請求後發現有頭If-None-Match 則與被請求資源的相應校驗串進行比對,決定是否命中協商快取;

ETag和Last-Modified的作用和用法,他們的區別:

1.Etag要優於Last-Modified。Last-Modified的時間單位是秒,如果某個檔案在1秒內改變了多次,那麼他們的Last-Modified其實並沒有體現出來修改,但是Etag每次都會改變確保了精度;

2.在效能上,Etag要遜於Last-Modified,畢竟Last-Modified只需要記錄時間,而Etag需要伺服器通過演算法來計算出一個hash值;

3.在優先順序上,伺服器校驗優先考慮Etag。

瀏覽器快取過程

1.瀏覽器第一次載入資源,伺服器返回200,瀏覽器將資原始檔從伺服器上請求下載下來,並把response header及該請求的返回時間一併快取;

2.下一次載入資源時,先比較當前時間和上一次返回200時的時間差,如果沒有超過cache-control設定的max-age,則沒有過期,命中強快取,不發請求直接從本地快取讀取該檔案(如果瀏覽器不支援HTTP1.1,則用expires判斷是否過期);如果時間過期,則向伺服器傳送header帶有If-None-Match和If-Modified-Since的請求

3.伺服器收到請求後,優先根據Etag的值判斷被請求的檔案有沒有做修改,Etag值一致則沒有修改,命中協商快取,返回304;如果不一致則有改動,直接返回新的資原始檔帶上新的Etag值並返回200;;

4.如果伺服器收到的請求沒有Etag值,則將If-Modified-Since和被請求檔案的最後修改時間做比對,一致則命中協商快取,返回304;不一致則返回新的last-modified和檔案並返回200;;

點選重新整理按鈕或者按F5

瀏覽器直接對本地的快取檔案過期,但是會帶上If-Modifed-Since,If-None-Match,這就意味著伺服器會對檔案檢查新鮮度,返回結果可能是304,也有可能是200.

使用者按Ctrl+F5(強制重新整理)

瀏覽器不僅會對本地檔案過期,而且不會帶上 If-Modifed-Since,If-None-Match,相當於之前從來沒有請求過,返回結果是200.

位址列回車

瀏覽器發起請求,按照正常流程,本地檢查是否過期,然後伺服器檢查新鮮度,最後返回內容。

 

總結

Http 快取設定起來並不複雜,但卻容易被輕視, 今天這篇文章結合2道題目,通過分析、解剖相關快取頭,從系統化角度對 Http 快取機制做了一個較完整的剖析:Http 快取機制實際上是 Http 快取策略三個要素(緯度)相互作用的集合,所以在分析和設定 Http 報文快取頭時,只要能從中精準的分解出快取三要素,我們就能非常準確的預判到快取設定最終能達到的效果。