徹底搞懂瀏覽器緩存

楔子

想象一下這樣的場景:當(dāng)你使用瀏覽器訪問某個(gè)web站點(diǎn)的時(shí)候,你的瀏覽器需要下載很多的資源文件。而你下載的這些資源文件中有一部分在某一段時(shí)間甚至是很長時(shí)間內(nèi)都不會發(fā)生變化,所以如果你每次訪問這個(gè)站點(diǎn)都需要把它們重新下載一遍的話,那將是一種巨大的資源浪費(fèi),這不僅僅會拖慢你打開網(wǎng)頁的速度,還會占用你的網(wǎng)絡(luò)帶寬,浪費(fèi)你寶貴的流量,對于響應(yīng)你的請求的源服務(wù)器也是一種巨大的負(fù)擔(dān)。所以,瀏覽器可以將這部分資源文件緩存到本地,以便將來你需要的時(shí)候可以直接從瀏覽器緩存讀取,而不用重新再從源服務(wù)器下載了,這就是瀏覽器緩存。

本文的目標(biāo),旨在盡可能闡述清楚你需要知道的關(guān)于瀏覽器緩存的所有重要知識點(diǎn),包括什么是瀏覽器緩存、它的意義和價(jià)值、緩存的目標(biāo)、緩存的工作原理、緩存的更新機(jī)制等等。在寫這篇文章之前,我看過很多官方和個(gè)人介紹瀏覽器緩存的博客文章,它們是本文重要的信息來源,而且我會在它們的基礎(chǔ)之上,結(jié)合一些實(shí)際測試的結(jié)論,補(bǔ)充講解一些它們未曾涉及到但是卻非常重要的、與實(shí)戰(zhàn)緊密相關(guān)的知識點(diǎn),來幫助大家更好地理解瀏覽器緩存的運(yùn)行機(jī)制,以便將來可以在實(shí)際的工作當(dāng)中更好地運(yùn)用瀏覽器緩存。

本文講解的重點(diǎn)是瀏覽器緩存,但是瀏覽器緩存也只是HTTP緩存的一種。所以在此之前,我們有必要先來了解一下HTTP緩存(以下簡稱緩存)。

1. 緩存的概念

緩存是指將某個(gè)請求的響應(yīng)內(nèi)容(包括響應(yīng)頭和響應(yīng)主體)緩存下來,以便下次相同請求過來時(shí)能直接使用緩存進(jìn)行響應(yīng),而不用重新再從源服務(wù)器下載。這樣做有三個(gè)好處:

第一,加快了響應(yīng)的速度。由于緩存服務(wù)器通常距離客戶端更近,所以響應(yīng)速度也更快。瀏覽器緩存其實(shí)就充當(dāng)了一個(gè)安裝在客戶端本地的緩存服務(wù)器,所以響應(yīng)的速度是極快的。

第二,減輕了源服務(wù)器的壓力,相同的請求不用每次都由源服務(wù)器來進(jìn)行響應(yīng),特別是一些很長時(shí)間甚至是根本不會發(fā)生變動的資源文件,例如:JavaScript庫文件、CSS庫文件以及圖片文件等等;

第三,節(jié)省了用戶的流量資源,降低了對網(wǎng)絡(luò)資源的占用,可以有效緩解網(wǎng)絡(luò)擁堵的情況。

所以,對于我們web開發(fā)者而言,深入地了解和利用好緩存是非常重要的,它對于提升我們的web應(yīng)用的性能具有非常重要的意義。

2. 緩存的分類

緩存主要分為兩大類:公有緩存(Shared Cache)和私有緩存(Local Cache)。顧名思義,公有緩存就是可以服務(wù)多個(gè)客戶端請求的緩存服務(wù)器,例如CDN緩存;私有緩存就是只服務(wù)單個(gè)客戶端的緩存服務(wù)器,例如瀏覽器緩存。其他的緩存類型包括:網(wǎng)關(guān)緩存、反向代理緩存以及負(fù)載均衡器等等。由于CDN緩存經(jīng)常會在瀏覽器和源服務(wù)器的通信中間扮演一個(gè)重要的角色,而且會對瀏覽器的緩存行為產(chǎn)生一些重要的影響,所以后面我們也會重點(diǎn)關(guān)注一下CDN緩存。

