簡述 HTTP 快取首部及其行為

0x7e2發表於2020-04-04

使用快取的優點

  • 快取減少了亢餘資料的傳輸,節省了你的網路費用
  • 快取緩解了網路瓶頸的問題,不需要更多的頻寬就可能更快的載入頁面
  • 快取降低了對原始伺服器的要求。伺服器可以更快的響應,避免過載的出現
  • 快取降低了距離時延,因為從較遠的地方載入頁面會更慢一些

快取的拓撲結構

私有快取

私有快取通常指的就是本地的快取,不單單指瀏覽器快取,npm、yarn、brew 等等,這些包管理器的快取也屬於此列。

公用快取

一般代理伺服器可能會允許快取資源,當使用者傳送請求的時候,會先經過代理,如果代理上邊快取的資源足夠新鮮,就可以直接返回而不需要向原始伺服器進行請求。

快取的處理步驟(摘自 HTTP 權威指南)

  1. 接收 ---- 快取從網路中讀取抵達的請求報文
  2. 解析 ---- 快取對報文進行解析,提取 URL 和各種首部
  3. 查詢 ---- 快取檢視是否有本地副本可用,如果沒有,就獲取一份副本(並儲存在本地)
  4. 新鮮度檢測 ---- 快取檢視已快取副本是否足夠新鮮,如果不是,就詢問伺服器是否有任何更新。
  5. 建立響應 ---- 快取會用新的首部和已快取的主體來構建一條響應報文。
  6. 傳送 ---- 快取通過網路將響應發回給客戶端
  7. 日誌 ---- 快取可選地建立一個日誌檔案條目來描述這個事務

其中 新鮮度檢測 是重中之重,接下來大部分內容主要說這個問題。

快取如何處理

在接收到請求報文並解析之後,瀏覽器首先在快取中進行查詢,判斷其是否命中快取。如果命中快取且資源足夠新鮮,則直接從瀏覽器自己的快取中讀取對應的資源,不會向伺服器發請求。建立響應,並設定響應為 200,並進行響應頭的改造等等。如果資源不夠新鮮,就需要進行伺服器再驗證,再對資源進行一系列的操作。上面的過程看似很合理,但是在開發中我們還會遇到一些由於快取導致的問題。有一個很常見的場景:一般情況下,開發者都會選擇使用 CDN 伺服器來託管靜態資源,來加速靜態資源的請求速度。對於這些資源,CDN 一般的設定是允許資源進行快取。由於快取命中之後,瀏覽器會直接從快取中載入,那麼即使線上的靜態資源發生了變化,仍然沒什麼卵用。所以一般的解決辦法是在靜態資源上加一個雜湊值,用來標識靜態資源的版本,同時在 html 檔案中實時更新資源的版本。這樣的解決辦法則要求開發者設定不允許瀏覽器每次載入 html 頁面時候都要向伺服器驗證。當然也不推薦把 html 文件也放到 CDN 上“加速”。

快取的新鮮度檢測

當某個請求命中快取的時候,響應狀態值為 200。在 Chrome 中的開發工具 network 卡中其 size 值會為 from cache,判斷是否 from cache 是通過其他的方法進行實現的,並不是根據響應的狀態值進行判斷。快取可以通過 Expires 或者 Cache-Control: max-age 這兩個響應頭來配置,都用來表示資源在客戶端允許被快取的時間。其中 Expires 是 HTTP/1.0+ 提出的一個用來表資源過期時間的響應頭,Cache-Control: max-age 則是 HTTP/1.1 推薦使用的。他們解決的問題是相同的,不過 Expires 設定的時間是基於伺服器時間的絕對時間,也就是會設定一個過期時間,超過這個時間點之後認為資源不夠新鮮,需要更新。同時需要注意,使用過期時間的時候,所有的 HTTP 日期和時間都會在格林尼治(GMT)過期。也就是 0 時區。如果使用者處在不同的時區內,就需要根據使用者所在的時區進行定製過期時間,這樣就會帶來一系列的玄學問題。比如期望一個資源在 2018 年 7 月 14 日凌晨 0 點過期,對於在東八區來說,格林尼治時間比東八區時間要晚 8 小時,那麼首部則需要設定如下:

Expires: Fri, 13 Jul 2018, 16:00:00 GMT
複製程式碼

換算到東八區剛好是 14 日凌晨 0 點。而 Cache-Control: max-age 則是設定一個相對時間,max-age 的值是資源的最大的合法存活時間,以秒為單位。這個時間不會因為時差問題而導致差異,所以比較推薦使用。當兩個首部同時出現且 HTTP 版本都支援的話,後者的優先順序較高。前面的內容都是假設請求命中強快取且資源並沒有過期,那麼如果命中了強快取但是資源已經過期了呢?當然現實的場景是我們一般會設定快取時間為一年,也就是 max-age 值為 315360000。但是這種場景也不是不存在,需要考慮。這會涉及到一個稱為 服務端再驗證 的情況。當資源已經過期但是服務端的資源沒有任何的變化,那麼快取只需要取得新的首部,包括一個新的過期日期,並對快取中的首部進行更新。

服務端再驗證

Cache-Control 優先順序高於 Expires 首部,一般常用的 Cache-Control 的取值有以下三種:

取值 含義
no-store 不允許快取
no-cache 在回源驗證前不允許複用快取
max-age 文件最大合法存活時間

