HTTP CACHE/VERSION CHECK
重新整理時,頁面先拿舊印章去敲門
你打開一個常看的網頁,昨天已經看過,今天按下重新整理。畫面很快回來,網路面板卻沒有搬回整頁內容。到底是頁面真的重新下載了,還是瀏覽器把舊的偷偷拿出來?
直接答案是:兩種情況都可能。若上一份回應仍在快取的有效期內,瀏覽器或中間快取可以直接使用它;若需要確認,瀏覽器會把上一份回應留下的版本標記送回去。伺服器發現目前選出的版本沒有變,就回 304 Not Modified,只送回確認用的標頭,不再送一份內容;若版本不同,伺服器才以 200 OK 送來新的回應內容。
往下看,先別把畫面回來當成整頁重傳
01/保存
先留下副本,也留下辨識方法
瀏覽器快取不是單純把 HTML 丟進抽屜。每一份可重用的回應,還會連同網址、回應標頭與新鮮度資訊一起留著。回應裡的 Cache-Control: max-age 可以讓快取知道,在一段時間內可以不問伺服器直接使用;Expires 也能提供到期時間。新鮮度是快取判斷能不能直接重用的條件,不會命令瀏覽器一定重新畫面。
如果副本已經不能直接重用,快取就可以走驗證路徑。最常見的兩把尺是 ETag 與 Last-Modified。ETag 是伺服器替某一份選定回應指定的不透明版本標記;它不一定是內容雜湊,也可能是內部修訂號、檔案屬性組合或其他只有伺服器知道的表示法。Last-Modified 則是伺服器提供的修改時間。兩者都不是讓瀏覽器猜內容,而是讓伺服器比較「我手上的這一份」和「現在選出的這一份」是否仍可視為同一份。
因此,同一個網址不一定永遠對應同一份位元組。語言、壓縮方式或其他請求偏好,可能讓伺服器為同一個網址選出不同的回應;後面談到的 Vary 標頭,就是把這個差異告訴快取。
02/副本仍新鮮
假設瀏覽器手上的回應還在新鮮度內。快取可以直接把這份內容交給瀏覽器,重新整理的畫面因此很快回來,而且這一次不必先把驗證請求送到伺服器。
03/只遞印章
假設瀏覽器手上的回應帶有 ETag: "r7"。當快取需要重新確認時,瀏覽器可以送出 GET /news.html,並附上 If-None-Match: "r7"。如果回應沒有 ETag,或系統需要使用時間作為驗證依據,也可能帶上 If-Modified-Since。
伺服器收到請求後,會先選出目前要提供的回應,再比較收到的條件。這裡送出去的是辨識方法,不是把瀏覽器手上的整份內容先搬回伺服器。
04/304 不是空白頁
若目前的 ETag 和 "r7" 相符,伺服器回 304 Not Modified。這不是一張空白頁,也不是伺服器忘了回內容;304 的意思是,伺服器確認瀏覽器已經有可沿用的那一份,所以不必再次傳送回應內容。304 只能在標頭區結束,不能帶內容本體;瀏覽器把自己的舊內容和這次更新的標頭接在一起使用。
05/200 送來新紙張
如果 ETag 不相符,伺服器就回 200 OK,附上目前的完整回應內容與新的驗證標記。瀏覽器用新內容替換舊副本,下一次需要確認時,再帶著新的印章回來。沒有可用驗證標記時,請求也可以直接取得完整的 200 回應,只是少了「先問能不能沿用」這一道門。
06/四條路徑
重新整理有不只一條路
同一個重新整理動作,可能走過四種不同的路徑。副本還新鮮時,快取可以直接把內容交給瀏覽器,甚至不必發出驗證請求;副本需要確認而印章相同時,請求會抵達伺服器,但 304 不帶內容本體,瀏覽器沿用舊副本;印章不同時,伺服器回 200,新的內容才重新走過網路;如果原本沒有印章,請求就只能把需要的內容取回來,再建立下一次可用的辨識方法。
這四條路徑的差別,不在畫面上有沒有「重新整理」這個按鈕,而在副本的新鮮度、驗證標記與伺服器當下選出的回應是否相符。304 省下的是內容傳輸,不代表請求完全沒有延遲;200 也不代表每次都從零開始,因為快取可能仍保留其他可用的回應或資源。
印章相同/304條件請求抵達伺服器,但伺服器確認目前版本仍是 "r7";304 不帶內容本體,瀏覽器沿用手上的回應。
- 副本還新:快取不必發出驗證請求,直接交出仍新鮮的內容。
- 印章相同:送出條件請求,收到 304/只回標頭,沿用舊副本。
- 印章不同:送出條件請求,收到 200/完整內容,替換舊副本。
- 沒有印章:沒有可用驗證標記,直接取得 200/完整內容。
07/選擇條件
印章要知道自己蓋在哪一張紙
ETag 比對的對象不是「這個網址歷來出現過的任何內容」,而是伺服器為這次請求選出的那一份回應。假設同一個網址會依 Accept-Language 選擇繁體中文或英文,依 Accept-Encoding 選擇不同壓縮形式;這些請求標頭就可能影響最後送出的表示方式。
Vary 標頭會列出影響選擇的請求欄位,提醒快取不能只用網址判斷兩份回應相同。快取要把 Vary 指出的欄位納入自己的比對條件;如果條件不吻合,就不能把原本那份回應直接套給另一個請求,除非先向來源重新驗證。於是,「印章相同」真正的意思是:在相同選擇條件下,這份已保存的回應仍是目前那一份,而不是任何語言、任何壓縮方式都共用一張通行證。
ETag 還有 strong 與 weak 的差別。帶有 W/ 的 weak ETag 允許伺服器表示兩份內容即使位元組有變,仍可在某些快取驗證情境視為足夠相近;If-None-Match 進行快取驗證時使用 weak comparison。這是伺服器對「相同」的精細定義,不是瀏覽器自行讀懂 ETag 內容後做出的猜測。
08/保存邊界
no-cache 不是 no-store
可以留著,但用前要問
快取標頭裡最容易被混在一起的是 no-cache 和 no-store。回應中的 Cache-Control: no-cache,不是命令快取立刻丟掉副本;它表示快取之後不能不經驗證就拿這份回應去滿足其他請求。只要能成功驗證,副本仍可能留下來,並透過 304 避免重傳內容。
不要留下可重用副本
Cache-Control: no-store 才是要求快取不要儲存這次請求或回應的任何部分,也不要拿它去滿足下一次請求。兩者服務不同目標:no-cache 偏向「每次使用前先問版本」,no-store 偏向「不要留下可重用的副本」。no-store 也不是完整的隱私保證;快取、網路與其他系統是否遵守它,不能單靠這一個標頭替所有資料安全負責。
所以,一個希望內容保持新鮮、又不想每次重新傳完整頁面的網站,可能會使用可驗證的快取策略;一個不應留下可重用回應的資料,則需要更嚴格的儲存與存取設計。看到 no-cache 時,先把它理解成「可以存,但用前要驗證」,不要誤讀成「完全沒有快取」。
09/回到按鈕
你按下的不是下載鍵
回到開頭那次重新整理:那個按鈕沒有保證頁面一定從伺服器搬回一份新的內容。按鈕只啟動一次取得或重新確認的流程;真正決定網路上要不要搬運內容的,是快取是否仍新鮮、請求有沒有帶驗證標記、Vary 條件是否一致,以及伺服器選出的回應有沒有改變。
比較準確的想像是:瀏覽器先把舊副本放在手邊,再帶著一枚版本印章去敲門。印章相同,伺服器只說「可以沿用」,舊內容留在原地;印章不同,伺服器才把新內容交出來。你看到的是同一個網址重新出現,網路背後卻可能是零內容、只傳標頭,或完整的新回應。
這個理解也留下了一條實用界線:畫面過時時,不要只問「瀏覽器有沒有快取」,而要問「哪一層保存了哪一份回應,以及它用什麼條件判定仍可沿用」。快取不是一個單一的抽屜,而是一串對版本、新鮮度與選擇條件都必須負責的門。
研究來源與事實邊界
規範說了什麼,本文又類比了什麼
本文依 IETF HTTP 規範說明快取驗證、ETag、Last-Modified、If-None-Match、If-Modified-Since、Vary、304 Not Modified 與 Cache-Control 的語意。文中的「版本印章/敲門」是把條件請求與回應驗證轉成日常可理解的工程類比;瀏覽器、代理快取、CDN 與來源伺服器的實際行為仍會受實作、設定、資源類型與請求條件影響,不能從一次重新整理的畫面反推出唯一的網路路徑。
- RFC 9110:HTTP Semantics,§8.8 Validator Fields,說明 ETag、Last-Modified、strong/weak validator 與不透明標記。
- RFC 9110:§12.5.5 Vary,說明哪些請求欄位可能影響表示選擇,以及快取如何擴大比對條件。
- RFC 9110:§13.1.2 If-None-Match、§13.1.3 If-Modified-Since,說明條件 GET、弱比對、以修改時間驗證與 304 分支。
- RFC 9110:§15.4.5 304 Not Modified,說明 304 的語意、不可帶內容本體與可更新的回應標頭。
- RFC 9111:§4.2 Freshness、§4.3 Validation,說明新鮮度與快取在副本不能直接使用時如何產生驗證請求。
- RFC 9111:§5.2.2.4 no-cache、§5.2.2.5 no-store,分開「仍可保存但使用前驗證」與「不要儲存」的語意。