一。分類
按快取的強勢程度分為:
-
強快取:有效期內,資源直接從本地快取取(disk cache 或 memory cache);有效期外或強制重新整理時,找 server 再要一份
-
協商快取:有效期內,同上;有效期外或強制重新整理時,帶著本地版本號詢問 server 資源是否有更新,得到回覆
304(更新過期時間等快取狀態,接著用本地版本)或200(把新版本快取起來,本地版本丟掉)
其中,協商快取可以細分為:
-
基於時間的:以資源修改時間(Last-Modified)為版本號
-
基於內容的:以資源內容 hash(ETag)為版本號
協商是快取失效(過期或棄用)之後才會發生的事情
二。相關 Header 欄位
HTTP Header 欄位分為 4 類:
-
general-header(通用頭):同時適用於請求和回應訊息
-
request-header(請求頭):允許 client 傳遞額外的資訊給 server,請求修飾符,作用相當於參數
-
response-header(回應頭):允許 server 傳遞關於該回應的額外資訊給 client,額外資訊包括 server 相關的,以及將來訪問該資源需要的一些資訊
-
entity-header(實體頭):給出訊息實體相關的 meta 資訊,如果沒有訊息實體的話,就是與請求對應的資源的資訊
P.S.關於 HTTP Header 的更多資訊,請檢視4.2 Message Headers
Pragma
HTTP 1.0 通用頭欄位,指定快取策略
Pragma = "Pragma" ":" 1#pragma-directive
pragma-directive = "no-cache" | extension-pragma
extension-pragma = token [ "=" ( token | quoted-string ) ]
Pragma 是一個含義模糊的欄位,RFC 僅指定了Pragma: no-cache出現在請求中時,即便快���有效,也應該回源去取新的,與Cache-Control: no-cache等價。出現在回應中時,沒有明確含義
P.S.關於 Pragma 的更多資訊,請檢視14.32 Pragma
Expires
HTTP 1.0 實體頭欄位,表示資源的過期時間,指定過期策略
Expires = "Expires" ":" HTTP-date
一個精確的時間點,在此之前,快取有效。這個時間點由 server 給出,如果 client 與 server 的時間不同步,快取過期策略就不可靠了
無法保證Expires給出的時間點在 client 和 srever 對應同一個時刻,所以 HTTP 1.1 新增了可以通過Cache-Control: max-age=<seconds>來定義保質期,給一個時間段,從 client 拿到資源後,再過seconds秒快取過期,這樣就只依賴 client 時間,不要求一致性了
Cache-Control
通用頭欄位,指定快取策略和過期策略
Cache-Control = "Cache-Control" ":" 1#cache-directive
cache-directive = cache-request-directive
| cache-response-directive
cache-extension = token [ "=" ( token | quoted-string ) ]
回應頭中可以出現 9 個值:
cache-response-directive =
; 資源將被客戶端和代理伺服器快取
"public"
; 資源僅被客戶端快取,不允許代理伺服器快取
| "private" [ "=" <"> 1#field-name <"> ]
; 不先回源檢查的話,不允許複用資源
| "no-cache" [ "=" <"> 1#field-name <"> ]
; 資源不允許被寫入快取
| "no-store"
; 禁止代理伺服器修改 Content-Encoding,Content-Range,Content-Type 欄位
| "no-transform"
; 不允許使用過期的資源,一旦過期,必須回源驗證(即使客戶端願意接受過期資源)
| "must-revalidate"
; 依賴 public,類似於 must-revalidate,僅適用於代理伺服器
| "proxy-revalidate"
; 快取資源,但是在指定時間 (單位為秒) 後快取過期
| "max-age" "=" delta-seconds
; 依賴 public,只在代理伺服器上有效,覆蓋 max-age
| "s-maxage" "=" delta-seconds
; 自定義擴充套件值
| cache-extension
請求頭中可以出現 7 個值:
cache-request-directive =
; 強制回源,不要來自快取的內容
"no-cache"
; 不允許把客戶端請求相關資訊寫入快取
| "no-store"
; 客戶端願意接受 age(代理伺服器快取時間)不超過 delta 秒的資源
| "max-age" "=" delta-seconds
; 客戶端願意接受過期 delta 秒內的舊內容
| "max-stale" [ "=" delta-seconds ]
; 客戶端希望回應內容在 delta 秒內都是有效的
| "min-fresh" "=" delta-seconds
; 客戶端不接受經過轉換的內容,例如 Content-Type
| "no-transform"
; 客戶端只想要已快取的資源,不重新請求資源
| "only-if-cached"
; 自定義擴充套件值
| cache-extension
注意no-store, no-cache, must-revalidate描述間的細微差異,同一欄位出現在請求頭和回應頭中的含義也都不同
Last-Modified
實體頭欄位,表示資源的最後修改時間,指定協商策略
Last-Modified = "Last-Modified" ":" HTTP-date
客戶端拿到之後會儲存起來,下一次向 server 請求資源時,會帶上這個時間點作為版本號,驗證本地快取資源是否仍然可用
資源被修改過,但內容沒變的話,發一份內容一樣的回應就顯得多餘了,所以也提供了基於內容的協商快取,避免這種情況
P.S.優先順序低於Cache-Control: max-age,同時出現時,以max-age為準
If-Modified-Since
請求頭欄位,基於時間的協商策略實現需要,比較資源最後修改時間(Last-Modified,資源最後修改時間)是否一致
If-Modified-Since = "If-Modified-Since" ":" HTTP-date
把Last-Modified版本號作為欄位值發回給 server,資源沒更新就返回 304 不給回應體,更新了返回 200,把新版本內容作為回應體
If-Unmodified-Since
同上,行為相反(比較資源最後修改時間是否不一致),如果不一致並且 method 為 POST/PUT 等更新操作時,返回 412(Precondition Failed,條件不滿足)表示更新執行失敗
ETag
回應頭欄位,表示資源的內容 hash,指定協商策略
ETag = "ETag" ":" entity-tag
客戶端會記下這個值,下一次請求該資源時作為版本號傳回給 server
P.S.ETag優先順序比Last-Modified高
If-Match
請求頭欄位,基於內容的協商策略實現需要,比較該欄位的值(ETag,資源內容 hash)是否一致
If-Match = "If-Match" ":" ( "*" | 1#entity-tag )
如果不一致,並且 method 為 POST/PUT 等更新操作時,返回 412 表示更新失敗
If-None-Match
同上,行為相反(比較該欄位的值是否不一致),如果一致,返回 304 告訴客戶端可以沿用快取版本,否則返回新資源
Age
回應頭欄位,表示資源在代理伺服器上已快取的時間
Age = "Age" ":" age-value
age-value = delta-seconds
計算方式為:
/*
* age_value
* is the value of Age: header received by the cache with
* this response.
* date_value
* is the value of the origin server's Date: header
* request_time
* is the (local) time when the cache made the request
* that resulted in this cached response
* response_time
* is the (local) time when the cache received the
* response
* now
* is the current (local) time
*/
apparent_age = max(0, response_time - date_value);
corrected_received_age = max(apparent_age, age_value);
response_delay = response_time - request_time;
corrected_initial_age = corrected_received_age + response_delay;
resident_time = now - response_time;
current_age = corrected_initial_age + resident_time;
Age:0表示剛從源 server 取過來,正值表示上次從源取過來到現在經過的秒數
三。強快取與協商快取
分別發生在快取的不同階段,快取生效時走強快取,不發請求,快取失效後才走協商快取,發請求詢問資源更新與否
強快取
回應內容命中強快取後,快取有效期內,瀏覽器不會向 server 發起請求,而是直接從本地快取(disk cache 或 memory cache)讀取
只要本地有該資源的快取版本,並且Cache-Control: max-age 或Expires沒有過期,就能命中強快取
協商快取
快取過期之後,再次訪問該資源,瀏覽器會帶上本地快取版本號去詢問 server,server 檢查客戶端遞過來的ETag或Last-Modified值,告訴客戶端要不要更新快取
回應頭中的ETag和Last-Modified是協商快取的開關,協商快取的好處是內容沒變的話,直接返回 304,不用傳輸回應體
四。啟發式快取
一種比較特殊的情況是回應頭沒有提供任何快取相關的資訊,此時瀏覽器會使用一個啟發式演算法來確定資源快取期限:
max-age = Date - Last-Modified / 10
預設的快取策略,就叫啟發式快取,啟發式是說基於經驗構造的,沒有嚴格的依據
五。重新整理行為
瀏覽器有 3 種不同的重新整理行為,在驗證 HTTP 快取時很容易被迷惑:
-
開新頁面:開啟�� tab 或者視窗,訪問頁面
-
普通重新整理:點選重新整理按鈕、地址列回車、
CMD + R -
強制重新整理:
CMD + Shift + R、Chrome 長按重新整理按鈕,選擇硬性重新載入 -
禁用快取再重新整理:勾選
Disable cache設定,再開新頁面/重新整理
開新頁面
請求頭不帶快取相關欄位,如果本地快取版本有效,從快取讀取,不發請求,並顯示個假請求頭:
Request Headers
Provisional headers are shown
Upgrade-Insecure-Requests:1
User-Agent:...
回應頭沿用快取的那份
普通重新整理
不從快取取,一定會向服務發起請求,請求頭會帶上If-Modified-Since和If-None-Match等快取頭(如果有的話),此外還會擅自添上:
Cache-Control:max-age=0
要求代理伺服器檢查快取是否過期
P.S.普通重新整理行為發生時,瀏覽器一定會發起請求,即便資源快取仍然有效,理應處於強快取狀態。因為使用者要求重新整理內容,希望看到新的,而關聯的資源(比如該頁面含有的 CSS,JS 等資源)不會被強制發起請求
強制重新整理
同樣會強制發起請求,帶上快取相關資訊,還會擅自添上:
Cache-Control:max-age=0
Pragma:no-cache
要求回源去取新的,即便快取沒過期
禁用快取再重新整理
禁用快取後,後續所有請求都會被添上:
Cache-Control:max-age=0
Pragma:no-cache
相當於全都走強制重新整理,包括關聯資源
P.S.Cache-Control:max-age=0,Pragma:no-cache的具體行為依賴 server 實現,實際上代理伺服器不一定會回源或者檢查過期
參考資料
-
瀏覽器快取機制剖析:快取機制流程圖不錯,Header 欄位含義描述不正確
-
HTTP 快取控制小結:內容很準確,且較全面
-
What heuristics do browsers use to cache resources not explicitly set to be cachable?
暫無評論,快來發表你的看法吧