Apache HTTP服务器 2.0版本
本文档描述如何使用 Apache 1.3 有效的架设大容量的虚拟主机服务器。
如果你的配置文件 httpd.conf
中包含类似下面的许多
<VirtualHost>
部分,并且其中的内容都大致相同的话,
你应该会对这里所要讲的技术比较感兴趣。
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>
# blah blah blah
<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>
最基本的思想是用动态的机制来实现所有这些静态的
<VirtualHost>
配置。这样做有许多优点:
主要的缺点是你无法针对每个虚拟主机用户使用不同的日志文件。 然而,如果真的在配置有大量虚拟主机的服务器上记录不同的日志文件的话, 很有可能会达到操作系统所允许的最大文件描述符(file descriptors)的数量。 更好的办法是把日志写到管道(pipe)或者先入先出(fifo)的堆栈, 并启用其他的进程来分发所得到的日志信息给用户(同时也可以做一些历史纪录的统计等等)。
一个虚拟主机有两部分来定义:一个是它的 IP 地址,
还有一个是 HTTP 请求中 Host:
头的内容。
动态大容量虚拟主机的技术,
是基于自动在所要返回的文件的路径中插入相关信息的想法而实现的。
使用mod_vhost_alias
可以很容易的实现,
但如果你的 Apache 版本低于 1.3.6 ,则你必须使用 mod_rewrite
。
两者在默认情况下都不启用;
要使用他们,必须在配置和编译 Apache 的阶段声明启用(enable)。
我们需要做很多伪装,才能使动态虚拟主机看起来像普通情况。
最重要的一点是 Apache 使用虚拟主机名字(Server Name)来生成自我参考(self-referential)
的 URLs 等等信息。这是用 ServerName
指令来配置的,
并且可以通过环境变量 SERVER_NAME
传递给 CGI 脚本。
在运行时所实际使用的值是由指令 UseCanonicalName
的设置情况来控制的。当 UseCanonicalName Off
时,
虚拟主机名字(server name)取自请求中的 Host:
头的内容。
当 UseCanonicalName DNS
时,则通过 DNS 反解析虚拟主机的IP 地址得到主机名字。
以前的做法是用基于名称的动态虚拟主机,近来常用基于 IP 地址的虚拟主机设置。
如果 Apache 无法决策虚拟主机名字,则可能是没有 Host:
头信息或者 DNS 解析失败,
遇到这样的情况,Apache 使用配置 ServerName
时所填写的主机名字。
这是 httpd.conf
文件中,完成和上文 动机
部分所提到的虚拟主机一样效果的配置方法,但这里采用了 mod_vhost_alias
。
# 从 Host: 头中取得服务器名字 Server Name
UseCanonicalName Off
# 这里的日志格式,可以在将来通过第一个参数域来分隔不同的虚拟主机的日志
LogFormat "%V %h %l %u %t \"%r\" %s %b" vcommon
CustomLog logs/access_log vcommon
# 在返回请求的文件名的路径中包含进服务器名字: server name
VirtualDocumentRoot /www/hosts/%0/docs
VirtualScriptAlias /www/hosts/%0/cgi-bin
将 UseCanonicalName Off
的配置改为 UseCanonicalName DNS
即可实现基于 IP 地址的虚拟主机。而在文件路径中所要插入的服务器名字(server name)
则通过虚拟主机的 IP 地址解析而得。
这里对上面的系统作了一点调整,便可作为 ISP 的个人主页服务器。
我们使用了略微复杂的方法,从服务器名字(Server Name)中提取子字符串,
并插入到文件路径中。在这个例子中,www.user.isp.com 的文档将在
/home/user/
中定位。并对所有虚拟主机使用单个 cgi-bin
目录。
# 所有之前的准备事项和上面一样,然后
# 在文件路径中包含服务器名字(server name)
VirtualDocumentRoot /www/hosts/%2/docs
# 单个 cgi-bin 目录
ScriptAlias /cgi-bin/ /www/std-cgi/
更复杂的关于 VirtualDocumentRoot
的设置,可以查阅 mod_vhost_alias
文档。
更复杂的设置,应该使用 Apache 的 <VirtualHost>
指令来管理各种虚拟主机配置的作用域。例如,你可以用一个 IP 地址来给个人主页客户使用,
同时用下面的配置提供给商业客户使用。自然的,
这两者通过运用 <VirtualHost>
结合到一起。
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>
在 第一个例子 中说过,转为基于 IP 地址的虚拟主机设置很容易做到。 但不幸的是,那种做法并不高效,因为这样会在每次处理请求时,需要查询 DNS 。 通过在文件系统中包含IP 地址的做法可以避免这样的问题。这样一来, 免去了和服务器名字的关联,在日志记录中也一样可以用 IP 来分离不同日志。 Apache 将不会为了确定服务器名字(server name)而去做 DNS 查询。
# 从 IP 地址反解析得到服务器名字(server name)
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 之后才出现的。
如果你的版本比较老,可以通过使用 mod_rewrite
来达到相同的目的,
如下所示。但只能是基于 Host: 头方式的虚拟主机。
此外还须注意日志方面的问题。Apache 1.3.6 是第一个支持 %V
日志格式指令的版本,
在版本 1.3.0 - 1.3.3 中,%v
选项做和 %V
一样的事情;
而在版本 1.3.4 中没有等价指令。在所有的这些版本中,指令 UseCanonicalName
可以出现在 .htaccess
文件中,这意味着客户的设置可能会导致日志记录紊乱。
所以最好的做法是使用 %{Host}i
指令,它可以直接记录 Host:
头;
注意,这样可能在末尾包含 :port
,而使用 %V
则不会这样。
mod_rewrite
实现简单的动态虚拟主机这里的例子摘自 httpd.conf
,效果等同于 第一个例子中的情况。
前半部分和上面的例子大致相似,只是为了后向兼容 mod_rewrite
作了适当修改;
后半部分配置 mod_rewrite
来做实际的工作。
有些特别的地方需要注意:默认情况下,mod_rewrite
在所有其他 URI
转换模块 (mod_alias
等)之前运行,所以如果使用这些模块的话,
mod_rewrite
必须作相应的调整。同时,我们还要为每个动态虚拟主机变些戏法,
使之等效于 ScriptAlias
。
# 从 Host: 头获取服务器名字
UseCanonicalName Off
# 可分割的日志
LogFormat "%{Host}i %h %l %u %t \"%r\" %s %b" vcommon
CustomLog logs/access_log vcommon
<Directory /www/hosts>
# ExecCGI is needed here because we can't force
# CGI execution in the way that ScriptAlias does
Options FollowSymLinks ExecCGI
</Directory>
# 接下来是关键部分
RewriteEngine On
# a ServerName derived from a Host: header may be any case at all
RewriteMap lowercase int:tolower
## 首先处理普通文档:
# 允许变名 /icons/ 起作用 - 其他变名类同
RewriteCond %{REQUEST_URI} !^/icons/
# 允许 CGIs
RewriteCond %{REQUEST_URI} !^/cgi-bin/
# 开始“变戏法”
RewriteRule ^/(.*)$ /www/hosts/${lowercase:%{SERVER_NAME}}/docs/$1
## 现在处理 CGIs - 我们需要强制使用一个 MIME 类型
RewriteCond %{REQUEST_URI} ^/cgi-bin/
RewriteRule ^/(.*)$ /www/hosts/${lowercase:%{SERVER_NAME}}/cgi-bin/$1 [T=application/x-httpd-cgi]
# 好了!
mod_rewrite
的个人主页系统这里的配置完成和第二个例子相同的工作。
RewriteEngine on
RewriteMap lowercase int:tolower
# 允许 CGIs 工作
RewriteCond %{REQUEST_URI} !^/cgi-bin/
# 检查 hostname 正确与否,之后才能使 RewriteRule 起作用
RewriteCond ${lowercase:%{SERVER_NAME}} ^www\.[a-z-]+\.isp\.com$
# 将虚拟主机名字廉洁到 URI 的开头
# [C] 表明本次重写的结果将在下一个 rewrite 规则中使用
RewriteRule ^(.+) ${lowercase:%{SERVER_NAME}}$1 [C]
# 现在创建实际的文件名
RewriteRule ^www\.([a-z-]+)\.isp\.com/(.*) /home/$1/$2
# 定义全局 CGI 目录
ScriptAlias /cgi-bin/ /www/std-cgi/
这样的布局利用了 mod_rewrite
的高级特性,
在独立的虚拟主机配置文件中转换。如此可以更为灵活,但需要较为复杂的设置。
vhost.map
文件包含了类似下面的内容:
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