Rewirte基本的功能就是实现URL的跳转和隐藏真实地址,基于Perl语言的正則表達式规范。平时帮助我们实现拟静态,拟文件夹,域名跳转,防止盗链等。本文将针对mod_rewrite和URL匹配的技术细节,以及RewriteCond与RewriteRule 指令格式进行探讨。
Rewirte模块内部处理
Rewirte模块的内部处理极为复杂,可是为了使一般用户避免犯低级错误。也让管理员能充分利用其功能。在此仍然做一下说明。
Rewirte模块API阶段
首先,你必须了解Apache是分若干阶段来处理HTTP请求的。Apache API对每一个阶段都提供了一个hook程序。
mod_rewrite使用两个hook程序:其一,从URL到文件名称的转换hook(用在读取HTTP请求之后、授权開始之前); 其二,修正hook(用在授权阶段和读取文件夹级配置(.htaccess)之后、内容处理器激活之前)。
所以,Apache收到一个请求而且确定了响应主机(或虚拟主机)之后,重写引擎即開始处理server级配置中的全部mod_rewrite指令(此时处于从URL到文件名称转换的阶段)。此阶段完毕后,终于的数据文件夹便确定了。
接下来进入修正程序段并触发文件夹级配置中的mod_rewrite指令。这两个阶段并非泾渭分明的。但都实施了把URL重写成新的URL或者文件名称。
尽管API最初不是为此目的而设计的。可是如今它已经成为了API的一种用途。记住下面两点,会有助于更好地理解:
1、尽管mod_rewrite能够将URL重写为新的URL或文件名称。甚至将文件名称重写为新的文件名称,可是之前的API仅仅提供从URL到文件名称的hook。在Apache 2.0中,添加了两个丢失的hook以使得处理过程更加清晰。只是这样做并没有给用户带来麻烦,用户仅仅需记住这样一个事实:借助从URL到文件名称的hook比最初API设计的目标功能更强大。
2、令人难以置信的是。mod_rewrite还提供了文件夹级的URL操作(.htaccess文件),而这些文件必须在将URL转换成文件名称之后才会被处理(这是必须的,由于.htaccess存在于文件系统中)。换句话说。依据API阶段,这时再处理不论什么URL操作已经太晚了。为了解决这个”鸡和蛋”的问题,mod_rewrite使用了一个小技巧:在进行一个文件夹级的URL/文件名称操作时。先把文件名称重写回对应的URL(通常这个操作是不可行的,可是參考以下的RewriteBase指令就能明确它是怎么实现的了)。然后。对这个新的URL建立一个新的内部的子请求,再又一次開始API阶段的运行。
另外,mod_rewrite尽力使这些复杂的操作对用户透明。但仍须记住:server级的URL操作速度快并且效率高。而文件夹级的操作因为这个”鸡和蛋”的问题速度较慢并且效率也低。但从还有一个側面看。这却是mod_rewrite得以为一般用户提供(局部限制的)URL操作的唯一方法。
Rewirte模块规则集的处理
当mod_rewrite在这两个API阶段中開始运行时。它会读取配置结构中配置好的 (或者是在服务启动时建立的server级的。或者是在遍历文件夹採集到的文件夹级的)规则集,然后,启动URL重写引擎来处理(带有一个或多个条件的)规则集。不管是server级的还是文件夹级的规则集,都是由同一个URL重写引擎处理,仅仅是终于结果处理不同而已。
规则集中规则的顺序是非常重要的。由于重写引擎是按一种特殊的顺序处理的:逐个遍历每一个规则(RewriteRule指令),假设出现一个匹配条件的规则,则可能回头遍历已有的规则条件(RewriteCond指令)。由于历史的原因,条件规则是前置的,所以控制流程略显冗长,细节见图-1。
图-1:重写规则集中的控制流
可见。URL首先与每一个规则的Pattern匹配。假设匹配失败,mod_rewrite将马上终止此规则的处理。继而处理下一个规则。假设匹配成功,mod_rewrite将寻找相应的规则条件,假设一个条件都没有,则简单地用Substitution构造的新值来替换URL。然后继续处理其它规则;可是假设条件存在,则開始一个内部循环按其列出的顺序逐个处理。
对规则条件的处理有所不同:URL并不与模式进行匹配,而是首先通过扩展变量、反向引用、查找映射表等步骤建立一个TestString字符串,然后用它来与CondPattern匹配。假设匹配失败。则整个条件集和相应的规则失败;假设匹配成功,则运行下一个规则直到全部条件运行完成。假设全部条件得以匹配,则以Substitution替换URL。而且继续处理。
(本部分引用译者:金步国)
RewriteCond指令格式
语法: RewriteCond TestString CondPattern [flags]
RewriteCond指令定义一条规则条件。在一条RewriteRule指令前面可能会有一条或多条RewriteCond指令。仅仅有当自身的模板(pattern)匹配成功且这些条件也满足时规则才被应用于当前URL处理。
1、 TestString是一个纯文本的字符串,除了包括普通的字符外,还能够包括下列的可扩展结构:
1)$N:RewriteRule后向引用,当中(0 <= N <= 9) 。$N引用紧跟在RewriteCond后面的RewriteRule中模板中的括号里的模板在当前URL中匹配的数据。
2)%N:RewriteCond后向引用,当中(0 <= N <= 9) 。%N引用最后一个RewriteCond的模板中的括号里的模板在当前URL中匹配的数据。
3)${mapname:key|default}:RewriteMap扩展。
2、CondPattern是条件pattern, 即一个应用于当前实例TestString的正則表達式, 即TestString将会被计算然后与CondPattern匹配。作为一个标准的扩展正则式,CondPattern有下面补充:
1)能够在模板串前添加一个!前缀。以用表示不匹配模板。但并非全部的test都能够加!前缀。
2)CondPattern中能够使用下面特殊变量:
'>CondPattern’ (大于) 将condPattern当作一个普通字符串。将它和TestString进行比較,当TestString 的字符大于CondPattern为真。
‘=CondPattern’ (等于) 将condPattern当作一个普通字符串。将它和TestString进行比較,当TestString 与CondPattern全然同样时为真.假设CondPattern仅仅是 “” (两个引號紧挨在一起) 此时需TestString 为空字符串方为真。
‘-d’ (是否为文件夹) 将testString当作一个文件夹名。检查它是否存在以及是否是一个文件夹。
‘-f’ (是否是regular file) 将testString当作一个文件名称。检查它是否存在以及是否是一个regular文件。
‘-s’ (是否为长度不为0的regular文件) 将testString当作一个文件名称,检查它是否存在以及是否是一个长度大于0的regular文件。
‘-l’ (是否为symbolic link) 将testString当作一个文件名称。检查它是否存在以及是否是一个 symbolic link。
‘-F’ (通过subrequest来检查某文件是否可訪问) 检查TestString是否是一个合法的文件。并且通过server范围内的当前设置的訪问控制进行訪问。
这个检查是通过一个内部subrequest完毕的, 因此须要小心使用这个功能以减少server的性能。
‘-U’ (通过subrequest来检查某个URL是否存在) 检查TestString是否是一个合法的URL,并且通过server范围内的当前设置的訪问控制进行訪问。这个检查是通过一个内部subrequest完毕的, 因此须要小心使用这个功能以减少server的性能。
3、[flags]是第三个參数。多个标志之间用逗号分隔。
1)’nocase|NC’ (不区分大写和小写) 在扩展后的TestString和CondPattern中,比較时不区分文本的大写和小写。注意。这个标志对文件系统和subrequest检查没有影响.
2)’ornext|OR’ (建立与下一个条件的或的关系) 默认的情况下,二个条件之间是AND的关系,用这个标志将关系改为OR。比如: RewriteCond %{REMOTE_HOST} ^host1.* [OR] RewriteCond %{REMOTE_HOST} ^host2.* [OR] RewriteCond %{REMOTE_HOST} ^host3.* RewriteRule … 假设没有[OR]标志。须要写三个条件/规则.
RewriteRule 指令
语法: RewriteRule Pattern Substitution [flags]
1) Pattern是一个作用于当前URL的兼容perl的正則表達式. 这里的“当前”是指该规则生效时的URL的值。
2) Substitution是,当原始URL与Pattern相匹配时,用以替代(或替换)的字符串。
3) 此外,Substitution还能够追加特殊标记[flags] 作为RewriteRule指令的第三个參数。 Flags是一个包括以逗号分隔的下列标记的列表:
redirect|R [=code] (强制重定向 redirect)
以 http://thishost[:thisport]/(使新的URL成为一个URI) 为前缀的Substitution能够强制性运行一个外部重定向。 假设code没有指定。则产生一个HTTP响应代码302(暂时性移动)。假设须要使用在300-400范围内的其它响应代码。仅仅需在此指定这个数值就可以, 另外,还能够使用下列符号名称之中的一个: temp (默认的), permanent, seeother. 用它能够把规范化的URL反馈给client,如, 重写“/~”为 “/u/”。或对/u/user加上斜杠,等等。
注意: 在使用这个标记时。必须确保该替换字段是一个有效的URL! 否则。它会指向一个无效的位置! 而且要记住,此标记本身仅仅是对URL加上 http://thishost[:thisport]/的前缀。重写操作仍然会继续。通常。你会希望停止重写操作而马上重定向。则还须要使用’L’标记.
forbidden|F (强制URL为被禁止的 forbidden)
强制当前URL为被禁止的。即,马上反馈一个HTTP响应代码403(被禁止的)。使用这个标记,能够链接若干RewriteConds以有条件地堵塞某些URL。
gone|G’(强制URL为已废弃的 gone)
强制当前URL为已废弃的,即,马上反馈一个HTTP响应代码410(已废弃的)。使用这个标记。能够标明页面已经被废弃而不存在了.
proxy|P (强制为代理 proxy)
此标记使替换成分被内部地强制为代理请求,并马上(即, 重写规则处理马上中断)把处理移交给代理模块。
你必须确保此替换串是一个有效的(比方常见的以 http://hostname开头的)可以为Apache代理模块所处理的URI。使用这个标记,可以把某些远程成分映射到本地server名称空间, 从而增强了ProxyPass指令的功能。
注意: 要使用这个功能,代理模块必须编译在Apacheserver中。 假设你不能确定,能够检查“httpd -l”的输出中是否有mod_proxy.c。 假设有。则mod_rewrite能够使用这个功能;假设没有,则必须启用mod_proxy并又一次编译“httpd”程序。
last|L (最后一个规则 last)
马上停止重写操作。并不再应用其它重写规则。 它相应于Perl中的last命令或C语言中的break命令。这个标记能够阻止当前已被重写的URL为其后继的规则所重写。
举例,使用它能够重写根路径的URL(’/’)为实际存在的URL, 比方, ‘/e/www/’.
next|N (又一次运行 next round)
又一次运行重写操作(从第一个规则又一次開始). 这时再次进行处理的URL已经不是原始的URL了,而是经最后一个重写规则处理的URL。
它相应于Perl中的next命令或C语言中的continue命令。
此标记能够又一次開始重写操作。即, 马上回到循环的头部。
可是要小心。不要制造死循环!
chain|C (与下一个规则相链接 chained)
此标记使当前规则与下一个(其本身又能够与其后继规则相链接的, 并能够如此重复的)规则相链接。
它产生这样一个效果: 假设一个规则被匹配。一般会继续处理其后继规则, 即,这个标记不起作用;假设规则不能被匹配,则其后继的链接的规则会被忽略。比方。在运行一个外部重定向时, 对一个文件夹级规则集。你可能须要删除“.www” (此处不应该出现“.www”的)。
type|T=MIME-type(强制MIME类型 type)
强制目标文件的MIME类型为MIME-type。
比方,它能够用于模拟mod_alias中的ScriptAlias指令,以内部地强制被映射文件夹中的全部文件的MIME类型为“application/x-httpd-cgi”。
nosubreq|NS (仅用于不正确内部子请求进行处理 no internal sub-request)
在当前请求是一个内部子请求时,此标记强制重写引擎跳过该重写规则。比方,在mod_include试图搜索可能的文件夹默认文件(index.xxx)时, Apache会内部地产生子请求。对子请求,它不一定实用的,并且假设整个规则集都起作用。它甚至可能会引发错误。所以,能够用这个标记来排除某些规则。
依据你的须要遵循下面原则: 假设你使用了有CGI脚本的URL前缀,以强制它们由CGI脚本处理,而对子请求处理的出错率(或者开销)非常高,在这样的情况下,能够使用这个标记。
nocase|NC (忽略大写和小写 no case)
它使Pattern忽略大写和小写。即, 在Pattern与当前URL匹配时,’A-Z’ 和’a-z’没有差别。
qsappend|QSA (追加请求串 query string append)
此标记强制重写引擎在已有的替换串中追加一个请求串,而不是简单的替换。假设须要通过重写规则在请求串中添加信息,就能够使用这个标记。
noescape|NE (在输出中不正确URI作转义 no URI escaping)
此标记阻止mod_rewrite对重写结果应用常规的URI转义规则。
普通情况下,特殊字符(如’%’, ‘$’, ‘;’等)会被转义为等值的十六进制编码。
此标记能够阻止这种转义,以同意百分号等符号出如今输出中,如:
RewriteRule /foo/(.*) /bar?arg=P1\=$1 [R,NE] 能够使’/foo/zed’转向到一个安全的请求’/bar?arg=P1=zed’.
passthrough|PT (移交给下一个处理器 pass through)
此标记强制重写引擎将内部结构request_rec中的uri字段设置为 filename字段的值,它仅仅是一个小改动,使之能对来自其它URI到文件名称翻译器的 Alias。ScriptAlias, Redirect 等指令的输出进行兴许处理。举一个能说明其含义的样例:假设要通过mod_rewrite的重写引擎重写/abc为/def,然后通过mod_alias使/def转变为/ghi。能够这样:
Alias /def /ghi
假设省略了PT标记,尽管mod_rewrite运作正常。 即, 作为一个使用API的URI到文件名称翻译器,它能够重写uri=/abc/…为filename=/def/…,可是,兴许的mod_alias在试图作URI到文件名称的翻译时。则会失效。
注意: 假设须要混合使用不同的包括URI到文件名称翻译器的模块时, 就必须使用这个标记。。混合使用mod_alias和mod_rewrite就是个典型的样例。
For Apache hackers
假设当前Apache API除了URI到文件名称hook之外。另一个文件名称到文件名称的hook, 就不须要这个标记了! 可是。假设没有这样一个hook,则此标记是唯一的解决方式。 Apache Group讨论过这个问题。并在Apache 2.0 版本号中会添加这样一个hook。
skip|S=num (跳过后继的规则 skip)
此标记强制重写引擎跳过当前匹配规则后继的num个规则。 它能够实现一个伪if-then-else的构造: 最后一个规则是then从句。而被跳过的skip=N个规则是else从句. (它和’chain|C’标记是不同的!)
env|E=VAR:VAL (环境变量设置 environment variable)
此标记使环境变量VAR的值为VAL, VAL能够包括可扩展的反向引用的正則表達式$N和%N。
此标记能够多次使用以设置多个变量。这些变量能够在其后很多情况下被间接引用,但一般是在XSSI (via ) or CGI (如 $ENV{’VAR’})中, 也能够在后继的RewriteCond指令的pattern中通过%{ENV:VAR}作引用。使用它能够从URL中剥离并记住一些信息。
cookie|CO=NAME:VAL:domain[:lifetime[:path]] (设置cookie)
它在client浏览器上设置一个cookie。 cookie的名称是NAME,其值是VAL。 domain字段是该cookie的域。比方’.apache.org’, 可选的lifetime是cookie生命期的分钟数,可选的path是cookie的路径。
案例:
city_map.txt的内容:
hangzhou 12
beijing 13
1、hangzhou.google.com/tianqi/20090401 跳转到 www.google.com/service/detail.html?id=tianqi&date=20090401
- RewriteMap city-map txt:/etc/httpd/conf.d/map/city_map.txt
- RewriteCond %{HTTP_HOST} ^(.+)\.google\.com$
- RewriteRule ^/([\w]+)/([\d]+)$ /service/detail\.html\?id=$1&date=$2&c=${city-map:%1|%1} [PT,L]
解释:
%{HTTP_HOST}:取请求的域名
^(.+)\.google\.com$:^,开头;$结尾。
.(逗号),除终止符外的随意字符。+,反复一个或一个以上的字符。\。转义字符。
^/([\w]+)/([\d]+)$:[],集合字符。\w,数字或字母。\d,数字。
$1:表示的是符合RewriteRule 中[\w]+正则式的字符串,也就是tianqi。
$2:表示的是符合RewriteRule 中[\d]+ 正则式的字符串,也就是20090401。
%1:表示的是符合RewriteCond 中.+正则式的字符串,也就是hangzhou。
${city-map:%1|%1}:表示取city-map中%1也就是hangzhou相应的值,假设没有则为%1也就是hangzhou。
2、能看出以下的规则是做了什么吗?
- RewriteCond %{HTTP_HOST} ^(.+)\.google\.com$
- RewriteRule ^/([\w]+)/([^-]+)-([^-]+)--([^-]+)-([^-]+)--([^-]+)-([^-]+)--([^-]+-[^-]+--[^-]+-[^-]+--[^-]+-[^-]+)$ /$1/$2=$3&$4=$5&$6=$7&$8 [C]
- RewriteCond %{HTTP_HOST} ^(.+)\.google\.com$
- RewriteRule ^/([\w]+)/([^-]+)-([^-]+)--([^-]+)-([^-]+)--([^-]+)-([^-]+)$ /service/list\.html\?frontCategoryId=${category-map:$1|0}&$2=$3&$4=$5&$6=$7&city=${city-map:%1|%1} [PT,L]
解释:
这个规则是想把-(中划线)转为=,把- -(两条中划线)转为&。
[^-]:^在字符集合符号([])之内表示反向选择,之外表示行首,所以表示不以-开头。
由于$N,N最大为9。所以使用了C,用第二条RewriteRule把第一条RewriteRule中的最后一个节点。即$8,进行继续转换。
此外,rewrite规则中假设遇到中文,相当有可能会出现乱码问题,由于apache在rewrite时会做一次url解码。这时jk进行请求转发时,就不会再是编码后的字符串了。此种情况,能够在一開始就进行两次编码(encode),或者在接收请求时先用ISO-8859-1取字节流,再使用UFT-8来new String。(new String(str.getBytes(”ISO-8859-1″),”UTF-8