公有緩存和私有緩存

3. 緩存的目標(biāo)

理論上所有類型請求(GET、POST、PUT等等)的響應(yīng)內(nèi)容都可以被緩存下來,但是通常我們只緩存GET請求的響應(yīng)。一般情況下緩存的key值就是請求的URI(也有URI和請求頭的組合key值的情況,后面會講到)。緩存的目標(biāo)包括以下幾種:

1、響應(yīng)碼為200的GET請求的響應(yīng)內(nèi)容,例如:HTML文檔、圖片等;

2、響應(yīng)碼為301的永久重定向響應(yīng);

3、響應(yīng)碼為404的空頁面響應(yīng);

4、響應(yīng)碼為206的部分響應(yīng);

5、其他適合緩存的非GET請求的響應(yīng)。

以我們的貝貸首頁(https://jr.beibei.com/loan/borrow-index.html)為例,來看看瀏覽器都為它緩存了哪些內(nèi)容。第一步先清空瀏覽器緩存(推薦使用Chrome插件Clear Cache),然后訪問貝貸首頁,第二步直接左上角刷新頁面,我們從下面的截圖中來看看它們的網(wǎng)絡(luò)資源訪問情況。

從上圖中可以看到,第一次訪問頁面時(shí),由于沒有瀏覽器緩存,所以所有的資源文件都是新下載的,可以從紅框中看到它們的文件大小和加載耗時(shí);第二次訪問頁面時(shí),由于部分資源文件已經(jīng)被瀏覽器所緩存,所以它們是從瀏覽器緩存中直接讀取的,不會真正發(fā)送網(wǎng)絡(luò)請求(如果此時(shí)你用Charles抓包看的話,你會發(fā)現(xiàn)根本看不到它們的請求)。從圖中可以看到,它們中有的是從內(nèi)存中(from memory cache)讀取的,且加載耗時(shí)都是0ms,有的是從磁盤中(from disk cache)讀取的,耗時(shí)也不過幾毫秒。至于為什么有些緩存文件會放在內(nèi)存中,有些會放在磁盤上,這估計(jì)就跟Chrome瀏覽器的緩存管理機(jī)制有關(guān)了。

4. 緩存的機(jī)制

4.1 基本原理

前面提到,最大限度地利用緩存可以有效提升客戶端頁面的打開速度,還能減輕源服務(wù)器的壓力,進(jìn)而提升源服務(wù)器的響應(yīng)效率。所以通常情況下,我們希望緩存文件存活的時(shí)間越長越好。但是當(dāng)源服務(wù)器上的資源文件有更新時(shí),我們也希望能盡快更新緩存服務(wù)器上的緩存文件。

但是,由于HTTP協(xié)議是一種基于請求/響應(yīng)模式的網(wǎng)絡(luò)協(xié)議,它只能由客戶端主動向服務(wù)器發(fā)起請求,然后服務(wù)器才能給予響應(yīng),服務(wù)器是沒有辦法主動與客戶端進(jìn)行通信的。所以源服務(wù)器在資源文件有更新時(shí)是無法主動通知各級緩存服務(wù)器更新緩存文件的。基于這個(gè)前提之下,源服務(wù)器在響應(yīng)緩存服務(wù)器的請求時(shí),必須要指明緩存文件的有效期。

雙方約定,在這個(gè)有效期內(nèi)的緩存文件是有效的,緩存服務(wù)器可以直接用來響應(yīng)客戶端的請求,超過這個(gè)有效期的緩存文件就失效了,緩存服務(wù)器就不能直接用來響應(yīng)客戶端的請求了,它必須先向源服務(wù)器發(fā)送校驗(yàn)請求以確認(rèn)緩存文件的有效性,然后再決定該如何響應(yīng)客戶端的請求。

當(dāng)源服務(wù)器收到緩存服務(wù)器發(fā)來的緩存文件有效性校驗(yàn)請求時(shí),它要么確認(rèn)緩存文件依然有效,然后返回304 Not Modified以及新的響應(yīng)頭(不包含響應(yīng)主體,即不包含資源文件內(nèi)容)更新緩存文件的有效期,要么發(fā)現(xiàn)緩存文件已經(jīng)失效,此時(shí)直接返回200 OK以及完整的響應(yīng)內(nèi)容(包含響應(yīng)頭以及響應(yīng)主體),此時(shí)就好像響應(yīng)一個(gè)普通的請求一樣。當(dāng)緩存服務(wù)器接收到源服務(wù)器返回的304 Not Modified響應(yīng)時(shí),它會先更新緩存文件的有效期,然后使用緩存文件響應(yīng)客戶端的請求,如果它接收到的是200 OK響應(yīng),那么它會重新緩存文件,并用新的緩存文件響應(yīng)客戶端的請求。

下面,我用一張圖來解釋一下“一般性的緩存工作基本原理”(注意,下圖中的緩存服務(wù)器可以是CDN緩存服務(wù)器或者是瀏覽器緩存服務(wù)器):

一般性的緩存工作基本原理

我們還是以貝貸首頁的HTML文件為例,來看看真實(shí)的情況如何。同樣,我們還是分兩步走,第一步清空緩存后訪問頁面,第二步直接訪問頁面,然后我們來看看它們的響應(yīng)內(nèi)容為何物(為了方便一張圖觀察到全部的重要信息,這里我使用的是Charles抓包截圖):

第一次請求時(shí)
第二次請求時(shí)

可以看到,第一次請求時(shí),服務(wù)器返回了200 OK以及完整的響應(yīng)內(nèi)容,耗時(shí)128ms,文件大小2.37KB,而第二次請求時(shí),服務(wù)器只返回了304 Not Modified以及部分響應(yīng)頭,并沒有響應(yīng)主體,耗時(shí)59ms,文件大小不足1KB(因?yàn)橹挥?04 Not Modified和部分響應(yīng)頭而已)。這說明了三個(gè)問題:第一,該HTML文件被瀏覽器緩存住了;第二,雖然它被緩存住了,但是當(dāng)它被請求時(shí),瀏覽器依然向源服務(wù)器發(fā)送了校驗(yàn)請求;第三,源服務(wù)器在接收到校驗(yàn)請求之后,驗(yàn)證緩存依然有效,所以只返回了304 Not Modified以及部分響應(yīng)頭。

上面的例子講的是瀏覽器緩存,這里再附上一張來自MDN的講解CDN緩存機(jī)制的示意圖,其實(shí)大致的過程都是一樣的:

CDN緩存機(jī)制的示意圖

4.2 緩存的控制

我們知道,為了能夠在HTTP協(xié)議請求/響應(yīng)模式的限制之下,盡可能地平衡好“讓緩存的時(shí)間更長”和“資源有更新時(shí)盡早刷新緩存”的沖突,源服務(wù)器會在響應(yīng)請求時(shí),通過加入一些緩存控制字段來指明“響應(yīng)是否可緩存”、“如果可以緩存有效期是多長”、“如果緩存過期失效該如何發(fā)送校驗(yàn)請求”等等重要信息。具體字段可以參考一下下面的這張圖:

響應(yīng)頭中的緩存控制字段

看過上面的這張圖之后,你的腦海中也許會有一些疑問,我曾經(jīng)也有過這些疑問,那么我就來嘗試解答一下你的這些問題(下面的這些結(jié)論,我都使用Chrome瀏覽器親自驗(yàn)證過)。

問題一:Cache-Control: no-cache和Cache-Control: no-store都是禁止緩存的意思嗎?

不是。只有Cache-Control:?no-store才是真正的禁止緩存,響應(yīng)頭中有這個(gè)字段的話,無論是公有緩存還是私有緩存都不會緩存這個(gè)響應(yīng)。而Cache-Control: no-cache的真正含義是:在使用緩存之前,必須先向源服務(wù)器發(fā)起請求校驗(yàn)緩存的有效性。

問題二:那Cache-Control: no-cache和Cache-Control: must-revalidate的作用是完全一樣的嗎?

不是。它們之間的共同點(diǎn)在于:使用緩存之前都會向源服務(wù)器發(fā)送請求校驗(yàn)緩存的有效性。但是它們之間不同的是,Cache-Control: must-revalidate是在緩存已過期時(shí)才會向源服務(wù)器發(fā)起校驗(yàn)請求,而Cache-Control: no-cache則強(qiáng)制要求必須要向源服務(wù)器發(fā)起校驗(yàn)請求,無論緩存是否已過期。

問題三:Cache-Control: max-age=N和Cache-Control: s-maxage=N有什么相同點(diǎn)和不同點(diǎn)?

他們的相同點(diǎn)是,都可以用來指示緩存的有效時(shí)長是多少秒,且都對公有緩存生效。但Cache-Control: s-maxage=N只對公有緩存生效(這個(gè)s應(yīng)該指的是server),對私有緩存不生效。當(dāng)響應(yīng)頭中同時(shí)包含max-age和s-maxage時(shí)(例如:Cache-Control: max-age=100, s-maxage=100),公有緩存優(yōu)先讀取s-maxage的值

問題四:那Expires和上面的兩個(gè)值又有什么相同點(diǎn)和不同點(diǎn)呢?

它們的不同點(diǎn)在于,Expires指定的是緩存的過期時(shí)間點(diǎn),是一個(gè)絕對時(shí)間,而max-age/s-maxage指定的是緩存的有效時(shí)長,是一個(gè)相對時(shí)間。

它們有幾個(gè)相同點(diǎn),第一是都是用于指定緩存的過期時(shí)間,第二是緩存服務(wù)器拿到它們之后,都是基于本地時(shí)間去計(jì)算緩存的實(shí)際過期時(shí)間的,無論是CDN緩存服務(wù)器還是瀏覽器緩存服務(wù)器都是如此。所以,如果使用絕對時(shí)間的話,那么由于每個(gè)緩存服務(wù)器的本地時(shí)間各不相同,會導(dǎo)致各自的實(shí)際緩存過期時(shí)間千差萬別。但是,如果使用相對時(shí)間的話,那么緩存服務(wù)器會依據(jù)本地的相對時(shí)間差值來計(jì)算緩存的過期時(shí)間,相比之下誤差會更小。

另外,緩存服務(wù)器會優(yōu)先使用max-age/s-maxage計(jì)算緩存的失效時(shí)間,如果max-age/s-maxage不存在才會去取Expires的值計(jì)算緩存的失效時(shí)間。

問題五:Cache-Control: public和Cache-Control: private有哪些相同點(diǎn)和不同點(diǎn)?

它們的不同點(diǎn)是,public針對公有緩存和私有緩存都生效,而private僅針對私有緩存生效。它們的相同點(diǎn)是,實(shí)際中很少會被用到,所以對它們僅作了解即可。

問題六:Cache-Control: no-cache和Pragma: no-cache有什么相同點(diǎn)和不同點(diǎn)?

相同的是它們的作用一模一樣,但是Pragma是HTTP/1.0的規(guī)范,所以添加它一般只是為了向下兼容。

問題七:Date和Age對于緩存失效時(shí)間的計(jì)算有什么影響?

Date是源服務(wù)器響應(yīng)請求時(shí)的源服務(wù)器系統(tǒng)時(shí)間(注意!是源服務(wù)器,不是緩存服務(wù)器!)。Age是緩存文件在CDN服務(wù)器上已消耗的有效時(shí)長,如果瀏覽器接收到的響應(yīng)含有Age字段,說明該響應(yīng)來自CDN服務(wù)器,而不是來自源服務(wù)器。這兩個(gè)值對于緩存過期時(shí)間的計(jì)算至關(guān)重要,接下來會詳細(xì)說明。

4.3 瀏覽器緩存過期時(shí)間的計(jì)算(基于Chrome v70測試)

接下來,我們一起來探討一下,瀏覽器緩存的過期時(shí)間是如何計(jì)算的。先來看一下下面的流程圖:

瀏覽器緩存過期時(shí)間的計(jì)算過程

可以看到,整個(gè)計(jì)算過程還是比較復(fù)雜的,我們一起來把其中的重要環(huán)節(jié)梳理一遍:

1、瀏覽器客戶端發(fā)起網(wǎng)絡(luò)請求,此時(shí)先不考慮命中瀏覽器緩存的情況,于是瀏覽器順利收到響應(yīng),先記錄一下此時(shí)的客戶端系統(tǒng)時(shí)間為CDate,接下來開始判斷是否需要緩存該響應(yīng)。

2、檢查響應(yīng)頭中是否有Age字段,如果沒有則把Age置為0。如果存在Age值,則說明該響應(yīng)來自CDN服務(wù)器。這個(gè)值代表的是,CDN服務(wù)器從上一次向源服務(wù)器發(fā)起請求更新緩存(可能是200 OK也可能是304 Not Modified)到本次響應(yīng)瀏覽器請求所經(jīng)過的時(shí)長,單位為秒。

3、檢查響應(yīng)頭中是否有Date字段,如果沒有則取Date為客戶端系統(tǒng)時(shí)間。注意,這個(gè)Date值是源服務(wù)器輸出該響應(yīng)時(shí)的源服務(wù)器系統(tǒng)時(shí)間,如果該響應(yīng)來自CDN服務(wù)器,那么說明你收到的響應(yīng)是CDN服務(wù)器上的緩存,而這個(gè)Date值是上一次CDN服務(wù)器向源服務(wù)器發(fā)起請求更新緩存(可能是200 OK也可能是304 Not Modified)時(shí)源服務(wù)器添加在響應(yīng)頭中的源服務(wù)器系統(tǒng)時(shí)間,而不是此次CDN服務(wù)器響應(yīng)瀏覽器請求時(shí)的CDN服務(wù)器時(shí)間,這一點(diǎn)一定要搞清楚!

4、檢查響應(yīng)頭中是否有Cache-Control字段,如果有的話:

? ? ? ? 4.1 檢查它的值是否包含no-store,包含的話則說明該響應(yīng)不允許被緩存,任何緩存服務(wù)器不得緩存該響應(yīng)。

? ? ? ? 4.2 否則,再檢查它的值是否包含no-cache,如果包含的話,再檢查響應(yīng)頭中是否有ETag或者Last-Modified字段,包含則緩存該響應(yīng)并標(biāo)記為已失效,否則就不緩存該響應(yīng)。

