標籤:

淺談HTTP緩存

什麼是緩存

緩存是指存儲指定資源的一份拷貝,並在下次請求該資源時提供該拷貝的技術,當 web 緩存發現請求的資源已經被存儲,它會攔截請求,返回該資源的拷貝,而不會去源伺服器重新下載。

為什麼需要緩存

首先減少帶寬

使用緩存就不需要再去下載資源,這樣使得網路帶寬大幅減少。

其次緩解伺服器壓力

伺服器不需要給每次請求都返回數據,這樣可以降低伺服器的壓力。

最重要的是提升網站的性能

使用緩存可以減少用戶等待的時間,這樣可以使得網站的性能上升,給用戶更好的體驗。

http 1.0 時代

主要通過 Pragma 和 Expires 這兩個欄位來規範。其實這樣控制緩存很古老,但是有些較舊的客戶端可能不支持 http 1.1,因此這兩個頭還在使用中。

Pragma

在響應頭頭裡面設置這個欄位值是 no-cache 的時候,意味著瀏覽器不會緩存資源,每次都要向伺服器發送一次請求。

Expires

Expires 可以用來設置緩存的時間,它的值是一個格林尼治時間,比如Mon, 20 Jul 2017 10:08:35 GMT 來告訴瀏覽器資源緩存過期時間,如果還沒過該時間點則不發請求

當 Pragma 頭部和 Expires 頭部同時存在的時候 ,起作用的是 Pragma,因此當設置 Pragma 為 no-cache 之後又設置 Expires 的話還是會向伺服器發送請求。

使用這兩個頭部控制緩存當然會存在一些問題, Expires 定義的緩存時間是相對伺服器上的時間而言的,如果客戶端上的時間跟伺服器上的時間不一致,那就沒什麼用了。因為這些問題 ,後來就有了 http 1.1 時代的緩存控制。

http 1.1 時代

主要是通過 Cache-Control 來區分對緩存機制的支持,其中請求頭和響應頭都支持。

Cache-Control

若報文中同時出現了 Expires 和 Cache-Control,則以 Cache-Control 為準。如果同時出現了三者,優先順序從高到低分別是 Pragma -> Cache-Control -> Expires 。

這個欄位的值可以進行一些組合

完全不緩存

緩存中不得存儲任何關於客戶端請求和服務端響應的內容。每次由客戶端發起的請求都會下載完整的響應內容。

Cache-Control: no-cache, no-store, must-revalidate

緩存過期

判斷緩存是否過期的一個最常使用的標誌是 max-age。相對 Expires 而言,max-age 是距離請求發起的時間的秒數。針對應用中那些不會改變的文件,通常可以手動設置一定的時長以保證緩存有效,例如圖片、css、js等靜態資源。

Cache-Control: max-age = 3600

這裡指緩存有效時間是一個小時,一個小時內不發請求,但是一個小時之後需要從伺服器拿到資源。

講到這裡,決定客戶端發送是否發送請求的頭部控制就已經說完了。但是現在還有一個同樣很重要的問題,是不是客戶端發送請求之後,伺服器端就必須要將全部的資源發回去。如果客戶端發請求之後,伺服器端資源並沒有改變呢?。就這個問題,就需要一個緩存校驗欄位。

Last-Modified

伺服器將資源傳遞給客戶端時,會將資源最後更改的時間以「Last-Modified: GMT」的形式加在實體首部上一起返回給客戶端。例如

Last-Modified: Fri, 23 Jul 2017 01:47:00 GMT

這個時候客戶端在請求資源的時候需要發送一個欄位來標記時間,伺服器端會根據這個時間與伺服器上該資源的最終修改時間對比,若一致,則說明該資源沒有被修改過。

客戶端發送的這個欄位一般有兩種:

If-Modified-Since: Last-Modified-value

該欄位告訴伺服器如果客戶端傳來的最後修改時間和伺服器上的一致,直接返回 304 狀態碼即可,當前各大瀏覽器均是使用該欄位來向伺服器傳遞保存的 Last-Modified 的值。如果時間不一致的話,伺服器會返回所有的這個資源並返回 200 狀態碼。

If-Unmodified-Since: Last-Modified-value

該欄位告訴伺服器如果客戶端傳來的最後修改時間和伺服器上的不一致,則直接返回 412 狀態碼(Precondition Failed)。

ETag

使用 Last-Modified 也存在一些問題,比如:

1、一些文件也許會周期性的更改,但是他的內容並不改變(僅僅改變的修改時間),這個時候我們並不希望客戶端認為這個文件被修改了,而重新GET;

