每一個Web開發(fā)者需要掌握的HTTP緩存知識
我們每時每刻使用的互聯(lián)網(wǎng)、移動手機APK,都是由各種各樣的資源拼成的HTML(JS、CSS)頁面。這些資源絕大多數(shù)是靜態(tài)資源,他們大多數(shù)都是不需要實時更新的。比如圖片,CSS樣式,JS庫,這些靜態(tài)資源構(gòu)成了互聯(lián)網(wǎng)的框架。比如我們用瀏覽器追蹤(F12->網(wǎng)絡)某知名互聯(lián)網(wǎng)網(wǎng)站首頁:

這些資源文件都很小,但是由于往往需要每次刷新頁面時候都會重新下載,如果有什么方法可以減少對這些圖像、樣式等固定文件的下載,只獲取必須API實時的數(shù)據(jù)然后渲染頁面則用戶訪問肯定會更快更流暢。其實上HTTP協(xié)議本身就提供一個強大的機制來解決這個問題,這就是今天蟲蟲要給大家介紹的HTTP Cache緩存。作為一個Web開發(fā)者必須熟練掌握HTTP的緩存機制,它可以幫我們節(jié)省大量的帶寬、服務器硬件,極大的優(yōu)化我們網(wǎng)站和APP的性能改善用戶體驗。
緩存基礎(chǔ)知識
我們首先從概述緩存基本概念講起。如果我們知道一些資源(圖片,CSS樣式文件等)在一段時間內(nèi)會不改變,則可以用緩存保存這些資源。在設置的時間內(nèi),資源被認為是新鮮(fresh),過了這段時間后設置它的狀態(tài)為過期(stale)。
緩存允許客戶端(例如瀏覽器)盡可能長時間地保留住資源,然后過期后丟棄它再從服務器獲取新版本。為了使緩存機制能生效,需要一種方法來發(fā)送資源的過期時間。
為了解決這個問題,HTTP提供了兩種主要方式。下面我們首先討論***種方法 。

緩存過期Expires和HTTP/1.0緩存的源起
緩存過期Expires
Expires是在HTTP/1.0協(xié)議中引入的,它與Pragma,Last-Modified和If-Modified-Since共同構(gòu)成了HTTP緩存體系。Expires也是我們可以使用的最簡單的HTTP緩存標識頭,表示給定資源過期的時間。我們來看一個例子:

上圖中這個logo的過期時間為"Expires: wed, 15 May 2019 88:07:42 GMT"。如果超過Expires指定的日期,瀏覽器就會嘗試重新獲這個資源取。到期之前瀏覽器都會緩存這個資源,刷新頁面時候并不會再從服務下載。
使用Last-Modified和If-Modified-Since驗證
要做到***的緩存,就要做到僅僅在確定資源更新時候才重新下載它。實現(xiàn)這個目標的一種方法是允許瀏覽器根據(jù)這個資源去詢問服務端。瀏覽器怎么確定目前資源的更新版本呢?有一個HTTP請求If-Modified-Since標識。
假設我們在該資源過期日期5月16日請求該資源,客戶端瀏覽器會發(fā)起請求:

請求頭總包含"If-Modified-Since",它表示瀏覽器已經(jīng)下載過服務器18年12月25日修改過的版本。收到該請求后,服務器會判斷,這個日期之后,圖像是否已經(jīng)更新,如果是,則服務器會響應下載新的圖像下載。否則響應"304 Not Modified" 。

收到此這個響應,瀏覽器就從瀏覽器緩存中讀取資源,不再從服務器下載。通過使用Last-Modified和If-Modified-Since可以確??蛻舳瞬粫貜拖螺d資源,也可以確保服務器端有變化時候,客戶端可以及時更新到***的資源。
用Pragma更新緩存
雖然HTTP/1.0沒方法讓服務器告訴客戶端不緩存特定資源,但通過客戶端請求可以設置HTTP請求頭,不為該資源請求緩存,這個頭方法叫Pragma:
Firefox的調(diào)試工具中,有個"禁用緩存"的復選框,選擇后,HTTP請求就會自動在請求頭中增加"Cache-Control: no-cache"。

該請求就不會使用緩存直接從服務器請求該資源,如下圖,HTTP狀態(tài)碼返回為200而非之前的304。

Pragma最初設計可能為了抓取標題所用。后續(xù)的HTTP/1.1為兼容也嚴格支持該選項。
HTTP/1.1和cache-control
為了克服Expires的局限性,HTTP/1.1中引入了cache-control,極大地增強了開發(fā)人員管理緩存資源的靈活性。cache-control不嚴格依賴日期,而通過一些指令來完成對緩存的管理。
輸入max-age指令
我們可以將max-age指令看成是對Expires的簡單替代方法。比如上面對應于5月15號,一個月過期的日期(259200s),對應的cache-control頭進行響應:

注意,max-age是對應于請求的時間的,所以在緩存生成時開始計算。單位為持續(xù)的秒數(shù),由于不用考慮時區(qū)等因素,這種方法更加簡單準確。
max-age指令可以支持的最多一年的持久時間,可以滿足絕大多數(shù)情況的需求。
使用Etag和If-None-Match更新緩存
HTTP/1.1還引入一種新的Etag緩存更新策略,用來補充If-Modified-Since。我們將實體標記視為服務器唯一標識Etag,響應標頭中使用帶有字母數(shù)字ID的資源版本表示方法:

客戶端下次請求時候,會使用"If-None-Match"頭通知服務器端目前緩存的資源版本的ID特定版本的資源:

如果資源的***版本與上面的實體標簽 ID"5c2209c2-14d05"不匹配,則服務器會響應新版本的ID。否則響應"304 Not Modified"。

為了防止ID名重名,一般會使用散列(比如MD5)來表示正Etag的ID,通過對資源進行計算散列可以保證文件變更和驗證,也能防止資源被篡改。
通過私有和公共方式確保緩存隱私
上面我們討論了,基于瀏覽器的本地HTTP緩存,他在***次請求時候在本地緩存資源?,F(xiàn)實中,我們請求的資源在被下載到本地之前通過一個或多個緩存或"共享"緩存(CDN)。這些緩存或者代理由ISP供應商或者或服務商IT部門提供。在HTTP訪問中,各級中間緩存都會緩存并且瀏覽這些資源。
為了解決這個問題,HTTP/1.1引入了私有緩存和公共緩存控制指令。盡管這些指令還不十分完善,但是,我們可以使用它來設置,某些資源不會被在公共代理中被緩存。
如果多個人共享電腦,他們則可以共享一個緩存。如果資源指定了私有緩存指令,那么瀏覽器只會讓請求他用戶可以使用它。
使用no-store和no-cache限制緩存
HTTP/1.1糾正了HTTP/1.0的Pragma頭的不足,并為Web開發(fā)人員提供了一種可以完全禁用緩存的方法。***個指令no-cache強制緩存在重用之前重新驗證。與must-revalidate不同,no-cache強制瀏覽器在必須重新驗證。
第二個指令,no-store 表示資源在任何情況下都不會被緩存。
限制特定請求的緩存
如果我們想要申請至少在一定時間內(nèi)刷新的資源,該怎么辦?也沒有問題!緩存控制不僅僅可以通過服務器控制客戶端的緩存,相應地客戶端也可以用來指示對某些緩存的限制。
max-age,no-cache和no-store指令都支持在客戶端請求頭中使用。但是注意具體的意義可能是相反的。例如,在請求中指定max-age標頭會通知代理服務器它們不能使用任何早于該標頭指定的持續(xù)時間的緩存響應。
除上面的三個指令外,我們還可以使用四個僅在請求頭中使用的緩存控制指令。
***個是min-fres: 它允許客戶請求在設定時間秒數(shù)內(nèi)會更新的資源。

max-stale指令通知緩存服務器,客戶端愿意接受過期的資源,且過期不超過設定秒數(shù)的緩存。

no-transform指令通知緩存服務器客戶端不希望請求任何版已經(jīng)被修改該過的資源的緩存。
***一個指令only-if-cached通知緩存服務器客戶端只需要一個緩存的響應,且不需要直接請求服務器獲得緩存狀態(tài)。如果緩存無法滿足請求,則應返回504網(wǎng)關(guān)超時響應。
Vary頭和服務器協(xié)商的響應
我們***要說明的瀏覽器如何識別緩存資源,以及服務器協(xié)商怎么進行。
瀏覽器緩存實際上只查看URL和方法,由于幾乎所有可緩存的請求都是GET請求,所以瀏覽器通過URL就能識別資源。客戶端服務器用于協(xié)商的HTTP頭標識,服務器通過Vary標頭傳送給客戶端。例如,客戶端發(fā)出以下請求:

Accept-Encoding頭表示在服務器端支持的情況下允Web服務器采用gzip對響應的資源進行壓縮傳輸。服務器需要響應協(xié)商請求頭時候會使用Vary標識頭,它會將其附加到其響應頭的Vary標頭中,如下圖所示:

這樣,對資源緩存時候不僅應該使用URL的值來緩存響應,而且加上使用請求頭的Accept-Encoding值來進一步限定緩存的鍵。因此使用不同Accept-Encoding標識頭的請求(例如deflate),則其緩存就不用gzip。
總結(jié)
緩存是增強Web服務和應用APP性能的一種非常強大的方法,本文旨在指導Web開發(fā)者和相關(guān)碼農(nóng)了解HTTP緩存,并將其作為一們必須的工具來學習。如果你想需要更深入的學習,可以參考MDN的文檔學習。
