就像食品擁有 保存期限,快取 (Cache) 擁有一段 新鮮時限 (freshness lifetime),
未過期則為 新鮮回應 (Fresh Response),反之,稱為 陳舊回應 (stale response)。
新鮮回應 (Fresh Response),可以用來滿足後續的 等效請求,
而 不必與 源伺服器 (Origin Server) 聯繫、也 無需「驗證 (validation)」,
減少了延遲與網絡開銷,從而提高效率,是快取最理想的情形。
當儲存的回應 不新鮮 (not fresh) 時,則與 Server 進行「驗證 (Validation)」,
通過驗證則可繼續使用,否則重新獲取回應。
若您開發網頁時,曾出現:
頁面 (or 影像) 沒有更新,需透過「強制」重新整理來解決。
極大機率便是因為:
它被 browser 快取住了,且被判定為 新鮮回應,
因此 被直接拿來使用,而無進行網路請求!
解決方案:
在檔案名稱中嵌入檔案的指紋碼或版本號碼,
或者,閱讀本篇文章,善用 HTTP 表頭 😆。
目錄
Date (日期)
在說明 過期時間 (expiration time) 前,先來認識 Date 表頭欄位 !
若做過 (5-3) 流量監控 (Traffic Monitor),你應該對他非常熟悉,
它幾乎出現在 所有回應訊息 中 (除非 Server 沒有時鐘機制)。
Date 表頭欄位 代表訊息發起的日期和時間,例如:
Date: Sat, 17 Jun 2017 23:59:59 GMT
(星期, 日 月 年 時:分:秒 GMT)
其中,Date 欄位值 稱為 http-date,是 區分大小寫 (case-sensitive) 的!
且是 世界協調時間 (UTC) 的時間,而非本地時間 (e.g., “Asia/Taipei” 的 GMT+8)。
生成此欄位相當容易,以 JavaScript 為例:
new Date().toUTCString();
PHP 則可使用 gmdate 函式 (格林威治標準 Greenwich Mean) :
echo gmdate('D, d M Y H:i:s T');
另有兩種少見格式,請參閱 RFC7231,
延伸閱讀 — 詳見 到底是 GMT+8 還是 UTC+8 ?
顯式過期時間 (explicit expiration time)
計算 食品 是否過期,普遍的兩種方法:
一、製造日期 + 保存期限 (e.g., 3 天)
二、有效日期
同理,Cache-Control 表頭欄位 的 max-age 指令 猶如 保存期限 (單位: 秒),
而傳統的 Expires 欄位 則指出 有效日期 (僅用於回應訊息),
且,只要未過期就是 新鮮回應 (Fresh Response),範例:
Cache-Control: max-age=31536000 Expires: Mon, 01 Jan 2018 16:00:00 GMT
為避免衝突,當兩者同時存在於回應訊息,
則 使用者代理 (User Agent) 必須 忽略 Expires 欄位,
並以較新的 Cache-Control 與 max-age 指令為主。
[註]:
問題是,如何知道回應的「製造日期」? 你可能會想到:
回應時間 (response_time) – Date 欄位值 (date_value)
答案很接近了 ! 可惜不正確,因為未考慮到 共享代理、CDN 與 延遲等問題,
算法非常簡單,有興趣可參考 RFC 7234 (4.2.3 節) 😄。
Expires (過期)
Expires 表頭欄位,是 HTTP/1.0 的古老產物,
用於 顯式地 指出快取的 過期時間 (explicit expiration time),
是分辨 新鮮回應 (Fresh Response) 與 陳舊回應 (stale response) 最簡單的方式,例如:
Expires: Thu, 01 Dec 1994 16:00:00 GMT
相信了解 Date 表頭欄位後,處理 http-date 都是 piece of cake 😜 !
賞味期限
比起有效日期,Expires 欄位 更像是「賞味期限」,
不意味著 原始資源 將在過期後 發生變化 或不復存在,
僅指此 陳舊回應 (stale response) 可能需再 驗證 (Validation),或重新獲取。
Photo By 食力 (foodNext).
強制驗證
若 Expires 值的格式錯誤,例如 0、-1…,
User Agent 須將其視為 過期 !
許多 Server (e.g., Google) 正利用此特點 ,刻意發送 錯誤 的 Expires 值 (Expires: -1),
或 明顯已過期的時間 (Expires: Thu, 01 Dec 1994 16:00:00 GMT),
以迫使 User Agent 請求時,需重新 驗證 (Validation) 快取。
解除一年限制
基於過去習慣,HTTP 要求 Expires 欄位值不得超過一年,
2014 年,HTTP/1.1 修訂版中,已移除此限制,
然而,使用極大的值仍會帶來許多問題。
Cache-Control (快取控制)
Cache-Control (快取控制) 是 HTTP/1.1 引入的表頭欄位,
用於指定 請求/回應鏈 中的 快取指令 (directive for cache)。
指令 (directive) 又分 請求 與 回應,且部分名稱重疊,
混肴時只需記住:『 請求指令 應用於 請求訊息;回應指令 應用於 回應訊息 』。
快取指令 是 單向的 (unidirectional),
意指 存在請求訊息中的指令,並不會用於回應,
且 相同的指令 用於 請求/回應,可能有不同含意。
max-age 指令
max-age 是最常見的 快取指令 之一,
用於「回應」,代表 「此回應」(快取) 的 存在時間 (age),在大於指定秒數後,視為過期。
例如,在 回應訊息 中,這代表 快取 將在一年後過期 !
( 60 秒 * 60 分 * 24 時 * 365 天 = 31536000 秒 )
Cache-Control: max-age=31536000
而 max-age 指令,用於「請求」,
則是「此請求」願意接受 快取存在時間 的 最大秒數,大於則 視為過期,
可當作 覆寫 快取 的 max-age 值。
提醒:
請求指令 用於 請求訊息;回應指令 用於 回應訊息。
務必小心:
max-age 用於請求、回應,意義不完全相同 !
max-age 用於請求、回應,意義不完全相同 !
max-age 用於請求、回應,意義不完全相同 !
— 覺得很重要 o.o
若 回應訊息 存在 max-age 指令,
送出請求前,會先 計算此 快取 的 age,並與 max-age 比大小,
若仍為 新鮮回應 (Fresh Response) 就可拿來用啦 !
例如,對一影像送出 GET 請求,並利用 Firefox 網路監控器 測試,
發現同時存在 Expires 欄位 與 max-age 回應指令 (需 忽略 Expires) :
顯然,此影像的快取仍是 新鮮的 (fresh),
因此不需驗證,且 並未送出網路請求,
欲再次獲取影像時,直接從瀏覽器快取中取得即可 !
強制驗證 (Server)
就像 Expires 欄位,能以 錯誤的值 或 已過期時間,
Server 可以在 回應訊息 中指派很小的值給 max-age 指令,
迫使 User Agent 請求時,需重新 驗證 (Validation) 快取。
例如:
Cache-Control: max-age=0
強制驗證 (Client)
Client 強制驗證方式,如出一轍,就算 快取 的 max-age=31536000,
可在 請求訊息 中使用 max-age 指令,「覆寫」其值:
Cache-Control: max-age=0
顯然所有 快取 (回應) 的 存在時間 (age) 皆已大於 0 秒,
因此 視為過期,並 發送網路請求,以重新 驗證 (validation)。
這正是許多 瀏覽器 執行「重新整理」的方式 (e.g., Firefox) :
提出請求 (Presented Request)
需注意的是:
若以 代理 (intercepting proxy) 攔截瀏覽器發送的一般請求,
並附加上 Cache-Control: max-age=999999 (極大值),並不會使瀏覽器使用快取,
因為 — — 請求 已發送 出去了。
而發送 0 以外的 max-age 值,通常對 JavaScript 特別有用,例如:
這是因為 :
所有「允許直接覆用快取」的請求,是在 被提出 (Presented) 時決定,而非發送時,
因此,不會有請求發送至網路。
e.g., XHR (JavaScript) 提出請求時,便會由 瀏覽器 進行檢查作業。
啟發式過期時間 (heuristic expiration time)
然而,源伺服器 並非總是提供 顯式過期時間 (e.g., Expires 欄位、max-age 指令) ,
當未指定時,Server 可以指派 啟發式過期時間 (heuristic expiration time),
以估計 快取 (回應) 的到期時間 與 新鮮度。
Last-Modified (最後修改時間)
HTTP/1.1 並未規範 啟發式過期時間的實作方式,
而最常見的做法是使用 Last-Modified 表頭欄位 !
回應中的 Last-Modified 欄位,指示:
源伺服器 認為此 所選表示 (representation),最後的修改日期和時間。
例如:
Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT
(現在看到 http-date,應已覺得十分親切 ☺️ ?)
Last-Modified 欄位 可與 Expires、max-age 等顯式欄位 共存,
若回應訊息 未提供 它們 (顯式),Last-Modified 才 使用於 啟發式過期時間 計算。
每個 使用者代理 (User Agent) 實作的方式不同,一個傳統的典型範例:
新鮮時限 (freshness_lifetime) = (date_value - last_modified_value) / 10
這也是為何,重複瀏覽一些網站 (顯式也會,但啟發式更明顯),
一開始會來自本機快取,回應 200 (OK),
過一會兒,便可能 送出網路請求 (驗證),並回應 304 (Not Modified)。
例如,初次請求此影像,
使用了 啟發式過期時間 (heuristic expiration time) :
過幾秒後再次請求,發現回應來自快取 (空心灰圓):
再等幾秒後,便可能送出網路請求 (驗證成功 → 304 Not Modified) :
另外,若使用了 啟發式過期時間,且 快取 (回應) 的 存在時間,超過 24 小時,
使用者代理 應生成 Warning (警告) 表頭欄位,
並使用 113 警告代碼 — Heuristic Expiration (啟發式過期),詳見 RFC 7234 (5.5 節)。
在《HTTP 免網路請求 !! — 新鮮回應 (fresh response)》中有 1 則留言
解說的非常清楚~ 把困難的東西用舉例的 一下子就讓人理解 你可以當老師了!^O^