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

這些資源文件都很小,但是由于往往需要每次刷新頁面時(shí)候都會(huì)重新下載,如果有什么方法可以減少對(duì)這些圖像、樣式等固定文件的下載,只獲取必須API實(shí)時(shí)的數(shù)據(jù)然后渲染頁面則用戶訪問肯定會(huì)更快更流暢。其實(shí)上HTTP協(xié)議本身就提供一個(gè)強(qiáng)大的機(jī)制來解決這個(gè)問題,這就是今天蟲蟲要給大家介紹的HTTP Cache緩存。作為一個(gè)Web開發(fā)者必須熟練掌握HTTP的緩存機(jī)制,它可以幫我們節(jié)省大量的帶寬、服務(wù)器硬件,極大的優(yōu)化我們網(wǎng)站和APP的性能改善用戶體驗(yàn)。
緩存基礎(chǔ)知識(shí)
我們首先從概述緩存基本概念講起。如果我們知道一些資源(圖片,CSS樣式文件等)在一段時(shí)間內(nèi)會(huì)不改變,則可以用緩存保存這些資源。在設(shè)置的時(shí)間內(nèi),資源被認(rèn)為是新鮮(fresh),過了這段時(shí)間后設(shè)置它的狀態(tài)為過期(stale)。
緩存允許客戶端(例如瀏覽器)盡可能長(zhǎng)時(shí)間地保留住資源,然后過期后丟棄它再從服務(wù)器獲取新版本。為了使緩存機(jī)制能生效,需要一種方法來發(fā)送資源的過期時(shí)間。
為了解決這個(gè)問題,HTTP提供了兩種主要方式。下面我們首先討論***種方法 。
緩存過期Expires和HTTP/1.0緩存的源起
緩存過期Expires
Expires是在HTTP/1.0協(xié)議中引入的,它與Pragma,Last-Modified和If-Modified-Since共同構(gòu)成了HTTP緩存體系。Expires也是我們可以使用的最簡(jiǎn)單的HTTP緩存標(biāo)識(shí)頭,表示給定資源過期的時(shí)間。我們來看一個(gè)例子:
上圖中這個(gè)logo的過期時(shí)間為"Expires: wed, 15 May 2019 88:07:42 GMT"。如果超過Expires指定的日期,瀏覽器就會(huì)嘗試重新獲這個(gè)資源取。到期之前瀏覽器都會(huì)緩存這個(gè)資源,刷新頁面時(shí)候并不會(huì)再從服務(wù)下載。
使用Last-Modified和If-Modified-Since驗(yàn)證
要做到***的緩存,就要做到僅僅在確定資源更新時(shí)候才重新下載它。實(shí)現(xiàn)這個(gè)目標(biāo)的一種方法是允許瀏覽器根據(jù)這個(gè)資源去詢問服務(wù)端。瀏覽器怎么確定目前資源的更新版本呢?有一個(gè)HTTP請(qǐng)求If-Modified-Since標(biāo)識(shí)。
假設(shè)我們?cè)谠撡Y源過期日期5月16日請(qǐng)求該資源,客戶端瀏覽器會(huì)發(fā)起請(qǐng)求:
請(qǐng)求頭總包含"If-Modified-Since",它表示瀏覽器已經(jīng)下載過服務(wù)器18年12月25日修改過的版本。收到該請(qǐng)求后,服務(wù)器會(huì)判斷,這個(gè)日期之后,圖像是否已經(jīng)更新,如果是,則服務(wù)器會(huì)響應(yīng)下載新的圖像下載。否則響應(yīng)"304 Not Modified" 。
收到此這個(gè)響應(yīng),瀏覽器就從瀏覽器緩存中讀取資源,不再從服務(wù)器下載。通過使用Last-Modified和If-Modified-Since可以確??蛻舳瞬粫?huì)重復(fù)下載資源,也可以確保服務(wù)器端有變化時(shí)候,客戶端可以及時(shí)更新到***的資源。
用Pragma更新緩存
雖然HTTP/1.0沒方法讓服務(wù)器告訴客戶端不緩存特定資源,但通過客戶端請(qǐng)求可以設(shè)置HTTP請(qǐng)求頭,不為該資源請(qǐng)求緩存,這個(gè)頭方法叫Pragma:
Firefox的調(diào)試工具中,有個(gè)"禁用緩存"的復(fù)選框,選擇后,HTTP請(qǐng)求就會(huì)自動(dòng)在請(qǐng)求頭中增加"Cache-Control: no-cache"。
該請(qǐng)求就不會(huì)使用緩存直接從服務(wù)器請(qǐng)求該資源,如下圖,HTTP狀態(tài)碼返回為200而非之前的304。

