HTTP/1.1 — (6-4) 驗證快取 !! — 條件請求 (Conditional Requests)

發表於 分類為「HTTP

 
所謂 驗證 (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):

if ((client.if_none_match_value != currentETag) &&
        (client.if_none_match_value == * && currentRepresentation == null)) {
    // None-Match
    return performRequest(); // 執行請求
} else {
    // Match -- 快取驗證成功
    if (requestMethod == GET || requestMethod == HEAD) {
        return 304; // Not Modified (未修改)
    } else {
        return 412; // Precondition Failed (先決條件失敗)
    }
}

 
 
注意上方 第二條件:

只要目標資源的 所選表示 存在 (!= 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 指令 加上 計算因子。
 
 
詳情請見 NginxApache 文件。
 
 
至於 非靜態資源 (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 同時 生成 ETagLast-Modified 回應,才是最佳實踐,

別忘記了,Last-Modified 同時可用於 啟發式過期時間 計算,

這在沒有 Expires、max-age…等顯式過期時間時 特別有用。
 
 
 
 



作者: 鄭中勝
喜愛音樂,但不知為何總在打程式😱
期許能重新審視、整理自身所學,幫助有需要的人。

發表迴響