# 內容協商
Apache支持HTTP/1.1規范中定義的內容協商,它可以根據瀏覽器提供的參數選擇一個資源最合適的媒體類型、語言、字符集和編碼的表現方式。它還實現了一些對瀏覽器發送不完整內容協商信息進行智能處理的能力。
內容協商由`mod_negotiation`模塊支持,并被默認編譯進服務器。
## 關于內容協商(Content 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
```
Apache支持HTTP/1.1規范中定義的"服務器驅動"的內容協商, 可以完全地支持`Accept`、`Accept-Language`、`Accept-Charset`、`Accept-Encoding`請求頭,這些是RFC2295和RFC2296中定義的實驗協商協議,但是不支持這些RFC中定義的"功能協商"。
**資源(resource)**是一個在URI(RFC2396)中定義的概念上的實體。一個HTTP服務器,比如Apache,以**表現形式(representation)**提供對其名稱空間中資源的訪問,各種表現形式由已定義的媒體類型、字符集和編碼的字節流構成。任何一個特定的時刻,一個資源可以沒有,或者有一個,或者有多個表現形式。如果有多個表現形式存在,則稱該資源是**可協商的(negotiable)**,其各種表現形式稱為**變種(variant)**。而一個可協商的資源的各種變種的區別途徑稱為**變元(dimension)**。
## Apache中的內容協商
可以使用下述兩種途徑之一向服務器提供有關各變種的信息,以實現對資源的協商:
* 使用類型表(也就是一個 `*.var`文件)明確指定各變種的文件名。
* 使用"MultiViews"搜索,即服務器執行一個隱含的文件名模式匹配,并在其結果中選擇。
### 使用類型表文件
類型表是一個與`type-map`處理器關聯的文檔(或者兼容早期Apache配置的[MIME類型](#calibre_link-223 "see glossary"):`application/x-type-map` )。要使用這個功能,必須在配置中建立處理器,以定義一個文件后綴為`type-map`,最好的方法是在配置文件中這樣設置:
```
AddHandler type-map .var
```
類型表文件應該與所描述的資源同名,且對每個有效變種都有一個塊(entry),每個塊由若干連續的HTTP頭行組成,不同變種的塊用空行分開,塊中不允許有空行。習慣上,類型表都以一個描述總體性質的組合塊作為開始(這不是必須的,如果有也會被忽略)。下例是一個描述資源`foo`的命名為`foo.var`的類型表文件:
```
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`設置為 `On` ,類型表仍然優先于文件后綴名。如果不同的變種具有不同的資源品質,就可以對媒體類型使用"qs"參數來表示這種不同。下例演示了一個圖片的 jpeg, gif, ASCII-art 三個有效變種:
```
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,取值為0.000的變種永遠不會被選擇,沒有指定qs值的變種其qs值為1.0。qs值表示一個變種相對于其他變種的"品質",比如在表現一張照片時,jpeg通常比字符構圖有更高的品質;而如果要表現的本來就是一個ASCII-art ,那么當然字符構圖就會比jpeg文件有更高的品質。因此,qs的值取決于變種所表現的資源本身。
[mod_negotation類型表](#calibre_link-554)文檔中有完整的HTTP頭的列表。
### Multiviews
`MultiViews`是一個針對每個目錄的選項,也就是說可以在`httpd.conf`或`.htaccess`(如果正確設置了`AllowOverride`)文件中的`<Directory>`、`<Location>`、`<Files>`配置段中,用`Options`指令來指定。注意,`Options All` 并不會設置`MultiViews` ,你必須明確地指定。
`MultiViews`的效果是:如果服務器收到對`/some/dir/foo`的請求,而`/some/dir/foo`并_不_存在,但是如果`/some/dir`啟用了`MultiViews` ,則服務器會查找這個目錄下所有的foo.* 文件,并有效地偽造一個說明這些foo.* 文件的類型表,分配給他們相同的媒體類型及內容編碼,并選擇其中最合適的匹配返回給客戶。
`MultiViews`還可以在服務器檢索一個目錄時,用于`DirectoryIndex`指令搜索的文件名。如果設置了:
```
DirectoryIndex index
```
而`index.html`和`index.html3`并存,則服務器會作一個權衡;如果都沒有,但是有`index.cgi` ,則服務器會執行它。
如果一個目錄中沒有任何文件具有`mod_mime`可以識別的表示其字符集、內容類型、語言和編碼的后綴,那么其結果將取決于`MultiViewsMatch`指令的設置,這個指令決定了在MultiViews協商中將使用的處理器、過濾器和其他后綴類型。
## 協商的方法
Apache從一個類型表或者某個目錄的文件名中得到一個資源變種列表以后,會使用兩種方法之一選擇可能的"最佳"變種返回給客戶。使用Apache的內容協商功能并不需要了解其細節,以下文檔對這些方法加以詳細說明,供有興趣的人看看。
協商有兩種方法:
1. **使用Apache算法的服務器驅動協商** 是通常情況下的默認方法。使用這個算法(下面有詳細的描述),為了得到更好的效果,Apache有時會"打亂"一個特定變元(dimension)的品質因子,其方法稍后會詳細闡述。
2. **透明內容協商** 僅當瀏覽器明確地用RFC2295中定義的機制發出請求時才使用。這種方法可以給予瀏覽器對"最佳"變種選擇的完全控制,因此其效果也取決于瀏覽器使用的算法。作為透明協商過程的一部分,瀏覽器可以要求Apache執行RFC2296中定義的"遠程變種選擇算法"。
### 協商的變元(Dimension)
| 變元 | 說明 |
| --- | --- |
| 媒體類型 | 瀏覽器在`Accept`頭中指明首選項,其中各項與品質因子關聯,變種描述也可以有品質因子(參數"qs")。 |
| 語言 | 瀏覽器在`Accept-Language`頭中指明首選項,其中各項與品質因子關聯,變種可以與零個、一個或多個語言關聯。 |
| 編碼 | 瀏覽器在`Accept-Encoding`頭中指明首選項,其中各項與品質因子關聯。 |
| 字符集 | 瀏覽器在`Accept-Charset`頭中指明首選項,其中各項與品質因子關聯,變種可以指定一個字符集作為媒體類型的一個參數。 |
### Apache協商算法
Apache使用下述算法選擇可能的"最佳"變種返回給瀏覽器。此算法不能被再配置。其過程如下:
1. 首先,對每個協商變元,檢查其適當的_Accept*_ 頭,并對每個變種指定一個品質。如果一個變元的_Accept*_ 頭指示不接受這個變種,則被剔除。如果最終沒有變種了,則轉到步驟4。
2. 順序執行以下的測試,使用逐步剔除的方法來選擇"最佳"變種。不能通過測試的變種將被剔除。每個測試完成后,如果僅剩一個變種,則作為最佳匹配,轉到步驟3;如果多于一個,則繼續下一個測試。
1. 將`Accept`頭的品質因子乘以該變種媒體類型的還原品質因子,選擇乘積最高者。
2. 選擇語言品質因子最高的變種。
3. 使用`Accept-Language`頭中的語言順序(如果存在的話),或者使用`LanguagePriority`指令中的語言順序(如果存在的話)選擇最匹配的語言。
4. 選擇最高"等級"媒體參數的變種(用以確定text/html的媒體類型)。
5. 選擇`Accept-Charset`頭中指定的最佳字符集媒體參數的變種。如果沒有明確指定,則使用ISO-8859-1字符集。具有`text/*` 媒體類型而沒有明確地與一個特定字符集關聯的變種,將使用ISO-8859-1。
6. 選擇與之關聯字符集_不是_ISO-8859-1的變種,如果沒有這樣的變種,則選擇所有的變種。
7. 選擇最佳編碼的變種。如果存在用戶代理可以接受的編碼的變種,則選擇之;否則,如果存在混合編碼的或者未編碼的變種,則選擇未編碼的變種。如果所有的變種都是編碼的,或者所有變種都是未編碼的,則選擇所有的變種。
8. 選擇內容長度最小的變種。
9. 選擇剩余變種的最前一個,這個變種或是類型表文件中的第一個,或從目錄中讀取變種被時,以ASCII編碼順序的第一個文件。
3. 這時,此算法已經選擇了一個"最佳"變種,并將之返回作為響應。HTTP響應頭的`Vary`會指明協商的變元(瀏覽器和緩存可以利用此信息緩存該資源)。
4. 如果沒有一個變種被選擇(因為沒有一種可以被瀏覽器接受),則返回一個狀態值為406響應體,并包含一個HTML格式的有效變種列表,同樣,在HTTP頭的`Vary`中指明了變種的變元。
## 打亂品質值
Apache有時會改變按照Apache協商算法應該被嚴格解析的品質值,從而在瀏覽器沒有發送完整的精確的信息時獲得更好的效果。有些很常用的瀏覽器在許多情況下,會發送導致變種選擇錯誤的`Accept`頭信息。如果一個瀏覽器發送了完整的且正確的信息,則不會有打亂操作。
### 媒體類型與通配符
`Accept:` 請求頭指明了媒體類型的首選項,也可以包含"通配"媒體類型,如"image/*"和匹配任何字符串的"*/*"。所以,如果一個請求包含:
```
Accept: image/*, */*
```
會指明可以接受任何以"image/"開頭的類型,和其他任何類型(因而前面的"image/*"就是多余的)。有些瀏覽器就會這樣例行公事地在明確指定允許的類型后面附加通配類型,比如:
```
Accept: text/html, text/plain, image/gif, image/jpeg, */*
```
其目的是表明,明確列出的是首選項,其他不同的表現也可以。這種用法不是不可以,但是"*/*"其實可以通配所有其他類型,所以不推薦這樣用,而應該對"*.*"賦予一個較低的品質(首選)值0.01,如:
```
Accept: text/html, text/plain, image/gif, image/jpeg, */*; q=0.01
```
明確指定的類型沒有品質值,所以其品質值是默認的最高值1.0,而"*/*"是較低的0.01,所以,只有在沒有匹配明確指定類型的變種時,才會返回其他類型。
如果`Accept:` 頭_沒有_指定任何q因子,那么Apache設置"*/*"的q值為0.01來模擬上述推薦的行為,還會設置"type/*"的q值為0.02,使之優先于"*/*"。如果`Accept:` 頭中任何媒體類型指定了q因子,則_不會_使用這些特殊值,以使正確發送信息的瀏覽器能正常運作。
### 語言協商的例外
在Apache 2.0中的協商算法中,新增了一些例外的規則,以允許在語言協商匹配失敗的情況下,作巧妙的妥協。
通常,當客戶端向服務器請求一個不能與瀏覽器`Accept-language`所匹配的唯一的頁面時,服務器會返回一個"No Acceptable Variant" 或者 "Multiple Choices" 響應。但是,有可能通過配置Apache,忽略這些情況下的`Accept-language` ,而返回一個不是非常匹配客戶請求的文本,以避免這些錯誤信息的出現。`ForceLanguagePriority`指令可以屏蔽這兩種錯誤信息,并接管由`LanguagePriority`指令控制的服務器裁定機制。
服務器還會在匹配失敗時嘗試用語言子集來匹配。例如,如果一個客戶請求了一個語言是`en-GB`的英國英語的頁面,而服務器只支持HTTP/1.1標準的簡單的`en` 。(注意,在`Accept-Language`中指定`en-GB`而不是`en`幾乎絕對是個錯誤,因為它似乎暗示閱讀的人懂英國英語卻不懂大眾英語。而不幸的是,許多流行的客戶端的默認配置卻是這樣的)。如果沒有可以匹配的語言,服務器將會忽略其語言子集的設定,返回"No Acceptable Variants"錯誤,或者按`LanguagePriority`指令作妥協。Apache會隱含地在客戶可接受語言的列表中附加一個具有很低品質值的父語言,但是,如果客戶請求"en-GB; q=0.9, fr; q=0.8" 那么將返回"fr"的文本,這對遵循HTTP/1.1標準以使正確配置的瀏覽器能正常工作是必須的。
為了支持用于確定用戶首選語言的高級技術(比如cookies或特殊的URL路徑),從2.0.47版本起`mod_negotiation`模塊開始支持`prefer-language`環境變量`模塊將會嘗試選擇一個匹配的變種。如果不存在這樣的變種,將會使用上述通常的協商過程。
### 示例
```
SetEnvIf Cookie "language=(.+)" prefer-language=$1
```
## 透明內容協商的擴展
Apache在變種列表中使用了一個新的`{encoding ..}`元素來標記變種,從而擴展了透明內容協商協議(RFC2295)。實現RVSA/1.0算法(RFC2296)的目的是識別列表中被編碼的變種,作為可以被`Accept-Encoding`請求頭接受的候選變種。在選擇最佳變種之前,RVSA/1.0的實現不會對品質因子作四舍五入的運算。
## 超鏈和名稱轉換說明
如果使用語言協商,由于文件可以有不止一個后綴,因此就可以選擇不同的名稱轉換,其后綴順序通常是無關緊要的(參見[mod_mime](#calibre_link-347)文檔)。
一個典型的有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關聯的表現形式(representation),那么下一次該URL被請求時,緩存就可以使用它。但是,如果這個資源在服務器端是可協商的,則可能只有第一次請求的變種是正確的,而其后由于緩存中命中而取出的結果是錯誤的。為避免這種情況的發生,Apache通常把內容協商之后返回的響應標記為不可以被HTTP/1.1客戶端緩沖。另外Apache還支持HTTP/1.1協議的功能以允許緩沖已協商的請求。
對來自HTTP/1.0客戶端的請求(瀏覽器或緩存),`CacheNegotiatedDocs`指令可以允許緩存服從協商的請求。此指令應該出現在主服務器或虛擬主機的配置中,沒有參數,并且對來自HTTP/1.1客戶端的請求沒有影響。
對于遵守HTTP/1.1規范的客戶端,Apache發送一個`Vary`應答頭以指定該應答的協商變元。緩存可以使用這個信息來判斷一個其后的請求是否可以從本地副本中提供服務。為了鼓勵緩存使用本地副本而不是協商變元,請設置`force-no-vary`[環境變量](#calibre_link-556)。
## 更多信息
更多有關內容協商的信息,可以參見Alan J. Flavell的[Language Negotiation Notes](http://ppewww.ph.gla.ac.uk/~flavell/www/lang-neg.html),但是注意,此文檔可能沒有升級以包含Apache2.0中的改變。
- Apache HTTP Server Version 2.2 文檔 [最后更新:2006年3月21日]
- 版本說明
- 從1.3升級到2.0
- 從2.0升級到2.2
- Apache 2.2 新特性概述
- Apache 2.0 新特性概述
- The Apache License, Version 2.0
- 參考手冊
- 編譯與安裝
- 啟動Apache
- 停止和重啟
- 配置文件
- 配置段(容器)
- 緩沖指南
- 服務器全局配置
- 日志文件
- 從URL到文件系統的映射
- 安全方面的提示
- 動態共享對象(DSO)支持
- 內容協商
- 自定義錯誤響應
- 地址和端口的綁定(Binding)
- 多路處理模塊
- Apache的環境變量
- Apache處理器的使用
- 過濾器(Filter)
- suEXEC支持
- 性能方面的提示
- URL重寫指南
- Apache虛擬主機文檔
- 基于主機名的虛擬主機
- 基于IP地址的虛擬主機
- 大批量虛擬主機的動態配置
- 虛擬主機示例
- 深入研究虛擬主機的匹配
- 文件描述符限制
- 關于DNS和Apache
- 常見問題
- 經常問到的問題
- Apache的SSL/TLS加密
- SSL/TLS高強度加密:緒論
- SSL/TLS高強度加密:兼容性
- SSL/TLS高強度加密:如何...?
- SSL/TLS Strong Encryption: FAQ
- 如何.../指南
- 認證、授權、訪問控制
- CGI動態頁面
- 服務器端包含入門
- .htaccess文件
- 用戶網站目錄
- 針對特定平臺的說明
- 在Microsoft Windows中使用Apache
- 在Microsoft Windows上編譯Apache
- Using Apache With Novell NetWare
- Running a High-Performance Web Server on HPUX
- The Apache EBCDIC Port
- 服務器和支持程序
- httpd - Apache超文本傳輸協議服務器
- ab - Apache HTTP服務器性能測試工具
- apachectl - Apache HTTP服務器控制接口
- apxs - Apache 擴展工具
- configure - 配置源代碼樹
- dbmmanage - 管理DBM格式的用戶認證文件
- htcacheclean - 清理磁盤緩沖區
- htdbm - 操作DBM密碼數據庫
- htdigest - 管理用于摘要認證的用戶文件
- httxt2dbm - 生成RewriteMap指令使用的dbm文件
- htpasswd - 管理用于基本認證的用戶文件
- logresolve - 解析Apache日志中的IP地址為主機名
- rotatelogs - 滾動Apache日志的管道日志程序
- suexec - 在執行外部程序之前切換用戶
- 其他程序
- 雜項文檔
- 與Apache相關的標準
- Apache模塊
- 描述模塊的術語
- 描述指令的術語
- Apache核心(Core)特性
- Apache MPM 公共指令
- Apache MPM beos
- Apache MPM event
- Apache MPM netware
- Apache MPM os2
- Apache MPM prefork
- Apache MPM winnt
- Apache MPM worker
- Apache模塊 mod_actions
- Apache模塊 mod_alias
- Apache模塊 mod_asis
- Apache模塊 mod_auth_basic
- Apache模塊 mod_auth_digest
- Apache模塊 mod_authn_alias
- Apache模塊 mod_authn_anon
- Apache模塊 mod_authn_dbd
- Apache模塊 mod_authn_dbm
- Apache模塊 mod_authn_default
- Apache模塊 mod_authn_file
- Apache模塊 mod_authnz_ldap
- Apache模塊 mod_authz_dbm
- Apache模塊 mod_authz_default
- Apache模塊 mod_authz_groupfile
- Apache模塊 mod_authz_host
- Apache模塊 mod_authz_owner
- Apache模塊 mod_authz_user
- Apache模塊 mod_autoindex
- Apache模塊 mod_cache
- Apache模塊 mod_cern_meta
- Apache模塊 mod_cgi
- Apache模塊 mod_cgid
- Apache模塊 mod_charset_lite
- Apache模塊 mod_dav
- Apache模塊 mod_dav_fs
- Apache模塊 mod_dav_lock
- Apache模塊 mod_dbd
- Apache模塊 mod_deflate
- Apache模塊 mod_dir
- Apache模塊 mod_disk_cache
- Apache模塊 mod_dumpio
- Apache模塊 mod_echo
- Apache模塊 mod_env
- Apache模塊 mod_example
- Apache模塊 mod_expires
- Apache模塊 mod_ext_filter
- Apache模塊 mod_file_cache
- Apache模塊 mod_filter
- Apache模塊 mod_headers
- Apache模塊 mod_ident
- Apache模塊 mod_imagemap
- Apache模塊 mod_include
- Apache模塊 mod_info
- Apache模塊 mod_isapi
- Apache模塊 mod_ldap
- Apache模塊 mod_log_config
- Apache模塊 mod_log_forensic
- Apache模塊 mod_logio
- Apache模塊 mod_mem_cache
- Apache模塊 mod_mime
- Apache模塊 mod_mime_magic
- Apache模塊 mod_negotiation
- Apache模塊 mod_nw_ssl
- Apache模塊 mod_proxy
- Apache模塊 mod_proxy_ajp
- Apache模塊 mod_proxy_balancer
- Apache模塊 mod_proxy_connect
- Apache模塊 mod_proxy_ftp
- Apache模塊 mod_proxy_http
- Apache模塊 mod_rewrite
- Apache模塊 mod_setenvif
- Apache模塊 mod_so
- Apache模塊 mod_speling
- Apache模塊 mod_ssl
- Apache模塊 mod_status
- Apache模塊 mod_suexec
- Apache模塊 mod_unique_id
- Apache模塊 mod_userdir
- Apache模塊 mod_usertrack
- Apache模塊 mod_version
- Apache模塊 mod_vhost_alias
- Developer Documentation for Apache 2.0
- Apache 1.3 API notes
- Debugging Memory Allocation in APR
- Documenting Apache 2.0
- Apache 2.0 Hook Functions
- Converting Modules from Apache 1.3 to Apache 2.0
- Request Processing in Apache 2.0
- How filters work in Apache 2.0
- Apache 2.0 Thread Safety Issues
- 詞匯和索引
- 詞匯表
- 指令索引
- 指令速查
- 模塊索引
- 站點導航