HTTP 快取

概述

HTTP 快取會儲存與請求相關的響應,並在後續請求中重用儲存的響應。

可重用性有很多優點。首先,由於無需將請求傳遞到源伺服器,因此客戶端和快取越近,響應速度越快。最典型的例子是瀏覽器本身為瀏覽器請求儲存快取。

此外,當響應可重用時,源伺服器無需處理請求——因此無需解析和路由請求,無需根據 cookie 恢復會話,無需查詢 DB 以獲取結果,也無需渲染模板引擎。這減輕了伺服器的負載。

快取的正常執行對於系統健康至關重要。

快取型別

HTTP 快取 規範中,主要有兩種型別的快取:私有快取共享快取

私有快取

私有快取是與特定客戶端繫結的快取——通常是瀏覽器快取。由於儲存的響應不會與其他客戶端共享,因此私有快取可以為該使用者儲存個性化響應。

另一方面,如果個性化內容儲存在私有快取以外的快取中,則其他使用者可能會檢索到這些內容——這可能會導致意外的資訊洩露。

如果響應包含個性化內容,並且您只想將響應儲存在私有快取中,則必須指定 private 指令。

http
Cache-Control: private

個性化內容通常由 cookie 控制,但 cookie 的存在並不總是意味著它是私有的,因此僅憑 cookie 無法使響應成為私有的。

共享快取

共享快取位於客戶端和伺服器之間,可以儲存可供使用者共享的響應。共享快取可以進一步細分為代理快取託管快取

代理快取

除了訪問控制功能之外,一些代理還實現快取以減少網路流量。這通常不是由服務開發人員管理的,因此必須透過適當的 HTTP 標頭等進行控制。但是,過去,過時的代理快取實現——例如,無法正確理解 HTTP 快取標準的實現——經常給開發人員帶來問題。

以下“萬金油”標頭用於嘗試解決“舊且未更新的代理快取”實現的問題,這些實現無法理解當前 HTTP 快取規範指令,例如 no-store

http
Cache-Control: no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate

但是,近年來,隨著 HTTPS 變得越來越普遍,客戶端/伺服器通訊變得加密,路徑中的代理快取只能隧道響應,而不能充當快取,在許多情況下。因此,在這種情況下,無需擔心過時的代理快取實現,這些實現甚至無法看到響應。

另一方面,如果 TLS 橋接代理透過從 PC 上安裝由組織管理的 CA(證書頒發機構)頒發的證書,以中間人方式解密所有通訊,並執行訪問控制等——就可以看到響應的內容並將其快取。但是,由於近年來 CT(證書透明度) 已經普及,一些瀏覽器只允許使用帶有 SCT(簽名證書時間戳)頒發的證書,因此此方法需要應用企業策略。在這樣的受控環境中,無需擔心代理快取“過時且未更新”。

託管快取

託管快取是由服務開發人員顯式部署的,用於解除安裝源伺服器並高效地傳遞內容。示例包括反向代理、CDN 以及與 Cache API 結合使用的服務工作者。

託管快取的特徵會因部署的產品而異。在大多數情況下,您可以透過 Cache-Control 標頭以及您自己的配置檔案或儀表板來控制快取的行為。

例如,HTTP 快取規範基本上沒有定義顯式刪除快取的方法——但是對於託管快取,可以透過儀表板操作、API 呼叫、重啟等隨時刪除儲存的響應。這允許更積極的快取策略。

也可以忽略標準 HTTP 快取規範協議,而選擇顯式操作。例如,可以指定以下內容,以選擇退出私有快取或代理快取,同時使用您自己的策略僅在託管快取中進行快取。

http
Cache-Control: no-store

例如,Varnish Cache 使用 VCL(Varnish 配置語言,一種 DSL 邏輯)來處理快取儲存,而與 Cache API 結合使用的服務工作者允許您在 JavaScript 中建立這種邏輯。

這意味著,如果託管快取故意忽略了 no-store 指令,則無需將其視為不符合標準。您應該做的是,避免使用“萬金油”標頭,但要仔細閱讀您正在使用的任何託管快取機制的文件,並確保以您選擇使用的機制提供的各種方式正確控制快取。