2、某些文件修改非常頻繁,比如在秒以下的時間內進行修改,(比方說1s內修改了N次),If-Modified-Since能檢查到的粒度是s級的,這種修改無法判斷(或者說UNIX記錄MTIME只能精確到秒);

3、某些伺服器不能精確的得到文件的最後修改時間。

為解決上面這些問題,又雙叒叕有 ETag 欄位來標註資源的唯一性。

伺服器端在返回的實體裡面定義一個欄位 ETag,這個欄位的值是由某種演算法計算出來的唯一標誌符(比如md5)。

而客戶端需要在請求資源的時候發送一個欄位去把伺服器給它的這個 ETag 值再傳給伺服器,伺服器會根據這個值和自己這個資源的ETag值進行對比,如果不同的話就返回這個資源並返回 200, 如果相同的話只需要返回 304 告訴瀏覽器直接從緩存中讀取就可以了。

客戶端發送的這個欄位一般有兩種:

If-None-Match: ETag-value

告訴服務端如果 ETag 沒匹配上需要重發資源數據,否則直接回送304 和響應報頭即可。

當前各瀏覽器均是使用的該請求首部來向伺服器傳遞保存的 ETag 值。

If-Match: ETag-value

該欄位告訴伺服器如果客戶端傳來的 Etag 值跟伺服器上的不一致,則直接返回 412 狀態碼(Precondition Failed)。

Last-Modified 與 ETag 是可以一起使用的,伺服器會優先驗證 ETag,一致的情況下,才會繼續比對 Last-Modified,最後才決定是否返回 304。

瀏覽器到底怎麼判斷緩存

說了這麼多,這麼多欄位混在一起的話,瀏覽器究竟怎麼去判斷緩存?

強緩存

強緩存就是之前扯的那一大堆 Pragma Expires 以及 Cache-control,命中強緩存之後的狀態碼是 200,後面會跟上 from cache。當然,現在高版本的 chrome 換成了 from disk cache(磁碟緩存) 和 from memory cache(內存緩存)。

協商緩存

協商緩存就是之前扯的 Last-Modified/If-Modified-Since 或者 ETag/If-None-Match,命中協商緩存之後的狀態碼是 304

判別規則

1.瀏覽器首先根據這個資源的頭部判斷是否命中強緩存,如果命中就直接從緩存中載入資源,不會再發請求。

2.如果沒有命中強緩存,就會發一個請求到伺服器端,伺服器根據這個頭部裡面的 If-Modified-Since或者 If-None-Match 來判斷是否命中協商緩存,如果命中就返回 304,瀏覽器會直接從緩存中拿資源。

3.如果協商緩存也沒有命中,就會直返回新的資源,並返回狀態碼 200。

用戶行為

用戶在使用地址欄回車、頁面鏈接跳轉、新開窗口以及前進後退的時候 Cache-Control/Expires 都還接著有效。

當用戶在按F5進行刷新的時候,會忽略 Cache-Control/Expires 的設置,再次發送請求去伺服器請求,而 Last-Modified/Etag 還是有效的,伺服器會根據情況判斷返回304還是200。

當用戶使用Ctrl+F5進行強制刷新的時候,只是所有的緩存機制都將失效,重新從伺服器拉去資源。

實際應用

當在實際應用中時,對於所有可緩存資源,一般需要指定一個 Expires 或 Cache-Control max-age以及一個Last-Modified或ETag。這樣既能控制強緩存,也能控制協商緩存。但是由於 Cache-Control 只支持 http 1.1,如果需要兼容老設備,還是需要 Expires,它既支持 http1.0 也支持 http 1.1。

但是如果對於一個不是經常改變的靜態資源來說,我們可以通過伺服器來告訴瀏覽器是否需要重新請求,這樣就避免了很多 304。

比如我們可以通過在 url 後面加上 md5 參數或者將 md5 參數寫成文件名的一部分來實現。

當資源沒有變動的時候直接使用緩存,不用發起請求,當資源發生變化時,其 url 就會發生變化。網址一經更改,系統就會強制瀏覽器重新抓取資源。

有不足歡迎指出。

參考資料:

HTTP 緩存

淺談瀏覽器http的緩存機制

使用瀏覽器緩存

緩存策略

HTTP緩存控制小結

Web瀏覽器的緩存機制

推薦閱讀:

HTTP為什麼要設計成無狀態的,這樣比有狀態有什麼好處,有哪些協議是典型的有狀態,好處又在哪裡?
關於keep-alive 這個問題?
如何深入地學習《HTTP權威指南》這本書?
如何實現200 from cache?
「 http:// 」 中為何有兩根斜杠?

TAG:前端开发 | HTTP |