詳解HTTP緩存

HTTP緩存是個大公司面試幾乎必考的問題,寫篇隨筆說一下HTTP緩存。html

1. HTTP報文首部中有關緩存的字段


在HTTP報文中,與緩存相關的信息都存在首部裏,簡單說一下首部。web

首部

HTTP首部字段向請求報文和相應報文中添加了一些附加信息。本質上來講,它們只是一些鍵值對的列表。好比,下面的首部行會向Content-Length首部字段賦值19:面試

Content-Length: 19

HTTP規範定義了幾中首部字段。應用程序也能夠隨意發明本身所用的首部。HTTP首部能夠分爲如下幾類:算法

  • 通用首部瀏覽器

    既能夠出如今請求報文中,也能夠出如今響應報文中。緩存

  • 請求首部服務器

    提供更多有關請求的信息。網絡

  • 響應首部分佈式

    提供更多有關響應的信息。ui

  • 實體首部

    描述主體的長度和內容,或資源自己。

  • 擴展首部

    規範中沒有定義的新首部。

想了解更多有關HTTP首部或報文的信息,我的推薦《HTTP權威指南》。

首部中與緩存有關的字段

  • 通用首部字段

  • 請求首部字段

  • 響應首部字段

  • 實體首部字段

2. 緩存的處理步驟


除了一些微小的細節,Web緩存的工做原理基本很簡單,對一條HTTP GET報文的基本緩存處理過程包括7個步驟。

  1. 接收——緩存從網絡中讀取抵達的請求報文。
  2. 解析——緩存對報文解析,提取出URL和各類首部。
  3. 查詢——緩存查看是否有本地副本可用,若是沒有就向服務器獲取一份副本,並將其保存在本地。
  4. 新鮮度檢測——緩存查看以緩存的副本是否新鮮,若是不是,就詢問服務器是否有更新。
  5. 建立響應——緩存會用新的首部和以緩存的主體來構建一條響應報文。
  6. 發送——緩存經過網路將響應發揮給客戶端。
  7. 日誌——緩存可選的建立一個日誌文件來描述這個事務。

緩存的處理步驟如圖:

3. 文檔過時和服務器再驗證


文檔過時

經過特殊的HTTP Cache-Control首部和Expires首部,HTTP讓原始服務器向每一個文檔加了一個過時時間,這些首部說明了多長時間內可將這些內容視爲新鮮的。

在文檔過時以前,緩存能夠以任意頻率使用這些副本,而無需與服務器進行聯繫,除非客戶端請求中包含有阻止提供已緩存或未驗證資源的首部。一旦已緩存的文檔過時,緩存就必須與服務器進行覈對,詢問文檔是否被修改過,若是被修改過,就要獲取一份新鮮的並帶有新的過時日期的副本。

服務器再驗證

可是緩存文檔過時並不意味着它與服務器上的文檔有實際的區別,只是覺得到了要進行覈對的時間了。這種狀況叫服務器再驗證,說明緩存須要詢問服務器文檔是否發生了變化。

  • 若是再驗證顯示內容發生了變化,緩存會獲取一份新的文檔副本,並將其存儲在舊文檔的位置上,而後將文檔發送給客戶端。

  • 若是再驗證顯示內容沒有發生變化,緩存只要獲取新的首部,包括一個新的過時日期,並對緩存中的首部進行更新就行了。

HTTP定義了幾個首部用來實現緩存是否新鮮的驗證,像開篇咱們說到的If-Modified-Since和If-None-Match、Last-Modified等都屬於這樣的首部。

強緩存和協商緩存

咱們根據是否須要向服務器發起請求將緩存分紅兩類,不須要向服務器發起請求的緩存叫強緩存,也就是上面所說的文檔沒過時時候用到的緩存。須要向服務器發起請求的緩存叫協商緩存,也就是上面服務器再驗證用到的緩存。

下面咱們詳細介紹強緩存和協商緩存。

4. 強緩存


上面咱們說能夠經過Cache-Control首部和Expires首部來標明文檔的過時時間。若是沒有過時的話,天然就是從緩存裏取文檔了:

