HTTP caching (第一篇)
大綱
底下網羅關於 HTTP Caching, HTTP Conditional Request 的 Headers,會在接下來的段落陸續介紹到
Header Name | Header Type | Explain |
---|---|---|
Cache-Control | Request/Response | Cache-Control |
Expires | Response | ❌ HTTP/1.0 就有的,逐漸被 Cache-Control 取代 |
Last-Modified | Response | 📗 Last-Modified: Sat, 12 Jul 2025 07:20:17 GMT |
ETag | Response | ETag |
Vary | Response | 📗 Vary: Accept-Encoding, Origin |
Pragma | Request/Response | ❌ Deprecated |
Age | Response | 📗 Age: 24 |
If-Range | Request | 📗 If-Range: Strong ETag 📗 If-Range: Last-Modified ✅ Must be use with Range Request Header |
If-Modified-Since | Request | 📗 If-Modified-Since: Last-Modified ✅ Conditional Request,主要用來更新快取 |
If-None-Match | Request | 📗 If-None-Match: Strong ETag | Weak ETag 👶 Weak Comparison ✅ Conditional Request,主要用來更新快取 ✅ If-None-Match 的優先度 > If-Modified-Since |
If-Unmodified-Since | Request | 📗 If-Modified-Since: Last-Modified ✅ Conditional Request,主要用來更新資源 |
If-Match | Request | 📗 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
Directive | Request | Response |
---|---|---|
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-cached | client 只想拿 cache 的資料 | - |
stale-if-error | 主流瀏覽器不支援 | - |
max-stale | 主流瀏覽器不支援 | - |
min-fresh | 主流瀏覽器不支援 | - |
小插曲,觀察 Chrome Disable Cache 的行為
隨便打開一個網頁,F12 > Network > Disable Cache 打勾,實際發送的是 Cache-Control: no-cache
取消勾選,再重整網頁,實際發送的是 Cache-Control: max-age=0
Conditional Requests
If-*
開頭的 Request HeadersIf-*
條件為true
,則執行對應的 HTTP Method 操作If-Range
+Range
用來發起 Conditional Range Request,true
回傳對應的 Range,false
回傳整個 resourceIf-None-Match
+If-Modified-Since
通常會一起使用,用來更新快取If-Match
+If-Unmodified-Since
通常會一起使用,用來更新資源,若If-*
條件為false
,則回傳 412 Precondition Failed
小結
HTTP Caching 跟 HTTP Conditional Requests 是兩個密不可分的概念。在第一個篇章,我們先有一個概觀,把這個主題會用到的 Headers 都介紹過一輪,接下來我們就會進到實作的環節~
參考資料
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Conditional_requests
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Expires
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Last-Modified
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Vary
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Pragma
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Age
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-Range
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-Match
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-Modified-Since
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-None-Match
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-Unmodified-Since
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/304
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/412
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/428
- https://www.rfc-editor.org/rfc/rfc9111.html
- https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_key
- https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path
- https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache