當 快取 (回應) 以 顯式、啟發式 計算為過期,則稱 陳舊回應 (Stale Response)。
當儲存的回應 不新鮮 (not fresh) 時,則與 Server 進行「驗證 (Validation)」,
通過驗證則可繼續使用,否則重新獲取回應。
所謂 驗證 (Validate) 或 重新驗證 (revalidate),
即是 使用 條件請求 (Conditional Requests) 機制,
以獲得 重複使用 快取的機會、更新快取的元資料,或者 取得 新的回應。
陳舊回應
然而,不是所有 陳舊回應 (Stale Response) 都必須進行驗證 才能重用快取,
且 新鮮回應 (Fresh Response) 也可能需要進行驗證。
簡單來說:
凡事都有例外
本篇便是為闡明這些「例外」情形 😀,
在進入每節副標題前,請務必牢記此目的,
才能快速進入狀況 ! 而非陷入 「表頭欄位 字典」!
Cache-Control (快取控制)
在開始前,快速複習:
Cache-Control (快取控制) 是 HTTP/1.1 引入的表頭欄位,
用於指定 請求/回應鏈 中的 快取指令 (directive for cache)。
指令 (directive) 又分 請求 與 回應,且部分名稱重疊,
混肴時只需記住:『 請求指令 應用於 請求訊息;回應指令 應用於 回應訊息 』。
快取指令 是 單向的 (unidirectional),
意指 存在請求訊息中的指令,並不會用於回應,
且 相同的指令 用於 請求/回應,可能有不同含意。
max-stale 指令
max-stale 是應用於 Cache-Control 表頭欄位的 快取指令 之一。
顧名思義 — 最大失效值 (單位: 秒),例如:
Cache-Control: max-stale=10
請求指令
max-stale 僅用於 請求訊息,不需要記兩種含義,
因此,基本上不會在 Server 的回應中看到它。
max-stale 代表「此請求」允許快取 過期後 還能使用多久,
例如,「此請求」允許快取過期後,還能使用一天,而 不需送出網路請求 (不驗證) :
( 60 秒 * 60 分 * 24 時 = 86400 秒 )
Cache-Control: max-stale=86400
不指定值
另一特殊用法是 不指定值,代表過期多久都無妨:
Cache-Control: max-stale
結合 max-age
max-stale 指令,能與 max-age (請求) 指令 共存,
以下範例代表:「此請求」將快取存在時間 大於 10 秒後,視為過期,但能再使用 87 秒 :
Cache-Control: max-age=10, max-stale=87
除非此種情形,使用者代理 (User Agent) 不得在未驗證的情形,使用 陳舊回應 (stale response)。
(某些瀏覽器,允許在斷網、Server 掛掉時,繼續使用陳舊回應。)
must-revalidate 指令
must-revalidate 是最常見的 快取指令 之一,
幸運地,must-revalidate 僅用於 回應訊息,不需要記兩種含義,
must-revalidate 是指:
『 一旦「此回應」不新鮮,
不得 在與 源伺服器 成功驗證前,使用它 (快取) 滿足後續請求 』。
這並非本文主旨中,所談的「例外」情形,
相反地,是用來杜絕上述 陳舊回應 (stale response) 能直接使用的例外。
(某些瀏覽器,允許在斷網、Server 掛掉時,繼續使用陳舊回應。)
這對許多需支援可靠性的操作是 必要的,
特別是,若快取驗證因某種原因無法到達 源伺服器 時,
必須生成 504 (Gateway Timeout) — 閘道逾時 錯誤,而非直接使用快取。
feat. max-stale
Q:
若是 Client 使用 max-stale 呢?
Ans:
HTTP/1.1 規範中,must-revalidate 指令,主要是針對無法聯繫 Origin Server 的情形,
因此,當 Client 指定了 max-stale,且仍在允許過期時間內,是 有可能直接使用快取 的!
(全憑各 User Agent 實作)
例如:
Firefox 53.0.3 (mac) 允許直接覆用快取。
Chrome 58.0.3029 (mac) 必須進行驗證。
新鮮回應
接下來,討論雖然是 新鮮回應 (Fresh Response),
卻得經過 驗證 或 不得覆用快取的情形。
此處是許多開法者混肴、模稜兩可 之處,
在進入每一小節之前,務必看清楚 指令 是用於「請求」還「回應」!
請求 no-cache 指令
no-cache 是最常見的 快取指令 之一,能分別用在 請求 與 回應,
幸運地,它們的涵義差別不大,可用於互相理解。
許多人認為 no-cache 指令 的命名很爛,
用於「請求」,並非 沒有快取、不使用快取,
而是指:『 不得在與 源伺服器 成功驗證前,使用快取滿足「此請求」 』。
意即:
可以儲存 回應 (快取),且若 後續請求未使用 no-cache 指令,
則允許直接使用 新鮮的 (fresh) 快取,而 不需發送網路請求。
例如,此請求雖擁有 新鮮的 (fresh) 快取,一旦使用 no-cache 請求指令,
則無論如何,需要 發送網路請求,以進行 驗證 (validation)。
GET / HTTP/1.1
Host: example.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0) Gecko/20100101 Firefox/53.0 Cache-Control: no-cache if-modified-since: Mon, 19 Jun 2017 12:06:02 GMT
儘管 請求訊息 中包含 no-cache 指令,
只要訊息中使用 條件請求 (Conditional Requests) 機制,且通過驗證,
仍能直接覆用 快取,而 不需重新下載回應 !
no-cache vs. max-age=0
是否發現了 ? 這跟 (6-2) 免網路請求 !! — 新鮮回應 (fresh response) 中,
Client 強制驗證 的方式很相似 !?
沒錯 ! 使用 no-cache, max-age=0,都能 強制 進行 快取 驗證 !
同樣的,差別在於 — 語意 (Sematic)。
這兩種方式大部分情況下,效果相同,
但 no-cache 強調的是「源伺服器 (Origin Server) 驗證」,
而 max-age=0 是做為 陳舊回應 (stale response) 處理。
例如,發送此 no-cache 請求訊息 (含 條件請求 欄位 If-Modified-Since) :
GET / HTTP/1.1
Host: example.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0) Gecko/20100101 Firefox/53.0 If-Modified-Since: Thu, 08 Dec 2016 01:00:57 GMT Cache-Control: no-cache
若 Server 驗證成功,則回應 304 (Not Modified) — 未修改,
將 Client 重新導向 到 已儲存的快取,猶如 200 (OK) 的結果 :
HTTP/1.1 304 Not Modified
Date: Mon, 19 Jun 2017 14:09:54 GMT Expires: Mon, 19 Jun 2017 14:09:54 GMT Cache-Control: private, max-age=31536000 Last-Modified: Thu, 08 Dec 2016 01:00:57 GMT Server: sffe
代理快取行為
因此,某些 共享代理 (shared proxy)、CDN 對待它們的方式並不相同,
因為: Cache-Control 指令,會沿著請求/回應鍊傳遞。
使用 no-cache 請求指令 可能使這些 中介 (intermediary),
重新檢查 源伺服器 的回應,而非直接以其快取滿足 Client。
例如,著名的 代理快取 Varnish 預設行為是:
不理會「請求」的 Cache-Control 表頭欄位
其一主要原因,便是 防止 Client 自行更新其快取,
(e.g., 發送 no-cache 等請求指令)
代理快取 的更新機制,大部分應取決於 Origin Server,
當然,你可以在後端解析 Cache-Control,實作客戶端快取更新。
略過快取 (Bypass Cache)
no-cache 指令 最常見的用法,並非做為 強制驗證 的手段,
而是更符合其字面涵義: 『 No 快取 』,
也就是 忽略快取,直接發送網路請求,並使用新的回應,
實現的方式非常簡單 :
不在請求中附加任何 條件請求 (Conditional Requests) 機制。
(這也另許多人誤解了 no-cache 的原始意義 😂)
沒有 條件請求,Server 就無從驗證,
當然就視為一般請求處理囉!
大部分的 使用者代理 (e.g., 瀏覽器),都會區分「重新整理」與「重新整理 (略過快取)」,
使用「重新整理」時,往往是送出 max-age=0,並附加 條件請求 欄位,以進行快取驗證 :
GET / HTTP/1.1
Host: example.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0) Gecko/20100101 Firefox/53.0 If-Modified-Since: Thu, 08 Dec 2016 01:00:57 GMT Cache-Control: max-age=0
使用「重新整理 (略過快取)」,則如上所述,
發送 no-cache 指令,且 不含條件請求欄位,
Server 可能回應 任何狀態碼:
GET / HTTP/1.1
Host: example.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0) Gecko/20100101 Firefox/53.0 Cache-Control: no-cache Pragma: no-cache
另外,還有少數 使用者代理 (user-agent) 使用「重新整理 (略過快取)」時,
是採用「先清空 後請求」的方式,或 強制對 新鮮回應 發送網路請求,且不包含 no-cache。
維基百科: 繞過瀏覽器快取 很好的說明了各家瀏覽器的使用方式,
時常有 Web 開發者,改 html, css, javascript 老半天,卻苦苦納悶畫面沒有變化,
此時,不妨試試 略過快取 😆。
Pragma: no-cache
可看到,上述的請求範例中,同時存在 Cache-Control 與 Pragma 表頭欄位。
Pragma 表頭欄位 是 HTTP/1.0 的古老產物,
因某些老舊瀏覽器未支援 Cache-Control 欄位,
Pragma 現今主要用於 向後兼容 (backwards compatibility) HTTP/1.0 快取,
若 User Agent 支援 Cache-Control 欄位,則可直接無視 Pragma。
回應 no-cache 指令
回應 no-cache 指令 與 請求 no-cache 意義相近,
不同的是,這次是由 Server 對「回應」下達規則,
是指:『 不得在與 源伺服器 成功驗證前,使用「此回應」做為快取 』。
意即:
可以儲存 回應 (快取),但每次都得驗證。
例如,若 Server 回應此訊息,
即使經 啟發式過期時間 計算是 新鮮回應 (fresh response),
每次使用前,也必須進行 驗證 (validation) !
HTTP/1.1 304 Not Modified
Date: Mon, 19 Jun 2017 14:09:54 GMT Cache-Control: no-cache Last-Modified: Thu, 08 Dec 2016 01:00:57 GMT Server: sffe
更新元資料 (Updating Metadata)
若 Server 驗證成功,則回應 304 (Not Modified) — 未修改,
將 Client 重新導向 到 已儲存的快取,猶如 200 (OK) 的結果 :
HTTP/1.1 304 Not Modified
Date: Mon, 19 Jun 2017 15:09:54 GMT Expires: Mon, 19 Jun 2018 14:09:54 GMT Cache-Control: private, max-age=31536000 Last-Modified: Thu, 08 Dec 2016 01:00:57 GMT Server: sffe
注意 !!
上方 回應範例 並未包含 no-cache 回應指令,
且提供了 顯式過期時間 (max-age)。
這將更新 快取 (回應) 的訊息表頭,
因此,後續請求 便不需發送網路請求,得以直接覆用 !
回應 no-cache 指令 提供這種 強制驗證 並更新 (快取) 元資料的作用,
且如同 請求 no-cache 指令,回應 no-cache 與 回應 max-age=0,
大部分情況下,效果相同,但仍有語意上的差異。
Google、Wikipedia 首頁 使用 max-age=0,
而 Facebook、AWS、Microsoft、GitHub 首頁 則為 no-cache。
(以撰文時間為準)
Pragma too ?
不同於請求,回應 no-cache 不會 搭配 Pragma 欄位使用,
因為 HTTP/1.0 的 Pragma 欄位,並未定義 回應訊息 的用途。
no-store 指令
no-store 是最常見的 快取指令 之一,能分別用在 請求 與 回應,
不幸地,涵義雖差別不大,但效果相差甚遠,務必釐清。
no-store 用於「請求/回應」,
皆是指:『 嚴禁 儲存 請求 與其 回應 的任何部分』。
(通常是對下游接收者,而非自身)
用於「回應」特別簡單 !
Client (含中介) 不得儲存 快取 (回應) 與 請求。
因為沒有 快取,當然,每次 都得發送網路請求 啦 !
事實上,無論用於 請求/回應,是否遵從此指令的「不儲存」,全依端點的良心 😂,
並不能將 no-store 指令 視為可靠的隱私保護機制。
用於「請求」時,因定義的模稜兩可,
User Agent 當下請求 不一定 會強制驗證 (依各自實作方式),
相對 回應 no-store 指令,較少使用。
但無論是進行驗證,或使用現有 cache 滿足,
請求 no-store "通常" 不會使已儲存過的快取無效 (消除)。
[註]:
Firefox 53.0.3 (mac) 就算快取新鮮,也會發送請求,且不會儲存回應。
Chrome 58.0.3029 (mac) 若快取新鮮,則直接使用,且可能儲存回應。
新鮮 vs. 陳舊
以上指令,皆允許進行搭配,以涵蓋所有回應情形,
以下是一些常見 回應訊息 範例,可依自身需求,選擇適當的快取方案 😄。
杜絕快取使用
Cache-Control: no-cache, no-store, must-revalidate Pragma: no-cache Expires: 0
杜絕快取使用 (含中介 — private 指令)
Cache-Control: private, no-cache, no-store, must-revalidate Pragma: no-cache Expires: 0
強制驗證 (錯誤 Expires 值)
Cache-Control: no-cache, must-revalidate Pragma: no-cache Expires: 0
強制驗證 (過去時間 Expires 值)
Cache-Control: no-cache, must-revalidate Pragma: no-cache Expires: Sat, 01 Jan 2000 00:00:00 GMT
強制驗證 (max-age)
Cache-Control: max-age=0 Expires: 0
更多 快取指令 請參考 RFC 7234 (7.1.3 節)。
並附上 Varnish 整理的 Cache-Control 列表:
Directive | Request | Response |
---|---|---|
no-cache | O | O |
no-store | O | O |
max-age | O | O |
s-maxage | O | |
max-stale | O | |
min-fresh | O | |
no-transform | O | O |
only-if-cached | O | |
public | O | |
private | O | |
must-revalidate | O | |
proxy-revalidate | O |
在《HTTP 何時驗證快取 no-cache? no-store?》中有 4 則留言
非常詳細且好懂,感謝
看了很多篇文章,看到這篇終於有點覺得看懂了XD
真的非常謝謝
感謝!很高興能幫到您
有任何疑問都歡迎留言喔 😁
看了一下Microsoft 官方資料, max-age, max-stale 的使用範例好像可以再補充更多, 會更清楚!
https://learn.microsoft.com/en-us/dotnet/framework/network-programming/cache-policy-interaction-maximum-age-and-maximum-staleness?redirectedfrom=MSDN