HTTP

HTTP 免網路請求 !! — 新鮮回應 (fresh response)

就像食品擁有 保存期限快取 (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) 又分 請求 與 回應,且部分名稱重疊,
混肴時只需記住:『 請求指令 應用於 請求訊息;回應指令 應用於 回應訊息 』。
 
中介 (Intermediaries) 示意圖
 

快取指令單向的 (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 則留言

  1. 解說的非常清楚~ 把困難的東西用舉例的 一下子就讓人理解 你可以當老師了!^O^

發表迴響