請注意,一些 CDN 提供了僅對該 CDN 有效的自己的標頭(例如,Surrogate-Control)。目前,正在進行定義 CDN-Cache-Control 標頭的工作,以將其標準化。

Type of Cache

啟發式快取

HTTP 旨在儘可能地進行快取,因此即使沒有提供 Cache-Control,如果滿足某些條件,響應也會被儲存和重用。這被稱為啟發式快取

例如,請考慮以下響應。此響應上次更新是在 1 年前。

http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
Last-Modified: Tue, 22 Feb 2021 22:22:22 GMT

<!doctype html>

啟發式地知道,在一年內未更新的內容在之後一段時間內也不會更新。因此,客戶端儲存此響應(儘管缺少 max-age),並將其重用一段時間。重用多長時間取決於實現,但規範建議大約 10%(在本例中為 0.1 年)的儲存時間。

啟發式快取是一種在 Cache-Control 支援得到廣泛採用之前出現的解決方法,基本上所有響應都應該明確指定 Cache-Control 標頭。

基於年齡的“新鮮”和“陳舊”

儲存的 HTTP 響應有兩種狀態:新鮮陳舊新鮮狀態通常表示響應仍然有效,可以重用,而陳舊狀態表示快取的響應已過期。

確定響應何時新鮮何時陳舊的標準是年齡。在 HTTP 中,年齡是響應生成以來的時間。這類似於其他快取機制中的 TTL

請考慮以下示例響應(604800 秒是一週)

http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
Cache-Control: max-age=604800

<!doctype html>

儲存了示例響應的快取計算自響應生成以來的時間,並將結果用作響應的年齡

對於示例響應,max-age 的含義如下:

  • 如果響應的年齡小於一週,則響應為新鮮
  • 如果響應的年齡大於一週,則響應為陳舊

只要儲存的響應保持新鮮,它將用於滿足客戶端請求。

當響應儲存在共享快取中時,可以告知客戶端響應的年齡。繼續以示例為例,如果共享快取儲存了響應一天,則共享快取會將以下響應傳送到後續的客戶端請求。

http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
Cache-Control: max-age=604800
Age: 86400

<!doctype html>

收到該響應的客戶端將發現它在剩餘的 518400 秒內保持新鮮,這是響應的 max-ageAge 之間的差值。

Expires 或 max-age

在 HTTP/1.0 中,新鮮度以前由 Expires 標頭指定。

Expires 標頭使用顯式時間而不是指定經過時間來指定快取的生存期。

http
Expires: Tue, 28 Feb 2022 22:22:22 GMT

但是,時間格式很難解析,發現許多實現錯誤,並且可以透過故意移動系統時鐘來誘發問題;因此,在 HTTP/1.1 中,為 Cache-Control 採用了 max-age——用於指定經過時間。

如果同時提供了 ExpiresCache-Control: max-age,則定義 max-age 為首選。因此,現在 HTTP/1.1 已得到廣泛使用,因此無需提供 Expires

Vary

區分響應的方式本質上是基於它們的 URL

URL 響應主體
https://example.com/index.html <!doctype html>...
https://example.com/style.css body { ...
https://example.com/script.js function main () { ...

但即使響應具有相同的 URL,它們的內容也不總是相同的。尤其是在執行內容協商時,伺服器返回的響應可能會取決於 AcceptAccept-LanguageAccept-Encoding 請求標頭的值。

例如,對於使用 Accept-Language: en 標頭返回並快取的英語內容,如果使用 Accept-Language: ja 請求標頭重新使用該快取響應,則不希望這樣做。在這種情況下,您可以透過將“Accept-Language”新增到 Vary 標頭的值來根據語言分別快取響應。

http
Vary: Accept-Language

這會導致快取的鍵基於響應 URL 和 `Accept-Language` 請求頭的組合,而不是僅基於響應 URL。

URL Accept-Language 響應主體
https://example.com/index.html ja-JP <!doctype html>...
https://example.com/index.html en-US <!doctype html>...
https://example.com/style.css ja-JP body { ...
https://example.com/script.js ja-JP function main () { ...

