HTTP

HTTP 快取驗證 與 條件請求 (Conditional Requests)

所謂 驗證 (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 指令 加上 計算因子。
 
詳情請見 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…等顯式過期時間時 特別有用。
 
 

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

在《HTTP 快取驗證 與 條件請求 (Conditional Requests)》中有 2 則留言

  1. If-None-Match 的程式語言示例这里,(client.if_none_match_value != currentETag) && (client.if_none_match_value == * && currentRepresentation == null) 为 True 时执行请求。
    也就是说,当 Client 的 ETag 值与 Server 的不相等且不存在 currentRepresentation 时才执行请求。
    是我理解错了吗?为什么是不存在 currentRepresentation 时才能执行请求。

發表迴響