所謂 驗證 (Validate) 或 重新驗證 (revalidate),
即是 使用 條件請求 (Conditional Requests) 機制,
以獲得 重複使用 快取的機會、更新快取的元資料,或者 取得 新的回應。
條件請求 (conditional request) 表頭欄位,用於 HTTP 請求訊息,
指示:在應用方法語義於目標資源之前,要測試的 先決條件 (precondition),
以確定 快取 是否等同於資源目前的 表示 (representation) [註],
其中,條件 GET 請求 是 HTTP 快取更新 (cache updates) 的主要有效機制。
[註]:
(2-2) 資源、表示、URI:
表示 (representation) 反映出資源的狀態,
同一資源,可能擁有多種表示 (e.g., JSON、XML),並且:
資料可能會更新、修改 或變更表示方式,
上星期 跟 現在 看到的結果或許不一樣 !
目錄
驗證 (Validate)
驗證時機
新鮮回應 (fresh response),可以用來滿足後續的 等效請求,
而 不必與 源伺服器 (Origin Server) 聯繫、也 無需「驗證 (validation)」,
減少了延遲與網絡開銷,從而提高效率,是快取最理想的情形。
當儲存的回應 不新鮮 (not fresh) 時,則與 Server 進行「驗證 (Validation)」,
通過驗證則可繼續使用,否則重新獲取回應。
然而,凡事總有例外,
(6-3) 何時驗證 說明了其他快取驗證時機。
驗證器 (Validators)
用於觀察 資源狀態 和測試 先決條件 (precondition) 主要的兩種形式:
— — 不透明實體標籤 (opaque entity tags) 和 修改日期 (modification dates)。
其分別對應 ETag (實體標籤) 與 Last-Modified (最後修改時間) 回應表頭欄位,
又統稱為 — 驗證器 (Validators)。
ETag (實體標籤)
實體標籤 — ETag 表頭欄位,其值由一組 雙引號字串 組成,
是區分同一資源 多個表示 的 不透明驗證器 (opaque validator),例如:
Etag: "b6131c8c6cdf908908fcd088e475d4e9"
不透明 是指 Server 能自由選擇產生 ETag 的演算法 (e.g., 雜湊、inode、時戳算法),Client 不必理解其含義。
Client 發送快取驗證時,透過將快取的 ETag 放置於 If-None-Match 條件請求欄位,
Server 接收請求,檢查是否與 目前表示 的 ETag 相同,相同則說明 表示並未更動,
因此通過驗證,並發送 304 (Not Modified) 回應,Client 能直接覆用快取回應!
// 注意 304 回應,並 無訊息主體
否則,Sever 得重新處理該請求,並發送任意狀態碼:
HTTP/1.1 200 OK
Date: Sun, 25 Jun 2017 03:01:57 GMT
ETag: "bbb"
Cache-Control: max-age=31536000
Last-Modified: Sun, 25 Jun 2017 02:01:57 GMT
Content-Type: text/plain
Content-Length: 7
WANZ611
ETag 欄位 在條件請求中,主要用於快取的 新鮮度評估,
這能大大減少網絡流量,是提高服務可擴展性和可靠性的重要因素,
因此 源伺服器 應為任何允許快取的 所選表示 (selected representation),發送一個 ETag 欄位。
If-None-Match
條件請求欄位 If-None-Match (如果未符合),
放置的是 Client 快取 中的 ETag 值。
指 如果 該值 未符合 Server 所選表示目前的 ETag,則 執行請求,
否則,直接回傳 304 (Not Modified) 並包含目前的元資料。
也就是:
False 才代表 驗證成功。
以程式語言表達 (Server):
int response() {
if (validate(client)) {
// None-Match
return performRequest(); // 執行請求
} else {
// Match -- 快取驗證成功
if (client.requestMethod == GET || client.requestMethod == HEAD)
return 304; // Not Modified (未修改)
else
return 412; // Precondition Failed (先決條件失敗)
}
}
boolean validate(Client client) {
if (client.if_none_match.equals("*"))
return currentRepresentation == null;
return !weakComparison(client.if_none_match, currentETag);
}
注意上方 第二條件:
只要目標資源的 所選表示 存在 (!= null),
能夠使用超作弊 萬用字元 * (沒有雙引號),強制 判斷為 False!
然而,其真正用途不是 無條件使用快取 啦 😂,
『 * 』主要用在非安全方法 (e.g., PUT),預防對「已存在表示」之資源造成危害。
若 驗證成功,Client 接收到 304 (Not Modified) 回應,
需依回應表頭欄位,更新相關的元資料,
並覆用快取內容以滿足請求,而 不需重新下載表示資料!
強驗證器 (Strong Validator)
驗證器分為 強驗證器 與 弱驗證器,
每個資源可選擇適合的驗證器種類。
像 ETag 這種:
Server 目標資源的 表示資料 一發生變化,
便可能使驗證器的值改變,則稱 強驗證器 (Strong Validator)。
例如,此 回應訊息 未使用 內容編碼 (Content-Encoding)
HTTP/1.1 200 OK
Date: Sun, 25 Jun 2017 00:01:57 GMT
ETag: "aaa"
Server: nginx/1.11.8
Content-Length: 5
Content-Type: text/plain
Hello
其 ETag 為:
Etag: "aaa"
若使用了 內容編碼 (Content-Encoding)?
HTTP/1.1 200 OK
Date: Sun, 25 Jun 2017 00:01:57 GMT
ETag: "bbb"
Server: nginx/1.11.8
Content-Length: 5
Content-Type: text/plain
Content-Encoding: gzip
Hello
其 ETag 改變為:
Etag: "bbb"
[註]:
相較之下,傳輸編碼 是訊息的屬性,
不會造成不同的 ETag (實體編碼),詳見 (3-4) 酬載。
強驗證器 (ETag) 對「同一資源」的 不同表示 具有 單一性,
然而,要小心的是,不同資源「可能」具有相同的 ETag 值 (通常是誤打誤撞)。
將 ETag 視為 表示 id,
僅在相同資源 (URI) 的 Context 中有意義。
弱驗證器 (Weak Validator)
相較之下:
Server 目標資源的 表示資料 一發生變化,
可能 不會 使驗證器的值改變,則稱 弱驗證器 (Weak Validator)。
這代表,多個表示能夠共用同一 弱驗證器 (Weak Validator),
對「同一資源」的 不同表示 不具單一性。
強驗證器 (ETag) 較為嚴苛,且能良好的判斷 表示資料 是否發生變化,
然而,弱驗證器 對驗證規則的放寬,許多時候仍是好事!
ETag 能加上前綴『 W/ 』(Weak) 變為 弱驗證器。
例如,此 回應訊息包含一 階層樣式表 (css):
HTTP/1.1 200 OK
Date: Sun, 25 Jun 2017 00:01:57 GMT
ETag: W/"ccc"
Server: nginx/1.10.0 (Ubuntu)
Content-Length: 112
Content-Type: text/css
.absolute-center {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
Client 已儲存此回應 為快取,其 ETag 為:
Etag: W/"ccc"
某天,Server 對其 CSS 做 minify,
除此之外,並沒有更改樣式內容,因此沿用 弱驗證器 W/”ccc”:
.absolute-center{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%)}
Client 送出條件請求時,便能驗證成功,
繼續使用 已儲存的 (未 minify) 的快取,而非重新下載 css。
HTTP/1.1 304 Not Modified
Date: Sun, 25 Jun 2017 01:02:00 GMT
ETag: W/"ccc"
Server: nginx/1.10.0 (Ubuntu)
Last-Modified: Sun, 25 Jun 2017 00:15:44 GMT
使用 ETag
大部分 Web Server 都支援 靜態檔案 (static files) ETag,
例如,Nginx 可使用 etag on;
指令 (預設已開啟),
而 Apache 則使用 FileETag
指令 加上 計算因子。
詳情請見 Nginx 與 Apache 文件。
至於 非靜態資源 (e.g., xxx.php),
可在 Server 端 儲存執行結果,將其轉為靜態資源 (e.g., index.html),
即可直接使用 Web Server 提供的 ETag 功能!
這便是最常見的 Server Caching 技術之一。
當然,也可自行以後端程式語言實作:
<?php
$eTag = myAlgorithm();
header("ETag: $eTag");
Last-Modified (最後修改時間)
回應中的 Last-Modified 欄位,指示:
源伺服器 認為 此 所選表示 (representation),最後的修改日期和時間。
例如:
Last-Modified: Date: Sun, 25 Jun 2017 04:08:07 GMT
在 (6-2) 新鮮回應 中,初步介紹了 Last-Modified 欄位,
事實上,它除了用於 啟發式過期時間 的計算,
更普遍做為 弱驗證器 (Weak Validator) 使用。
Client 發送快取驗證時,透過將快取的 Last-Modified 放置於 If-Modified-Since 條件請求欄位,
Server 接收請求,檢查是否與 目前表示 的 Last-Modified 相同,相同則說明 表示並未更動,
或者 表示可能更動,但不影響使用 (弱驗證器特性),
因此通過驗證,並發送 304 (Not Modified) 回應,Client 能直接覆用快取回應!
// 注意 304 回應,並 無訊息主體
否則,Sever 得重新處理該請求,並發送任意狀態碼:
HTTP/1.1 200 OK
Date: Sun, 25 Jun 2017 03:05:20 GMT
Cache-Control: max-age=31536000
Last-Modified: Sun, 25 Jun 2017 02:15:45 GMT
Content-Type: text/plain
Content-Length: 7
XVSR213
這能大大減少網絡流量,是提高服務可擴展性和可靠性的重要因素,
因此,只要能合理一致地確認修改日期,
源伺服器 應為任何 所選表示,發送一個 Last-Modified 欄位。
If-Modified-Since
就像 ETag 使用 If-None-Match (如果未符合) 欄位 來進行 GET | HEAD 驗證,
Last-Modified 則使用 If-Modified-Since 條件請求欄位。
例如:
If-Modified-Since: Sun, 25 Jun 2017 02:15:45 GMT
意指 如果 資料 自 Last-Modified 有明顯 改變,則 執行請求,
否則,直接回傳 304 (Not Modified) 並包含目前的元資料。
也就是:
False 才代表 驗證成功。
以程式語言表達 (Server):
if (client.if_modified_since_value < lastModified) {
// Modified
return performRequest();
} else {
// Non-Modified -- 快取驗證成功
if (requestMethod == GET || requestMethod == HEAD) {
return 304; // Not Modified (未修改)
} else {
return 412; // Precondition Failed (先決條件失敗)
}
}
若 驗證成功,Client 接收到 304 (Not Modified) 回應,
需依回應表頭欄位,更新相關的元資料,
並覆用快取內容以滿足請求,而 不需重新下載表示資料!
接收者 (e.g., proxy、Origin Server) 需根據 源伺服器的時鐘 (通常是 世界協調時間 UTC),
來計算 If-Modified-Since 的時戳。
最佳實踐
If-None-Match 的條件比 If-Modified-Since 更為精準 (i.e., 強驗證 > 弱驗證),
如果請求包含 If-None-Match,接收者 必須 忽略 If-Modified-Since。
因此:
Server 與 Client 優先的快取驗證機制,皆是以 ETag 為主。
儘管如此,Server 同時 生成 ETag 與 Last-Modified 回應,才是最佳實踐,
別忘記了,Last-Modified 同時可用於 啟發式過期時間 計算,
這在沒有 Expires、max-age…等顯式過期時間時 特別有用。
在《HTTP 快取驗證 與 條件請求 (Conditional Requests)》中有 2 則留言
If-None-Match 的程式語言示例这里,(client.if_none_match_value != currentETag) && (client.if_none_match_value == * && currentRepresentation == null) 为 True 时执行请求。
也就是说,当 Client 的 ETag 值与 Server 的不相等且不存在 currentRepresentation 时才执行请求。
是我理解错了吗?为什么是不存在 currentRepresentation 时才能执行请求。
誒 是我筆誤了 😂
非常感謝 已修正