此外,如果你基於使用者代理提供內容最佳化(例如,用於響應式設計),你可能會傾向於在 `Vary` 頭部的值中包含 "User-Agent"。但是,User-Agent 請求頭通常具有大量變體,這會大大降低快取被重用的可能性。因此,如果可能,請考慮使用功能檢測而不是基於 User-Agent 請求頭來改變行為。

對於使用 cookie 來防止他人重用快取的個性化內容的應用程式,你應該指定 `Cache-Control: private`,而不是為 `Vary` 指定 cookie。

驗證

陳舊的響應不會立即被丟棄。HTTP 具有將陳舊的響應轉換為新的響應的機制,方法是詢問源伺服器。這稱為 **驗證**,有時也稱為 **重新驗證**。

驗證是透過使用包含 `If-Modified-Since` 或 `If-None-Match` 請求頭的 **條件請求** 來完成的。

If-Modified-Since

以下響應是在 22:22:22 生成的,並且具有 1 小時的 `max-age`,因此你知道它在 23:22:22 之前是新鮮的。

http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
Last-Modified: Tue, 22 Feb 2022 22:00:00 GMT
Cache-Control: max-age=3600

<!doctype html>

在 23:22:22 時,響應變為陳舊,並且快取無法被重用。因此,下面的請求顯示了一個客戶端傳送帶有 `If-Modified-Since` 請求頭的請求,以詢問伺服器自指定時間以來是否進行了任何更改。

http
GET /index.html HTTP/1.1
Host: example.com
Accept: text/html
If-Modified-Since: Tue, 22 Feb 2022 22:00:00 GMT

如果內容自指定時間以來沒有更改,伺服器將返回 `304 Not Modified`。

由於此響應僅指示“無更改”,因此沒有響應主體,只有狀態程式碼,因此傳輸大小非常小。

http
HTTP/1.1 304 Not Modified
Content-Type: text/html
Date: Tue, 22 Feb 2022 23:22:22 GMT
Last-Modified: Tue, 22 Feb 2022 22:00:00 GMT
Cache-Control: max-age=3600

收到該響應後,客戶端將儲存的陳舊響應恢復為新鮮狀態,並在剩餘的 1 小時內可以重用它。

伺服器可以從作業系統檔案系統中獲取修改時間,對於服務靜態檔案的情況,這相對容易。但是,也有一些問題;例如,時間格式很複雜,難以解析,並且分散式伺服器難以同步檔案更新時間。

為了解決這些問題,`ETag` 響應頭被標準化為替代方案。

ETag/If-None-Match

`ETag` 響應頭的值是由伺服器生成的任意值。伺服器生成該值的限制,因此伺服器可以自由地根據它們選擇的方式設定該值,例如,主體內容的雜湊值或版本號。

例如,如果使用雜湊值作為 `ETag` 頭部,並且 `index.html` 資源的雜湊值為 `33a64df5`,則響應將如下所示

http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
ETag: "33a64df5"
Cache-Control: max-age=3600

<!doctype html>

如果該響應已陳舊,客戶端將獲取快取響應的 `ETag` 響應頭的值,並將其放入 `If-None-Match` 請求頭中,以詢問伺服器該資源是否已被修改

http
GET /index.html HTTP/1.1
Host: example.com
Accept: text/html
If-None-Match: "33a64df5"

如果伺服器為請求的資源確定的 `ETag` 頭部值與請求中的 `If-None-Match` 值相同,則伺服器將返回 `304 Not Modified`。

但如果伺服器確定請求的資源現在應該具有不同的 `ETag` 值,則伺服器將改用 `200 OK` 和最新版本的資源進行響應。