Cache-Control 可用取值有很多,其中響應可以出現的值有 9 種,請求可以出現的值有 7 種。支援自定義,只要服務端識別就 OK。

簡述 HTTP 快取首部及其行為

簡述 HTTP 快取首部及其行為

對於 Cache-Control: no-store,意味著完全禁止快取對響應複製,快取通常像非快取代理伺服器一樣,向客戶端轉發一條 no-store 響應,然後刪除物件。對於 Cache-Control: no-cache,意味著響應實際上是可以儲存在本地快取區中的。只是在與原始伺服器進行新鮮度再驗證之前,快取不能再提供給客戶端使用。其實這個首部使用 do-not-serve-from-cache-without-revalidation 更恰當一些,但是它太長了。HTTP/1.1 同樣提供了 Pragma: no-cache 首部,目的是為了相容於 HTTP/1.0+。但是由於現在 HTTP/1.0+ 基本已經淘汰,故不再深入進行了解。只要是和 HTTP/1.1 或者 HTTP/2 應用進行互動時候,都應該使用 Cache-Control: no-cache 來進行互動。事實上 Pragma 的優先順序最高,不過淘汰的東西就讓它安靜的消失就好了。對於 Cache-Control: max-age,用來標識文件最大合法存活時間,對於共享快取,還會有一個 s-maxage 行為和 max-age 相似,其單位同樣為秒。當 max-age 值為 0 時候,每次訪問的時候都會進行資源的請求。對於 Expires 首部,則是 HTTP/1.0+ 提供用於控制快取的首部,值為一個絕對的 GMT 時間,這個時間需要根據時區再進行轉換。nginx 是一個非常輕量級的 http 伺服器,通常被用作負載均衡器。在這個專案中,筆者通過使用 nginx 的 add_header 為響應新增 Cache-Control 首部,並設定不同值,來觀察 Cache-Control 不同值的作用,以及不同情況下快取更新情況。

用條件方法進行再驗證

HTTP 的條件方法可以高效的實現再驗證。HTTP 允許快取向原始伺服器傳送一個“條件 GET”,請求伺服器只有快取的物件和現有的副本不同時,才回送物件主體。只有條件為真時,Web 伺服器才會返回物件,否則返回一個 304。HTTP 定義了 5 個條件請求首部。對快取在驗證來說最有用的 2 個首部是 If-Modified-SinceIf-None-Match。所有條件首部都是以字首 “If-” 開頭。下面是快取再驗證中使用的條件請求首部。

簡述 HTTP 快取首部及其行為

If-Modifed-Since

If-Modified-Since 在驗證請求通常可以叫做 IMS 請求。只有當這個首部的條件為真(即文件修改過),通常 GET 請求就會成功執行,攜帶新首部的新文件替換原來的快取,同時會更新過期時間。如果文件沒有被修改過,那麼服務端返回一個小的 304 Not Modified 報文。這個報文不會包含文件主體,只會返回需要更新的新首部,一般會是一個新的過期時間。If-Modified-Since 首部可以和 Last-Modified 伺服器響應首部配合工作。服務端使用 Last-Modifed 首部來將最後修改日期附加給所提供的文件。當快取要對已快取的文件進行驗證的時候,就會在 If-Modified-Since 帶上攜帶有最後修改已快取副本的日期。

If-None-Match

僅僅只有 IMS 對最後修改日期進行驗證還是不夠的,因為會有這樣的情況:

  • 有些文件會被週期性的重寫,儘管內容沒有變化,但是最後修改時間會變化
  • 有些文件被修改了,但是並不重要,不需要重新快取資料
  • 有些伺服器不能準確判斷資源的最後修改日期
  • 最後修改日期一般是秒級的變化,但是某些檔案會在一秒變化很多次

為了解決這些問題,HTTP 允許使用者對被稱作 實體標籤(ETag) 的版本識別符號進行比較。ETag 目前沒有一個明確的生成方法,各方可以自定義。在 Nginx 上可以通過 etag off 指令關閉。Nginx 官方採用的格式是 檔案最後修改時間(hex)-檔案長度(hex)由於 Etag 沒有明確的生成方法,所以就有使用 Etag 來儲存使用者的 uid 的做法,來彌補使用 cookie 追蹤使用者的不足。當然現在也有使用瀏覽器指紋追蹤的技術,具體可以參考這個。言歸正傳,當首部中帶有 INM 首部的時候,服務端會根據 INM 提供的 Etag 值和當前檔案實際的 Etag 值進行對比,如果二者相同的話,就會返回 304 Not Modified。

INM 和 IMS 首部都存在

當 INM 和 IMS 首部都存在的時候,客戶端向服務端回送了實體標籤和資源過期時間,那麼只有兩個驗證都通過的時候,快取才會被認為有效,服務端返回 304 Modified。否則需要返回資源並更新快取。

三種重新整理方式

  1. 開啟新頁面,不會帶快取相關欄位,可以命中本地快取
  2. 普通重新整理,文件會攜帶 Cache-Control: max-age=0,進行請求而不考慮是否過期,但是關聯資源可以在不過期的情況下直接讀取本地快取
  3. 強制重新整理,文件會攜帶 Cache-Control: max-age=0,和 Pragma: no-cache,關聯資源也會重新整理
  4. 開啟開發工具,勾選 disable cache 之後的重新整理,所有請求都走強制重新整理。
    長按兩秒識別二維碼關注
    簡述 HTTP 快取首部及其行為

相關文章