? ? ? ? 4.3 否則,再檢查它的值是否包含max-age字段,如果包含的話,再結(jié)合Age字段、Date字段以及客戶端本地時(shí)間CDate值進(jìn)行后續(xù)的緩存過期時(shí)間的計(jì)算,完整的判斷過程都在圖中,主要分為三種情況,我在這里說一下我個(gè)人的一個(gè)理解。第一種情況:Date + MaxAge <= CDate,說明此時(shí)即便服務(wù)器時(shí)間加上緩存的完整有效時(shí)長都比客戶端本地時(shí)間要晚,因?yàn)橛?jì)算出來的緩存過期時(shí)間永遠(yuǎn)是跟客戶端本地時(shí)間做對比的,所以客戶端可以直接認(rèn)為這份緩存已經(jīng)失效了,然后再查看響應(yīng)頭中是否有ETag或者Last-Modified字段來判斷是緩存該響應(yīng)并標(biāo)記為已失效還是直接不緩存該響應(yīng);第二種情況:Date + MaxAge > CDate &&?Date + Age <= CDate,這種情況下我測試出來的緩存過期時(shí)間為Date + MaxAge,其實(shí)Date + Age就是此時(shí)源服務(wù)器的當(dāng)前時(shí)間,也就是說此時(shí)源服務(wù)器的時(shí)間要早于客戶端本地時(shí)間,基于目前測試的結(jié)果,緩存過期時(shí)間是Date + MaxAge而不是CDate + LeftAge,我只能暫且認(rèn)為,源服務(wù)器寧可讓客戶端本地緩存早點(diǎn)過期早點(diǎn)發(fā)緩存有效性驗(yàn)證請求,也不希望當(dāng)源服務(wù)器資源有更新時(shí),客戶端緩存不能及時(shí)更新;第三種情況:Date + Age > CDate,說明此時(shí)源服務(wù)器的時(shí)間要晚于客戶端本地時(shí)間,那么緩存的過期時(shí)間為CDate + LeftAge也在意料之中,因?yàn)檫@樣瀏覽器緩存和CDN緩存就都會在LeftAge秒之后過期,跟源服務(wù)器一開始約定好的緩存過期時(shí)間點(diǎn)就匹配上了。

5、如果響應(yīng)頭中不包含Cache-Control但包含Expires的話,那么緩存的過期時(shí)間就等于Expires減去Age的值,也就是Expires往前推Age秒之后的時(shí)間值。

6、如果Cache-Control和Expires都沒有的話,那么查看是否存在Last-Modified字段,如果存在的話,那么先使用其他博客中所謂的“啟發(fā)式緩存算法”計(jì)算出LeftAge的值,也就是Date和Last-Modified差值的十分之一,然后判斷Date和CDate誰更小,用那個(gè)更小的值加上LeftAge就可以計(jì)算出緩存的過期時(shí)間了。

7、如果最后連Last-Modified都沒有的話,那么再查看是否有ETag字段來判斷是緩存該響應(yīng)并標(biāo)記為已失效還是直接不緩存該響應(yīng)。

8、上面某些情況下緩存過期時(shí)間計(jì)算出來之后有可能是小于客戶端本地時(shí)間的,也就是說緩存會立即變成失效狀態(tài),那么這個(gè)時(shí)候仍然要通過判斷ETag和Last-Modified字段是否存在來判斷是緩存該響應(yīng)并標(biāo)記為已失效還是直接不緩存該響應(yīng)。

好了,以上就是瀏覽器緩存過期時(shí)間計(jì)算的整個(gè)過程了,需要提前說明的是,上面的所有結(jié)論我都用Chrome瀏覽器親自驗(yàn)證過,但我不保證所有的瀏覽器或者web內(nèi)核的處理方式都是這樣的,所以整個(gè)過程僅供參考。

接下來,我會嘗試用一段偽JS代碼來描述整個(gè)過程:

瀏覽器緩存過期時(shí)間計(jì)算的偽JS代碼

4.4 緩存的有效性校驗(yàn)

上一節(jié)我們講的是瀏覽器如何計(jì)算緩存的過期時(shí)間,這一節(jié)我們再來聊聊當(dāng)緩存過期時(shí),緩存服務(wù)器如何向源服務(wù)器發(fā)送校驗(yàn)請求,源服務(wù)器又是如何響應(yīng)緩存服務(wù)器的校驗(yàn)請求的。其實(shí)主要的過程已經(jīng)在4.1 基本原理中說明過了,這里我們只需要把一些技術(shù)細(xì)節(jié)和注意事項(xiàng)補(bǔ)充講解一下就可以了。