注意:RFC9110 優先於伺服器儘可能為 `200` 響應傳送 `ETag` 和 `Last-Modified`。在快取重新驗證期間,如果 `If-Modified-Since` 和 `If-None-Match` 都存在,則 `If-None-Match` 優先於驗證器。如果你只考慮快取,你可能會認為 `Last-Modified` 是不必要的。但是,`Last-Modified` 不僅僅對快取有用;它是一個標準的 HTTP 頭部,也被內容管理 (CMS) 系統用來顯示最後修改時間,被爬蟲用來調整爬取頻率,以及其他各種用途。因此,考慮到整個 HTTP 生態系統,最好同時提供 `ETag` 和 `Last-Modified`。

強制重新驗證

如果你不希望重用響應,而是希望始終從伺服器獲取最新內容,則可以使用 `no-cache` 指令來強制驗證。

透過在響應中新增 `Cache-Control: no-cache` 以及 `Last-Modified` 和 `ETag`(如下所示),客戶端將在請求的資源已更新時收到 `200 OK` 響應,否則將在請求的資源未更新時收到 `304 Not Modified` 響應。

http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
Last-Modified: Tue, 22 Feb 2022 22:00:00 GMT
ETag: deadbeef
Cache-Control: no-cache

<!doctype html>

通常說,`max-age=0` 和 `must-revalidate` 的組合與 `no-cache` 的含義相同。

http
Cache-Control: max-age=0, must-revalidate

`max-age=0` 表示響應立即變為陳舊,而 `must-revalidate` 表示一旦變為陳舊,則必須在重新驗證之前不能重用,因此,結合起來,語義似乎與 `no-cache` 相同。

但是,`max-age=0` 的這種用法是 HTTP/1.1 之前許多實現無法處理 `no-cache` 指令的事實的殘餘,為了解決這一限制,`max-age=0` 被用作解決方法。

但是,現在 HTTP/1.1 相容伺服器已廣泛部署,因此沒有理由使用 `max-age=0` 和 `must-revalidate` 的組合,你應該只使用 `no-cache`。

不要快取

`no-cache` 指令不會阻止儲存響應,而是會阻止在未進行重新驗證的情況下重用響應。

如果你不希望將響應儲存在任何快取中,請使用 `no-store`。

http
Cache-Control: no-store

但是,一般來說,實際上“不要快取”的要求相當於以下幾種情況

  • 出於隱私原因,不希望響應被特定客戶端以外的任何人儲存。
  • 希望始終提供最新資訊。
  • 不知道過時的實現中會發生什麼。

在這種情況下,`no-store` 並不總是最合適的指令。

以下部分將更詳細地介紹這些情況。

不要與他人共享

如果帶有個性化內容的響應意外地對快取的其他使用者可見,這將是一個問題。

在這種情況下,使用 `private` 指令將導致個性化響應僅儲存在特定客戶端,而不會洩露到快取的任何其他使用者。

http
Cache-Control: private

在這種情況下,即使給出了 `no-store`,也必須給出 `private`。

每次提供最新內容

`no-store` 指令會阻止響應被儲存,但不會刪除相同 URL 的任何已儲存的響應。

換句話說,如果某個特定 URL 已經存在舊的響應,則返回 `no-store` 不會阻止重用舊的響應。

但是,`no-cache` 指令將強制客戶端在重用任何儲存的響應之前傳送驗證請求。

http
Cache-Control: no-cache

如果伺服器不支援條件請求,則可以強制客戶端每次訪問伺服器,並始終使用 `200 OK` 獲取最新響應。

處理過時的實現

作為對忽略 `no-store` 的過時實現的解決方法,你可能會看到使用以下廚房水槽頭。

http
Cache-Control: no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate

建議使用 `no-cache` 作為處理此類過時實現的替代方案,如果從一開始就給出 `no-cache`,則不會有任何問題,因為伺服器將始終收到請求。

如果你擔心的是共享快取,則可以新增 `private` 來確保阻止無意中的快取

http
Cache-Control: no-cache, private

`no-store` 會丟失什麼

你可能會認為新增 `no-store` 是退出快取的正確方法。

但是,不建議隨意授予 `no-store`,因為你會失去 HTTP 和瀏覽器擁有的許多優勢,包括瀏覽器的後退/前進快取。

因此,為了獲得 Web 平臺完整功能集的優勢,建議使用 `no-cache` 與 `private` 組合使用。

