跳到主要內容
黯羽輕揚每天積累一點點

HTTP 快取

免費2017-09-10#HTTP#HTTP cache header#HTTP cache-control#browser default cache#浏览器默认缓存#HTTP缓存策略

明明命中了強快取,為什麼還發請求?

一。分類

按快取的強勢程度分為:

  • 強快取:有效期內,資源直接從本地快取取(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-ageExpires沒有過期,就能命中強快取

協商快取

快取過期之後,再次訪問該資源,瀏覽器會帶上本地快取版本號去詢問 server,server 檢查客戶端遞過來的ETagLast-Modified值,告訴客戶端要不要更新快取

回應頭中的ETagLast-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-SinceIf-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=0Pragma:no-cache的具體行為依賴 server 實現,實際上代理伺服器不一定會回源或者檢查過期

參考資料

評論

暫無評論,快來發表你的看法吧

提交評論