談 HTTP 標頭(header) Vary 的作用

Posted on  May 23, 2024  in  HTTP headers  by  Amo Chen  ‐ 4 min read

HTTP header “Vary” 是 1 個很重要的 Header, 它之所以重要,是因為 Vary 會對內容傳遞網路(CDN), 代理伺服器(Proxy)以及瀏覽器等有作用產生。

p.s. 而且用得不好就可能會產生災難⋯⋯

要理解 Vary Header 必須先知道,對 1 組 URL 發出 HTTP request 時,伺服器可以產生不同內容的回應。

例如 GET /index.html 要求取得資源 index.html 時,伺服器可以選擇回應 HTML 格式(text/html)的內容,也可以選擇回應純文字(text/plain)的內容。

但是伺服器到底要挑那一種內容給使用者才對咧?

伺服器回應內容時,會遵循稱為 content negotiation 的標準,這個標準底下又分為 3 種方式:

  1. Server-driven Negotiation
  2. Agent-driven Negotiation
  3. Transparent Negotiation

大家現在瀏覽網頁其實都是 Server-driven Negotiation 為主,其實是 Server 自動幫你挑內容的意思,但 Server 也不是在毫無資訊的情況下亂挑,而是根據 HTTP request headers 裡面有的資訊,例如 Accept , Accept-Encoding , Accept-Language 這些 headers 告訴 Server, 發出 request 的 Client 支援什麼格式、偏好什麼編碼、偏好什麼語言等等, Server 就能夠依照這些資訊產生回應,譬如 Accept: text/plain 就代表 Client 只接受純文字回應,所以 Server 有支援純文字回應的情況下,就會回應純文字給 Client 端,但如果不支援就會回應 406 (Not Acceptable)或 415 (Unsupported Media Type)狀態碼告訴你回應有問題,也有可能直接不管 Client 的偏好直接回應內容。

content-negotiation.png

至於 Agent-driven Negotiation 則是由 Client 端選擇要什麼內容, Server 則會回應有哪些內容可以選擇;而 Transparent Negotiation 則是結合 Server-driven Negotiation 與 Agent-driven Negotiation 兩種機制,簡單來講是有 cache 時就選 Server-driven Negotiation 模式,沒有 cache 就選 Agent-driven Negotiation 模式。因為 Agent-driven Negotiation 與 Transparent Negotiation 目前不是主流,所以有興趣的人請自行研讀 RFC-2616 的細節囉!

知道 content negotiation 之後,再來談談瀏覽器這邊的快取(cache)。

現代瀏覽器支援多種方式的快取,這些快取一方面可以增加瀏覽器渲染的速度,一方面也可以減少發往伺服器端的要求數,伺服器可以告訴瀏覽器將回應的內容快取起來,如果之後請求相同資源,就直接用快取回應,其中最簡單的快取方式就是使用 URL 當作快取的 Key 。

這時候問題來了,既然伺服器針對同 1 個 URL 的請求,可以產生不同內容的回應,那只針對 URL 所做的快取機制不就造成問題?

例如同樣是 GET /index.html ,瀏覽器使用 Accept-Language: en 發出要求,然後 Server 也以 en 進行回應,並且告訴瀏覽器要快取回應內容,接著瀏覽器又改使用 Accept-Language: de 發出要求時,因為都是請求 GET /index.html , 所以瀏覽器就會直接拿 en 的回應內容給使用者看⋯⋯。

相同的問題也會發生使用代理伺服器(proxy)的情況下,因為代理伺服器也是使用類似的快取方式。

所以 Server 需要 1 個額外的手段告訴瀏覽器、代理伺服器要使用快取時,還需要額外考慮什麼,那就是 Vary header 的作用!

The Vary field value indicates the set of request-header fields that fully determines, while the response is fresh, whether a cache is permitted to use the response to reply to a subsequent request without revalidation.