重新載入和強制重新載入

驗證可以針對請求和響應執行。

重新載入和 **強制重新載入** 操作是瀏覽器端執行的驗證的常見示例。

重新載入

為了從視窗損壞中恢復或更新到資源的最新版本,瀏覽器為使用者提供了重新載入功能。

瀏覽器重新載入期間傳送的 HTTP 請求的簡化檢視如下所示

http
GET / HTTP/1.1
Host: example.com
Cache-Control: max-age=0
If-None-Match: "deadbeef"
If-Modified-Since: Tue, 22 Feb 2022 20:20:20 GMT

(來自 Chrome、Edge 和 Firefox 的請求非常類似於上面;來自 Safari 的請求會略有不同。)

請求中的 `max-age=0` 指令指定“重用年齡為 0 或更小的響應”,因此實際上不會重用中間儲存的響應。

因此,請求將透過 `If-None-Match` 和 `If-Modified-Since` 進行驗證。

此行為也在 Fetch 標準中定義,可以透過在快取模式設定為 `no-cache` 的情況下呼叫 `fetch()` 來在 JavaScript 中重現(請注意,`reload` 不是這種情況的正確模式)

js
// Note: "reload" is not the right mode for a normal reload; "no-cache" is
fetch("/", { cache: "no-cache" });

強制重新載入

出於向後相容性的原因,瀏覽器在重新載入期間使用 `max-age=0`,因為 HTTP/1.1 之前許多過時的實現不理解 `no-cache`。但是,`no-cache` 在這種情況下現在很好,**強制重新載入** 是繞過快取響應的另一種方式。

瀏覽器 **強制重新載入** 期間的 HTTP 請求如下所示

http
GET / HTTP/1.1
Host: example.com
Pragma: no-cache
Cache-Control: no-cache

(來自 Chrome、Edge 和 Firefox 的請求非常類似於上面;來自 Safari 的請求會略有不同。)

由於這不是帶有 `no-cache` 的條件請求,因此可以確保從源伺服器獲得 `200 OK`。

此行為也在 Fetch 標準中定義,可以透過在快取模式設定為 `reload` 的情況下呼叫 `fetch()` 來在 JavaScript 中重現(請注意,它不是 `force-reload`)

js
// Note: "reload" — rather than "no-cache" — is the right mode for a "force reload"
fetch("/", { cache: "reload" });

避免重新驗證

永不更改的內容應透過使用快取清除來指定長時間的 `max-age`,也就是說,透過在請求 URL 中包含版本號、雜湊值等。

但是,當用戶重新載入時,即使伺服器知道內容是不可變的,也會發送重新驗證請求。

為了防止這種情況,可以使用 `immutable` 指令明確指示不需要重新驗證,因為內容永遠不會更改。

http
Cache-Control: max-age=31536000, immutable

這可以防止在重新載入期間進行不必要的重新驗證。

請注意,Chrome 已更改其實現,而不是實現該指令,Chrome 已更改其實現,因此在重新載入期間不會針對子資源執行重新驗證。

刪除儲存的響應

基本上沒有辦法刪除已使用長時間 `max-age` 儲存的響應。

假設以下來自 `https://example.com/` 的響應已儲存。

http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Cache-Control: max-age=31536000

<!doctype html>

您可能希望在響應在伺服器上過期後覆蓋該響應,但一旦響應被儲存,伺服器就無能為力了——因為由於快取,不再有請求到達伺服器。

規範中提到的方法之一是向同一 URL 傳送一個使用不安全方法(如 POST)的請求,但這對於許多客戶端來說通常很難故意做到。

還有一個關於 Clear-Site-Data: cache 標頭和值的規範,但 並非所有瀏覽器都支援它——即使使用它,它也隻影響瀏覽器快取,對中間快取沒有影響。

因此,應該假定任何儲存的響應將在其 max-age 週期內保持不變,除非使用者手動執行重新載入、強制重新載入或清除歷史記錄操作。

