HTTP

HTTP 何時驗證快取 no-cache? no-store?

當 快取 (回應) 以 顯式、啟發式 計算為過期,則稱 陳舊回應 (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) 又分 請求 與 回應,且部分名稱重疊,
混肴時只需記住:『 請求指令 應用於 請求訊息;回應指令 應用於 回應訊息 』。
 
中介 (Intermediaries) 示意圖
 

快取指令單向的 (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-ControlPragma 表頭欄位。
 
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 列表:

DirectiveRequestResponse
no-cacheOO
no-storeOO
max-ageOO
s-maxage O
max-staleO 
min-freshO 
no-transformOO
only-if-cachedO 
public O
private O
must-revalidate O
proxy-revalidate O

 
 

作者: 鄭中勝
喜愛音樂,但不知為何總在打程式 ? 期許能重新審視、整理自身所學,幫助有需要的人。

在《HTTP 何時驗證快取 no-cache? no-store?》中有 4 則留言

    1. 看了很多篇文章,看到這篇終於有點覺得看懂了XD
      真的非常謝謝

發表迴響