跳至主要内容

HTTP caching (第一篇)

大綱

底下網羅關於 HTTP Caching, HTTP Conditional Request 的 Headers,會在接下來的段落陸續介紹到

Header NameHeader TypeExplain
Cache-ControlRequest/ResponseCache-Control
ExpiresResponse❌ HTTP/1.0 就有的,逐漸被 Cache-Control 取代
Last-ModifiedResponse📗 Last-Modified: Sat, 12 Jul 2025 07:20:17 GMT
ETagResponseETag
VaryResponse
📗 Vary: Accept-Encoding, Origin
PragmaRequest/Response❌ Deprecated
AgeResponse📗 Age: 24
If-RangeRequest
📗 If-Range: Strong ETag
📗 If-Range: Last-Modified
✅ Must be use with Range Request Header
If-Modified-SinceRequest
📗 If-Modified-Since: Last-Modified
✅ Conditional Request,主要用來更新快取
If-None-MatchRequest
📗 If-None-Match: Strong ETag | Weak ETag
👶 Weak Comparison
✅ Conditional Request,主要用來更新快取
✅ If-None-Match 的優先度 > If-Modified-Since
If-Unmodified-SinceRequest
📗 If-Modified-Since: Last-Modified
✅ Conditional Request,主要用來更新資源
If-MatchRequest
📗 If-Match: Strong ETag
💪 Strong Comparison
✅ Conditional Request,主要用來更新資源
✅ If-Match 的優先度 > If-Unmodified-Since

ETag

  • 全名是 Entity Tag
  • 語法
ETag: W/"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk" # Weak
ETag: "0-2jmj7l5rSw0yVb/vlWAYkK/YBwk" # Strong

Weak ETag

  • 通常是把 file metadata 拿去 hash,方法沒有規定
  • 同樣的 Weak ETag,不能確保 file content 完全一致
  • 但用在 cache 優化很有效

看看 NodeJS etag 的實作,就是拿 lastModified檔案大小 去做 hash

function stattag(stat) {
var mtime = stat.mtime.getTime().toString(16);
var size = stat.size.toString(16);

return '"' + size + "-" + mtime + '"';
}

Strong ETag

  • 通常是把 file content 拿去 hash,方法沒有規定
  • 同樣的 Strong ETag,可以確保 file content 完全一致
  • Strong ETag 的生成,效能比 Weak ETag 更差

看看 NodeJS etag 的實作,實作上也是非常樸實無華

function entitytag(entity) {
if (entity.length === 0) {
// fast-path empty
return '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"';
}

// compute hash of entity
var hash = crypto
.createHash("sha1")
.update(entity, "utf8")
.digest("base64")
.substring(0, 27);

// compute length of entity
var len =
typeof entity === "string"
? Buffer.byteLength(entity, "utf8")
: entity.length;

return '"' + len.toString(16) + "-" + hash + '"';
}

Cache-Control

  • 用來控制 cache 的各種行為
  • cache 存放位置(public, private)
  • 是否允許 cache(no-cache, no-store)
  • cache 有效期控制(max-age, s-maxage)
  • cache 過期後的行為 (must-revalidate, proxy-revalidate, stale-while-revalidate)
  • cache 可否被轉換(no-transform)
  • cache 優化策略(only-if-cached, immutable)
  • 其他很少用到的(must-understand)

Directives

DirectiveRequestResponse
max-age
📗 Cache-Control: max-age=600
代表 Response 在生成後的 600 秒內都算 fresh
s-maxage (shared-maxage)-
📗 Cache-Control: s-maxage=600
同 max-age
優先度 > max-age
no-cache同 ➡️可被 cache,但是每次都需跟 origin server 驗證
no-store同 ➡️禁止任何形式的 cache
must-revalidate-
Cache-Control: max-age=600, must-revalidate
600 秒內可以使用 cache,超過的話就必須重新驗證
proxy-revalidate-同 must-revalidate,for shared caches only
private-Response 只能被存在 private cache
public-Response 可被存在 shared cache
no-transform同 ➡️禁止中間層把 response body 做轉換
immutable-response 在 fresh 期間不會異動
stale-while-revalidate-
Cache-Control: max-age=600, stale-while-revalidate=300
swr 套件的命名來源
must-understand-
Cache-Control: must-understand, no-store
必須了解 status code 的涵義,才可以 cache
only-if-cachedclient 只想拿 cache 的資料-
stale-if-error主流瀏覽器不支援-
max-stale主流瀏覽器不支援-
min-fresh主流瀏覽器不支援-

小插曲,觀察 Chrome Disable Cache 的行為

隨便打開一個網頁,F12 > Network > Disable Cache 打勾,實際發送的是 Cache-Control: no-cache

chrome-disable-cache-no-cache

取消勾選,再重整網頁,實際發送的是 Cache-Control: max-age=0

chrome-enable-cache-max-age-0

Conditional Requests

  • If-* 開頭的 Request Headers
  • If-* 條件為 true,則執行對應的 HTTP Method 操作
  • If-Range + Range 用來發起 Conditional Range Request,true 回傳對應的 Range,false 回傳整個 resource
  • If-None-Match + If-Modified-Since 通常會一起使用,用來更新快取
  • If-Match + If-Unmodified-Since 通常會一起使用,用來更新資源,若 If-* 條件為 false,則回傳 412 Precondition Failed

小結

HTTP Caching 跟 HTTP Conditional Requests 是兩個密不可分的概念。在第一個篇章,我們先有一個概觀,把這個主題會用到的 Headers 都介紹過一輪,接下來我們就會進到實作的環節~

參考資料