Pragma最初設(shè)計(jì)可能為了抓取標(biāo)題所用。后續(xù)的HTTP/1.1為兼容也嚴(yán)格支持該選項(xiàng)。
HTTP/1.1和cache-control
為了克服Expires的局限性,HTTP/1.1中引入了cache-control,極大地增強(qiáng)了開發(fā)人員管理緩存資源的靈活性。cache-control不嚴(yán)格依賴日期,而通過一些指令來完成對(duì)緩存的管理。
輸入max-age指令
我們可以將max-age指令看成是對(duì)Expires的簡(jiǎn)單替代方法。比如上面對(duì)應(yīng)于5月15號(hào),一個(gè)月過期的日期(259200s),對(duì)應(yīng)的cache-control頭進(jìn)行響應(yīng):
注意,max-age是對(duì)應(yīng)于請(qǐng)求的時(shí)間的,所以在緩存生成時(shí)開始計(jì)算。單位為持續(xù)的秒數(shù),由于不用考慮時(shí)區(qū)等因素,這種方法更加簡(jiǎn)單準(zhǔn)確。
max-age指令可以支持的最多一年的持久時(shí)間,可以滿足絕大多數(shù)情況的需求。
使用Etag和If-None-Match更新緩存
HTTP/1.1還引入一種新的Etag緩存更新策略,用來補(bǔ)充If-Modified-Since。我們將實(shí)體標(biāo)記視為服務(wù)器唯一標(biāo)識(shí)Etag,響應(yīng)標(biāo)頭中使用帶有字母數(shù)字ID的資源版本表示方法:
客戶端下次請(qǐng)求時(shí)候,會(huì)使用"If-None-Match"頭通知服務(wù)器端目前緩存的資源版本的ID特定版本的資源:
如果資源的***版本與上面的實(shí)體標(biāo)簽 ID"5c2209c2-14d05"不匹配,則服務(wù)器會(huì)響應(yīng)新版本的ID。否則響應(yīng)"304 Not Modified"。
為了防止ID名重名,一般會(huì)使用散列(比如MD5)來表示正Etag的ID,通過對(duì)資源進(jìn)行計(jì)算散列可以保證文件變更和驗(yàn)證,也能防止資源被篡改。
通過私有和公共方式確保緩存隱私
上面我們討論了,基于瀏覽器的本地HTTP緩存,他在***次請(qǐng)求時(shí)候在本地緩存資源?,F(xiàn)實(shí)中,我們請(qǐng)求的資源在被下載到本地之前通過一個(gè)或多個(gè)緩存或"共享"緩存(CDN)。這些緩存或者代理由ISP供應(yīng)商或者或服務(wù)商IT部門提供。在HTTP訪問中,各級(jí)中間緩存都會(huì)緩存并且瀏覽這些資源。
為了解決這個(gè)問題,HTTP/1.1引入了私有緩存和公共緩存控制指令。盡管這些指令還不十分完善,但是,我們可以使用它來設(shè)置,某些資源不會(huì)被在公共代理中被緩存。
如果多個(gè)人共享電腦,他們則可以共享一個(gè)緩存。如果資源指定了私有緩存指令,那么瀏覽器只會(huì)讓請(qǐng)求他用戶可以使用它。
使用no-store和no-cache限制緩存
HTTP/1.1糾正了HTTP/1.0的Pragma頭的不足,并為Web開發(fā)人員提供了一種可以完全禁用緩存的方法。***個(gè)指令no-cache強(qiáng)制緩存在重用之前重新驗(yàn)證。與must-revalidate不同,no-cache強(qiáng)制瀏覽器在必須重新驗(yàn)證。
第二個(gè)指令,no-store 表示資源在任何情況下都不會(huì)被緩存。
限制特定請(qǐng)求的緩存
如果我們想要申請(qǐng)至少在一定時(shí)間內(nèi)刷新的資源,該怎么辦?也沒有問題!緩存控制不僅僅可以通過服務(wù)器控制客戶端的緩存,相應(yīng)地客戶端也可以用來指示對(duì)某些緩存的限制。
max-age,no-cache和no-store指令都支持在客戶端請(qǐng)求頭中使用。但是注意具體的意義可能是相反的。例如,在請(qǐng)求中指定max-age標(biāo)頭會(huì)通知代理服務(wù)器它們不能使用任何早于該標(biāo)頭指定的持續(xù)時(shí)間的緩存響應(yīng)。
除上面的三個(gè)指令外,我們還可以使用四個(gè)僅在請(qǐng)求頭中使用的緩存控制指令。
***個(gè)是min-fres: 它允許客戶請(qǐng)求在設(shè)定時(shí)間秒數(shù)內(nèi)會(huì)更新的資源。
max-stale指令通知緩存服務(wù)器,客戶端愿意接受過期的資源,且過期不超過設(shè)定秒數(shù)的緩存。
no-transform指令通知緩存服務(wù)器客戶端不希望請(qǐng)求任何版已經(jīng)被修改該過的資源的緩存。
***一個(gè)指令only-if-cached通知緩存服務(wù)器客戶端只需要一個(gè)緩存的響應(yīng),且不需要直接請(qǐng)求服務(wù)器獲得緩存狀態(tài)。如果緩存無法滿足請(qǐng)求,則應(yīng)返回504網(wǎng)關(guān)超時(shí)響應(yīng)。
Vary頭和服務(wù)器協(xié)商的響應(yīng)
我們***要說明的瀏覽器如何識(shí)別緩存資源,以及服務(wù)器協(xié)商怎么進(jìn)行。
瀏覽器緩存實(shí)際上只查看URL和方法,由于幾乎所有可緩存的請(qǐng)求都是GET請(qǐng)求,所以瀏覽器通過URL就能識(shí)別資源??蛻舳朔?wù)器用于協(xié)商的HTTP頭標(biāo)識(shí),服務(wù)器通過Vary標(biāo)頭傳送給客戶端。例如,客戶端發(fā)出以下請(qǐng)求:
Accept-Encoding頭表示在服務(wù)器端支持的情況下允Web服務(wù)器采用gzip對(duì)響應(yīng)的資源進(jìn)行壓縮傳輸。服務(wù)器需要響應(yīng)協(xié)商請(qǐng)求頭時(shí)候會(huì)使用Vary標(biāo)識(shí)頭,它會(huì)將其附加到其響應(yīng)頭的Vary標(biāo)頭中,如下圖所示:
這樣,對(duì)資源緩存時(shí)候不僅應(yīng)該使用URL的值來緩存響應(yīng),而且加上使用請(qǐng)求頭的Accept-Encoding值來進(jìn)一步限定緩存的鍵。因此使用不同Accept-Encoding標(biāo)識(shí)頭的請(qǐng)求(例如deflate),則其緩存就不用gzip。
總結(jié)
緩存是增強(qiáng)Web服務(wù)和應(yīng)用APP性能的一種非常強(qiáng)大的方法,本文旨在指導(dǎo)Web開發(fā)者和相關(guān)碼農(nóng)了解HTTP緩存,并將其作為一們必須的工具來學(xué)習(xí)。如果你想需要更深入的學(xué)習(xí),可以參考MDN的文檔學(xué)習(xí)。
