舉前述 Accept-Language 變動的例子來說,因為 Server 會根據 Client 的 Accept-Language 產生不同的內容,所以 Server 需要在回應時加上 Vary: Accept-Language HTTP header, 藉此告訴瀏覽器、代理伺服器,這個內容會因為 Accept-Language 值的不同而「可能」變動,所以瀏覽器跟代理伺服器在做 cache key 時,就會一起將 Accept-Language 納入,一旦對相同 URL 的請求的 Accept-Language 值有所不同,它就會選擇不要使用快取!

說那麼多,實驗一次最有感覺!

Vary header 的作用可以在 local 環境使用下列步驟實驗。

使用以下程式碼建立 1 個極簡 HTTP Server, 該 sever 的回應會請 Client 端快取 1 天之外,也使用 Vary header 告訴 Client 須考慮 X-DEVICE 才能決定要不要使用快取的內容:

app.py

import json
import time

from flask import Flask, request

app = Flask(__name__)


@app.route('/')
def index():
    device = request.headers.get('X-DEVICE', 'UNKNOWN')
    return f'<html><body><h1>{device}</h1></body></html>', {
        'Content-Type': 'text/html',
       	'Vary': 'X-DEVICE',
        'Cache-Control': 'max-age=86400'
    }


if __name__ == '__main__':
    app.run(debug=True)

接著用 Python 執行 app.py

$ python app.py

打開瀏覽器,輸入網址 http://127.0.0.1/ 可以看到以下畫面:

http-vary.png

最後打開發者工具,在 Conosole 輸入以下 JavaScript 程式碼 3 次:

await fetch('/', {headers: {'X-DEVICE': 'a', 'X-PLATFORM': 'b'}})

fetch-x3.png

再來切換到 Network 將可以發現有 2 個 requests 是直接從 cache 回應,回應的恰好都是 {'X-DEVICE': 'a', 'X-PLATFORM': 'b'} 的要求:

fetch-cached.png

最後,再將 X-DEVICE 改為 b 再發送一次 request, 又會發現這次不會從 cache 回應了:

fetch-no-cache-b.png

之所以會有這些行為差異,就是因為瀏覽器按照 Vary 的規定,將 X-DEVICE 也納入要不要使用 cache 的考量,一旦 X-DEVICE 有變動,就不會使用既有的 cache 。

p.s. 如果有興趣的話,可以改看看 X-PLATFORM ,不管怎麼改都會直接從 cache 回應。

這就是 Vary header 的作用!

最後講講 Vary header 的規定, Vary header 只能放 request 的 headers, 因為 Server 是根據 request 的 headers 決定內容,如果有多個 request headers 會影響 Server 產生內容的話, Server 端可以用逗號分隔指定,例如:

Vary: Accept, Accept-Language

但如果看到 Vary: *代表資源 uncacheable, 不是把所有 headers 都考慮進去的意思喔!

使用上,如果 1 項資源屬於可快取的話,「應該」要附上 Vary header 在 Server 的 response 。

另外, Vary: Authorization 是不需要的,因為 Authorization header 本身就規定禁止為不同使用者回應相同 cache 。

References

Vary - HTTP | MDN

https://datatracker.ietf.org/doc/html/rfc2616#section-12

https://httpwg.org/specs/rfc7234.html#caching.negotiated.responses

對抗久坐職業傷害

研究指出每天增加 2 小時坐著的時間,會增加大腸癌、心臟疾病、肺癌的風險,也造成肩頸、腰背疼痛等常見問題。

然而對抗這些問題,卻只需要工作時定期休息跟伸展身體即可!

你想輕鬆改變現狀嗎?試試看我們的 PomodoRoll 番茄鐘吧! PomodoRoll 番茄鐘會根據你所設定的專注時間,定期建議你 1 項辦公族適用的伸展運動,幫助你打敗久坐所帶來的傷害!

贊助我們的創作

看完這篇文章了嗎? 休息一下,喝杯咖啡吧!

如果你覺得 MyApollo 有讓你獲得實用的資訊,希望能看到更多的技術分享,邀請你贊助我們一杯咖啡,讓我們有更多的動力與精力繼續提供高品質的文章,感謝你的支持!