HTTP 快取
HTTP 快取儲存與請求關聯的響應,並對後續請求重用儲存的響應。
可重用性有幾個優點。首先,由於無需將請求傳送到源伺服器,因此客戶端和快取越近,響應速度就越快。最典型的例子是瀏覽器本身為瀏覽器請求儲存快取。
此外,當響應可重用時,源伺服器無需處理請求——因此無需解析和路由請求、根據 cookie 恢復會話、查詢資料庫以獲取結果或渲染模板引擎。這減少了伺服器的負載。
快取的正常執行對系統的健康至關重要。
快取型別
在HTTP 快取規範中,主要有兩種快取型別:私有快取和共享快取。
私有快取
私有快取是與特定客戶端繫結的快取——通常是瀏覽器快取。由於儲存的響應不與其他客戶端共享,私有快取可以為該使用者儲存個性化響應。
另一方面,如果個性化內容儲存在私有快取以外的快取中,那麼其他使用者可能會檢索這些內容——這可能會導致無意的資訊洩露。
如果響應包含個性化內容,並且您只想將響應儲存在私有快取中,則必須指定private指令。
Cache-Control: private
個性化內容通常由 cookie 控制,但 cookie 的存在並不總是表示它是私有的,因此僅憑 cookie 不能使響應成為私有的。
共享快取
共享快取位於客戶端和伺服器之間,可以儲存可在使用者之間共享的響應。共享快取可以進一步細分為代理快取和託管快取。
代理快取
除訪問控制功能外,一些代理還實現快取以減少網路流量。這通常不由服務開發人員管理,因此必須透過適當的 HTTP 標頭等進行控制。然而,過去,過時的代理快取實現——例如無法正確理解 HTTP 快取標準的實現——經常給開發人員帶來問題。
以下“萬能標頭”用於嘗試解決“舊的且未更新的代理快取”實現,這些實現不理解當前的 HTTP 快取規範指令,例如no-store。
Cache-Control: no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate
然而,近年來,隨著 HTTPS 變得越來越普遍,客戶端/伺服器通訊變得加密,路徑中的代理快取在許多情況下只能隧道傳輸響應,而不能充當快取。因此在這種情況下,無需擔心甚至無法看到響應的過時代理快取實現。
另一方面,如果 TLS 橋接代理透過在 PC 上安裝由組織管理的 CA(證書頒發機構)的證書,以中間人方式解密所有通訊,並執行訪問控制等操作——它可以看到響應的內容並快取它。然而,近年來 CT(證書透明度)已廣泛普及,並且某些瀏覽器僅允許帶有 SCT(簽名證書時間戳)的證書,因此此方法需要應用企業策略。在這種受控環境中,無需擔心代理快取“過時且未更新”。
託管快取
託管快取由服務開發人員明確部署,旨在減輕源伺服器的負載並高效地交付內容。示例包括反向代理、CDN 和與 Cache API 結合使用的 Service Worker。
託管快取的特性因部署的產品而異。在大多數情況下,您可以透過Cache-Control標頭和自己的配置檔案或儀表板控制快取的行為。
例如,HTTP 快取規範本質上沒有定義顯式刪除快取的方法——但透過託管快取,可以透過儀表板操作、API 呼叫、重啟等方式隨時刪除儲存的響應。這允許更主動的快取策略。
也可以忽略標準 HTTP 快取規範協議,轉而進行顯式操作。例如,可以指定以下內容以選擇退出私有快取或代理快取,同時使用您自己的策略僅在託管快取中進行快取。
Cache-Control: no-store
例如,Varnish Cache 使用 VCL(Varnish 配置語言,一種DSL)邏輯來處理快取儲存,而與 Cache API 結合使用的 Service Worker 允許您在 JavaScript 中建立該邏輯。
這意味著如果託管快取有意忽略no-store指令,則無需將其視為“不符合”標準。您應該做的是,避免使用萬能標頭,而是仔細閱讀您正在使用的任何託管快取機制的文件,並確保您以所選機制提供的方式正確控制快取。
請注意,某些 CDN 提供僅對該 CDN 有效的自己的標頭(例如,Surrogate-Control)。目前,正在努力定義一個CDN-Cache-Control標頭以使這些標準化。
啟發式快取
HTTP 旨在儘可能多地進行快取,因此即使未給出Cache-Control,如果滿足某些條件,響應也會被儲存和重用。這稱為啟發式快取。
例如,看以下響應。此響應最後更新於 1 年前。
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/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/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-age和Age之間的差值。
Expires 或 max-age
在 HTTP/1.0 中,新鮮度曾經透過Expires標頭指定。
Expires標頭透過指定明確的時間而不是經過的時間來指定快取的生命週期。
Expires: Tue, 28 Feb 2022 22:22:22 GMT
然而,時間格式難以解析,發現了許多實現錯誤,並且可能透過故意移動系統時鐘引起問題;因此,HTTP/1.1 中在Cache-Control中採用了max-age——用於指定經過的時間。
如果Expires和Cache-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。特別是當執行內容協商時,來自伺服器的響應可能取決於Accept、Accept-Language和Accept-Encoding請求標頭的值。
例如,對於以Accept-Language: en標頭返回並快取的英語內容,不希望隨後將該快取的響應重用於具有Accept-Language: ja請求標頭的請求。在這種情況下,您可以透過將Accept-Language新增到Vary標頭的值中,使響應根據語言分別快取。
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 生成,max-age為 1 小時,因此您知道它在 23:22:22 之前都是新鮮的。
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請求標頭的請求,以詢問伺服器自指定時間以來是否進行了任何更改。
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/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/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請求標頭中,以詢問伺服器資源是否已修改
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/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具有相同的含義。
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。
Cache-Control: no-store
然而,一般來說,實踐中“不快取”的要求等同於以下情況:
- 出於隱私原因,不希望除特定客戶端之外的任何人儲存響應。
- 希望始終提供最新資訊。
- 不知道過時的實現會發生什麼。
在這種情況下,no-store並非總是最合適的指令。
以下部分將更詳細地探討這些情況。
不要與他人共享
如果包含個性化內容的響應意外地被快取的其他使用者看到,那將是問題。
在這種情況下,使用private指令將導致個性化響應僅儲存在特定客戶端中,而不會洩露給快取的任何其他使用者。
Cache-Control: private
在這種情況下,即使給定no-store,也必須給定private。
每次提供最新內容
no-store指令會阻止響應被儲存,但不會刪除同一 URL 的任何已儲存響應。
換句話說,如果某個 URL 已儲存舊響應,返回no-store不會阻止舊響應被重用。
然而,no-cache指令將強制客戶端在重用任何儲存的響應之前傳送驗證請求。
Cache-Control: no-cache
如果伺服器不支援條件請求,您可以強制客戶端每次訪問伺服器並始終獲取帶有200 OK的最新響應。
處理過時的實現
作為解決忽略no-store的過時實現的權宜之計,您可能會看到使用以下“萬能標頭”。
Cache-Control: no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate
建議使用no-cache作為處理此類過時實現的替代方案,如果從一開始就給定no-cache,則不會有問題,因為伺服器將始終接收請求。
如果您擔心共享快取,可以透過新增private來確保防止意外快取
Cache-Control: no-cache, private
no-store會失去什麼
您可能認為新增no-store是選擇退出快取的正確方法。
然而,不建議隨意授予no-store,因為您會失去 HTTP 和瀏覽器擁有的許多優勢,包括瀏覽器的回退/前進快取。
因此,為了獲得 Web 平臺的完整功能集的優勢,請優先使用no-cache與private的組合。
重新載入和強制重新載入
驗證可以對請求和響應執行。
重新載入和強制重新載入操作是從瀏覽器端執行的驗證的常見示例。
重新載入
為了從視窗損壞中恢復或更新到資源的最新版本,瀏覽器為使用者提供了重新載入功能。
瀏覽器重新載入期間傳送的 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標準中定義,可以透過呼叫fetch()並將快取模式設定為no-cache在 JavaScript 中重現(請注意,reload在這種情況下不是正確的模式)
// 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 請求如下所示
GET / HTTP/1.1
Host: example.com
Pragma: no-cache
Cache-Control: no-cache
(來自 Chrome、Edge 和 Firefox 的請求看起來非常像上面;來自 Safari 的請求看起來會有點不同。)
由於這不是帶有no-cache的條件請求,因此您可以確保會從源伺服器獲取200 OK。
這種行為也在Fetch標準中定義,可以透過呼叫fetch()並將快取模式設定為reload在 JavaScript 中重現(請注意,它不是force-reload)
// Note: "reload" — rather than "no-cache" — is the right mode for a "force reload"
fetch("/", { cache: "reload" });
避免重新驗證
永不更改的內容應透過使用快取清除(即,在請求 URL 中包含版本號、雜湊值等)來賦予較長的max-age。
然而,當用戶重新載入時,即使伺服器知道內容是不可變的,也會發送重新驗證請求。
為了防止這種情況,可以使用immutable指令明確指示不需要重新驗證,因為內容永不更改。
Cache-Control: max-age=31536000, immutable
這可以防止在重新載入期間進行不必要的重新驗證。
請注意,Chrome 已更改其實現,以便在重新載入子資源時不再執行重新驗證,而不是實現該指令。
刪除儲存的響應
無法刪除已儲存有較長max-age的中間伺服器上的響應。
想象一下,以下來自https://example.com/的響應已儲存。
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Cache-Control: max-age=31536000
<!doctype html>
…
您可能希望在伺服器上過期後覆蓋該響應,但一旦響應儲存,伺服器就無能為力——因為由於快取,不再有請求到達伺服器。
規範中提到的一種方法是使用不安全的方法(例如POST)傳送相同 URL 的請求,但對於許多客戶端來說,這很難做到。
Clear-Site-Data: cache標頭和指令值可用於清除瀏覽器快取——但對中間快取沒有影響。否則,響應將保留在瀏覽器快取中,直到max-age過期,除非使用者手動執行重新載入、強制重新載入或清除歷史記錄操作。
快取減少了對伺服器的訪問,這意味著伺服器失去了對該 URL 的控制。如果伺服器不想失去對 URL 的控制——例如,在資源頻繁更新的情況下——您應該新增no-cache,以便伺服器將始終接收請求併發送預期的響應。
請求合併
共享快取主要位於源伺服器之前,旨在減少對源伺服器的流量。
因此,如果多個相同的請求同時到達共享快取,中間快取將代表自己向源伺服器轉發單個請求,然後可以重用結果以服務所有客戶端。這稱為請求合併。
請求合併發生在請求同時到達時,因此即使在響應中給定max-age=0或no-cache,它也會被重用。
如果響應針對特定使用者進行個性化設定,並且您不希望它在合併中共享,則應新增private指令
常見快取模式
Cache-Control規範中有許多指令,可能難以理解所有這些指令。但大多數網站都可以透過少量模式的組合來覆蓋。
本節描述了設計快取的常見模式。
預設設定
如上所述,快取的預設行為(即,沒有Cache-Control的響應)不僅僅是“不快取”,而是根據所謂的“啟發式快取”進行隱式快取。
為了避免這種啟發式快取,最好明確地為所有響應提供預設的Cache-Control標頭。
為了確保預設情況下始終傳輸資源的最新版本,通常的做法是使預設的Cache-Control值包含no-cache
Cache-Control: no-cache
此外,如果服務實現 cookie 或其他登入方法,並且內容針對每個使用者進行個性化,則還必須給定private,以防止與其他使用者共享
Cache-Control: no-cache, private
快取清除
最適合快取的資源是內容永不更改的靜態不可變檔案。對於確實更改的資源,一種常見的最佳實踐是每次內容更改時更改 URL,以便可以更長時間地快取 URL 單元。
例如,考慮以下 HTML
<script src="bundle.js"></script>
<link rel="stylesheet" href="build.css" />
<body>
hello
</body>
在現代 Web 開發中,JavaScript 和 CSS 資源會隨著開發的進展頻繁更新。此外,如果客戶端使用的 JavaScript 和 CSS 資源版本不同步,顯示將中斷。
因此,上面的 HTML 使得用max-age快取bundle.js和build.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 發生變化,快取將不會再次重用。
<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 位元組。
數字37、38和41分別表示一週、一個月和一年。
由於快取會在儲存新條目時刪除舊條目,因此即使將max-age設定為 1 周,儲存的響應在一週後仍然存在的可能性也不高。因此,實際上,選擇哪一個並沒有太大區別。
請注意,數字41的max-age最長(1 年),但帶有public。
public值的作用是使響應即使在存在Authorization標頭的情況下也可以儲存。
注意:僅當需要儲存響應時Authorization標頭已設定時才應使用public指令。否則,它不是必需的,因為只要給出max-age,響應就會儲存在共享快取中。
因此,如果響應透過基本身份驗證進行個性化,則public的存在可能會導致問題。如果您擔心這一點,可以選擇第二長的值,38(1 個月)。
# 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-Modified和ETag標頭,這樣在重新載入時就不必重新傳輸資源。對於預構建的靜態檔案,生成這些標頭很容易。
這裡的ETag值可能是檔案的雜湊值。
# response for bundle.v123.js
Last-Modified: Tue, 22 Feb 2022 20:20:20 GMT
ETag: "YsAIAAAA-QG4G6kCMAMBAAAAAAAoK"
此外,可以新增immutable以防止在重新載入時進行驗證。
綜合結果如下所示。
# bundle.v123.js
HTTP/1.1 200 OK
Content-Type: text/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 實現使用的編碼演算法。
Cache-Control: public, max-age=31536000
Cache-Control: immutable
主資源
與子資源不同,主資源無法進行快取清除,因為它們的 URL 無法像子資源 URL 那樣進行修飾。
如果以下 HTML 本身已儲存,即使內容在伺服器端更新,也無法顯示最新版本。
<script src="bundle.v123.js"></script>
<link rel="stylesheet" href="build.v123.css" />
<body>
hello
</body>
在這種情況下,no-cache會更合適——而不是no-store——因為我們不想儲存 HTML,而只是希望它始終保持最新。
此外,新增Last-Modified和ETag將允許客戶端傳送條件請求,並且如果 HTML 沒有更新,則可以返回304 Not Modified
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/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.ico、manifest.json、.well-known和其 URL 無法透過快取清除更改的 API 端點。
大多數 Web 內容可以透過上述兩種模式的組合來覆蓋。
關於託管快取的更多資訊
使用前面部分描述的方法,子資源可以透過快取清除長時間快取,但主資源(通常是 HTML 文件)則不能。
快取主資源很困難,因為僅使用 HTTP 快取規範中的標準指令,無法在伺服器上內容更新時主動刪除快取內容。
但是,透過部署託管快取(例如 CDN 或 Service Worker)可以實現這一點。
例如,允許透過 API 或儀表板操作清除快取的 CDN 將允許更積極的快取策略,透過儲存主資源並在伺服器上發生更新時僅顯式清除相關快取。
如果 Service Worker 可以在伺服器上發生更新時刪除 Cache API 中的內容,它也可以這樣做。
有關更多資訊,請參閱 CDN 的文件,並查閱Service Worker 文件。