快取減少了對伺服器的訪問,這意味著伺服器失去了對該 URL 的控制。如果伺服器不想失去對 URL 的控制——例如,在資源經常更新的情況下——您應該新增 no-cache,以便伺服器始終接收請求併發送預期的響應。

請求合併

共享快取主要位於源伺服器之前,旨在減少對源伺服器的流量。

因此,如果多個相同的請求同時到達共享快取,中間快取將代表自己轉發一個請求到源,然後可以為所有客戶端重用結果。這被稱為“**請求合併**”。

當請求同時到達時,就會發生請求合併,因此即使響應中給出了 max-age=0no-cache,它也會被重用。

如果響應是針對特定使用者個性化的,並且您不希望它在合併中共享,您應該新增 private 指令。

Request Collapse

常見快取模式

Cache-Control 規範中有很多指令,可能很難理解所有指令。但大多數網站都可以透過少數幾種模式的組合來涵蓋。

本節介紹了快取設計中的常見模式。

預設設定

如上所述,快取的預設行為(即,對於沒有 Cache-Control 的響應)並非僅僅是“不快取”,而是根據所謂的“啟發式快取”隱式快取。

為了避免啟發式快取,最好明確地為所有響應提供一個預設的 Cache-Control 標頭。

為了確保預設情況下始終傳輸資源的最新版本,通常的做法是使預設的 Cache-Control 值包含 no-cache

http
Cache-Control: no-cache

此外,如果服務實現了 cookie 或其他登入方法,並且內容是針對每個使用者個性化的,則還必須給出 private,以防止與其他使用者共享。

http
Cache-Control: no-cache, private

快取清除

最適合快取的資源是內容永不改變的靜態不可變檔案。對於那些確實會更改的資源,通常的做法是在每次內容更改時更改 URL,以便 URL 單位可以快取更長時間。

例如,請考慮以下 HTML:

html
<script src="bundle.js"></script>
<link rel="stylesheet" href="build.css" />
<body>
  hello
</body>

在現代 Web 開發中,JavaScript 和 CSS 資源經常在開發過程中更新。此外,如果客戶端使用的 JavaScript 和 CSS 資源的版本不同步,顯示將被破壞。

因此,上面的 HTML 使得難以使用 max-age 來快取 bundle.jsbuild.css

因此,您可以使用包含基於版本號或雜湊值的更改部分的 URL 來提供 JavaScript 和 CSS。下面列出了一些執行此操作的方法。

# version in filename
bundle.v123.js

# version in query
bundle.js?v=123

# hash in filename
bundle.YsAIAAAA-QG4G6kCMAMBAAAAAAAoK.js

# hash in query
bundle.js?v=YsAIAAAA-QG4G6kCMAMBAAAAAAAoK

由於快取根據 URL 區分資源,因此如果資源更新時 URL 發生更改,快取將不再被重用。

html
<script src="bundle.v123.js"></script>
<link rel="stylesheet" href="build.v123.css" />
<body>
  hello
</body>

透過這種設計,JavaScript 和 CSS 資源都可以快取很長時間。那麼 max-age 應該設定為多長時間呢?QPACK 規範提供了這個問題的答案。

QPACK 是一種用於壓縮 HTTP 標頭欄位的標準,其中定義了常用欄位值的表格。

下面列出了一些常用的快取標頭值。

36 cache-control max-age=0
37 cache-control max-age=604800
38 cache-control max-age=2592000
39 cache-control no-cache
40 cache-control no-store
41 cache-control public, max-age=31536000

如果您選擇其中一個編號選項,則可以在透過 HTTP3 傳輸時以 1 位元組壓縮值。

數字 373841 分別對應於一周、一個月和一年。

由於快取會在儲存新條目時刪除舊條目,因此即使 max-age 設定為 1 周,儲存的響應在一週後仍然存在的機率並不高。因此,在實踐中,您選擇哪一個並沒有太大區別。

請注意,數字 41 具有最長的 max-age(1 年),但帶有 public

public 值的作用是即使存在 Authorization 標頭,也能使響應可儲存。

注意:只有在需要在設定 Authorization 標頭時儲存響應的情況下,才應使用 public 指令。否則不需要,因為只要給出 max-age,響應就會儲存在共享快取中。