顧名思義,這裏的memory cache內存了,disk cache就是磁盤的緩存了,再往下說就到了webkit的緩存機制了。

Expires

爲何要先說Expires呢?由於相比於Cache-Control,Expires出現的較早,是HTTP1.0的東西,而Cache-Control是HTTP1.1的東西。

Expires的值對應一個GMT,也就是格林尼治時間,好比「Mon, 22 Jan 2019 11:12:01 GMT」來告訴瀏覽器資源緩存過時時間,若是還沒過該時間點則不發請求。

在客戶端咱們一樣可使用meta標籤來知會IE(也僅有IE能識別)頁面(一樣也只對頁面有效,對頁面上的資源無效)緩存時間:

<meta http-equiv="expires" content="mon, 18 apr 2016 14:30:00 GMT">

若是但願在IE下頁面不走緩存,但願每次刷新頁面都能發新請求,那麼能夠把「content」裏的值寫爲「-1」或「0」。可是是該方式僅僅做爲知會IE緩存時間的標記,你並不能在請求或響應報文中找到Expires字段。

那麼若是Pragma和Expires一塊兒出現的話,Pragma的優先級是高的。

注意:響應報文中Expires所定義的緩存時間是相對服務器上的時間而言的,若是客戶端上的時間跟服務器上的時間不一致,特別是若是你修改了本身電腦的系統時間,那緩存時間可能就沒什麼意義了。

Pragma

既然咱們已經說了Expires是HTTP1.0的遺留物,那咱們也要介紹下Pragma。

當該字段值爲「no-cache」的時候,會通知客戶端不要對該資源讀緩存,即每次都得向服務器發一次請求才行。

Pragma屬於通用首部字段,在客戶端上使用時,常規要求咱們往html上加上這段meta元標籤:

<meta http-equiv="Pragma" content="no-cache">

它告訴瀏覽器每次請求頁面時都不要讀緩存,都得往服務器發一次請求才行。

可是這種禁用緩存的形式做用不是那麼太大:

  1. 僅有IE才能識別這段meta標籤含義,其它主流瀏覽器僅能識別「Cache-Control: no-store」的meta標籤。

  2. 在IE中識別到該meta標籤含義,並不必定會在請求字段加上Pragma,但的確會讓當前頁面每次都發新請求,可是僅限頁面,頁面上的資源則不受影響。

因此這種在客戶端定義Pragma並無多少做用。

可是在響應報文中加上這個字段就不同了,瀏覽器在收到服務器的Pragma字段後會對資源進行標記,禁用其緩存行爲,進然後續每次刷新頁面均能從新發出請求而不走緩存。

Cache-Control

針對上述的「Expires時間是相對服務器而言,沒法保證和客戶端時間統一」的問題,HTTP1.1新增了 Cache-Control 來定義緩存過時時間,若報文中同時出現了 Pragma、Expires 和 Cache-Control,會以 Cache-Control 爲準。

Cache-Control也是一個通用首部字段,這意味着它能分別在請求報文和響應報文中使用。在RFC中規範了 Cache-Control 的格式爲:

"Cache-Control" ":" cache-directive"

做爲請求首部時,cache-directive的可選值有:

做爲響應首部時,cache-directive的可選值有:

咱們依舊能夠在HTML頁面加上meta標籤來給請求報頭加上 Cache-Control字段,而且能夠有多個值:

Cache-Control: max-age=3600, must-revalidate

它意味着該資源是從原服務器上取得的,且其新鮮度的有效時間爲一小時,在後續一小時內,用戶從新訪問該資源則無須發送請求。

固然這種組合的方式也會有些限制,好比 no-cache 就不能和 max-age、min-fresh、max-stale 一塊兒搭配使用。

組合的形式還能作一些瀏覽器行爲不一致的兼容處理。例如在IE咱們可使用 no-cache 來防止點擊「後退」按鈕時頁面資源從緩存加載,但在 Firefox 中,須要使用 no-store 才能防止歷史回退時瀏覽器不從緩存中去讀取數據,故咱們在響應報頭加上以下組合值便可作兼容處理:

Cache-Control: no-cache, no-store

5. 協商緩存


顧名思義,客戶端經過與服務器進行協商是否使用緩存。前面咱們已經說過了HTTP提供了實現緩存文件是否新鮮的驗證、提高緩存的複用率的幾個首部,就來講說這些首部。其實它們都是HTTP1.1新增的。

Last-Modified

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

客戶端會爲資源標記上該信息,下次再次請求時,會把該信息附帶在請求報文中一併帶給服務器去作檢查,若傳遞的時間值與服務器上該資源最終修改時間是一致的,則說明該資源沒有被修改過,直接返回304狀態碼便可。

至於傳遞標記起來的最終修改時間的請求報文首部字段一共有兩個:

⑴ If-Modified-Since: Last-Modified-value

示例:

If-Modified-Since: Thu, 31 Mar 2016 07:07:52 GMT

該請求首部告訴服務器若是客戶端傳來的最後修改時間與服務器上的一致,則直接回送304 和響應報頭便可。

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

⑵ If-Unmodified-Since: Last-Modified-value

告訴服務器,若Last-Modified沒有匹配上,即資源在服務端的最後更新時間改變了,則應當返回412(Precondition Failed) 狀態碼給客戶端。

當遇到下面狀況時,If-Unmodified-Since 字段會被忽略:

  1. Last-Modified值相等,即資源在服務端沒有新的修改;

  2. 服務端需返回2XX和412以外的狀態碼;

  3. 傳來的指定日期不合法

Last-Modified 說好卻也不是特別好,由於若是在服務器上,一個資源被修改了,但其實際內容根本沒發生改變,會由於Last-Modified時間匹配不上而返回了整個實體給客戶端。

ETag

爲了解決上述Last-Modified可能存在的不許確的問題,Http1.1還推出了 ETag 實體首部字段。

服務器會經過某種算法,給資源計算得出一個惟一標誌符(好比md5標誌),在把資源響應給客戶端的時候,會在實體首部加上「ETag: 惟一標識符」一塊兒返回給客戶端。

客戶端會保留該 ETag 字段,並在下一次請求時將其一併帶過去給服務器。服務器只須要比較客戶端傳來的ETag跟本身服務器上該資源的ETag是否一致,就能很好地判斷資源相對客戶端而言是否被修改過了。

若是服務器發現ETag匹配不上,那麼直接以常規GET 200回包形式將新的資源以及新的Etag發給客戶端;若是ETag是一致的,則直接返回304知會客戶端直接使用本地緩存便可。

那麼客戶端是如何把標記在資源上的 ETag 傳去給服務器的呢?請求報文中有兩個首部字段能夠帶上 ETag 值:

⑴ If-None-Match: ETag-value

示例爲

If-None-Match: "56fcccc8-1699"

告訴服務端若是 ETag 沒匹配上須要重發資源數據,不然直接回送304 和響應報頭便可。

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

⑵ If-Match: ETag-value

告訴服務器若是沒有匹配到ETag,或者收到了「*」值而當前並無該資源實體,則應當返回412(Precondition Failed) 狀態碼給客戶端。不然服務器直接忽略該字段。

If-Match 的一個應用場景是,客戶端走PUT方法向服務端請求上傳/更替資源,這時候能夠經過 If-Match 傳遞資源的ETag。

須要注意的是,若是資源是走分佈式服務器(好比CDN)存儲的狀況,須要這些服務器上計算ETag惟一值的算法保持一致,纔不會致使明明同一個文件,在服務器A和服務器B上生成的ETag卻不同。

若是 Last-Modified 和 ETag 同時被使用,則要求它們的驗證都必須經過纔會返回304,若其中某個驗證沒經過,則服務器會按常規返回資源實體及200狀態碼。

若是面試的時候能說出這些,就表明了你對HTTP緩存理解的很不錯了,若是是一百分的話也應該能夠拿到八十分了。

相關文章
相關標籤/搜索