一個URL常常需要代表若干不同的資源。例如那種需要以多種語言提供其內容的網站站點。如果某個站點有說法語的和說英語的兩種用戶,它可能想用這兩種語言提供網站站點信息。理想情況下,服務器應當向英語用戶發送英文版,向法語用戶發送法文版——用戶只要訪問網站主頁就可以得到相應語言的內容。
HTTP提供了內容協商方法,允許客戶端和服務器作這樣的決定。通過這些方法,單一的URL就可以代表不同的資源(比如,同一個網站頁面的法語版和英語版),這些不同的版本稱為變體。本文將詳細介紹內容協商。
一、概述
我們在日常的抓包過程中經常可以看到以Accept開頭的請求首部,比如:Accept-Language 有一個q值,肯定有人好奇在HTTP規范中為什么要定義這個q值;還有在響應首部有一個名為Vary的首部,這個首部又有什么意義?如圖所示:
二、內容協商總結
要講清楚這兩個問題,我們需要引入HTTP協議的內容協商概念:某一資源,服務器有多個版本,客戶端告知服務器自己的偏好,服務器根據偏好選擇合適的版本響應客戶端的請求。
共有3種不同的方法可以決定服務器上哪個頁面最適合客戶端:讓客戶端來選擇、服務器自動判定、讓中間代理來選。這3種技術分別稱為客戶端驅動的協商、服務器驅動的協商以及透明協商。
(1)客戶端驅動
客戶端發起請求,服務器發送可選項列表,客戶端作出選擇后在發送第二次請求。
優點:比較容易實現;
缺點:增加了時延,至少要發送兩次請求,第一次請求獲取資源列表,第二次獲取選擇的副本;
(2)服務器驅動
服務器檢查客戶端的請求首部集並決定提供哪個版本的頁面。
優點:比客戶端驅動的協商要快。HTTP提供了q機制,允許服務器近似匹配,還提供了vary首部供服務器告知下游的設備(如代理服務器)如何對請求估值;
缺點:首部集不匹配,服務器要做猜測;
(3)透明協商
某個中間設備(通常是緩存代理)代表客戶端進行協商。
優點:免除了web服務器的協商開銷,比客戶端驅動的協商要快;
缺點:HTTP並沒有提供相應的規范;
其中,服務器驅動的解決方案應用的較為廣泛。
三、通用的內容協商首部集
客戶端可以用下面列出的HTTP首部集發送用戶的偏好信息:
Accept:告知服務器發送何種媒體類型;
Accept-Language:告知服務器發送何種語言;
Accept-Charset:告知服務器發送何種字符集;
Accept-Encoding:告知服務器采用何種編碼;
【注意】這些首部與實體首部非常類似。不過,這兩種首部的用途截然不同。
實體首部集像運輸標簽,它們描述了把報文從服務器傳輸給客戶端的過程中必須的各種報文主體屬性。
而內容協商首部集是由客戶端發送給服務器用於交換偏好信息的,以便服務器可以從文檔的不同版本中選擇出最符合客戶端偏好的那個來提供服務。
服務器用下面列出的實體首部集來匹配客戶端的Accept首部集:
Accept首部 實體首部
Accept Content-Type
Accept-Language Content-Language
Accept-Charset Content-Type
Accept-Encoding Content-Encoding
四、q質量值的應用場景
假設客戶端的Accept-Language指定的是西班牙語,但是服務端只有英語與法語版本,這個客戶端希望在沒有西班牙語的時候優先返回英語。這就意味着,我們需要一種HTTP機制更詳細的描述偏好。這種機制就是質量值(q值)。示例如下:
Accept-Language: en;q=0.5, fr;q=0.0, nl;q=1.0, tr;q=0.0
這個首部表示:用戶最願意接受荷蘭語(nl),英文也行(en),就是不願意接受法語(fr)或者土耳其語(tr);
五、vary首部的應用場景
服務器的決策不是依據Accept首部集(常規的內容協商首部集),而是比如Accept-Encoding。
假設整個請求過程是這樣的:客戶端 -> 代理服務器(具備緩存功能) ->web服務器。
第一個支持gzip壓縮的客戶端向中間代理服務器發送請求,代理服務器轉發該請求,向web服務器拉取內容,拿到內容后代理服務器緩存該內容(由於請求首部有Accept-Encoding: gzip 所以內容會被壓縮)。
第二個不支持gzip壓縮的客戶端也向中間代理服務器發送同一個請求,代理服務器發現該請求已經被緩存了,於是就把壓縮后的內容響應給該客戶端。悲劇了,因為該客戶端根本不支持gzip壓縮,也就沒法解壓。
六、透明協商
透明協商機制試圖從服務器上去除服務器驅動協商所需的負載,並用中間代理來代表客戶端以使與客戶端的報文交換最小化。假定代理了解客戶端的預期,這樣就可以代表客戶端與服務器協商,在客戶端請求內容的時候,代理已經收到了客戶端的預期。
為了支持透明內容協商,服務器必須有能力告知代理,服務器需要檢査哪些請求首部,以便對客戶端的請求進行最佳匹配。HTTP/1.1規范中沒有定義任何透明協商機制,但定義了Vary首部。服務器在響應中發送了Vary首部,以告知中間節點需要使用哪些請求首部進行內容協商。
代理緩存可以為通過單個URL訪問的文檔保存不同的副本。如果服務器把它們的決策過程傳給緩存,這些代理就能代表服務器與客戶端進行協商。緩存同時也是進行內容轉碼的好地方,因為部署在緩存里的通用轉碼器能對任意服務器,而不僅僅是一台服務器傳來的內容進行轉碼。
1、緩存與備用候選
對內容進行緩存的時候是假設內容以后還可以重用。然而,為了確保對客戶端請求回送的是正確的已緩存響應,緩存必須應用服務器在回送響應時所用到的大部分決策邏輯。
上面描述了客戶端發送的Accept首部集,以及為了給每條請求選擇最佳的響應,服務器使用的與這些首部集匹配的相應實體首部集。緩存也必須使用相同的首部集來決定回送哪個已緩存的響應。
下圖展示了涉及緩存的正確及錯誤的操作序列。緩存把第一個請求轉發給服務器,並存儲其響應。對於第二個請求,緩存根據URL査找到了匹配的文檔。但是,這份文檔是法語版的,而請求者想要的是西班牙語版的。如果緩存只是把文檔的法語版本發給請求者的話,它就犯了錯誤。
因此,緩存也應該把第二條請求轉發給服務器,並保存該URL的響應與“備用候選”響應。緩存現在就保存了同一個URL的兩份不同的文檔,與服務器上一樣。這些不同的版本稱為變體(variant)或備用候選(alternate)。內容協商可看成是為客戶端請求選擇最合適變體的過程。
2、Vary首部
這里是瀏覽器和服務器發送的一些典型的請求及響應首部:
然而,如果服務器的決策不是依據Accept首部集,而是比如User-Agent首部的話,情況會如何?這不像聽起來這么極端。例如,服務器可能知道老版本的瀏覽器不支持JavaScript語言,因此可能會回送不包含JavaScript的頁面版本。如果服務器是根據其他首部來決定發送哪個頁面的話,緩存必須知道這些首部是什么,這樣才能在選擇回送的頁面時做出同樣的邏輯判斷。
HTTP的Vary響應首部中列出了所有客戶端請求首部,服務器可用這些首部來選擇文檔或產生定制的內容(在常規的內容協商首部集之外的內容)。例如,若所提供的文檔取決於User-Agent首部,Vary首部就必須包含User-Agent。
當新的請求到達時,緩存會根據內容協商首部集來尋找最佳匹配。但在把文檔提供給客戶端之前,它必須檢査服務器有沒有在已緩存響應中發送Vary首部。如果有Vary首部,那么新請求中那些首部的值必須與舊的已緩存請求里相應的首部相同。因為服務器可能會根據客戶端請求的首部來改變響應,為了實現透明協商,緩存必須為每個已緩存變體保存客戶端請求首部和相應的服務器響應首部,參見下圖:
如果某服務器的Vary首部看起來像下面這樣,大量不同的User-Agent和Cookie值將會產生非常多的變體:
Vary: User-Agent, Cookie
緩存必須為每個變體保存其相應的文檔版本。當緩存執行査找時,首先會對內容協商首部集進行內容匹配,然后比較請求的變體與緩存的變體。如果無法匹配,緩存就從原始服務器獲取文檔。
參考鏈接:http://blog.csdn.net/meimeizhuzhuhua/article/details/70665344