源服務(wù)器在響應(yīng)緩存服務(wù)器的請求時(shí),會在響應(yīng)頭中加入一些字段,一些是為了指明緩存的過期時(shí)間,還有一些就是為了在緩存失效時(shí)做校驗(yàn)用的。這些校驗(yàn)字段分為強(qiáng)校驗(yàn)字段(ETag)和弱校驗(yàn)字段(Last-Modified)兩種。

強(qiáng)校驗(yàn)字段ETag,通常是由源服務(wù)器根據(jù)資源文件的內(nèi)容生成的唯一hash值,資源文件內(nèi)容不變時(shí)hash值不變,資源文件內(nèi)容有任何變化時(shí)hash值也會跟著變化,所以可以用來檢驗(yàn)文件是否有改動。緩存服務(wù)器在發(fā)現(xiàn)緩存過期且緩存響應(yīng)頭包含ETag時(shí),會向源服務(wù)器發(fā)送帶有If-None-Match請求頭的校驗(yàn)請求,它的值就是緩存響應(yīng)頭ETag的值,源服務(wù)器通過比對If-None-Match的值和當(dāng)前資源文件的hash值是否相同即可判斷出緩存服務(wù)器上的緩存文件是否需要更新。

弱校驗(yàn)字段Last-Modified,顧名思義,就是文件的最后修改時(shí)間,也可以用來檢測文件是否有改動。同樣,緩存服務(wù)器在發(fā)現(xiàn)緩存過期且緩存響應(yīng)頭包含Last-Modified時(shí),會向源服務(wù)器發(fā)送帶有If-Modified-Since請求頭的校驗(yàn)請求,它的值就是緩存響應(yīng)頭Last-Modified的值,源服務(wù)器通過查看當(dāng)前資源文件的Last-Modified日期是否等于If-Modified-Since日期即可判斷出緩存服務(wù)器上的緩存文件是否需要更新。

同樣,我們還是用一張流程圖來說明整個(gè)過程:

源服務(wù)器響應(yīng)緩存有效性校驗(yàn)請求的過程

我們還是以貝貸首頁為例,先清空緩存再訪問頁面,返回了200 OK響應(yīng),如下圖所示:

清空緩存后首次訪問貝貸首頁

分析一下上面的截圖,有以下幾個(gè)關(guān)鍵信息:

1、返回了Cache-Control: max-age=0, s-maxage=300,說明源服務(wù)器希望瀏覽器緩存該文件但立即過期,也說明它希望CDN能緩存該文件且有效時(shí)長是300秒;

2、沒有Age字段的返回,說明該響應(yīng)來自源服務(wù)器而不是CDN服務(wù)器,事實(shí)上我們也沒有把它放到CDN上,所以上面的s-maxage=300其實(shí)是無用的;

3、返回了Date: Sun, 09 Dec 2018 01:06:49 GMT就是此時(shí)源服務(wù)器的時(shí)間;

4、返回了Last-Modified: Thu, 06 Dec 2018 09:39:33 GMT,指明了緩存過期后發(fā)起校驗(yàn)請求的方式;

接下來,我們刷新頁面,返回了304 Not Modified響應(yīng),如下圖所示:

刷新頁面第二次訪問貝貸首頁?

仔細(xì)看你就會發(fā)現(xiàn),第二次請求時(shí)請求頭中多了一個(gè)If-Modified-Since字段,它的值就是第一次請求時(shí)返回的Last-Modified的值,而且本次請求返回的Last-Modified值跟上次一樣無變化,說明文件的最后修改時(shí)間沒有發(fā)生變化,且響應(yīng)主體也是空的。

