Apache HTTP Server 版本2.2
本文檔描述如何使用Apache有效的架設(shè)大批量虛擬主機(jī)。
如果你的配置文件httpd.conf
中包含類似下面的許多<VirtualHost>
段,并且其中的內(nèi)容都大致相同的話,你應(yīng)該會(huì)對(duì)這里所講的技術(shù)感興趣。比如:
NameVirtualHost 111.22.33.44
<VirtualHost 111.22.33.44>
ServerName www.customer-1.com
DocumentRoot /www/hosts/www.customer-1.com/docs
ScriptAlias /cgi-bin/ /www/hosts/www.customer-1.com/cgi-bin
</VirtualHost>
<VirtualHost 111.22.33.44>
ServerName www.customer-2.com
DocumentRoot /www/hosts/www.customer-2.com/docs
ScriptAlias /cgi-bin/ /www/hosts/www.customer-2.com/cgi-bin
</VirtualHost>
# 等 等 等 。。。
<VirtualHost 111.22.33.44>
ServerName www.customer-N.com
DocumentRoot /www/hosts/www.customer-N.com/docs
ScriptAlias /cgi-bin/ /www/hosts/www.customer-N.com/cgi-bin
</VirtualHost>
最基本的思想是用動(dòng)態(tài)的機(jī)制來(lái)實(shí)現(xiàn)所有這些靜態(tài)的<VirtualHost>
配置段。這樣做有許多優(yōu)點(diǎn):
主要的缺點(diǎn)是你無(wú)法針對(duì)每個(gè)虛擬主機(jī)使用不同的日志文件。然而,如果真的在配置有大量虛擬主機(jī)的服務(wù)器上記錄不同的日志文件的話,很有可能會(huì)達(dá)到操作系統(tǒng)所允許的最大文件描述符的數(shù)量。更好的辦法是把日志寫(xiě)到管道或者先入先出的棧,并啟用其他的進(jìn)程來(lái)分揀所得到的日志信息(同時(shí)也可以做一些歷史紀(jì)錄的統(tǒng)計(jì)等等)。
一個(gè)虛擬主機(jī)由兩部分來(lái)定義:一個(gè)是它的IP地址,還有一個(gè)是HTTP的"Host:
"請(qǐng)求頭。動(dòng)態(tài)大量虛擬主機(jī)的技術(shù),是基于自動(dòng)在所要返回的文件路徑中插入相關(guān)信息的想法實(shí)現(xiàn)的。使用mod_vhost_alias
可以很容易的實(shí)現(xiàn),但如果你的Apache版本低于1.3.6 ,則你必須使用mod_rewrite
。兩者在默認(rèn)情況下都不啟用;要使用他們,必須在配置和編譯Apache的階段啟用。
我們需要做很多"偽裝",才能使動(dòng)態(tài)虛擬主機(jī)看起來(lái)像普通主機(jī)。最重要的一點(diǎn)是Apache使用虛擬主機(jī)名(ServerName)來(lái)生成自引用(self-referential)URL等信息。這是用ServerName
指令來(lái)配置的,并且可以通過(guò)環(huán)境變量SERVER_NAME
傳遞給CGI腳本。運(yùn)行時(shí)實(shí)際使用的值是由UseCanonicalName
指令的設(shè)置來(lái)控制的。當(dāng) UseCanonicalName Off
時(shí),虛擬主機(jī)名(ServerName)取自請(qǐng)求中的"Host:
"頭。當(dāng) UseCanonicalName DNS
時(shí),則通過(guò)DNS反解析虛擬主機(jī)的IP地址得到主機(jī)名。以前的做法是基于名稱的動(dòng)態(tài)虛擬主機(jī),現(xiàn)在常用基于IP地址的虛擬主機(jī)。如果Apache無(wú)法判斷虛擬主機(jī)名,則可能是沒(méi)有"Host:
"頭或是DNS解析失敗,這樣種情況下,Apache將使用配置ServerName
時(shí)所填寫(xiě)的主機(jī)名。
另一件需要"偽裝"的事情是文檔根目錄(由DocumentRoot
配置并可以通過(guò)DOCUMENT_ROOT
環(huán)境變量為CGI腳本所使用)。在通常的配置方式下,這些設(shè)置信息由核心(core)模塊在將URI映射到文件系統(tǒng)的時(shí)候使用,但是如果使用動(dòng)態(tài)虛擬主機(jī)配置,這些信息將由另外一個(gè)使用不同于核心(core)模塊將URI映射到文件系統(tǒng)的方式的模塊(mod_vhost_alias
或mod_rewrite
)使用。這兩個(gè)模塊都不負(fù)責(zé)設(shè)置DOCUMENT_ROOT
環(huán)境變量,所以如果CGI或SSI程序使用了DOCUMENT_ROOT
環(huán)境變量,那么將得到錯(cuò)誤的值。
這是httpd.conf
文件中,完成和上文動(dòng)機(jī)部分所提到的虛擬主機(jī)一樣效果的配置方法,但這里采用了mod_vhost_alias
模塊:
# 從"Host:"頭中取得主機(jī)名
UseCanonicalName Off
# 這種日志格式可以從第一個(gè)字段中提取出主機(jī)名
LogFormat "%V %h %l %u %t \"%r\" %s %b" vcommon
CustomLog logs/access_log vcommon
# 在返回請(qǐng)求的文件名路徑中包含主機(jī)名
VirtualDocumentRoot /www/hosts/%0/docs
VirtualScriptAlias /www/hosts/%0/cgi-bin
將 UseCanonicalName Off
的配置改為 UseCanonicalName DNS
即可實(shí)現(xiàn)基于IP地址的虛擬主機(jī)。而在文件路徑中所要插入的服務(wù)器名則通過(guò)虛擬主機(jī)的IP地址解析得到。
這里對(duì)上面的系統(tǒng)作了一點(diǎn)調(diào)整,便可作為ISP的個(gè)人主頁(yè)服務(wù)器。我們使用了略微復(fù)雜的方法,從主機(jī)名(ServerName)中提取子字符串,并插入到文件路徑中。在這個(gè)例子中www.user.isp.com
的文檔將在/home/user/
中定位。并對(duì)所有虛擬主機(jī)使用單個(gè)cgi-bin
目錄。
# 所有之前的準(zhǔn)備事項(xiàng)和上面一樣,然后在文件路徑中包含主機(jī)名
VirtualDocumentRoot /www/hosts/%2/docs
# 單個(gè)cgi-bin目錄
ScriptAlias /cgi-bin/ /www/std-cgi/
更復(fù)雜的關(guān)于VirtualDocumentRoot
的設(shè)置,可以查閱mod_vhost_alias
文檔。
更復(fù)雜的設(shè)置,應(yīng)該使用Apache的<VirtualHost>
容器來(lái)管理各種虛擬主機(jī)配置的作用域。例如,你可以用一個(gè)IP地址來(lái)給個(gè)人主頁(yè)客戶使用,同時(shí)用下面的配置提供給商業(yè)客戶使用。自然的,這兩者通過(guò)運(yùn)用<VirtualHost>
結(jié)合到一起。
UseCanonicalName Off
LogFormat "%V %h %l %u %t \"%r\" %s %b" vcommon
<Directory /www/commercial>
Options FollowSymLinks
AllowOverride All
</Directory>
<Directory /www/homepages>
Options FollowSymLinks
AllowOverride None
</Directory>
<VirtualHost 111.22.33.44>
ServerName www.commercial.isp.com
CustomLog logs/access_log.commercial vcommon
VirtualDocumentRoot /www/commercial/%0/docs
VirtualScriptAlias /www/commercial/%0/cgi-bin
</VirtualHost>
<VirtualHost 111.22.33.45>
ServerName www.homepages.isp.com
CustomLog logs/access_log.homepages vcommon
VirtualDocumentRoot /www/homepages/%0/docs
ScriptAlias /cgi-bin/ /www/std-cgi/
</VirtualHost>
在第一個(gè)例子中說(shuō)過(guò),轉(zhuǎn)為基于IP地址的虛擬主機(jī)設(shè)置很容易做到。但不幸的是,那種做法并不高效,因?yàn)檫@樣會(huì)在每次處理請(qǐng)求時(shí),需要查詢DNS。通過(guò)在文件系統(tǒng)中包含IP地址的做法可以避免這樣的問(wèn)題。這樣一來(lái),免去了和主機(jī)名的關(guān)聯(lián),在日志記錄中也一樣可以用IP來(lái)分離不同日志。Apache將不會(huì)為了確定主機(jī)名(ServerName)而去做DNS查詢。
# 從IP地址反解析得到主機(jī)名
UseCanonicalName DNS
# 在日志中包含IP地址,便于以后分揀
LogFormat "%A %h %l %u %t \"%r\" %s %b" vcommon
CustomLog logs/access_log vcommon
# 在文件路徑中包含IP地址
VirtualDocumentRootIP /www/hosts/%0/docs
VirtualScriptAliasIP /www/hosts/%0/cgi-bin
上面的例子基于mod_vhost_alias
,但它是在版本1.3.6之后才出現(xiàn)的。如果你的版本比較老,可以通過(guò)使用mod_rewrite
來(lái)達(dá)到相同的目的,如下所示。但只能是基于"Host:"頭方式的虛擬主機(jī)。
此外還須注意日志方面的問(wèn)題。Apache1.3.6是第一個(gè)支持"%V
"日志格式指令的版本,在版本1.3.0-1.3.3中,"%v
"選項(xiàng)做和"%V
"一樣的事情;而在版本1.3.4中沒(méi)有等價(jià)指令。在所有的這些版本中,指令UseCanonicalName
可以出現(xiàn)在.htaccess
文件中,這意味著客戶的設(shè)置可能會(huì)導(dǎo)致日志記錄紊亂。所以最好的做法是使用"%{Host}i
"指令,它可以直接記錄"Host:
"頭;注意,這樣可能在末尾包含":port
",而使用"%V
"則不會(huì)這樣。
mod_rewrite
實(shí)現(xiàn)簡(jiǎn)單的動(dòng)態(tài)虛擬主機(jī)這里的例子摘自httpd.conf
,效果等同于第一個(gè)例子中的情況。前半部分和上面的例子大致相似,只是為了向后兼容mod_rewrite
作了適當(dāng)修改;后半部分配置mod_rewrite
來(lái)做實(shí)際的工作。
有些特別的地方需要注意:默認(rèn)情況下,mod_rewrite
在所有其他URI轉(zhuǎn)換模塊(mod_alias
等)之前運(yùn)行,所以如果使用這些模塊的話,mod_rewrite
必須作相應(yīng)的調(diào)整。同時(shí),我們還要為每個(gè)動(dòng)態(tài)虛擬主機(jī)變些戲法,使之等效于ScriptAlias
# 從"Host:"頭獲取主機(jī)名
UseCanonicalName Off
# 可分揀的日志
LogFormat "%{Host}i %h %l %u %t \"%r\" %s %b" vcommon
CustomLog logs/access_log vcommon
<Directory /www/hosts>
# 這里需要ExecCGI ,因?yàn)槲覀儾荒軓?qiáng)制CGI以與ScriptAlias相同的方式執(zhí)行
Options FollowSymLinks ExecCGI
</Directory>
# 接下來(lái)是關(guān)鍵部分
RewriteEngine On
# 來(lái)自"Host:"頭的ServerName ,可能大小寫(xiě)混雜
RewriteMap lowercase int:tolower
## 首先處理普通文檔
# 允許變名/icons/起作用,其他變名類同
RewriteCond %{REQUEST_URI} !^/icons/
# 允許CGI
RewriteCond %{REQUEST_URI} !^/cgi-bin/
# 開(kāi)始"變戲法"
RewriteRule ^/(.*)$ /www/hosts/${lowercase:%{SERVER_NAME}}/docs/$1
## 現(xiàn)在處理CGI(我們需要強(qiáng)制使用一個(gè)MIME類型)
RewriteCond %{REQUEST_URI} ^/cgi-bin/
RewriteRule ^/(.*)$ /www/hosts/${lowercase:%{SERVER_NAME}}/cgi-bin/$1 [T=application/x-httpd-cgi]
# ok 了!
mod_rewrite
的個(gè)人主頁(yè)系統(tǒng)這里的配置完成和第二個(gè)例子相同的工作。
RewriteEngine on
RewriteMap lowercase int:tolower
# 允許CGI工作
RewriteCond %{REQUEST_URI} !^/cgi-bin/
# 檢查hostname正確與否,之后才能使RewriteRule起作用
RewriteCond ${lowercase:%{SERVER_NAME}} ^www\.[a-z-]+\.isp\.com$
# 將虛擬主機(jī)名字連接到URI的開(kāi)頭
# [C]表明本次重寫(xiě)的結(jié)果將在下一個(gè)rewrite規(guī)則中使用
RewriteRule ^(.+) ${lowercase:%{SERVER_NAME}}$1 [C]
# 現(xiàn)在創(chuàng)建實(shí)際的文件名
RewriteRule ^www\.([a-z-]+)\.isp\.com/(.*) /home/$1/$2
# 定義全局CGI目錄
ScriptAlias /cgi-bin/ /www/std-cgi/
這樣的布局利用了mod_rewrite
的高級(jí)特性,在獨(dú)立的虛擬主機(jī)配置文件中轉(zhuǎn)換。如此可以更為靈活,但需要較為復(fù)雜的設(shè)置。
vhost.map
文件包含了類似下面的內(nèi)容:
www.customer-1.com /www/customers/1
www.customer-2.com /www/customers/2
# ...
www.customer-N.com /www/customers/N
http.conf
包含了:
RewriteEngine on
RewriteMap lowercase int:tolower
# 定義映射文件
RewriteMap vhost txt:/www/conf/vhost.map
# 和上面的例子一樣,處理別名
RewriteCond %{REQUEST_URI} !^/icons/
RewriteCond %{REQUEST_URI} !^/cgi-bin/
RewriteCond ${lowercase:%{SERVER_NAME}} ^(.+)$
# 這里做基于文件的重新映射
RewriteCond ${vhost:%1} ^(/.*)$
RewriteRule ^/(.*)$ %1/docs/$1
RewriteCond %{REQUEST_URI} ^/cgi-bin/
RewriteCond ${lowercase:%{SERVER_NAME}} ^(.+)$
RewriteCond ${vhost:%1} ^(/.*)$
RewriteRule ^/(.*)$ %1/cgi-bin/$1