mod_rewrite模块提供了一个基于规则的(使用正则表达式分析器的)实时转向URL请求的引擎。 支持每个规则可以拥有不限数量的规则以及附加条件规则的灵活而且强大的URL操作机制。 此URL操作可以取决于各种测试,比如服务器变量、环境变量、HTTP头、时间标记, 甚至各种格式的用于匹配URL组成部分的查找数据库。
mod_rewrite模块可以操作URL的所有部分(包括路径信息部分), 在服务器级的(httpd.conf)和目录级的(.htaccess)配置都有效, 还可以生成最终请求串。此重写操作的结果可以是内部子处理,也可以是外部请求的转向, 甚至还可以是内部代理处理。
但是,所有这些功能和灵活性带来一个问题,那就是复杂性, 因此,不要指望一天之内就能看懂整个模块。
内部处理
mod_rewrite模块的内部处理极为复杂,但是,为了使一般用户避免犯低级错误, 也让管理员能充分利用其功能,在此仍然做一下说明。
API程序段
首先,你必须了解,Apache是通过若干程序段来处理HTTP请求的。 Apache API 对每个程序段提供了一个hook程序。 Mod_rewrite使用两个hook程序: 其一是,URL到文件名的转译hook,用在读取HTTP请求之后,而在授权开始之前; 其二是,修正hook,用在授权程序段和读取目录级配置文件(.htaccess)之后, 而在内容处理器激活之前。
所以,Apache收到一个请求并且确定了响应主机(或者是虚拟主机)之后, 重写引擎即开始执行URL到文件名程序段,以处理服务器级的配置中所有的mod_rewrite指令。 在最终数据目录确定以后,进入修正程序段并触发目录级配置中的mod_rewrite指令。 这两个程序段并不是泾渭分明的,但都实施把URL重写成新的URL或者文件名。 虽然API最初不是为此设计的,但它已经成为API的一种用途, 而在Apache 1.x 中这是mod_rewrite唯一的实现方法。 记住以下两点,会有助于更好地理解:
虽然mod_rewrite可以重写URL为URL,重写URL为文件名, 甚至重写文件名为文件名,但是目前API只提供一个URL到文件名的hook。 在Apache 2.0 中,增加了两个丢失hook以使处理过程更清晰。 但是,这样做并没有给用户带来麻烦,只需记住这样一个事实: Apache借助URL到文件名的hook而比API设计的目标功能更强大。
难以置信的是,mod_rewrite提供了目录级的URL操作,即,.htaccess文件, 而这些文件必须在URL转换成文件名以后的较多步骤完成之后才会被处理。 这也是必须的,因为.htaccess文件存在于文件系统中,所以处理已经到达这个层面。 换句话说,根据API程序段,这时再处理任何URL操作已经太晚了。 为了解决这个鸡和蛋的问题,mod_rewrite使用了一个技巧: 在进行一个目录级的URL/文件名的操作时,mod_rewrite先把文件名重写回相应的URL (通常这个操作是不可行的,但是参考下面的RewriteBase指令就明白它是怎么实现的), 然后,对这个新的URL建立一个新的内部的子请求,以此重新开始API程序段的执行。
另外,mod_rewrite尽力使这些复杂的操作对用户全透明,但仍须记住: 服务器级的URL操作速度快而且效率高,而目录级的操作由于这个鸡和蛋的问题速度慢效率也低。 但从另一个侧面看,这却是mod_rewrite得以为一般用户提供(局部限制的)URL操作的唯一方法。
牢记这两点!
规则集的处理
当mod_rewrite在这两个程序段中开始执行时,它会读取配置结构中的配置好的 (或者是在服务启动时建立的服务器级的,或者是Apache核心在遍历目录采集到的目录级的)规则集, 随后,启动URL重写引擎来处理(带有一个或多个条件)的规则集。 无论是服务器级的还是目录级的规则集,都是由同一个URL重写引擎处理,只是处理结果不同而已。
规则集中规则的顺序是很重要的,因为重写引擎是按一种特殊的(非常规的)顺序处理的, 其原则是:逐个遍历每个规则(RewriteRule directives), 如果出现一个匹配条件的规则,则可能回头遍历已有的规则条件(RewriteConddirectives)。 由于历史的原因,条件规则是置前的,所以控制流程略显冗长,细节见Figure 1。
Figure 1:The control flow through the rewriting ruleset
可见,URL首先与每个规则的Pattern匹配, 如果匹配不成功,mod_rewrite立即终止此规则的处理,继而处理下一个规则。 如果匹配成功,mod_rewrite寻找响应的规则条件,如果一个条件都没有, 则简单地用Substitution构造的新的值来替换URL,然后继续处理其他规则。 如果条件存在,则开始一个内部循环按其列出的顺序逐个处理。 对规则的条件的处理有所不同:URL并不与pattern匹配, 而是,首先通过扩展变量、反向引用、查找映射表等步骤建立一个TestString的字符串, 随后,用它来与CondPattern匹配。如果匹配不成功,则整个条件集和对应的规则失败; 如果匹配成功,则执行下一个规则直到所有条件执行完毕。 如果所有条件得以匹配,则以Substitution替换URL,并且继续处理。
特殊字符的引用
在Apache 1.3.20, TestString and Substitution 字符串中的特殊字符可以用前缀的斜杠来实现转义(即,忽略其特殊含义而视之为普通字符)。 比如,Substitution可以用'\$'来包含一个美元符号, 以避免mod_rewrite把它视为反向引用。
正则表达式的反向引用能力
这是很重要的一点:一旦在Pattern或者CondPattern使用了圆括号, 就会建立内部的反向引用,可以使用$N和%N来调用(见下述), 并且,在Substitution和TestString中都有效。 Figure 2 说明了反向引用被转换扩展的位置。
Figure 2: The back-reference flow through a rule.
虽然mod_rewrite内部处理的这个过程是比较杂乱的, 但是了解这些可以帮助你阅读下文中指令的讲述。
环境变量
mod_rewrite模块会跟踪两个额外的(非标准的)CGI/SSI环境变量, SCRIPT_URL和SCRIPT_URI。 他们包含了当前资源的逻辑的网络状态, 而标准的CGI/SSI变量SCRIPT_NAME和 SCRIPT_FILENAME包含的是物理的系统状态。
注意: 这些变量保持的是其最初被请求时的URI/URL, 即, 在任何重写操作之前的。 其重要性在于他们是重写操作重写URL到物理路径名的原始依据。
举例
SCRIPT_NAME=/sw/lib/w3s/tree/global/u/rse/.www/index.html
SCRIPT_FILENAME=/u/rse/.www/index.html
SCRIPT_URL=/u/rse/
SCRIPT_URI=http://en1.engelschall.com/u/rse/
实用方案
我们还提供另外一个文档URL Rewriting Guide, 列举了许多基于URL的问题的实用方案,其中你可以找到真实有用的规则集和mod_rewrite的更多信息。
RewriteBase 指令
RewriteBase指令显式地设置了目录级重写的基准URL。 在下文中,你可以看见RewriteRule可以用于目录级的配置文件中(.htaccess), 并在局部范围内起作用,即,规则实际处理的只是剥离了本地路径前缀的一部分。 处理结束后,这个路径会被自动地附着回去。 默认值是,RewriteBase physical-directory-path
在对一个新的URL进行替换时,mod_rewrite模块必须把这个URL重新注入到服务器处理中。 为此,它必须知道其对应的URL前缀或者说URL基准。通常,此前缀就是对应的文件路径。 但是,大多数网站URL不是直接对应于其物理文件路径的,因而一般不能做这样的假定! 所以在这种情况下,就必须用RewriteBase指令来指定正确的URL前缀。
如果你的网站服务器URL不是与物理文件路径直接对应的, 而又需要使用RewriteRule指令, 则必须在每个对应的.htaccess文件中指定RewriteBase。
举例,目录级配置文件内容如下:
#
# /abc/def/.htaccess -- per-dir config file for directory /abc/def
# Remember: /abc/def is the physical path of /xyz, i.e., the server
# has a 'Alias /xyz /abc/def' directive e.g.
#
RewriteEngine On
# let the server know that we were reached via /xyz and not
# via the physical path prefix /abc/def
RewriteBase /xyz
# now the rewriting rules
RewriteRule ^oldstuff\.html$ newstuff.html
上述例子中,对/xyz/oldstuff.html 的请求被正确地重写为物理的文件/abc/def/newstuff.html.
For Apache Hackers
以下列出了内部处理的详细步骤:
Request:
/xyz/oldstuff.html
Internal Processing:
/xyz/oldstuff.html -> /abc/def/oldstuff.html (per-server Alias)
/abc/def/oldstuff.html -> /abc/def/newstuff.html (per-dir RewriteRule)
/abc/def/newstuff.html -> /xyz/newstuff.html (per-dir RewriteBase)
/xyz/newstuff.html -> /abc/def/newstuff.html (per-server Alias)
Result:
/abc/def/newstuff.html
虽然这个过程看来很繁复,但是由于目录级重写的到来时机已经太晚了, 它不得不把这个(重写)请求重新注入到Apache核心中,所以Apache内部确实是这样处理的。 但是:它的开销并不象看起来的那样大,因为重新注入完全在Apache服务器内部进行, 而且这样的过程在Apache内部也为其他许多操作所使用。 所以,你可以充分信任其设计和实现是正确的。
RewriteCond 指令
RewriteCond指令定义了一个规则的条件,即,在一个RewriteRule指令之前有一个或多个RewriteCond指令。 条件之后的重写规则仅在当前URI与pattern匹配并且符合这些条件的时候才会起作用。
TestString是一个纯文本的字符串,但是还可以包含下列可扩展的成分:
RewriteRule反向引用: 引用方法是
$N
(0 <= N <= 9) 引用当前(带有若干RewriteCond指令的)RewriteRule中的 与pattern匹配的分组成分(圆括号!)。
RewriteCond反向引用: 引用方法是
%N
(1 <= N <= 9) 引用当前若干RewriteCond条件中最后符合的条件中的分组成分(圆括号!)。
RewriteMap 扩展: 引用方法是
${mapname:key|default}
细节请参见the documentation for RewriteMap。
服务器变量: 引用方法是
%{ NAME_OF_VARIABLE }
NAME_OF_VARIABLE可以是下表列出的字符串之一: HTTP headers: connection & request:
HTTP_USER_AGENT
HTTP_REFERER
HTTP_COOKIE
HTTP_FORWARDED
HTTP_HOST
HTTP_PROXY_CONNECTION
HTTP_ACCEPT
REMOTE_ADDR
REMOTE_HOST
REMOTE_USER
REMOTE_IDENT
REQUEST_METHOD
SCRIPT_FILENAME
PATH_INFO
QUERY_STRING
AUTH_TYPE
server internals: system stuff: specials:
DOCUMENT_ROOT
SERVER_ADMIN
SERVER_NAME
SERVER_ADDR
SERVER_PORT
SERVER_PROTOCOL
SERVER_SOFTWARE
TIME_YEAR
TIME_MON
TIME_DAY
TIME_HOUR
TIME_MIN
TIME_SEC
TIME_WDAY
TIME
API_VERSION
THE_REQUEST
REQUEST_URI
REQUEST_FILENAME
IS_SUBREQ
这些都对应于类似命名的HTTP MIME头、Apache服务器的C变量以及Unix系统中的 struct tm字段,大多数都在其他的手册或者CGI规范中有所讲述。 而其中为mod_rewrite所特有的变量有:
IS_SUBREQ
如果正在处理的请求是一个子请求,它包含字符串"true",否则就是"false"。 模块为了解析URI中的附加文件,有可能会产生子请求。
API_VERSION
这是正在使用的httpd中(服务器和模块之间内部接口)的Apache模块API的版本, 其定义位于include/ap_mmn.h中。mod_rewrite模块版本对应于正在使用的Apache的版本 (比如,在Apache 1.3.14的发行版中,这个值是19990320:10)。 通常,对它感兴趣的是模块的作者。
THE_REQUEST
这是由浏览器发送给服务器的完整的HTTP请求行。(比如, "GET /index.html HTTP/1.1"). 它不包含任何浏览器发送的附加头信息。
REQUEST_URI
这是在HTTP请求行中所请求的资源。(比如上述例子中的"/index.html".)
REQUEST_FILENAME
这是与请求相匹配的完整的本地文件系统的文件路径名或描述.
特别注意事项:
SCRIPT_FILENAME和REQUEST_FILENAME包含的值是相同的,即, Apache服务器的内部request_rec结构中的filename字段。 第一个其实就是大家都知道的CGI变量名,而第二个则是( 包含了request_rec结构中的uri字段的)REQUEST_URI的一个副本,
特殊形式: %{ENV:variable} 其中的variable可以是任何环境变量。 它是通过查找Apache内部结构得到的, 或者(如果没找到的话)是由Apache服务器进程通过getenv()得到的。
特殊形式: %{HTTP:header} 其中的header可以是任何HTTP MIME头的名称。 它是通过查找HTTP请求得到的。比如: %{HTTP:Proxy-Connection}就是HTTP头 ``Proxy-Connection:''的值.
特殊形式 %{LA-U:variable} 它是一个预设的值, variable的最终值在执行一个内部的(基于URL的)子请求后决定。 在重写需要使用一个尚未有效的但是会在之后的API程序段中设置的变量的时候,就会使用这个方法。 比如,需要在服务器级配置(httpd.conf文件)中重写REMOTE_USER变量, 则,必须使用%{LA-U:REMOTE_USER},因为此变量是由认证程序段设置的, 而这个程序段是在mod_rewrite所在的URL转译程序段之后才执行的。 但是,因为mod_rewrite是通过API修正程序段来实现目录级(.htaccess file)配置的, 而这个程序段在认证程序段之前就执行了,所以用%{REMOTE_USER}就可以了。
特殊形式: %{LA-F:variable} 它是一个预设的值, variable的最终值在执行一个内部的(基于文件名的)子请求后决定。 大多数情况下和上述的LA-U是相同的.
CondPattern是条件pattern, 即, 一个应用于当前实例TestString的正则表达式, 即, TestString将会被计算然后与CondPattern匹配.
谨记: CondPattern是一个兼容perl的正则表达式, 但是还有若干增补:
可以在pattern串中使用'!' 字符(惊叹号)来实现匹配的反转。
CondPatterns有若干特殊的变种。除了正则表达式的标准用法,还有下列用法:
'<CondPattern' (词典顺序的小于)
将CondPattern视为纯字符串,与TestString以词典顺序相比较. 如果按词典顺序,TestString小于CondPattern,则为真.
'>CondPattern' (词典顺序的大于)
将CondPattern视为纯字符串,与TestString以词典顺序相比较. 如果按词典顺序,TestString大于CondPattern,则为真.
'=CondPattern' (词典顺序的等于)
将CondPattern视为纯字符串,与TestString以词典顺序相比较. 如果按词典顺序,TestString等于CondPattern,则为真,即, 两个字符串(逐个字符地)完全相等。如果CondPattern只是""(两个引号), 则TestString将与空串相比较.
'-d' (是一个目录[directory])
将TestString视为一个路径名并测试它是否存在而且是一个目录.
'-f' (是一个常规的文件[file])
将TestString视为一个路径名并测试它是否存在而且是一个常规的文件.
'-s' (是一个非空的常规文件[size])
将TestString视为一个路径名并测试它是否存在而且是一个尺寸大于0的常规的文件.
'-l' (是一个符号连接[link])
将TestString视为一个路径名并测试它是否存在而且是一个符号连接.
'-F' (对子请求有效的业已存在的文件)
测试TestString是否一个有效的文件, 而且可以被服务器当前已经配置的所有存取控制所存取。 它用一个内部子请求来做判断,由于会降低服务器的性能,请小心使用!
'-U' (对子请求有效的业已存在的URL)
测试TestString是否一个有效的URL, 而且可以被服务器当前已经配置的所有存取控制所存取。 它用一个内部子请求来做判断,由于会降低服务器的性能,请小心使用!
注意
所有这些测试都可以用惊叹号作前缀('!')以实现条件的反转.
另外,还可以为CondPattern追加特殊的标记
[flags]
作为RewriteCond指令的第三个参数。 Flags是一个以逗号分隔的以下标记的列表:
'nocase|NC' (no case)
它使测试忽略大小写, 即, 扩展后的TestString和CondPattern中, 'A-Z' 和'a-z'是没有区别的。此标记仅作用于TestString和CondPattern的比较, 而对文件系统和子请求的测试不起作用。
'ornext|OR' (or next condition)
它以OR方式组合若干规则的条件,而不是隐含的AND。典型的例子如下:
RewriteCond %{REMOTE_HOST} ^host1.* [OR]
RewriteCond %{REMOTE_HOST} ^host2.* [OR]
RewriteCond %{REMOTE_HOST} ^host3.*
RewriteRule ...some special stuff for any of these hosts...
如果不用这个标记,则必须使用三个 条件/规则。
举例:
如果要按请求头中的``User-Agent:'重写一个站点的主页,可以这样写:
RewriteCond %{HTTP_USER_AGENT} ^Mozilla.*
RewriteRule ^/$ /homepage.max.html [L]
RewriteCond %{HTTP_USER_AGENT} ^Lynx.*
RewriteRule ^/$ /homepage.min.html [L]
RewriteRule ^/$ /homepage.std.html [L]
含义: 如果你使用的浏览器是Netscape Navigator(其识别标志是'Mozilla'), 则你将得到内容最大化的主页,包括Frames等等; 如果你使用的是(基于终端的)Lynx,则你得到的是内容最小化的主页,不包含tables等等; 如果你使用的是其他的浏览器,则你得到的是一个标准的主页。
RewriteEngine 指令
RewriteEngine指令打开或关闭运行时刻的重写引擎。 如果设置为off,则mod_rewrite模块不执行任何运行时刻的重写操作, 甚至也不更新SCRIPT_URx环境变量。