因此,如果響應使用基本身份驗證進行個性化,則 public 的存在可能會導致問題。如果您對此感到擔憂,可以選擇第二長的值 38(1 個月)。

http
# response for bundle.v123.js

# If you never personalize responses via Authorization
Cache-Control: public, max-age=31536000

# If you can't be certain
Cache-Control: max-age=2592000

驗證

不要忘記設定 Last-ModifiedETag 標頭,以便您在重新載入時不必重新傳輸資源。對於預先構建的靜態檔案,生成這些標頭很容易。

這裡的 ETag 值可以是檔案的雜湊值。

http
# response for bundle.v123.js
Last-Modified: Tue, 22 Feb 2022 20:20:20 GMT
ETag: YsAIAAAA-QG4G6kCMAMBAAAAAAAoK

此外,可以新增 immutable 來防止重新載入時的驗證。

組合結果如下所示。

http
# bundle.v123.js
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Length: 1024
Cache-Control: public, max-age=31536000, immutable
Last-Modified: Tue, 22 Feb 2022 20:20:20 GMT
ETag: YsAIAAAA-QG4G6kCMAMBAAAAAAAoK

快取清除是一種透過在內容更改時更改 URL 來使響應在很長時間內可快取的技術。該技術可以應用於所有子資源,例如影像。

注意:在評估使用 immutable 和 QPACK 時:如果您擔心 immutable 會更改 QPACK 提供的預定義值,請考慮在這種情況下,immutable 部分可以透過將 Cache-Control 值拆分為兩行來單獨編碼——儘管這取決於特定 QPACK 實現使用的編碼演算法。

http
Cache-Control: public, max-age=31536000
Cache-Control: immutable

主資源

與子資源不同,主資源無法進行快取清除,因為它們的 URL 無法像子資源 URL 那樣進行裝飾。

如果儲存以下 HTML 本身,即使伺服器端更新了內容,也無法顯示最新版本。

html
<script src="bundle.v123.js"></script>
<link rel="stylesheet" href="build.v123.css" />
<body>
  hello
</body>

對於這種情況,no-cache 會更合適——而不是 no-store——因為我們不希望儲存 HTML,而是希望它始終保持最新。

此外,新增 Last-ModifiedETag 將允許客戶端傳送條件請求,如果 HTML 沒有更新,則可以返回 304 Not Modified

http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Cache-Control: no-cache
Last-Modified: Tue, 22 Feb 2022 20:20:20 GMT
ETag: AAPuIbAOdvAGEETbgAAAAAAABAAE

該設定適用於非個性化 HTML,但對於使用 cookie 進行個性化的響應——例如,登入後——不要忘記也指定 private

http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Cache-Control: no-cache, private
Last-Modified: Tue, 22 Feb 2022 20:20:20 GMT
ETag: AAPuIbAOdvAGEETbgAAAAAAABAAE
Set-Cookie: __Host-SID=AHNtAyt3fvJrUL5g5tnGwER; Secure; Path=/; HttpOnly

同樣適用於 favicon.icomanifest.json.well-known 和無法使用快取清除更改 URL 的 API 端點。

大多數 Web 內容都可以透過上面介紹的兩種模式的組合來涵蓋。

關於託管快取的更多資訊

使用前面部分介紹的方法,子資源可以透過快取清除來快取很長時間,但主資源(通常是 HTML 文件)卻不能。

快取主資源很困難,因為僅僅使用 HTTP 快取規範中的標準指令,就沒有辦法在伺服器端更新內容時主動刪除快取內容。

但是,透過部署託管快取(如 CDN 或服務工作者)可以做到這一點。

例如,允許透過 API 或儀表板操作進行快取清除的 CDN 將允許更積極的快取策略,方法是儲存主資源,並且僅在伺服器端發生更新時顯式清除相關快取。

如果服務工作者能夠在伺服器端發生更新時刪除快取 API 中的內容,它也可以做到這一點。

有關更多資訊,請參閱 CDN 的文件,並查閱 服務工作者文件

另請參閱