之所以稱Last-Modified是弱校驗(yàn)字段,原因就在于它只能精確到秒,如果資源文件在一秒鐘之內(nèi)被修改了多次,那么就有可能導(dǎo)致緩存服務(wù)器讀取到的資源文件內(nèi)容不是最新的,而且由于Last-Modified值沒變化,從而導(dǎo)致緩存無法被更新。另外,有些資源文件是服務(wù)器動態(tài)生成的,那么就會出現(xiàn)某些資源文件雖然內(nèi)容無變化但是Last-Modified值卻一直在變的情況,導(dǎo)致一些不必要的刷新緩存操作。而且,在分布式系統(tǒng)當(dāng)中使用Last-Modified時(shí),也務(wù)必要保證各個(gè)服務(wù)器的時(shí)間是同步的,否則也會造成一些“該刷新的沒刷新,不該刷新的卻刷新了”的誤判情況。

不過,使用ETag也有一些情況需要注意一下,也同樣是在分布式系統(tǒng)當(dāng)中,我們要保證各個(gè)系統(tǒng)生成資源文件的ETag值的算法是一致的,否則也會造成誤判的情況。

除了ETag/If-None-Match和Last-Modified/If-Modified-Since之外,其實(shí)還有一些其他的不怎么常用的校驗(yàn)字段,這里就不一一列舉了,有興趣的小伙伴可以自行查閱。

五、Vary響應(yīng)頭

前面我們說過,緩存的key值就是請求的URI,這樣的設(shè)計(jì)足夠簡單,也有利于緩存機(jī)制的推廣,但是也會有不滿足需求的情況。

例如,某電商網(wǎng)站提供了多國語言版本的官網(wǎng),需要在用戶訪問同一官網(wǎng)地址時(shí)根據(jù)用戶設(shè)定的瀏覽器語言返回相對應(yīng)的版本。如果它也想使用緩存的話,那么只把請求的URI作為緩存的key值顯然是不行的。所以,HTTP協(xié)議設(shè)計(jì)了Vary響應(yīng)頭,它指示緩存服務(wù)器命中該緩存的條件除了URI要相同之外,Vary中指定的請求頭也都要匹配得上才能命中緩存。所以,針對上述案例,我們可以在響應(yīng)頭中加入Vary:?Accept-Language,這樣緩存服務(wù)器就會緩存不同語言版本的官網(wǎng),然后根據(jù)用戶請求時(shí)傳過來的Accept-Language值返回相對應(yīng)語言版本的官網(wǎng)。

再次盜用一張MDN的圖,雖然它是以文件編碼格式Accept-Encoding為例做的講解,但其實(shí)意思是一樣的:

六、如何禁用瀏覽器緩存

在Chrome的開發(fā)者工具的Network中勾選Disable cache可以讓所有的請求都不檢查瀏覽器緩存而直接發(fā)出,但是返回的資源該緩存的仍然會被緩存,只不過這個(gè)選項(xiàng)被勾中的話,在發(fā)請求階段不會去檢查瀏覽器緩存而已。它的實(shí)際做法就是在發(fā)請求階段,給所有的請求頭都加上Pragma: no-cache和Cache-Control: no-cache來達(dá)到跳過檢查瀏覽器緩存階段的效果的。

或者,你也可以使用Chrome插件Clear Cache來清空Chrome的瀏覽器緩存,這個(gè)插件的做法才是真正的清空瀏覽器緩存。

在Charles中勾選No Caching也可以達(dá)到相同的效果,它們都是通過在請求頭中加入Pragma: no-cache和Cache-Control: no-cache來達(dá)到禁用緩存的效果的。在Chrome瀏覽器中,你還可以通過刷新按鈕的右鍵菜單選擇強(qiáng)制刷新,或者干脆使用來得更方便。

另外,如果你直接在瀏覽器的地址欄訪問某個(gè)本可以被緩存的資源文件地址的話,它是不會直接走瀏覽器緩存的,而是每次刷新時(shí),瀏覽器都會向源服務(wù)器發(fā)送校驗(yàn)請求,就好像這個(gè)資源文件的響應(yīng)頭被設(shè)置了Cache-Control: no-cache一樣(其實(shí)并沒有)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容