HTTP

HTTP 內容協商 (Content Negotiation)

一個 資源,可能具有不同的 表示 (representation) 方式 (不同的類型、編碼 或 語言…),
(2-2) 資源、表示、URI 所述,「使用者資料 」概念,我們可以用 JSON 表示:

{
  "id": 9487,
  "name": "Jason",
  "speciality": "sleep",
  "gender": "male",
  "blog": "NotFalse 技術客",
  "blog_url": "https://NotFalse.net"
}

 
也可以用 XML 表示 (或其它任意方式):

<id>9487</id>
<name>Jason</name>
<speciality>sleep</speciality>
<gender>male</gender>
<blog>NotFalse 技術客</blog>
<blog_url>https://NotFalse.net</blog_url>

 
不同的 使用者 或 使用者代理 對於 表示,可能具有不同的功能,特徵或偏好。
Client 與 Server 如何協調,以傳輸最理想的 表示 的機制,
則稱為 — — 內容協商 (Content Negotiation)
 
 
內容協商 (Content Negotiation) 主要有三種模式:

  • 伺服端驅動 (server-driven)
  • 使用者代理驅動 (agent-driven)
  • 代理驅動 (proxy-driven)

 


 

伺服端驅動 (server-driven)

伺服端驅動協商 (server-driven negotiation),是 內容協商 使用最頻繁的模式,
又稱 主動協商 (Proactive Negotiation) 或 搶先式協商 (preemptive negotiation)。
 
是指 使用者代理 (User Agent) 在請求中發送 期望的 偏好表示資訊,
使 Server 根據該偏好資訊,使用適當的演算法,以 選擇 (select) 優選的表示。
 
當然,結果可能事與願違,輕則回應非預期的表示,
重則回應 客戶端錯誤 (e.g., 415 Unsupported Media Type, 406 Not Acceptable)。
 
伺服端驅動協商
 
 
 

偏好資訊

使用者代理 (User Agent) 能藉由顯示的 協商欄位,指明對表示的 期望選項:

  • Accept
  • Accept-Charset
  • Accept-Encoding
  • Accept-Language

 
Accept 欄位 對映於 Content-Type (內容類型) 欄位
 
Accept-Charset 欄位 對映於 Content-Type 的 charset 參數
主要用於文本回應內容。
然而,隨著對 UTF-8 良好的支持,其通常做為首選字元集合,
大部分通用的使用者代理,並 不會 發送 Accept-Charset 欄位。
 
Accept-Encoding 欄位 對映於 Content-Encoding (內容編碼) 欄位
也可以使用 空值 或 “identity” (本體), 指明不進行編碼 (no encoding)。
例如:

Accept-Encoding: Accept-Encoding: identity

 
Accept-Language 欄位 對映於 Content-Language (內容語言) 欄位
 
協商請求範例:

GET / HTTP/1.1

Host: example.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0) Gecko/20100101 Firefox/53.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate

 

 
與多數欄位相同,值與值之間大多以 逗號「,」分隔
分號「;」則是做為 參數前綴,多用於連接「q 值」(稍後提及)。
 
 

星號 (*)

星號「*」是 萬用字元
Accept:

// 所有類型 Accept: */* // 所有文字類型 Accept: text/*

 
例如,對 api.github.com 發送:

GET / HTTP/1.1

Host: api.github.com Accept: application/xml User-Agent: Test

 

 
將導致回應 415 (Unsupported Media Type) 客戶端錯誤,
因其僅擁有 application/json 的 媒體類型。
 
加上 */* 就解決啦 (因包含 application/json) :

GET / HTTP/1.1

Host: api.github.com Accept: application/xml,*/* User-Agent: Test

 

 
要小心的是,於 Accept-Encoding:

// 並非接受所有 Content-Encoding,而是沒有列出其偏好 (a.k.a 隨便、都可以、試試看) Accept-Encoding: *

 
 

質量值 (Quality Values)

Quality Values 俗稱 q 值
主要用於分配 協商欄位值相對 “權重”
是介於 0 ~ 1 的數,未指定的話 預設為 1
 
例如:

Accept: application/xml;q=0.9,application/json, text/*;q=0.8

 
代表最想接收的 Content-Type 為:
application/json (q=1),其次為 application/xml (q=0.9)。
 
 

Vary (變更)

除了顯式的 協商欄位 ,許多 隱含特徵 (implicit characteristics) 也會影響 內容的選擇,
例如 User-Agent 欄位、Client 的網路位址、Cookie…。
 
Vary 表頭欄位 用於指出「選擇表示的過程中」使用了哪些表頭欄位,
以利後續的請求,並使此 快取回應 僅在 符合原始請求條件 (Vary 中的欄位),才允許使用。
 
金融時報 推出的 JavaScript 自動補完函式庫服務 polyfill-service
便是根據請求訊息的 User-Agent 欄位,判定是否支援某 JavaScript API,
若已支援,則不回應冗余內容,以節省頻寬、增進效能。
 
Vary 表頭欄位:

Vary: User-Agent, Accept-Encoding

 
對於主動協商請求,回應通常會發送 Vary 表頭欄位
以指示選擇演算法中,使用了哪些請求資訊。
 


 

使用者代理驅動 (agent-driven)

使用者代理驅動 (agent-driven negotiation),又稱 被動/反應 協商 (Reactive Negotiation)
 
是指 在從 源伺服器 接收到 包含 資源表示列表 的初始回應之後 (不管回應狀態碼),
使用者代理 再從中選擇,並執行 GET 請求,以獲得最佳的 表示方式 (representation)
改善了 主動協商 瞎子摸象的問題。
 
簡單說就是:

第一次請求: 獲取表示列表
第二次請求: 獲取最佳表示
(若初始回應即滿足,則不需發送第二次請求)

 
不像 主動協商 (Proactive Negotiation) 擁有完整的機制,
被動協商 是一種概念,沒有規範如何實作、選擇,
但在我們生活中已隨處可見,
例如,上網看片時,通常會預設高畫質影片,有需要才做切換。
 
這便是最佳實踐 — — 設法 以初始回應滿足使用者
即可大幅改善 被動協商 的缺點 — — 2 次請求。
 
被動協商 的優點是,不必在每個請求中都發送 協商欄位,有更多 的上下文資訊 (context information),且能夠在多種不同表示中做選擇,而不會對緩存產生影響,主/被 動協商,並非互斥,每種模式都具有適用性和實用性的權衡。
 


 

代理驅動 (proxy-driven)

代理驅動 (proxy-driven negotiation)
又稱 透明協商 (Transparent Negotiation)
是指由一或多個 代理 (proxy) 來代替選擇最佳的表示。
 
請求有可能因 快取命中,而不必發送額外的網路請求,
增進了效率,並節省網路頻寬,
 
事實上,代理驅動 大多是 主動協商 的延伸應用 — — 由 代理 來發起主動協商,
並提供適當的 Vary 表頭欄位,避免快取回應的混肴。

 
 

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

發表迴響