# 深入研究虛擬主機的匹配
虛擬主機部分的代碼在**Apache 1.3**中進行了完全的重寫。本文檔試圖詳細解釋Apache在接受到請求后如何確定使用哪一個虛擬主機進行伺服。在新的`NameVirtualHost`指令的幫助下,虛擬主機的配置比1.3版以前更加簡單和安全。
如果您只是想<cite class="calibre27">讓它能夠工作</cite>而不愿意進行深入理解,這里有[一些示例](#calibre_link-38)。
## 解析配置文件
在`<VirtualHost>`配置段外有一個_主服務器(main_server)_段中包含著所有定義。其中有`<VirtualHost>`配置段中定義的叫做_虛擬主機(vhost)_的虛擬服務器。
`Listen`, `ServerName`, `ServerPath`, `ServerAlias`指令可以出現在一個服務器定義段的任何地方。而且每個指令都會覆蓋前面出現的同樣定義(在那個服務器配置中)。
主服務器段中`Listen`指令的默認值是80。主服務器段沒有默認的`ServerPath`和`ServerAlias`指令值。`ServerName`的默認值是由服務器的IP地址推斷而來。
主服務器的`Listen`指令有兩個功能:其一是決定Apache將要綁定的網絡端口;其二是在重定向中指定絕對URI將使用的端口號。
不象在主服務器里,虛擬服務器的端口_不會_影響到Apache的監聽端口。
每個`VirtualHost`指令中的地址都可以附帶一個可選的端口。如果沒有進行特別的指定,這個端口默認為主服務器中最近的一個`Listen`指令指定的值。特殊的端口"`*`"表示匹配所有端口。所有這一系列地址(包括由DNS查詢出的所有`A`記錄)統稱虛擬主機的_地址集(address set)_。
如果沒有對一個特定的IP地址使用`NameVirtualHost`指令,那么第一個使用這個地址的虛擬主機將被視為基于IP的虛擬主機。IP地址也可以用通配符"`*`"表示。
如果使用了基于域名的虛擬主機,那么_必須_用`NameVirtualHost`指令為這個基于域名的虛擬主機指定IP地址集。換句話說,您必須在配置文件中通過`NameVirtualHost`指令指定包括主機名映射(CNAME)的IP地址。
可以使用很多`NameVirtualHost`指令來分別對應一套`NameVirtualHost`指令,但對于每個特定的"IP:port"對來說,只能使用一次`NameVirtualHost`指令。
`NameVirtualHost`和`VirtualHost`指令出現的順序并不重要。只有對應_同一個_IP地址的`VirtualHost`指令的次序才是重要的。所以下面兩例所起的作用是完全相同的:
```
NameVirtualHost 111.22.33.44
<VirtualHost 111.22.33.44>
# server A
...
</VirtualHost>
<VirtualHost 111.22.33.44>
# server B
...
</VirtualHost>
NameVirtualHost 111.22.33.55
<VirtualHost 111.22.33.55>
# server C
...
</VirtualHost>
<VirtualHost 111.22.33.55>
# server D
...
</VirtualHost>
```
```
<VirtualHost 111.22.33.44>
# server A
</VirtualHost>
<VirtualHost 111.22.33.55>
# server C
...
</VirtualHost>
<VirtualHost 111.22.33.44>
# server B
...
</VirtualHost>
<VirtualHost 111.22.33.55>
# server D
...
</VirtualHost>
NameVirtualHost 111.22.33.44
NameVirtualHost 111.22.33.55
```
(為了使您的配置文件更具可讀性,我們推薦您使用左邊的格式)
在解析完`VirtualHost`指令后,虛擬主機服務器將被賦予在它的`VirtualHost`指令中第一個名字對應的端口作為默認的`Listen`端口。
如果所有域名都指向同一個地址集的話,`VirtualHost`指令中的所有域名列表都將會得到和`ServerAlias`指令一樣的處理(但不會被其他`ServerAlias`語句覆蓋)。請注意,這個虛擬主機自帶的`Listen`指令將不能影響到那個地址集的端口號。
在初始化的過程中,將會為每一個IP地址產生一個列表,并插入到一個散列表中。如果這個IP地址是用在一個`NameVirtualHost`指令中的,這個列表將會包含所有指定為這個IP地址的基于域名的虛擬主機。如果沒有虛擬主機針對這個IP地址,那么`NameVirtualHost`指令將被忽略,并會在日志中記錄一個錯誤信息。對于基于IP的虛擬主機而言,這個散列表中的列表為空。
因為使用了高效的散列算法,使得在請求到達的時候在其中查找IP地址的開銷變得很小,或者根本不需考慮。而且這個表格還為只有最后一個八進制位不同的IP地址做了優化。
虛擬主機的每個變量都有初始值。特別是以下這些:
1. 如果虛擬主機沒有`ServerAdmin`, `ResourceConfig`, `AccessConfig`, `Timeout`, `KeepAliveTimeout`, `KeepAlive`, `MaxKeepAliveRequests`, `ReceiveBufferSize`, `SendBufferSize`指令,那么將從主服務器繼承它們的值。(也就是說,使用在主服務器中最后出現的設定值)。
2. 虛擬主機的默認目錄權限將繼承主服務器的設置(包括所有模塊針對每個目錄的配置信息)。
3. 虛擬主機將繼承主服務器中每個模塊針對主服務器的設置。
本質上,主服務器在建立每個虛擬主機的時候,充當了一個默認值或根基的角色。但這些存在于主服務器中的定義的位置是無關緊要的——主服務器的配置在與虛擬主機整合之前就已經解析過了。所以即使一個主服務器的配置出現在虛擬主機定義的后面,它也同樣會影響到虛擬主機的配置。
如果沒有定義主服務器中的`ServerName` ,那么將由運行這個`httpd`服務的機器的主機名來代替。我們將由DNS查找此`ServerName`返回的IP地址稱為_主服務器地址集(main_server address set)_。
在沒有定義`ServerName`的情況下,一個基于域名的虛擬主機默認采用定義虛擬主機時在`VirtualHost`指令中最先出現的地址。
所有使用了"`_default_`"通配符的虛擬主機將被賦予和主服務器相同的`ServerName` 。
## 虛擬主機匹配
服務器用下述方法來確定對一個特定的請求使用哪個虛擬主機:
### 散列表查找
當客戶端第一次連接的時候,會從內部的IP散列表中查找客戶端想要連接的IP地址。
如果查找失敗(沒有找到相應的IP地址),而所請求的端口又存在一個"`_default_`"虛擬主機,那么這個請求將會由這個虛擬主機來伺服。如果沒有找到這樣的"`_default_`"虛擬主機,那么這個請求將會由主服務器來伺服。
如果在散列表中沒有找到IP地址,但存在一個"`NameVirtualHost *`"指令與所請求的端口號相匹配,那么將用這個虛擬主機來處理這個請求。
如果查找成功(找到了對應于這個IP地址的列表),下一步就是看我們要處理的是一個基于IP的虛擬主機還是一個基于域名的虛擬主機。
### 基于IP的虛擬主機
如果返回的列表中域名列表為空,那么我們處理的就是一個基于IP的虛擬主機,這個虛擬主機將會直接進行處理而不會有其他步驟。
### 基于域名的虛擬主機
如果返回的域名列表包含一個或多個虛擬主機的結構,那么我們處理的就是一個基于域名的虛擬主機。這個列表包含的虛擬主機的順序與配置文件中相應`VirtualHost`指令出現的順序是相同的。
這個列表中第一個虛擬主機(也就是在配置文件中第一個指定了這個IP地址的虛擬主機)對處理請求有著最高的優先級。所有對未知服務器名或沒有"`Host:`"頭的請求都將由它進行處理。
如果客戶端在請求中提供了一個"`Host:`"頭,那么將在列表中查找第一個`ServerName`或`ServerAlias`與其符合的虛擬主機,并將其用于伺服這個請求。盡管"`Host:`"頭中可以包含端口號,但Apache還是會用收到請求的那個真實端口來進行匹配。
如果客戶端提交了一個不包含"`Host:`"頭的HTTP/1.0的請求,我們將無法確認客戶端想要連接那個服務器。而如果存在一個`ServerPath`與客戶端提交的請求中的URI相對應,那么列表中第一個符合條件的虛擬主機將用于伺服這個請求。
如果還是找不到對應的虛擬主機,那么這個請求將會由客戶端連接的IP對應的列表中的第一個與請求的端口相同的虛擬主機來伺服(如前所述)。
### 持久連接
上述IP查找對一個特定的TCP/IP進程只執行_一次_。但在持久連接(KeepAlive)中,_每個_請求都會進行一次這樣的查找過程。換句話說,一個客戶端在一個持久連接中可以向位于不同的基于域名的虛擬主機的頁面提出請求。
### 絕對URI
如果請求提交的URI是一個絕對URI,而其中的主機名和端口號又和主服務器或某個虛擬主機相符合,_并且_也與作為此請求提交對象的地址和端口相符,那么這個請求的類型/主機名/端口前綴將被抹除,僅留下相對URI為對應的主服務器或虛擬主機所伺服。如果不滿足上述符合條件,這個URI將保留原樣,而此請求將被作為一個代理請求處理。
### 備忘錄
* 基于域名的虛擬主機和基于IP的虛擬主機之間互相不干擾。基于IP的虛擬主機只接受發送到它自身地址集的請求,而不接受其他IP地址。基于域名的虛擬主機也是一樣,它們只接受`NameVirtualHost`指令定義的地址集的訪問。
* 永遠不會對一個基于IP的虛擬主機執行`ServerAlias`和`ServerPath`檢查。
* 在配置文件中,基于域名的虛擬主機、基于IP的虛擬主機、"`_default_`"虛擬主機和`NameVirtualHost`指令出現的順序并不重要。而對于某個指定的地址集來說,基于域名的虛擬主機的順序是不能混淆的:在配置文件中較先出現的虛擬主機在相應的地址集中有較高的優先權。
* 出于安全性的考慮,在"`Host:`"頭中出現的端口號將不用于匹配。Apache會一直使用客戶端所連接的真實端口作為匹配。
* 如果一個`ServerPath`指令湊巧是后面出現的另外一個`ServerPath`指令的前綴,前者將用于匹配,而后者將被忽略。(這里討論的是沒有"`Host:`"頭來將這兩個情況分開的情況下)
* 如果有兩個基于IP的虛擬主機使用了同一個地址,則在配置文件中首先出現的那個用于匹配。這種事情可能發生在你疏忽的時候。當服務器遇到這種情況的時候,會在日志文件中寫入一個錯誤信息。
* 僅當沒有其他虛擬主機符合客戶端請求的IP地址和端口號時,"`_default_`"虛擬主機才會捕獲這個請求。_并且_僅當"`_default_`"虛擬主機的端口號(默認值由您的`Listen`指定)與客戶端發送請求的目的端口號相符時,這個請求才會被捕獲。也可以使用通配符(例如:"`_default_:*`")來捕獲任何端口號的請求。這也同樣適用于"`NameVirtualHost *`"的虛擬主機。
* 僅當客戶端連接的目的IP地址和端口號沒有指定而且不與任何一個虛擬主機(包括"`_default_`"虛擬主機)匹配的時候,才會用主服務器來伺服請求。換句話說,主服務器僅捕獲沒有指定IP地址和端口的請求(除非存在一個匹配端口的"`_default_`"虛擬主機)。
* 如果客戶端連接到一個用于基于域名的虛擬主機使用的地址(和端口),比如說使用了`NameVirtualHost`指令,那么一個未知的或沒有"`Host:`"頭的請求就_不會_與"`_default_`"虛擬主機或是主服務器相匹配。
* 絕對不能在`VirtualHost`指令中使用DNS名稱,否則您的服務器就會依賴DNS來進行啟動。而且,如果您無法控制列表中所有的域,您將會面臨安全威脅。您可以在這里獲得關于這個問題和以下兩個問題的[更多詳情](#calibre_link-55)。
* 應當為每個虛擬主機設定`ServerName` 。否則就會需要為每個虛擬主機進行DNS查詢。
## 小技巧
作為[DNS問題](#calibre_link-56)頁面小技巧的附加,這里有些額外的技巧:
* 將所有主服務器的定義放在所有`VirtualHost`定義之前(為了增加可讀性),否則會使得類似在虛擬主機旁邊的定義影響到所有的虛擬主機這樣的問題不容易發現。
* 將您配置中相應的`NameVirtualHost`和`VirtualHost`定義放到一起,以獲得更好的可讀性。
* 避免前一個`ServerPaths`是后一個`ServerPaths`的前綴。如果您無法避免這樣的情況,您最好確保在您的配置文件中"長在前,短在后"(也就是說:"ServerPath/abc/def"應當出現在"ServerPath/abc"之前)。
- 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
- 詞匯和索引
- 詞匯表
- 指令索引
- 指令速查
- 模塊索引
- 站點導航