Apache HTTPD支持HTTP/1.1
規範中描述的內容協商。它可以根據流覽器提供的媒體類型,語言,字元集和編碼首選項,選擇資源的最佳表示形式。它還實現了一些功能,可以更智能地處理來自發送不完整協商資訊的流覽器的請求。
內容協商由mod_negotiation
模組提供,該模組默認編譯。
關於內容協商
資源可以以多種不同的表示形式提供。例如,它可能以不同語言或不同媒體類型或組合形式提供。選擇最合適選擇的一種方法是為用戶提供索引頁面,然後讓他們選擇。但是,伺服器通常可以自動選擇。這是可以做到的,因為流覽器可以在每個請求中發送有關他們喜歡的表示形式的資訊。例如,如果可能的話,流覽器可以表明它希望看到法語資訊,否則會發送英語資訊。流覽器通過請求中的標題指示其首選項。僅請求法語的表示,那麼流覽器將發送法語內容資訊。
Accept-Language: fr
請注意,只有在可以選擇表示時才會應用此首選項,並且這些首選項因語言而異。
作為更複雜請求的示例,此流覽器已配置為接受法語和英語,但更喜歡法語,並接受各種媒體類型,更喜歡HTML而不是純文本或其他文本類型,並且優先選擇GIF或JPEG而不是其他媒體類型 ,但也允許任何其他媒體類型作為最後的手段:
Accept-Language: fr; q=1.0, en; q=0.5
Accept: text/html; q=1.0, text/*; q=0.8, image/gif; q=0.6, image/jpeg; q=0.6, image/*; q=0.5, */*; q=0.1
httpd支持“伺服器驅動的”內容協商,如HTTP/1.1
規範中所定義。它完全支持Accept
,Accept-Language
,Accept-Charset
和Accept-Encoding
請求標頭。httpd還支持“透明”內容協商,這是RFC 2295
和RFC 2296
中定義的實驗性協商協議。它不支持這些RFC中定義的“功能協商”。
資源是由URI(RFC 2396)標識的概念實體。像Apache HTTP Server這樣的HTTP伺服器提供對其命名空間內資源表示的訪問,每個表示形式為具有已定義媒體類型,字元集,編碼等的位元組序列。每個資源可以關聯在任何給定時間使用零個,一個或多個表示。如果有多個表示可用,則該資源被稱為可協商的,並且其每個表示被稱為變體。可轉讓資源的變體的變化方式稱為協商的維度。
httpd的協商
為了協商資源,需要向伺服器提供有關每個變體的資訊。這可以通過以下兩種方式之一完成:
- 使用類型映射(即
*.var
檔)明確命名包含變體的檔,或 - 使用“MultiViews”搜索,伺服器執行隱式檔案名模式匹配,並從結果中進行選擇。
使用類型映射檔
類型映射是與名為type-map
的處理程式相關聯的文檔(或者,對於與舊的httpd配置的向後相容性,MIME類型的應用程式/x-type-map
)。請注意,要使用此功能,必須在配置中設置一個處理程式,將檔尾碼定義為type-map
是最好的。
AddHandler type-map .var
在伺服器配置檔中。
類型映射檔應與其描述的資源具有相同的名稱,後跟擴展名.var
。在下面顯示的示例中,資源名為foo
,因此類型映射檔案名為foo.var
。
此檔應包含每個可用變體的條目; 這些條目由連續的HTTP格式標題行組成。不同變體的條目由空行分隔。空白行在條目中是非法的。通常開始一個映射檔,其中包含整個組合實體的條目(儘管這不是必需的,如果存在則將被忽略)。示例映射檔如下所示。
此檔中的URI相對於類型映射檔的位置。通常,這些檔將與類型映射檔位於同一目錄中,但這不是必需的。可以為與映射檔位於同一伺服器上的任何檔提供絕對或相對URI。
URI: foo
URI: foo.en.html
Content-type: text/html
Content-language: en
URI: foo.fr.de.html
Content-type: text/html;charset=iso-8859-2
Content-language: fr, de
請注意,即使啟用了Multiviews,類型映射檔也將優先於檔案名的擴展名。如果變體具有不同的源品質,則可以通過媒體類型的“qs”參數指示,如此圖片(可用作JPEG,GIF或ASCII藝術):
URI: foo
URI: foo.jpeg
Content-type: image/jpeg; qs=0.8
URI: foo.gif
Content-type: image/gif; qs=0.5
URI: foo.txt
Content-type: text/plain; qs=0.01
qs
值可以在0.000
到1.000
的範圍內變化。請注意,永遠不會選擇qs
值為0.000
的任何變體。沒有’qs’參數值的變數的因數為1.0
。qs
參數表示此變體與其他可用變體相比的相對“qs”,與客戶端的能力無關。例如,如果JPEG檔試圖表示照片,則該檔通常具有比ASCII檔更高的源品質。但是,如果表示的資源是原始ASCII藝術,那麼ASCII表示將具有比JPEG表示更高的源品質。因此,qs
值特定於給定變體,具體取決於它所代表的資源的性質。
MultiviewsMultiViews
是一個每個目錄選項,它可以在httpd.conf
中的<Directory>
,<Location>
或<Files>
部分中使用Options
指令設置,或者在.htaccess
檔中設置(如果正確設置了AllowOverride
)。請注意,選項全部不設置MultiViews
; 必須通過名字指定它。
MultiViews的效果如下:如果伺服器收到/some/dir/foo
的請求,如果/some/dir
啟用了MultiViews,並且/some/dir/foo
不存在,那麼伺服器會讀取尋找的目錄名為foo.*
的檔,有效地偽造了一個類型映射,該映射命名所有這些檔,為它們分配相同的媒體類型和內容編碼,如果客戶端通過名稱請求其中一個檔。然後,它會根據客戶的要求選擇最佳匹配。
如果伺服器嘗試索引目錄,MultiViews也可能適用於搜索DirectoryIndex
指令指定的檔。如果配置檔指定:
DirectoryIndex index
然後,如果兩者都存在,伺服器將在index.html
和index.html3
之間進行選擇。如果兩者都不存在,並且存在index.cgi
伺服器將運行index.cgi
。
如果在讀取目錄時找到的其中一個檔沒有mod_mime
識別的擴展名來指定其Charset
,Content-Type
,Language
或Encoding
,則結果取決於MultiViewsMatch
指令的設置。此指令確定處理程式,篩檢程式和其他擴展類型是否可以參與MultiViews
協商。
協商方法
在httpd從類型映射檔或目錄中的檔案名獲得給定資源的變體列表之後,它會調用兩種方法之一來決定要返回的“最佳”變體(如果有)。為了使用httpd的內容協商功能,沒有必要知道協商實際發生的任何細節。
有兩種協商方法:
伺服器驅動協商 - 在正常情況下使用與httpd演算法的伺服器驅動的協商。httpd演算法在下面更詳細地解釋。當使用該演算法時,httpd有時可以“擺弄”特定維度的品質因數以獲得更好的結果。httpd可以改變品質因素的方式在下面更詳細地解釋。
透明內容協商 - 當流覽器通過RFC 2295中定義的機制專門請求時,使用透明內容協商。此協商方法使流覽器完全控制決定“最佳”變體,因此結果取決於流覽器使用的特定演算法。作為透明協商過程的一部分,流覽器可以要求httpd運行RFC 2296中定義的“遠程變數選擇演算法”。
httpd協商演算法
httpd可以使用以下演算法選擇“最佳”變體(如果有)返回流覽器。該演算法不是可配置的。它的運作方式如下:
第1步 - 首先,對於協商的每個維度,檢查相應的Accept *
標頭字段並為每個變體分配品質。如果任何維度的Accept *
標頭暗示此變體不可接受,則將其刪除。如果沒有變體,請轉到第4步。
第2步 - 通過消除過程選擇“最佳”變體。按順序應用以下每個測試。在每次測試中未選擇的任何變體都被消除。每次測試後,如果只剩下一個變數,請將其選為最佳匹配,然後繼續執行第3步。如果仍有多個變數,請繼續進行下一個測試。
- 將Accept標頭中的品質因數與此變體媒體類型的源品質因數相乘,並選擇具有最高值的變體。
- 選擇具有最高語言品質因數的變體。
- 使用
Accept-Language
標頭中的語言順序(如果存在)或LanguagePriority
指令中的語言順序(如果存在),選擇具有最佳語言匹配的變體。 - 選擇具有最高“級別”媒體參數的變體(用於提供
text/html
媒體類型的版本)。 - 選擇具有最佳字元集媒體參數的變體,如
Accept-Charset
標題行所示。除非明確排除,否則使用Charset ISO-8859-1
。假定具有text/*
媒體類型但未與特定字元集明確關聯的變體在ISO-8859-1
中。 - 選擇那些具有非
ISO-8859-1
關聯字元集媒體參數的變體。如果沒有此類變體,請選擇所有變體。 - 選擇具有最佳編碼的變體。如果存在具有用戶代理可接受的編碼的變體,請僅選擇這些變體。否則,如果存在編碼和非編碼變體的混合,請僅選擇未編碼的變體。如果編碼所有變體或未編碼所有變體,請選擇所有變體。
- 選擇內容長度最小的變體。
- 選擇剩餘的第一個變體。這將是類型映射檔中列出的第一個,或者從目錄中讀取變體時,使用ASCII代碼順序排序時文件名首先出現的變數。
- 該演算法現在選擇了一個“最佳”變體,因此將其作為回應返回。HTTP回應頭Vary設置為指示協商的維度(流覽器和緩存可以在緩存資源時使用此信息)。
- 到達此處意味著未選擇任何變體(因為流覽器不接受任何變體)。返回406狀態(表示“無法接受的表示”),其回應正文由列出可用變體的HTML文檔組成。還要設置HTTP Vary標頭以指示方差的維度。
擺弄品質值
httpd有時會改變上面對httpd協商演算法的嚴格解釋所期望的品質值。這是為了從不發送完整或準確資訊的流覽器的演算法中獲得更好的結果。一些最流行的流覽器發送Accept頭資訊,否則會導致在許多情況下選擇錯誤的變體。如果流覽器發送完整且正確的資訊,則不會應用這些小提琴。
媒體類型和通配符Accept:request
標頭指示媒體類型的首選項。它還可以包括“通配符”媒體類型,例如image/*
或*/*
,其中*
匹配任何字串。所以請求包括:
Accept: image/*, */*
表示任何以image/
開頭的類型都是可以接受的,就像任何其他類型一樣。除了可以處理的顯式類型之外,一些流覽器還會定期發送通配符。例如:
Accept: text/html, text/plain, image/gif, image/jpeg, */*
這樣做的目的是表明明確列出哪些類型是首選的,但如果有不同的表示,那也沒關係。使用顯式品質值,流覽器真正想要的是:
Accept: text/html, text/plain, image/gif, image/jpeg, */*; q=0.01
顯式類型沒有品質因數,因此它們默認為1.0(最高)的偏好。通配符*/*
的優先順序為0.01
,因此只有在沒有變數與明確列出的類型匹配時才會返回其他類型。
如果Accept:標頭根本不包含q
因數,httpd將*/*
”的q
值設置為0.01
以模擬所需的行為。它還將格式為type/*
的通配符的q
值設置為0.02
(因此這些優先於匹配*/*
的匹配。如果Accept:標頭上的任何媒體類型包含aq
因數,則這些特殊值不是應用,所以來自流覽器的請求發送顯式資訊以按預期開始工作。
語言協商異常
在httpd 2.0中新增了一些異常,在協商演算法中添加了一些異常,以便在語言協商找不到匹配時允許優雅的回退。
當客戶端請求伺服器上的頁面,但伺服器找不到與流覽器發送的Accept語言匹配的單個頁面時,伺服器將向客戶端返回“No Acceptable Variant”或“Multiple Choices”回應。為了避免這些錯誤消息,可以將httpd配置為在這些情況下忽略Accept語言,並提供一個未明確匹配客戶端請求的文檔。ForceLanguagePriority
指令可用於覆蓋這些錯誤消息中的一個或兩個,並以LanguagePriority
指令的形式替換伺服器判斷。
當沒有找到其他匹配時,伺服器還將嘗試匹配語言子集。例如,如果客戶端請求英語為en-GB語言的文檔,則HTTP/1.1標準通常不允許伺服器將其與標記為en的文檔進行匹配。(請注意,在Accept-Language
標題中包含en-GB
肯定是一個配置錯誤,因為讀者不太可能理解英國英語,但一般不懂英語。不幸的是,很多當前客戶端具有與此類似的默認配置。)但是,如果沒有其他語言匹配且伺服器即將返回“No Acceptable Variants”錯誤或回退到LanguagePriority,則伺服器將忽略子集規範並匹配en-GB相應檔。隱式地,httpd會將父語言添加到客戶端可接受的語言列表中,並且品質非常低。但請注意,如果客戶端請求en-GB; q=0.9,fr; q=0.8
,並且伺服器具有指定為“en”和“fr”的文檔,則將返回“fr”文檔。這對於保持對HTTP/1.1
規範的遵從性以及與正確配置的客戶端有效地工作是必要的。
為了支持高級技術(例如cookie或特殊URL路徑)來確定用戶的首選語言,因為httpd 2.0.47 mod_negotiation
識別環境變數prefer-language
。如果它存在且包含適當的語言標記,則mod_negotiation
將嘗試選擇匹配的變體。如果沒有這樣的變體,則適用正常的協商過程。
示例 -
SetEnvIf Cookie "language=(.+)" prefer-language=$1
Header append Vary cookie
透明內容協商的擴展
httpd擴展了透明內容協商協議(RFC 2295),如下所示。在變體列表中使用新的{encoding...}
元素來標記僅具有特定內容編碼的變體。RVSA/1.0演算法(RFC 2296)的實現被擴展為識別列表中的編碼變體,並且根據Accept-Encoding
請求頭,只要它們的編碼是可接受的,就將它們用作候選變體。在選擇最佳變體之前,RVSA/1.0實現不會將計算的品質因數舍入到5位小數。
關於超鏈接和命名約定的注釋
如果您正在使用語言協商,則可以在不同的命名約定之間進行選擇,因為檔可以具有多個擴展名,並且擴展的順序通常無關緊要。
當具有該檔的不同語言變體時,典型檔具有MIME類型擴展(例如,html
),可能是編碼擴展(例如,gz
),當然還有語言擴展(例如,en
)。
例子:
foo.en.html
foo.html.en
foo.en.html.gz
這裏有一些檔案名的例子以及有效和無效的超鏈接:
檔案名 | 有效的超鏈接 | 無效的超鏈接 |
---|---|---|
foo.html.en |
foo ,foo.html |
— |
foo.en.html |
foo |
foo.html |
foo.html.en.gz |
foo ,foo.html |
foo.gz ,foo.html.gz |
foo.en.html.gz |
foo |
foo.html ,foo.html.gz ,foo.gz |
foo.gz.html.en |
foo ,foo.gz ,foo.gz.html |
foo.html |
foo.html.gz.en |
foo ,foo.html ,foo.html.gz |
foo.gz |
查看上表,應該注意到在超鏈接(例如,foo)中始終可以使用沒有任何擴展名的名稱。優點是可以隱藏文檔rsp
的實際類型。檔並可以在以後更改它,例如,從html更改為shtml或cgi而不更改任何超鏈接引用。
如果您想繼續在超鏈接中使用MIME類型(例如foo.html
),則語言擴展(包括編碼擴展,如果有的話)必須位於MIME類型擴展的右側(例如,foo.html.en
)。
關於緩存的注意事項
當緩存存儲表示時,它將其與請求URL相關聯。下次請求該URL時,緩存可以使用存儲的表示。但是,如果資源在伺服器上可協商,則可能導致僅緩存第一個請求的變體,並且後續緩存命中可能返回錯誤的回應。為了防止這種情況,httpd通常將內容協商後返回的所有回應標記為HTTP/1.0客戶端不可緩存。httpd還支持HTTP/1.1協議功能,以允許緩存協商的回應。
對於來自HTTP/1.0
相容客戶端(流覽器或緩存)的請求,可以使用指令CacheNegotiatedDocs
來緩存需要協商的回應。該指令可以在伺服器配置或虛擬主機中給出,並且不帶參數。它對來自HTTP/1.1
客戶端的請求沒有影響。
對於HTTP/1.1
客戶端,httpd發送Vary HTTP回應頭以指示回應的協商維度。緩存可以使用此信息來確定是否可以從本地副本提供後續請求。要鼓勵緩存使用本地副本而不管協商維度,請設置force-no-vary
環境變數。