HTTP緩存協(xié)議實(shí)戰(zhàn)
一、什么是緩存
緩存,又稱作Cache,我們把臨時(shí)存儲(chǔ)數(shù)據(jù)的地方叫做緩存池,緩存池里面放的數(shù)據(jù)就叫做緩存。當(dāng)用戶需要使用這些數(shù)據(jù),首先在緩存中尋找,如果找到了則直接使用。如果找不到,則再去其他數(shù)據(jù)源中查找。
二、為什么要使用緩存技術(shù)
緩存的本質(zhì)就是用空間換時(shí)間,以臨時(shí)存儲(chǔ)的數(shù)據(jù)暫時(shí)代替數(shù)據(jù)源中讀取最新的數(shù)據(jù),這種方式帶來(lái)的好處在不同的場(chǎng)景下是不一樣的。
舉個(gè)例子:
當(dāng)我們需要喝水時(shí),我們會(huì)拿出一個(gè)水杯,去水龍頭接一杯水來(lái)喝。大家可以思考一下,為什么用杯子來(lái)喝水,而不是直接用嘴巴在水龍頭接水喝。
用杯子喝水確實(shí)存在一些既有的問(wèn)題,比如杯子里面的水容易變涼,而水龍頭流出的水確是恒溫的。我們可以想象一下,公司里的同事們排隊(duì)在水龍頭下面喝水的場(chǎng)面,確實(shí)有點(diǎn)滑稽,我們寧愿接受杯子里的水會(huì)變涼這個(gè)既有問(wèn)題。
用杯子喝水有以下幾個(gè)優(yōu)勢(shì):
- 用杯子喝水解決了總是要去找水龍頭的問(wèn)題,因?yàn)楸涌梢砸淮谓痈嗟乃?/li>
- 用杯子喝水更不容易灑出來(lái),不容易浪費(fèi)水。
- 用杯子喝水比趴在水龍頭下喝水更優(yōu)雅。
我們把杯子看成一個(gè)緩存池,杯中的水看成緩存,我們接受了杯中水會(huì)變涼的問(wèn)題,相當(dāng)于犧牲了數(shù)據(jù)的實(shí)時(shí)性。把這些優(yōu)勢(shì)換一個(gè)方式來(lái)描述,于是使用緩存的優(yōu)勢(shì)變成了下面幾個(gè):
- 降低了系統(tǒng)壓力;
- 節(jié)省了資源消耗;
- 優(yōu)化用戶體驗(yàn)。
三、HTTP緩存的作用
網(wǎng)絡(luò)的其中一個(gè)特點(diǎn)就是不穩(wěn)定性,很多用戶受到網(wǎng)速慢的困擾。
服務(wù)器在大量用戶訪問(wèn)的場(chǎng)景下實(shí)時(shí)計(jì)算數(shù)據(jù)也很容易產(chǎn)生瓶頸,導(dǎo)致服務(wù)變慢。從緩存技術(shù)具備的優(yōu)勢(shì)來(lái)看,很適合解決網(wǎng)絡(luò)服務(wù)不穩(wěn)定的問(wèn)題。
四、HTTP緩存協(xié)議
協(xié)議是溝通過(guò)程中雙方都遵守并且使用的一種規(guī)則。舉個(gè)栗子,客戶端和服務(wù)器兩位大兄弟在新款機(jī)型問(wèn)題上進(jìn)行了幾次溝通?
- 客戶端:大哥,新款nex發(fā)布沒(méi)?
- 服務(wù)器:老弟,還沒(méi)發(fā),你記住,別老來(lái)問(wèn)我!
一周后......
- 客戶端:大哥,我又來(lái)了,最新情況如何?
- 服務(wù)器:跟上次一樣。
一個(gè)月后.....
- 客戶端:大哥,這都一個(gè)月了,怎么樣了啊?!
- 服務(wù)器:已經(jīng)開(kāi)售啦!
在這個(gè)例子里面,客戶端與服務(wù)端溝通過(guò)程中就遵循某種規(guī)則,我們來(lái)看一下:
- 數(shù)據(jù)部分:機(jī)型的內(nèi)容;
- 協(xié)議部分:1)別老來(lái)問(wèn)我,2)最新情況如何,3)跟上次一樣。
服務(wù)端說(shuō)的這些話,客戶端都能看懂并且明白這些話中所蘊(yùn)含的意義,這就是客戶端與服務(wù)端之間達(dá)成的某種通訊協(xié)議。
4.1 HTTP消息頭
在介紹HTTP緩存協(xié)議之前,我們先來(lái)了解一下HTTP消息頭的基礎(chǔ)知識(shí)。我們對(duì)HTTP/HTTPS的數(shù)據(jù)請(qǐng)求都比較熟悉,在HTTP的數(shù)據(jù)請(qǐng)求中有一種信息叫做“頭部信息”。
頭部信息是在客戶端請(qǐng)求或者服務(wù)端響應(yīng)是傳遞給對(duì)方的一種信息。我們來(lái)看一下HTTP協(xié)議的組成部分。
- HTTP 請(qǐng)求的組成:狀態(tài)行、請(qǐng)求頭、消息主體三部分組成。
- HTTP 響應(yīng)的組成:狀態(tài)行、響應(yīng)頭、響應(yīng)正文。
其中,請(qǐng)求頭和響應(yīng)頭就是我們這里說(shuō)的“頭部信息”或者又叫“消息頭”。那么頭部信息有什么作用呢?
4.2 請(qǐng)求頭
如圖所示:
4.3 響應(yīng)頭
如圖所示:
我們今天要講的緩存協(xié)議——Cache-Control, 也是放在消息頭中進(jìn)行控制的。
4.4 緩存協(xié)議
在第一節(jié)中,我們介紹了使用緩存技術(shù)的三個(gè)優(yōu)勢(shì),在網(wǎng)絡(luò)數(shù)據(jù)交換的過(guò)程中,使用緩存技術(shù)同樣有這三個(gè)優(yōu)勢(shì)。
(1)降低系統(tǒng)壓力
使用HTTP緩存技術(shù),可以有效的降低服務(wù)端的壓力,服務(wù)端不需要實(shí)時(shí)計(jì)算數(shù)據(jù)并返回?cái)?shù)據(jù)。
(2)節(jié)省資源消耗
使用HTTP緩存技術(shù),可以有效的避免大量的重復(fù)數(shù)據(jù)傳輸,降低流量消耗。
(3)優(yōu)化用戶體驗(yàn)
使用HTTP緩存技術(shù),本地緩存可以以較快的速度加載,減少用戶等待時(shí)間。
在講HTTP協(xié)議如何實(shí)現(xiàn)緩存之前,我們先來(lái)講一下緩存類型。HTTP緩存一般被分為兩類,私有緩存和共享緩存。
4.4.1 私有緩存
緩存被存儲(chǔ)在設(shè)備本地或者獨(dú)立的賬戶體系下,僅供當(dāng)前用戶使用,他可以用來(lái)降低服務(wù)器壓力,提高用戶體驗(yàn),甚至實(shí)現(xiàn)離線瀏覽。
4.4.2 共享緩存
共享緩存是在代理服務(wù)器或者其他中間服務(wù)器中進(jìn)行二次緩存的數(shù)據(jù),一般這里我們常見(jiàn)的是CDN,這種緩存可以被多個(gè)用戶訪問(wèn),用來(lái)減少流量和延遲。
對(duì)于一次網(wǎng)絡(luò)數(shù)據(jù)交互,本地緩存和共享緩存可以同時(shí)存在,HTTP協(xié)議中規(guī)定了如何進(jìn)行控制這些緩存的使用和更新。在HTTP中,控制緩存有兩種字段:一個(gè)是Pragma;另一個(gè)是cache-control。
Pragma 是一個(gè)在 HTTP/1.0 中定義的字段,從mozilla官網(wǎng)文檔上查詢,Pragma 支持現(xiàn)有的幾乎所有瀏覽器。
但是作為舊時(shí)代的產(chǎn)物,cache-control正在逐步的替代它。cache-control 是從 HTTP/1.1開(kāi)始引入的協(xié)議。有些前端開(kāi)發(fā)者會(huì)選擇在cache-control的基礎(chǔ)上增加Pragma 來(lái)向下兼容,事實(shí)上android的webview即支持Pragma 又支持cache-control。
而當(dāng)Pragma 和 cache-control 同時(shí)出現(xiàn)時(shí),Pragma 的優(yōu)先級(jí)大于cache-control 當(dāng)然,這不是今天的重點(diǎn),有興趣的同學(xué)可以自行查閱相關(guān)資料。
下面我們就具體的來(lái)講一下cache-control緩存協(xié)議的具體定義。HTTP協(xié)議規(guī)定,服務(wù)端通過(guò)響應(yīng)頭中的cache-control將緩存方式通知給客戶端,同時(shí)客戶端也可以通過(guò)請(qǐng)求頭中的cache-control來(lái)將自己的緩存需求通知給服務(wù)器。
4.4.3 響應(yīng)頭中的cache-control
響應(yīng)頭中的cache-control一般有如下取值:
- Cache-control: public
- Cache-control: private
- Cache-control: no-cache
- Cache-control: no-store
- Cache-control: no-transform
- Cache-control: must-revalidate
- Cache-control: proxy-revalidate
- Cache-Control: max-age=
- Cache-control: s-maxage=
4.4.4 請(qǐng)求頭中的cache-control
請(qǐng)求頭中的cache-control一般有如下取值:
- Cache-Control: max-age=
- Cache-Control: max-stale[=]
- Cache-Control: min-fresh=
- Cache-control: no-cache
- Cache-control: no-store
- Cache-control: no-transform
- Cache-control: only-if-cached
mozilla開(kāi)發(fā)者網(wǎng)站將這些取值分為如下幾個(gè)類別進(jìn)行描述。
4.4.5 可緩存性控制
(1) public
表明響應(yīng)可以被任何對(duì)象(包括:發(fā)送請(qǐng)求的客戶端,代理服務(wù)器,等等)緩存,即使是通常不可緩存的內(nèi)容。例如:
- 該響應(yīng)沒(méi)有max-age指令或Expires消息頭;
- 該響應(yīng)對(duì)應(yīng)的請(qǐng)求方法是 POST 。
(2) private
表明響應(yīng)只能被單個(gè)用戶緩存,不能作為共享緩存(即代理服務(wù)器不能緩存它)。私有緩存可以緩存響應(yīng)內(nèi)容,比如:對(duì)應(yīng)用戶的本地瀏覽器。
(3) no-cache
在發(fā)布緩存副本之前,強(qiáng)制要求緩存把請(qǐng)求提交給原始服務(wù)器進(jìn)行驗(yàn)證(協(xié)商緩存驗(yàn)證)。
(4) no-store
緩存不應(yīng)存儲(chǔ)有關(guān)客戶端請(qǐng)求或服務(wù)器響應(yīng)的任何內(nèi)容,即不使用任何緩存。
4.4.6 緩存有效性控制
(1) max-age=
設(shè)置緩存存儲(chǔ)的最大周期,超過(guò)這個(gè)時(shí)間緩存被認(rèn)為過(guò)期(單位秒)。與Expires相反,時(shí)間是相對(duì)于請(qǐng)求的時(shí)間。
(2) s-maxage=
覆蓋max-age或者Expires頭,但是僅適用于共享緩存(比如各個(gè)代理),私有緩存會(huì)忽略它。
(3) max-stale[=]
表明客戶端愿意接收一個(gè)已經(jīng)過(guò)期的資源。可以設(shè)置一個(gè)可選的秒數(shù),表示響應(yīng)不能已經(jīng)過(guò)時(shí)超過(guò)該給定的時(shí)間。
(4) min-fresh=
表示客戶端希望獲取一個(gè)能在指定的秒數(shù)內(nèi)保持其最新?tīng)顟B(tài)的響應(yīng)。
(5) stale-while-revalidate=
表明客戶端愿意接受陳舊的響應(yīng),同時(shí)在后臺(tái)異步檢查新的響應(yīng)。秒值指示客戶愿意接受陳舊響應(yīng)的時(shí)間長(zhǎng)度。
(6) stale-if-error=
表示如果新的檢查失敗,則客戶愿意接受陳舊的響應(yīng)。秒數(shù)值表示客戶在初始到期后愿意接受陳舊響應(yīng)的時(shí)間。
4.4.7 重新驗(yàn)證和重新加載
(1) must-revalidate
一旦資源過(guò)期(比如已經(jīng)超過(guò)max-age),在成功向原始服務(wù)器驗(yàn)證之前,緩存不能用該資源響應(yīng)后續(xù)請(qǐng)求。
(2) proxy-revalidate
與must-revalidate作用相同,但它僅適用于共享緩存(例如代理),并被私有緩存忽略。
4.4.8 其他控制
(1) no-transform
不得對(duì)資源進(jìn)行轉(zhuǎn)換或轉(zhuǎn)變。Content-Encoding、Content-Range、Content-Type等HTTP頭不能由代理修改。例如,非透明代理或者如Google's Light Mode可能對(duì)圖像格式進(jìn)行轉(zhuǎn)換,以便節(jié)省緩存空間或者減少緩慢鏈路上的流量。no-transform指令不允許這樣做。
(2) only-if-cached
表明客戶端只接受已緩存的響應(yīng),并且不要向原始服務(wù)器檢查是否有更新的拷貝。
從這些描述以及分類中可以看出來(lái),可緩存性控制+緩存有效性控制+其他控制 ,這幾個(gè)控制維度是不沖突的,可以共同實(shí)現(xiàn)緩存的實(shí)現(xiàn)方式限定。
事實(shí)上cache-control確實(shí)是可以同時(shí)接受多個(gè)取值的,多個(gè)不同的指令可以搭配使用來(lái)對(duì)緩存進(jìn)行控制。如果使用了相矛盾的多個(gè)指令取值,那么指令就會(huì)按照優(yōu)先級(jí)進(jìn)行緩存控制。
比如no-store和max-age這兩種在行為上矛盾的指令取值放在一起下發(fā),那么終端就只會(huì)按照no-store來(lái)進(jìn)行緩存。
4.4.9 協(xié)議工作實(shí)戰(zhàn)分析
專業(yè)的運(yùn)維人員,一定很了解這些描述所表達(dá)的意思。然而作為客戶端或者前端的我們,光是看這些專業(yè)術(shù)語(yǔ),可能很難理解不同配置取值下實(shí)際的緩存效果。
因此為了搞明白取值對(duì)實(shí)際緩存效果的影響。我使用兩臺(tái)電腦,分別搭建了一個(gè)靜態(tài)資源服務(wù)器(源服務(wù)器),一個(gè)代理服務(wù)器,通過(guò)模擬線上服務(wù)器的場(chǎng)景,來(lái)對(duì)常見(jiàn)的幾種緩存控制模式進(jìn)行驗(yàn)證。nginx的安裝比較簡(jiǎn)單,此處不在贅述。
(1) 靜態(tài)資源服務(wù)器(源服務(wù)器)
windows+nginx,配置如下:
(2) 代理服務(wù)器
windows+nginx,配置如下:
服務(wù)器搭建完成后,我們逐個(gè)改變cache-control的取值,來(lái)模擬幾種常見(jiàn)的緩存控制模式,來(lái)幫助大家理解這些取值,加深印象。在日常的使用過(guò)程中,cache-control更多的是被放在響應(yīng)頭中來(lái)控制瀏覽的緩存行為,因此我們先來(lái)驗(yàn)證一下cache-control放在響應(yīng)頭中的情況。
場(chǎng)景:靜態(tài)資源服務(wù)器(源服務(wù)器)的響應(yīng)頭中沒(méi)有添加任何cache-control標(biāo)識(shí)。沒(méi)有添加標(biāo)識(shí),其實(shí)對(duì)應(yīng)的就是public標(biāo)識(shí)。
public通常可以看成默認(rèn)值,如果我們不在響應(yīng)中添加任何有關(guān)Cache-control的header,那么這次響應(yīng)默認(rèn)的處理邏輯就類似Cache-control:public。
(這里使用"通常","類似"這種不確定的字眼,需要解釋一下,如果服務(wù)器返回了302或者307這種重定向響應(yīng)時(shí),添加Cache-control:public會(huì)讓瀏覽器把重定向響應(yīng)也緩存起來(lái),但是如果不添加Cache-control,則不會(huì)緩存,也存在不同網(wǎng)絡(luò)框架或者瀏覽器做不同處理的可能性)。
public的意思是瀏覽器或者代理服務(wù)器都可以對(duì)靜態(tài)資源服務(wù)器(源服務(wù)器)返回的資源進(jìn)行緩存。使用瀏覽器直接訪問(wèn)靜態(tài)資源服務(wù)器(不經(jīng)過(guò)代理服務(wù)器)。
(3) 第一次訪問(wèn)
第一次訪問(wèn),服務(wù)器返回了200狀態(tài)并將靜態(tài)html傳回給客戶端。同時(shí),服務(wù)器還帶上了ETag和Last-Modified兩個(gè)字段,我們先繼續(xù)往下看。此時(shí)客戶端做了幾件事情:
- 緩存了靜態(tài)資源的內(nèi)容;
- 記錄了該內(nèi)容的ETag和Last-Modified。
(4) 點(diǎn)擊瀏覽器刷新按鈕
點(diǎn)擊瀏覽器的刷新按鈕后,客戶端瀏覽器帶上了第一次請(qǐng)求時(shí)返回的ETag和Last-Modified再次請(qǐng)求了服務(wù)器。服務(wù)端通過(guò)這兩個(gè)參數(shù)認(rèn)為客戶端已經(jīng)緩存了資源,服務(wù)器不需要再次返回資源了。于是服務(wù)器返回了304。
那如果有代理服務(wù)器摻和進(jìn)來(lái)又是一個(gè)什么樣的場(chǎng)景呢?還記得我們之前配置的那臺(tái)代理服務(wù)器嗎,我們將代理服務(wù)的代理緩存時(shí)間設(shè)定在了10秒。
(5) 第一次訪問(wèn)
(6) 點(diǎn)擊瀏覽器刷新按鈕
點(diǎn)擊瀏覽器的刷新按鈕時(shí),客戶端瀏覽器帶上了第一次請(qǐng)求時(shí)返回的ETag和Last-Modified再次請(qǐng)求了服務(wù)器。服務(wù)端通過(guò)這兩個(gè)參數(shù)認(rèn)為客戶端已經(jīng)緩存了資源,服務(wù)器不需要再次返回資源了,于是服務(wù)器返回了304。
注意這次刷新時(shí),ngiux-cache-status的狀態(tài)時(shí)HIT標(biāo)識(shí)這次命中了代理服務(wù)器的緩存,這次的客戶端緩存有效性判斷是由代理服務(wù)器完成的。
(7) 10秒后的第三次刷新
前面說(shuō)了 代理服務(wù)器的緩存有效期,我們配置成了10秒。第三次刷新時(shí)服務(wù)器依然返回了304,資源不需要更新。
但是這次刷新時(shí),ngiux-cache-status的狀態(tài)是EXPIRED,這標(biāo)識(shí)代理服務(wù)器的緩存已經(jīng)失效了,不能用來(lái)做有效性判斷, 這個(gè)時(shí)候,代理服務(wù)器就會(huì)將這次的請(qǐng)求透?jìng)鹘o靜態(tài)資源服務(wù)器(源服務(wù)器),通過(guò)靜態(tài)資源服務(wù)器(源服務(wù)器)完成的緩存的有效性判斷。
在這個(gè)過(guò)程中,代理服務(wù)器又會(huì)對(duì)自己的緩存進(jìn)行更新,于是有了下面第四次。
(8) 第四次刷新
邏輯圖如下:
通過(guò)這四次請(qǐng)求,我們能夠清晰的了解了整個(gè)的邏輯,代理服務(wù)器在某些情況下直接代替了靜態(tài)資源服務(wù)器(源服務(wù)器)。因?yàn)閜ublic指令告訴代理服務(wù)器,可以緩存數(shù)據(jù),于是代理服務(wù)器按照配置將數(shù)據(jù)緩存了10秒,超過(guò)10秒后就會(huì)重新將請(qǐng)求轉(zhuǎn)發(fā)給靜態(tài)資源服務(wù)器(源服務(wù)器),同時(shí)重新進(jìn)行緩存。
這時(shí)候有的同學(xué)會(huì)問(wèn)了,代理服務(wù)器有緩存的時(shí)間限制,在沒(méi)有達(dá)到時(shí)間限制之前是不會(huì)重新請(qǐng)求靜態(tài)資源服務(wù)器(源服務(wù)器)的,這時(shí)候就降低了靜態(tài)資源服務(wù)器(源服務(wù)器)的壓力。那為什么在上面的例子里面,瀏覽器一直在請(qǐng)求代理服務(wù)器呢?
這里要跟大家說(shuō)明一下,在上述的案例中,我們其實(shí)一直在點(diǎn)擊瀏覽器的刷新按鈕,刷新按鈕的意思就是讓客戶端瀏覽器重新請(qǐng)求服務(wù)器來(lái)驗(yàn)證緩存內(nèi)容的有效性。
大家仔細(xì)看下所有截圖中的Request-Header 是不是都有一個(gè)max-age = 0 ,這個(gè)指令就是瀏覽器在刷新請(qǐng)求時(shí),告訴服務(wù)器——我本地的緩存可能到期了,你要幫我驗(yàn)證一下。如果你嘗試將網(wǎng)址復(fù)制到瀏覽器的新窗口然后點(diǎn)擊回車打開(kāi)url,而不是點(diǎn)擊刷新按鈕,這個(gè)時(shí)候就會(huì)像下圖這樣。
瀏覽器不會(huì)訪問(wèn)網(wǎng)絡(luò),注意看Status Code 那里括號(hào)里面的備注,Status Code: 200 OK (from disk cache) 表示這次的響應(yīng)數(shù)據(jù),其實(shí)是從磁盤緩存里面拿的。
在android系統(tǒng)的WebView中,正常情況下是沒(méi)有提供刷新按鈕的(除非開(kāi)發(fā)者自己寫一個(gè))那么這種場(chǎng)景下webview就不會(huì)請(qǐng)求網(wǎng)絡(luò),每次都從磁盤緩存中拿數(shù)據(jù),對(duì)應(yīng)在抓包時(shí),就看不到網(wǎng)絡(luò)請(qǐng)求。
了解了整個(gè)邏輯之后,我們?cè)賮?lái)看mozilla提供的描述,再結(jié)合上述的邏輯,是不是就已經(jīng)有了初步的概念了。
4.4.10 在響應(yīng)頭中的可緩存性控制
(1) public
表明響應(yīng)可以被任何對(duì)象(包括:發(fā)送請(qǐng)求的客戶端,代理服務(wù)器,等等)緩存,即使是通常不可緩存的內(nèi)容。例如:
- 該響應(yīng)沒(méi)有max-age指令或Expires消息頭;
- 該響應(yīng)對(duì)應(yīng)的請(qǐng)求方法是 POST 。
這個(gè)其實(shí)就是我們剛剛驗(yàn)證的場(chǎng)景。
(2) private
表明響應(yīng)只能被單個(gè)用戶緩存,不能作為共享緩存(即代理服務(wù)器不能緩存它)。私有緩存可以緩存響應(yīng)內(nèi)容,比如:對(duì)應(yīng)用戶的本地瀏覽器。
如果使用private,代表著這個(gè)資源,可以被私有用戶緩存,緩存不會(huì)被共享,實(shí)際測(cè)試,當(dāng)標(biāo)注為private時(shí),瀏覽器可以進(jìn)行緩存,但是代理服務(wù)器不會(huì)緩存這個(gè)資源。有些材料里面提到,private是可以指定緩存的user_id的,這種屬于比較復(fù)雜的配置了,有興趣的同學(xué)可以研究下。
(3) no-cache
強(qiáng)制要求緩存把請(qǐng)求提交給原始服務(wù)器進(jìn)行驗(yàn)證(協(xié)商緩存驗(yàn)證)。
這是一個(gè)服務(wù)端經(jīng)常使用的指令,也是一個(gè)比較容易與no-store混淆的指令,許多前端和客戶端的同學(xué)都認(rèn)為當(dāng)服務(wù)端的響應(yīng)中標(biāo)注了no-cache,那么客戶端就不會(huì)進(jìn)行緩存,每次都會(huì)請(qǐng)求服務(wù)器獲取新的內(nèi)容。其實(shí)只說(shuō)對(duì)了一半。
在這種場(chǎng)景下,瀏覽器確實(shí)會(huì)每次都請(qǐng)求服務(wù)器,但是并不意味著瀏覽器不緩存資源,mozilla的官方解釋是“把請(qǐng)求提交給原始服務(wù)器進(jìn)行驗(yàn)證”如果緩存沒(méi)有問(wèn)題,那么服務(wù)器就會(huì)返回304,讓瀏覽器繼續(xù)使用自己本地的緩存”。
(4) no-store
不應(yīng)存儲(chǔ)有關(guān)客戶端請(qǐng)求或服務(wù)器響應(yīng)的任何內(nèi)容,即不使用任何緩存。
這個(gè)指令就是完全不使用本地緩存,在這種模式下,客戶端不會(huì)記錄任何緩存,包括Etag等,每次都會(huì)重新發(fā)起請(qǐng)求,并且得到200響應(yīng)和對(duì)應(yīng)的數(shù)據(jù)。如果前端希望自己的網(wǎng)頁(yè)完全不被緩存,那么可以試下這個(gè)指令。
以上指令解決了客戶端以及代理服務(wù)器能不能緩存的問(wèn)題,有的同學(xué)就會(huì)有疑問(wèn)了,如果讓客戶端進(jìn)行本地緩存,那么正常情況下如果不去手動(dòng)刷新,客戶端是不會(huì)請(qǐng)求服務(wù)器的,前端發(fā)新版后,客戶端如何選擇合適的時(shí)機(jī)請(qǐng)求服務(wù)器呢?
這個(gè)時(shí)候就要用到緩存有效性控制。瀏覽器和服務(wù)器之間的緩存校驗(yàn)是相互的 ,也就是說(shuō)服務(wù)器可以告知瀏覽器 這個(gè)緩存你能用多久,能保留多久。
先來(lái)看下服務(wù)器是如何通知客戶端緩存可以用多久的。緩存有效性控制指令一般會(huì)與可緩存性指令共同下發(fā)給客戶端。
我們?cè)趕erver的header中增加max-age屬性,同時(shí),為了避免代理服務(wù)器提前將代理緩存置為無(wú)效,我們將代理服務(wù)器的緩存有效時(shí)間設(shè)置到100秒,超過(guò)靜態(tài)資源服務(wù)器(源服務(wù)器)設(shè)置的max-age = 20。
第一次請(qǐng)求:
我們使用刷新功能刷新瀏覽器,在20秒內(nèi)我們持續(xù)得到HIT的狀態(tài),說(shuō)明命中了代理服務(wù)器的緩存。20秒之后 代理服務(wù)器返回EXPIRED 說(shuō)明代理服務(wù)器響應(yīng)了靜態(tài)資源服務(wù)器(源服務(wù)器)的指示,讓本地代理失效了,而代理服務(wù)器設(shè)置的100秒本地緩存時(shí)間,這個(gè)時(shí)候被忽略了。
這次我們依然使用了瀏覽器的刷新功能來(lái)強(qiáng)制瀏覽器去服務(wù)器校驗(yàn)緩存的有效性,也就是說(shuō)其實(shí)在上面的測(cè)試中,瀏覽器每次都是自己忽略max-age,去訪問(wèn)服務(wù)器的。
結(jié)論:新增的max-age,控制了代理服務(wù)器保留的緩存時(shí)長(zhǎng),本地代理會(huì)忽略配置中的緩存時(shí)長(zhǎng)直接使用靜態(tài)資源服務(wù)器(源服務(wù)器)下發(fā)的max-age作為緩存時(shí)長(zhǎng)。
下面為了測(cè)試瀏覽器如何使用本地緩存,我們用android上的webview來(lái)進(jìn)行實(shí)驗(yàn),因?yàn)閣ebview是沒(méi)有刷新按鈕的(除非開(kāi)發(fā)者自己造一個(gè))。
第一次打開(kāi):
打開(kāi)后在后面我們每隔兩秒再打開(kāi)一次:
可以看到20秒內(nèi),webview都沒(méi)有重復(fù)請(qǐng)求服務(wù)器下載站點(diǎn)的index.html,在上面的截圖中,每顯示一個(gè)favicon.ico就是我打開(kāi)一次站點(diǎn)鏈接,因?yàn)槲覜](méi)有在源服務(wù)器中配置favicon.ico,所以每次打開(kāi),webview都在找服務(wù)器下載這個(gè)資源。
超過(guò)20秒后,webview發(fā)起了請(qǐng)求,此次服務(wù)器返回了304,要求客戶端繼續(xù)使用緩存進(jìn)行展示,這次max-age指令體現(xiàn)出來(lái)了。而webview在這次校驗(yàn)之后,會(huì)將本地的緩存再延長(zhǎng)20秒的有效期,在下一個(gè)20秒后,webview才會(huì)再次發(fā)起新的緩存驗(yàn)證請(qǐng)求。
總結(jié):客戶端webview會(huì)在public指令下緩存index.html,然后在max-age要求限制的時(shí)間內(nèi),都不會(huì)發(fā)起任何網(wǎng)絡(luò)請(qǐng)求來(lái)校驗(yàn)資源。
在官網(wǎng)商城的一個(gè)案例中,網(wǎng)站上線后,運(yùn)維沒(méi)有配置任何cache-control協(xié)議,在默認(rèn)public的模式下,客戶端webview一直使用本地緩存,開(kāi)發(fā)人員發(fā)現(xiàn)前端發(fā)版后,客戶端無(wú)法及時(shí)更新頁(yè)面。于是在每一個(gè)打開(kāi)的網(wǎng)址后面手動(dòng)拼接了一個(gè)時(shí)間戳,來(lái)強(qiáng)制改變網(wǎng)址,讓瀏覽器的緩存失效,其實(shí)只要使用nocache或者max-age作為cache-control協(xié)議就可以解決該問(wèn)題。
除了max-age,cache-control在可緩存性控制指令的基礎(chǔ)上還可以增加如下幾個(gè)控制;
(1) no-transform
源服務(wù)端告訴客戶端,客戶端在緩存數(shù)據(jù)的時(shí)候不可以對(duì)文件進(jìn)行改變,比如壓縮,格式修改等...
(2) must-revalidate
源服務(wù)端告知客戶端,一旦資源過(guò)期,在向靜態(tài)資源服務(wù)器(源服務(wù)器)發(fā)起驗(yàn)證之前,該資源不得使用。
(3) proxy-revalidate
與must-revalidate作用相同,僅僅適用于共享緩存(例如代理)。
(4) max-age=
靜態(tài)資源服務(wù)器(源服務(wù)器)告知客戶端,X秒內(nèi),客戶端都不需要對(duì)緩存進(jìn)行校驗(yàn),可以直接使用。
(5) s-maxage=
靜態(tài)資源服務(wù)器(源服務(wù)器)告知代理服務(wù)器,代理服務(wù)器可以在X秒內(nèi)使用該緩存,并且不需要進(jìn)行校驗(yàn),直接可以使用,但是客戶端會(huì)忽略這個(gè)指令。
問(wèn)題又來(lái)了,在驗(yàn)證的過(guò)程中,服務(wù)器是怎么判斷瀏覽器的緩存是否有效的呢?
客戶端瀏覽器在有機(jī)會(huì)訪問(wèn)服務(wù)器的時(shí)候就會(huì)告訴服務(wù)器,我的本地緩存是什么時(shí)候的數(shù)據(jù)(Last-Modified),數(shù)據(jù)內(nèi)容是什么(ETag),這樣服務(wù)端就能根據(jù)這兩個(gè)值來(lái)判斷客戶端的緩存是否是有效的。
我們來(lái)模擬一次前端的發(fā)版操作,將index.html的內(nèi)容進(jìn)行修改;然后使用android webview進(jìn)行請(qǐng)求。
這一次服務(wù)器毫不吝嗇的返回了200和數(shù)據(jù)。大家仔細(xì)觀察請(qǐng)求頭和響應(yīng)頭;
請(qǐng)求頭中的if-None-Match 其實(shí)就是保持的上次服務(wù)器返回的ETag;
請(qǐng)求頭中的if-Modified-Match 其實(shí)就是保持的上次服務(wù)器返回的Last-Modified;
現(xiàn)在這兩個(gè)值跟服務(wù)端的都對(duì)應(yīng)不上了,所以服務(wù)器返回了最新的數(shù)據(jù)和200狀態(tài)碼,并且?guī)狭俗钚碌腅tag,Last-Modified。而客戶端下一次請(qǐng)求時(shí),就會(huì)帶上最新的Etag和Last-Modified。
在某些情況下,服務(wù)器返回的校驗(yàn)字段會(huì)不完整,比如缺失了Etag和Last-Modified中某一個(gè),那么這種情況下的緩存校驗(yàn)就會(huì)存在風(fēng)險(xiǎn)。
在PC官網(wǎng)的一個(gè)案例中,源站點(diǎn)服務(wù)器返回了靜態(tài)資源的Etag和Last-Modified,但是代理服務(wù)器,也就是CDN廠商在返回時(shí)將Etag給清除了,導(dǎo)致缺少了Etag校驗(yàn)。在正常情況下,服務(wù)器只使用文件的最后一次修改時(shí)間來(lái)做緩存校驗(yàn)也沒(méi)啥問(wèn)題。但是有這么一個(gè)用戶,他的瀏覽器內(nèi)緩存的靜態(tài)資源損壞了,瀏覽器每次讀取出來(lái)的資源無(wú)法使用,也就無(wú)法正常渲染頁(yè)面,但是在每次與服務(wù)器校驗(yàn)資源的時(shí)候,服務(wù)器依然會(huì)告知客戶端304(緩存可用)。這種場(chǎng)景下,只要源站點(diǎn)服務(wù)器不進(jìn)行資源更新,也就是不變動(dòng)這個(gè)Last-Modified,那么用戶將永遠(yuǎn)打不開(kāi)這個(gè)文件。
講完了這些,差不多整個(gè)緩存協(xié)議的下行及交互部分大家已經(jīng)略知一二了。剩下的就是緩存協(xié)議的上行部分了,所謂上行部分就是將cache-control寫在瀏覽器訪問(wèn)的請(qǐng)求頭上面。
前面我們也提過(guò),瀏覽器的刷新請(qǐng)求,其實(shí)就是在請(qǐng)求頭里面加了一個(gè)cache-control :max-age = 0 。這其實(shí)是告知服務(wù)器,客戶端希望接收一個(gè)存在時(shí)間不大于0秒的緩存,一般的源服務(wù)器,特別是靜態(tài)資源服務(wù)器,這個(gè)時(shí)候就會(huì)根據(jù)客戶端的緩存情況返回200或者304。
4.4.11 在請(qǐng)求頭中的可緩存性控制
(1) no-cache
告知代理服務(wù)器,不直接使用緩存,要求向源服務(wù)器發(fā)起請(qǐng)求。
(2) no-store
所有的文件都不緩存到本地或者臨時(shí)文件夾中。
(3) max-age
告知服務(wù)器客戶端希望接收一個(gè)存在時(shí)間不大于X秒的資源。
(4) max-statle
告知服務(wù)器客戶端愿意接受一個(gè)超過(guò)緩存時(shí)間的資源,時(shí)間為X秒。
(5) min-fresh
告知服務(wù)器客戶端希望接收一個(gè)在小于X秒內(nèi)被更新過(guò)得資源。
(6) no-transform
告知代理服務(wù)器,不允許代理服務(wù)器對(duì)資源進(jìn)行壓縮,轉(zhuǎn)化,比如有些代理服務(wù)器會(huì)對(duì)圖片進(jìn)行壓縮,格式轉(zhuǎn)換。
(7) only-if-cached
告知代理服務(wù)器如果代理服務(wù)器有緩存內(nèi)容,就直接給,不用再找源服務(wù)器要。
請(qǐng)求頭中的緩存控制因?yàn)橛玫谋容^少,我就不過(guò)多的去解讀了,有興趣的同學(xué)可以去研究下。
五、總結(jié)
HTTP的cache-control協(xié)議規(guī)定了客戶端,代理服務(wù)器,源服務(wù)器三者之間的緩存交互邏輯。做為客戶端開(kāi)發(fā),經(jīng)常出現(xiàn)一些與cache相關(guān)的問(wèn)題在排查時(shí)無(wú)從下手,通過(guò)學(xué)習(xí)了解這部分內(nèi)容,可以幫助快速的分析定位這部分問(wèn)題。
前端同學(xué)熟悉cache-control的邏輯后,也可以根據(jù)業(yè)務(wù)的形態(tài)跟運(yùn)維討論自己緩存需求,有效的降低服務(wù)器的壓力和用戶的流量,提高網(wǎng)頁(yè)打開(kāi)速度。