Apache Rewrite 大全
Apache的rewrite模块,提供了一个基于规则的重写(rewrite,也许译为重构更为合适)引擎,来实时重写发送到Apache的请求URL。因功能极其强大,被称为URL重写的“瑞士军刀”。
这个模块使用一个基于正则表达式解析器开发的重写引擎,根据web管理员定义的规则来实时(on the fly)重写请求URL。它支持任意数目的重写规则,以及附加到一条规则上的任意数目的规则条件,从而提供了一套非常灵活和功能强大的URL处理机制。URL处理操作的实施与否,依赖于各种各样的条件检查,如检查服务器变量、环境变量、HTTP头字段、时间戳的值,甚至外部数据库的检索结果。这个模块可以在服务器范围内(http.conf)、目录范围内(.htaccess)或请求串(query-string)的一部分处理有关的URL。重写的结果URL,可以指向一个站内的处理程序、指向站外的重定向或者一个站内的代理。与灵活和功能强大相随的是设置的复杂,别指望一天内弄明白整个模块。(所以,这个学习笔记也分了几部分:)
内部处理过程
API阶段
首先,Apache处理HTTP请求是分阶段进行的,Apache API为每个阶段提供了一个钩子(hook)。Mod_rewrite使用了其中的两个钩子:一个用来在HTTP请求被读取但还没有访问授权验证之前进行URL_to_filename转换,一个用来在授权验证完成且目录设置文件(.htaccess)读取之后、但内容处理器(content handler)被调用之前激化,进行修补(fixup).因此,当一个请求到达,Apache决定了相关的服务器(或虚拟服务器)以后进行URL_to_filename阶段,重写引擎(rewrite engine)开始处理服务器设置中的重写指令(mod_rewrite directives).接下来几个阶段过后进入修补阶段,此时最终的数据所在的物理目录已经找到,目录配置中的重写指令开始执行。在这两个阶段,mod_rewrite都是将URL重写为新的URL或文件名,所以看起来并没有明显的区别。对API的这种应用,并不是一开始就是这样设计的,而是Apache1.x不得已而为之。为了搞清这个问题,以下两点需要记住。
1)虽然mod_rewrite能进行URL到URL、URL到文件名字甚至文件名字到文件名字的转换,API(1.x)目前提供了一个URL_to_filename转换。在Apache2.0中,这两个钩子会被加进去,整个过程会更加清晰。一个事实必须清楚的记得:Apache在URL_to_filename钩子中,做得比API设计的功能更多。
2)不可思议的是,mod_rewrite能在目录范围内(如根据.htaccess文件的指令配置)进行URL处理,虽然URL很早就已经被转换为文件名字了。只所以会如此,是因为.htaccess文件存在于文件系统中。也就是说,在这个阶段来进行URL处理,是非常晚的时候了。为了解决这个"先有鸡还是先有蛋"的问题,mod_rewrite用了一个小技巧:当在目录范围内处理URL/filename时,mod_rewrite先将文件名逆转回相关的URL(虽然通常是不可能的,但请参见下面用以实现这个技巧的RewriteBase指令),然后据这个新URL生成一个站内的子请求(internal sub-request),这又重开始了API进程。Mod_rewrite尽量使这些复杂的步骤对用户透明,但应要记住:虽然目录范围URL的真正处理过程很快很高效,但这一阶段会因为这个"鸡和蛋"的问题而变得很慢和低效。从另一方面来看,这也是mod_rewrite提供给普通用户进行目录范围内的URL处理的唯一途径.
规则集(RewriteRule指令集合)处理过程
当mod_rewrite在上述的两个API阶段被激活时,它会从它的配置数据结构(在开始服务器上下文(per-server context)或目录上下文(per-directory context)时创建的)中读取配置的规则集,然后URL重写引擎启动来执行包含的规则集(一个或多条规则以及它们的条件)。两种上下文中的处理过程都是一样的,差别只是在最后的结果处理过程上。
规则集中规则的顺序是非常重要的,因为重写引擎以特定的顺序来处理它们。重写引擎顺序遍历规则集,当一条规则匹配时,引擎会去遍历与它相关的条件集(RewriteCond指令集合).由于历史的原因,条件集先被列出来,因此控制流流程有点曲折(long-winded).如图一所示:
正如所看到的,首先URL会与每条规则的模板(pattern)比较,当匹配失败时,立即停止对当前规则的处理进入下一条规则。当匹配成功时,mod_rewrite寻找相关的规则条件。如果找不到相关的条件,则直接执行规则中定义的替换,然后回到规则遍历的过程。如果找到了相关的条件,则启动一个内部循环,依次检查各个条件。对于检查,我们不是拿一个模板来匹配当前的URL,而是先创建一个TestString串,将串内的变量、后向引用(bakc-reference)、查询结果(map lookups)等展开,然后用这个TestString和条件式中的CondPattern进行匹配,如果匹配失败,则整个条件集且这个规则都不再执行,重要回到规则遍历中;如果匹配成功,则检查下一个条件,如果所有的条件都满足,则执行规则中定义的替换动作。
特殊字符的转义
既然基于正则式,则当然会有特殊字符的问题。在1.3.20版本的Apache中,通过在特殊字符前加一个“\”来将TestString或Sustitution串的特殊字符转义。
正则式的后向引用
有一点需要记住:一旦在模板(pattern)或条件模板(CondPattern)中使用了括号,则后向引用已经自动产生了,你可以在Sustitution或TestString中通过$N或%N来引用相关的值。如图,描述了后向引用的值可以传到的位置。
配置指令(Configuration Directives)
指令 |
语法 |
默认值 |
说明 |
备注 |
RewriteEngine |
RewriteEngine on|off |
Off |
开关重构引擎 |
默认时不能继承,故每个虚拟主机都要有自己的开关指令。 |
RewriteOptions |
RewriteOptions Option |
MaxRedirects=10 |
设置一些特殊参数 |
inherit:配置是否继承,MaxRedirects=number:内部重定向次数 |
RewriteLog |
RewriteLog file-path |
None |
设定重写log文件 |
用RewriteLogLevel 0来禁止日志 |
RewriteLogLevel |
RewriteLogLevel Level |
RewriteLogLevel 0 |
设置日志级别 |
表示没有,2以上用于debug,9及以上表示全部信息 |
RewriteLock |
RewriteLock file-path |
None |
设置RewriteMap程序的同步锁文件 |
要求是本地文件,此文件只对rewriting map-program有效。 |
RewriteMap |
RewriteMap MapName MapType:MapSource |
Notused per default |
定义重写影射 |
具体说明参见文档 |
RewriteBase |
RewriteBase URL-path |
physical directory path |
设置目录范围内重写的基本URL |
具体说明参见文档 |
RewriteCond |
RewriteCond TestString CondPattern |
None |
定义规则条件 |
具体说明参见文档 |
RewriteRule |
RewriteRule Pattern Substitution |
None |
定义重写规则 |
具体说明参见文档 |
今天学习重写规则的语法。
RewriteRule
Syntax: RewriteRule Pattern Substitution [flags]
一条RewriteRule指令,定义一条重写规则,规则间的顺序非常重要。对Apache1.2及以后的版本,模板(pattern)是一个POSIX正则式,用以匹配当前的URL。当前的URL不一定是用记最初提交的URL,因为可能用一些规则在此规则前已经对URL进行了处理。
对mod_rewrite来说,!是个合法的模板前缀,表示“非”的意思,这对描述“不满足某种匹配条件”的情况非常方便,或用作最后一条默认规则。当使用!时,不能在模板中有分组的通配符,也不能做后向引用。
当匹配成功后,Substitution会被用来替换相应的匹配,它除了可以是普通的字符串以外,还可以包括:
1. $N,引用RewriteRule模板中匹配的相关字串,N表示序号,N=0..9
2. %N,引用最后一个RewriteCond模板中匹配的数据,N表示序号
3. %{VARNAME},服务器变量
4. ${mapname:key|default},映射函数调用
这些特殊内容的扩展,按上述顺序进行。
一个URL的全部相关部分都会被Substitution替换,而且这个替换过程会一直持续到所有的规则都被执行完,除非明确地用L标志中断处理过程。
当susbstitution有”-”前缀时,表示不进行替换,只做匹配检查。
利用RewriteRule,可定义含有请求串(Query String)的URL,此时只需在Sustitution中加入一个?,表示此后的内容放入QUERY_STRING变量中。如果要清空一个QUERY_STRING变量,只需要以?结束Substitution串即可。
如果给一个Substitution增加一个http://thishost[:port]的前缀,则mod_rewrite会自动将此前缀去掉。因此,利用http://thisthost做一个无条件的重定向到自己,将难以奏效。要实现这种效果,必须使用R标志。
Flags是可选参数,当有多个标志同时出现时,彼此间以逗号分隔。
1. 'redirect|R [=code]' (强制重定向)
给当前的URI增加前缀http://thishost[:thisport]/, 从而生成一个新的URL,强制生成一个外部重定向(external redirection,指生的URL发送到客户端,由客户端再次以新的URL发出请求,虽然新URL仍指向当前的服务器). 如果没有指定的code值,则HTTP应答以状态值302 (MOVED TEMPORARILY),如果想使用300-400(不含400)间的其它值可以通过在code的位置以相应的数字指定,也可以用标志名指定: temp (默认值), permanent, seeother.
注意,当使用这个标志时,要确实substitution是个合法的URL,这个标志只是在URL前增加http://thishost[:thisport]/前缀而已,重写操作会继续进行。如果要立即将新URL重定向,用L标志来中重写流程。
2. 'forbidden|F' (强制禁止访问URL所指的资源)
立即返回状态值403 (FORBIDDEN)的应答包。将这个标志与合适的RewriteConds 联合使用,可以阻断访问某些URL。
3. 'gone|G' (强制返回URL所指资源为不存在(gone))
立即返回状态值410 (GONE)的应答包。用这个标志来标记URL所指的资源永久消失了.
4. # 'proxy|P' (强制将当前URL送往代理模块(proxy module))
这个标志,强制将substitution当作一个发向代理模块的请求,并立即将共送往代理模块。因此,必须确保substitution串是一个合法的URI (如, 典型的情况是以http://hostname开头),否则会从代理模块得到一个错误. 这个标志,是ProxyPass指令的一个更强劲的实现,将远程请求(remote stuff)映射到本地服务器的名字空间(namespace)中来。
注意,使用这个功能必须确保代理模块已经编译到Apache 服务器程序中了. 可以用“httpd -l ”命令,来检查输出中是否含有mod_proxy.c来确认一下。如果没有,而又需要使用这个功能,则需要重新编译``httpd''程序并使用mod_proxy有效。
5. 'last|L' (最后一条规则)
中止重写流程,不再对当前URL施加更多的重写规则。这相当于perl的last命令或C的break命令。
6. 'next|N' (下一轮)
重新从第一条重写规则开始执行重写过程,新开的过程中的URL不应当与最初的URL相同。 这相当于Perl的next命令或C的continue命令. 千万小心不要产生死循环。
7. # 'chain|C' (将当前的规则与其后续规则??绑(chained))
当规则匹配时,处理过程与没有??绑一样;如果规则不匹配,则??绑在一起的后续规则也不在检查和执行。
8. 'type|T=MIME-type' (强制MIME类型)
强制将目标文件的MIME-type为某MIME类型。例如,这可用来模仿mod_alias模块对某目录的ScriptAlias指定,通过强制将该目录下的所有文件的类型改为 “application/x-httpd-cgi”.
9. 'nosubreq|NS' (used only if no internal sub-request )
这个标志强制重写引擎跳过为内部sub-request的重写规则.例如,当mod_include试图找到某一目录下的默认文件时 (index.xxx),sub-requests 会在Apache内部发生. Sub-requests并非总是有用的,在某些情况下如果整个规则集施加到它上面,会产生错误。利用这个标志可排除执行一些规则。
10. 'nocase|NC' (模板不区分大小写)
这个标志会使得模板匹配当前URL时忽略大小写的差别。
11. 'qsappend|QSA' (追加请求串(query string))
这个标志,强制重写引擎为Substitution的请求串追加一部分串,则不是替换掉原来的。借助这个标志,可以使用一个重写规则给请求串增加更多的数据。
12. 'noescape|NE' (不对输出结果中的特殊字符进行转义处理)
通常情况下,mod_write的输出结果中,特殊字符(如'%', '$', ';', 等)会转义为它们的16进制形式(如分别为'%25', '%24', and '%3B')。这个标志会禁止mod_rewrite对输出结果进行此类操作。 这个标志只能在 Apache 1.3.20及以后的版本中使用。
13. 'passthrough|PT' (通过下一个处理器)
这个标志强制重写引擎用filename字段的值来替换内部request_rec数据结构中uri字段的值。. 使用这个标志,可以使后续的其它URI-to-filename转换器的Alias、ScriptAlias、Redirect等指令,也能正常处理RewriteRule指令的输出结果。用一个小例子来说明它的语义:如果要用mod_rewrite的重写引擎将/abc转换为/def,然后用mod_alas将/def重写为ghi,则要:
RewriteRule ^/abc(.*) /def$1 [PT]
Alias /def /ghi
如果PT标志被忽略,则mod_rewrite也能很好完成工作,如果., 将 uri=/abc/...转换为filename=/def/... ,完全符合一个URI-to-filename转换器的动作。接下来 mod_alias 试图做 URI-to-filename 转换时就会出问题。
注意:如果要混合都含有URL-to-filename转换器的不同的模块的指令,必须用这个标志。最典型的例子是mod_alias和mod_rewrite的使用。
14. 'skip|S=num' (跳过后面的num个规则)
当前规则匹配时,强制重写引擎跳过后续的num个规则。用这个可以来模仿if-then-else结构:then子句的最后一条rule的标志是skip=N,而N是else子句的规则条数。
15. 'env|E=VAR:VAL' (设置环境变量)
设置名为VAR的环境变量的值为VAL,其中VAL中可以含有正则式的后向引用($N或%N)。这个标志可以使用多次,以设置多个环境变量。这儿设置的变量,可以在多种情况下被引用,如在XSSI或CGI中。另外,也可以在RewriteCond模板中以%{ENV:VAR}的形式被引用。
16.
注意:一定不要忘记,在服务器范围内的配置文件中,模板(pattern)用以匹配整个URL;而在目录范围内的配置文件中,目录前缀总是被自动去掉后再进行模板匹配的,且在替换完成后自动再加上这个前缀。这个功能对很多种类的重写是非常重要的,因为如果没有去前缀,则要进行父目录的匹配,而父目录的信息并不是总能得到的。一个例外是,当substitution中有http://打头时,则不再自动增加前缀了,如果P标志出现,则会强制转向代理。
注意:如果要在某个目录范围内启动重写引擎,则需要在相应的目录配置文件中设置“RewriteEngine on”,且目录的“Options FollowSymLinks”必须设置。如果管理员由于安全原因没有打开FollowSymLinks,则不能使用重写引擎。
今天学习重写规则的条件。
RewriteCond
Syntax: RewriteCond TestString CondPattern [flags]
RewriteCond指令定义一条规则条件。在一条RewriteRule指令前面可能会有一条或多条RewriteCond指令,只有当自身的模板(pattern)匹配成功且这些条件也满足时规则才被应用于当前URL处理。
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扩展.
具体参见RewriteMap
4. %{ NAME_OF_VARIABLE } ,服务器变量。
变量的名字如下表(分类显示)
HTTP headers: |
connection & request: |
server internals: |
system stuff: |
HTTP_USER_AGENT |
REMOTE_ADDR |
DOCUMENT_ROOT |
TIME_YEAR |
HTTP_REFERER |
REMOTE_HOST |
SERVER_ADMIN |
TIME_MON |
HTTP_COOKIE |
REMOTE_USER |
SERVER_NAME |
TIME_DAY |
HTTP_FORWARDED |
REMOTE_IDENT |
SERVER_ADDR |
TIME_HOUR |
HTTP_HOST |
REQUEST_METHOD |
SERVER_PORT |
TIME_MIN |
HTTP_PROXY_CONNECTION |
SCRIPT_FILENAME |
SERVER_PROTOCOL |
TIME_SEC |
HTTP_ACCEPT |
PATH_INFO |
SERVER_SOFTWARE |
TIME_WDAY |
|
QUERY_STRING |
|
TIME |
|
AUTH_TYPE |
|
|
5.
specials: |
说明 |
API_VERSION |
Apache与模块间的接口的版本号 |
THE_REQUEST |
客户端发送到来的HTTP请求行的整行信息,不含其它的头字段信息,如("GET /index.html HTTP/1.1") |
REQUEST_URI |
HTTP请求行中请求的资源 |
REQUEST_FILENAME |
请求中对应的服务器本地文件系统中全路径文件名 |
IS_SUBREQ |
根据是否为SubRequest,分别值为”true”或”false” |
6.
特别说明:
o SCRIPT_FILENAME和REQUEST_FILENAME变量含有相同的值,也就是Apache服务器内部数据结构request_rec的filename字段的值。第一个变量是一个CGI变量,而第二个则与REQUEST_URI(含有request_rec数据结构中uri字段的值)保持一致。
o %{ENV:variable}中的variable可以是任何环境变量的名字。对其值的查找,先通过Apache内部的数据结构,(如找不到)再在Apache服务器进程中通过getenv()查找。
o %{HTTP:header}中的header可以是任何HTTP MIME-header的名字,其值通过查找HTTP请求信息而得。
o %{LA-U:variable} 用来引用后续API阶段中定义的、当前还不知道的值,具体实现是通过执行一个基于URL的内部的sub-request来决定的variable的最终的值。例如,假如你想在服务器范围内利用REMOTE_USER的值来完成重写,但这个值是在验证阶段设置的,而验证阶段是在URL转换阶段的后面。从另一方面讲,由于mod_rewrite在修补(fixup)API阶段进行目录范围的重写,而修补阶段在验证阶段的后面,所以此时只要用%{REMOTE_USER}就可以取得该值了。
o %{LA-F:variable},执行一个基于文件名字(filename)的内部sub-request来决定variable的最终的值。大多数时间内,这和LA-U相同。
CondPattern是一个条件模板,也就是说,是一个扩展正则式(extended regular expression),用与跟TestString进行匹配。作为一个标准的扩展正则式,CondPattern有以下补充:
1. 可以在模板串前增加一个!前缀,以用表示不匹配模板。但并不是所有的test都可以加!前缀。
2. CondPattern中可以使用以下特殊变量:
o ' 将condPattern当作一个普通字符串,将它和TestString进行比较,当TestString 的字符小于CondPattern为真.
o '>CondPattern' (大于)
将condPattern当作一个普通字符串,将它和TestString进行比较,当TestString 的字符大于CondPattern为真.
o '=CondPattern' (等于)
将condPattern当作一个普通字符串,将它和TestString进行比较,当TestString 与CondPattern完全相同时为真.如果CondPattern只是 "" (两个引号紧挨在一起) 此时需TestString为空字符串方为真.
o '-d' (是否为目录)
将testString当作一个目录名,检查它是否存在以及是否是一个目录.
o '-f' (是否是regular file)
将testString当作一个文件名,检查它是否存在以及是否是一个regular文件.
o '-s' (是否为长度不为的regular文件)
将testString当作一个文件名,检查它是否存在以及是否是一个长度大于的regular文件
o '-l' (是否为symbolic link)
将testString当作一个文件名,检查它是否存在以及是否是一个 symbolic link.
o '-F' (通过subrequest来检查某文件是否可访问)
检查TestString是否是一个合法的文件,而且通过服务器范围内的当前设置的访问控制进行访问。这个检查是通过一个内部subrequest完成的, 因此需要小心使用这个功能以降低服务器的性能。
o '-U' (通过subrequest来检查某个URL是否存在)
检查TestString是否是一个合法的URL,而且通过服务器范围内的当前设置的访问控制进行访问。这个检查是通过一个内部subrequest完成的, 因此需要小心使用这个功能以降低服务器的性能。
[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]标志,需要写三个条件/规则.
例子:根据客户端浏览器的不同,返回不同的首页面。
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]
今天学习重写影射等内容。
RewriteMap
Syntax: RewriteMap MapName MapType:MapSource
RewriteMap指令定义一个重写影射(Rewriting Map),在规则的substitution串中,通过影射函数(mapping-functions)来查找关键字(key),并用关键字对应的值来进行来行插入或替换操作。这个查找的对象,可以是各种各样的。
MapName是影射的名字,将用来通过下列的某种结构来为substitution定义影射函数:
${ MapName : LookupKey }
${ MapName : LookupKey | DefaultValue }
当这些结构之一出现substitution串中时,重写引擎会到mapname影射中查找lookupkey关键字,如果找到了就用返回的值(substvalue)来替换该结构,如果找不到就用defaultvalue来替换该结构,如果没有defaultvalue,就用空串来替换。
MapType 和mapSource组合有以下几种:
1. 标准的普通文本(Standard Plain Text)
MapType: txt, MapSource: Unix文件系统中合法的带有路径的regular file名
此种情况下,MapSource文件是一个普通的ASCII文本文件,可以含有空行、注释行(以#打头),及以下结构的键值对行(每个键值对一行)。
MatchingKey SubstValue
例如:Mapsource文件叫/path/to/file/map.txt,其内容为
##
## map.txt -- rewriting map
##
Ralf.S.Engelschall rse # Bastard Operator From Hell
Mr.Joe.Average joe # Mr. Average
在配置文件中则可以这样定义重写映射:
RewriteMap real-to-user txt:/path/to/file/map.txt
2. 随机的普通文本(Randomized Plain Text)
MapType: rnd, MapSource: Unix文件系统中合法的带有路径的regular file名
这种情况与标准普通文本情况很相似,差别只是在SubstValue的格式上:此时的SubstValue由一些用”|”分隔的值组成的串,这个“|”是“或者”的意思。当根据键值找到对应的SubstValue后,mod_rewrite借助“|”将此串分解为一些候选项,然后随机选择一项作为最终的SubstValue的值返回。这听起来有点疯狂或毫无用处,其实这是设计用来在反向代理(reverse proxy)的情况下做负载均衡,用来查找服务器的名字。
例如:MapSource文件的名字为/path/to/file/map.txt,内容如下:
##
## map.txt -- rewriting map
##
static www1|www2|www3|www4
dynamic www5|www6
在配置文件中定义的重写影射为:
RewriteMap servers rnd:/path/to/file/map.txt
3. Hash File
MapType: dbm, MapSource: Unix文件系统中合法的带有路径的regular file名
这儿的Mapsource文件是一个二进制的NDBM格式的文件,含有与普通文本格式文件时相同的内容,为了实现快速查找进行了优化处理后以一种特殊的格式来表达。 可以用任何NDBM工具或者用下面的Perl脚本txt2dbm.pl来创建这种格式的文件:
#!/path/to/bin/perl
##
## txt2dbm -- convert txt map to dbm format
##
use NDBM_File;
use Fcntl;
($txtmap, $dbmmap) = @ARGV;
open(TXT, "<$txtmap") or die "Couldn't open $txtmap!\n";
tie (%DB, 'NDBM_File', $dbmmap,O_RDWR|O_TRUNC|O_CREAT, 0644) or die "Couldn't create $dbmmap!\n";
while () {
next if (/^\s*#/ or /^\s*$/);
$DB{$1} = $2 if (/^\s*(\S+)\s+(\S+)/);
}
untie %DB;
close(TXT);
此脚本的使用方法如下:
$ perl txt2dbm.pl map.txt map.db
4. Apache内部函数(Internal Function)
MapType: int, MapSource: Apache内部函数
这时,MapSource是一个Apache内置函数,目前还不能创建自己的内部函数,只能使用Apache已经定义好的:
o toupper
将键值转换为大写
o tolower
将键值转换为小写
o escape
将键值中的特殊字符转换为以16进制表示。
o unescape
将键值中16进制表示的特殊字符转换为原来的样子。
5. 外部重写程序(External Rewriting Program)
MapType: prg, MapSource: Unix文件系统中合法的带有路径的regular file名
这儿的MapSource是一个程序而不是一个影射文件。你可以使用任何语言创建这个程序,但这个程序必须是可执行的(也就是说,要么是二进制码,要么是首行带有”#!/path/to/interpreter”格式的解释器的可执行脚本)。
这个程序在Apache启动时被运行,然后它就用它的标准输入与标准输出的文件句柄与重写引擎通信(暗指程序能无限时等待标准输入,见下列)。对每个影射函数的查找要求,它把其标准输入中得到的字符串(newline-terminated string?)作为键值,然后通过标准输出返回查找到的值,如果找不到相应的值,则返回以四字符的字符串”NULL“。下面一个简单的例子,实现将键值作为查找结果返回:
#!/usr/bin/perl
$| = 1;
while () {
# ...put here any transformations or lookups...
print $_;
}
注意:
1. ``Keep it simple, stupid'' (KISS),因为当规则执行时,一量这个外部程序挂起,整个Apache服务器就挂起了
2. 避免下述常见错误:缓存了标准输出的I/O。这会导致死循环。这也是为什么上例中有``$|=1''这么一行的原因
3. 用RewriteLock指令定义一个lockfile,以同步与外部程序的通信。默认情况下并没有这样的同步
RewriteMap可以出现不只一次。每个影射函数用RewriteMap来声明它的影射文件. While you cannot declare a map in per-directory context it is of course possible to use this map in per-directory context.(?)对普通文本和DBM格式的文件,其键值被缓存在Apache内核中,直到影射文件的mtime 变化了或Apache重启动了。此时,可以将规则中的影射函数用于每个请求。
RewriteBase
Syntax: RewriteBase URL-path
RewriteBase明确地指出目录范围内的重写结果的baseURL.RewriteRule指令可以用在目录范围内的配置文件里(.htaccess)。在目录范围的重写实施时,由本地路径信息构成的前缀已经被去掉,重写规则只对剩余的部分进行处理。处理结束后,被去掉的路径信息要自动加上,因为当对一个URL进行替换后,重新引擎需要将它重新插入到服务器的处理流程中去。如果服务器端的URL与文件的物理路径有直接有关系,则每当在.htacess文中定义重写规则时都需要用RewriteBase指URL-path。
杂项Environment Variables
mod_rewrite还维护着两个非标准的CGI/SSI环境变量,名为SCRIPT_URL和 SCRIPT_URI,存放着当前资源的逻辑web视图(logical Web-view)。标准的CGI/SSI变量SCRIPT_NAME 和 SCRIPT_FILENAME中存放着当前资源的物理系统视图(physical System-view)。请看例子:
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 |
RewriteRule ^/~([^/]+)/?(.*) /u/$1/$2 [R] |
将/~user重写为/u/user的形式 |
|
RewriteRule ^/([uge])/([^/]+)$ /$1/$2/ [R] |
将/u/user末尾漏掉的/补上 |
|
|
|
规范化HostName |
RewriteCond %{HTTP_HOST} !^fully\.qualified\.domain\.name [NC] |
域名不合格 |
|
RewriteCond %{HTTP_HOST} !^$ |
不空 |
|
RewriteCond %{SERVER_PORT} !^80$ |
不是80端口 |
|
RewriteRule ^/(.*) http://fully.qualified.domain.name:%{SERVER_PORT}/$1 [L,R] |
重写 |
|
RewriteCond %{HTTP_HOST} !^fully\.qualified\.domain\.name [NC] |
|
|
RewriteCond %{HTTP_HOST} !^$ |
|
|
RewriteRule ^/(.*) http://fully.qualified.domain.name/$1 [L,R] |
|
|
|
|
URL根目录转移 |
RewriteEngine on |
|
|
RewriteRule ^/$ /e/www/ [R] |
从/移到/e/www/ |
|
|
|
末尾目录补斜线 |
RewriteEngine on |
|
(目录范围内) |
RewriteBase /~quux/ |
|
|
RewriteRule ^foo$ foo/ [R] |
/~quux/foo是一个目录,补/ |
|
|
|
|
RewriteEngine on |
|
|
RewriteBase /~quux/ |
|
|
RewriteCond %{REQUEST_FILENAME} -d |
如果请文件名是个目录 |
|
RewriteRule ^(.+[^/])$ $1/ [R] |
URL末尾不是斜线时补上 |
|
|
|
Web集群 |
RewriteEngine on |
|
|
RewriteMap user-to-host txt:/path/to/map.user-to-host |
用户-服务器映射 |
|
RewriteMap group-to-host txt:/path/to/map.group-to-host |
组-服务器映射 |
|
RewriteMap entity-to-host txt:/path/to/map.entity-to-host |
实体-服务器映射 |
|
RewriteRule ^/u/([^/]+)/?(.*) http://${user-to-host:$1|server0}/u/$1/$2 |
用户均衡 |
|
RewriteRule ^/g/([^/]+)/?(.*) http://${group-to-host:$1|server0}/g/$1/$2 |
组均衡 |
|
RewriteRule ^/e/([^/]+)/?(.*) http://${entity-to-host:$1|server0}/e/$1/$2 |
实体均衡 |
|
RewriteRule ^/([uge])/([^/]+)/?$ /$1/$2/.www/ |
|
|
RewriteRule ^/([uge])/([^/]+)/([^.]+.+) /$1/$2/.www/$3\ |
|
|
|
|
URL根目录搬迁 |
RewriteEngine on |
|
|
RewriteRule ^/~(.+) http://newserver/~$1 [R,L] |
到其它服务器 |
|
|
|
所用户名首字母分 |
RewriteEngine on |
|
|
RewriteRule ^/~(([a-z])[a-z0-9]+)(.*) /home/$2/$1/.www$3 |
内一层括号为$2 |
|
|
|
NCSA imagemap移 |
RewriteEngine on |
|
植为mod_imap |
RewriteRule ^/cgi-bin/imagemap(.*) $1 [PT] |
|
|
|
|
多目录查找资源 |
RewriteEngine on |
|
|
# first try to find it in custom/... |
|
|
RewriteCond /your/docroot/dir1/%{REQUEST_FILENAME} -f |
|
|
RewriteRule ^(.+) /your/docroot/dir1/$1 [L] |
|
|
# second try to find it in pub/... |
|
|
RewriteCond /your/docroot/dir2/%{REQUEST_FILENAME} -f |
|
|
RewriteRule ^(.+) /your/docroot/dir2/$1 [L] |
|
|
# else go on for other Alias or ScriptAlias directives, |
|
|
RewriteRule ^(.+) - [PT] |
|
|
|
|
据URL设置环境变量 |
RewriteEngine on |
|
|
RewriteRule ^(.*)/S=([^/]+)/(.*) $1/$3 [E=STATUS:$2] |
|
|
|
|
虚拟主机 |
RewriteEngine on |
|
|
RewriteCond %{HTTP_HOST} ^www\.[^.]+\.host\.com$ |
基于用户名 |
|
RewriteRule ^(.+) %{HTTP_HOST}$1 [C] |
|
|
RewriteRule ^www\.([^.]+)\.host\.com(.*) /home/$1$2 |
|
|
|
|
内外人有别 |
RewriteEngine on |
|
|
RewriteCond %{REMOTE_HOST} !^.+\.ourdomain\.com$ |
基于远程主机 |
|
RewriteRule ^(/~.+) http://www.somewhere.com/$1 [R,L] |
|
|
|
|
错误重定向 |
RewriteEngine on |
|
|
RewriteCond /your/docroot/%{REQUEST_FILENAME} !-f |
不是regular文件 |
|
RewriteRule ^(.+) http://webserverB.dom/$1 |
|
|
|
|
程序处理特殊协议 |
RewriteRule ^xredirect:(.+) /path/to/nph-xredirect.cgi/$1 \ |
Xredirect协议 |
|
[T=application/x-httpd-cgi,L] |
|
|
|
|
最近镜像下载 |
RewriteEngine on |
|
|
RewriteMap multiplex txt:/path/to/map.cxan |
顶级域名与最近ftp服务器映射 |
|
RewriteRule ^/CxAN/(.*) %{REMOTE_HOST}::$1 [C] |
|
|
RewriteRule ^.+\.([a-zA-Z]+)::(.*)$ ${multiplex:$1|ftp.default.dom}$2 [R,L] |
据顶级域名不同提供不同的FTP服务器 |
|
|
|
基于时间重写 |
RewriteEngine on |
|
|
RewriteCond %{TIME_HOUR}%{TIME_MIN} >0700 |
|
|
RewriteCond %{TIME_HOUR}%{TIME_MIN} <1900 |
|
|
RewriteRule ^foo\.html$ foo.day.html |
白天为早晚7点间 |
|
RewriteRule ^foo\.html$ foo.night.html |
其余为夜间 |
|
|
|
向前兼容扩展名 |
RewriteEngine on |
|
|
RewriteBase /~quux/ |
|
|
# parse out basename, but remember the fact |
|
|
RewriteRule ^(.*)\.html$ $1 [C,E=WasHTML:yes] |
|
|
# rewrite to document.phtml if exists |
|
|
RewriteCond %{REQUEST_FILENAME}.phtml -f |
如果存在$1.phtml则重写 |
|
RewriteRule ^(.*)$ $1.phtml [S=1] |
|
|
# else reverse the previous basename cutout |
|
|
RewriteCond %{ENV:WasHTML} ^yes$ |
如果不存在$1.phtml,则保持不变 |
|
RewriteRule ^(.*)$ $1.html |
|
|
|
|
文件改名(目录级) |
RewriteEngine on |
内部重写 |
|
RewriteBase /~quux/ |
|
|
RewriteRule ^foo\.html$ bar.html |
|
|
|
|
|
RewriteEngine on |
重定向由客户端再次提交 |
|
RewriteBase /~quux/ |
|
|
RewriteRule ^foo\.html$ bar.html [R] |
|
|
|
|
据浏览器类型重写 |
RewriteCond %{HTTP_USER_AGENT} ^Mozilla/3.* |
|
|
RewriteRule ^foo\.html$ foo.NS.html [L] |
|
|
RewriteCond %{HTTP_USER_AGENT} ^Lynx/.* [OR] |
|
|
RewriteCond %{HTTP_USER_AGENT} ^Mozilla/[12].* |
|
|
RewriteRule ^foo\.html$ foo.20.html [L] |
|
|
RewriteRule ^foo\.html$ foo.32.html [L] |
|
|
|
|
动态镜像远程资源 |
RewriteEngine on |
|
|
RewriteBase /~quux/ |
|
|
RewriteRule ^hotsheet/(.*)$ http://www.tstimpreso.com/hotsheet/$1 [P] |
利用了代理模块 |
|
|
|
|
RewriteEngine on |
|
|
RewriteBase /~quux/ |
|
|
RewriteRule ^usa-news\.html$ http://www.quux-corp.com/news/index.html [P] |
|
|
|
|
反向动态镜像 |
RewriteEngine on |
|
|
RewriteCond /mirror/of/remotesite/$1 -U |
|
|
RewriteRule ^http://www\.remotesite\.com/(.*)$ /mirror/of/remotesite/$1 |
|
|
|
|
负载均衡 |
RewriteEngine on |
利用代理实现round-robin效果 |
|
RewriteMap lb prg:/path/to/lb.pl |
|
|
RewriteRule ^/(.+)$ ${lb:$1} [P,L] |
|
|
|
|
|
#!/path/to/perl |
|
|
$| = 1; |
|
|
$name = "www"; # the hostname base |
|
|
$first = 1; # the first server (not 0 here, because 0 is myself) |
|
|
$last = 5; # the last server in the round-robin |
|
|
$domain = "foo.dom"; # the domainname |
|
|
$cnt = 0; |
|
|
while (<STDIN>) { |
|
|
$cnt = (($cnt+1) % ($last+1-$first)); |
|
|
$server = sprintf("%s%d.%s", $name, $cnt+$first, $domain); |
|
|
print "http://$server/$_"; |
|
|
} |
|
|
##EOF## |
|
|
|
|
静态页面变脚本 |
RewriteEngine on |
|
|
RewriteBase /~quux/ |
|
|
RewriteRule ^foo\.html$ foo.cgi [T=application/x-httpd-cgi] |
|
|
|
|
阻击机器人 |
RewriteCond %{HTTP_USER_AGENT} ^NameOfBadRobot.* |
|
|
RewriteCond %{REMOTE_ADDR} ^123\.45\.67\.[8-9]$ |
|
|
RewriteRule ^/~quux/foo/arc/.+ - [F] |
|
|
|
|
阻止盗连你的图片 |
RewriteCond %{HTTP_REFERER} !^$ |
|
|
RewriteCond %{HTTP_REFERER} !^http://www.quux-corp.de/~quux/.*$ [NC] |
自己的连接可不能被阻止 |
|
RewriteRule .*\.gif$ - [F] |
|
|
|
|
|
RewriteCond %{HTTP_REFERER} !^$ |
|
|
RewriteCond %{HTTP_REFERER} !.*/foo-with-gif\.html$ |
|
|
RewriteRule ^inlined-in-foo\.gif$ - [F] |
|
|
|
|
拒绝某些主机访问 |
RewriteEngine on |
|
|
RewriteMap hosts-deny txt:/path/to/hosts.deny |
|
|
RewriteCond ${hosts-deny:%{REMOTE_HOST}|NOT-FOUND} !=NOT-FOUND [OR] |
|
|
RewriteCond ${hosts-deny:%{REMOTE_ADDR}|NOT-FOUND} !=NOT-FOUND |
|
|
RewriteRule ^/.* - [F] |
|
|
|
|
用户授权 |
RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST} !^[email protected]\.com$ |
|
|
RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST} !^[email protected]\.com$ |
|
|
RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST} !^[email protected]\.com$ |
|
|
RewriteRule ^/~quux/only-for-friends/ - [F] |
|
|
|
|
外部重写程序模板 |
RewriteEngine on |
|
|
RewriteMap quux-map prg:/path/to/map.quux.pl |
|
|
RewriteRule ^/~quux/(.*)$ /~quux/${quux-map:$1} |
|
|
|
|
|
#!/path/to/perl |
|
|
$| = 1; |
|
|
while (<>) { |
|
|
s|^foo/|bar/|; |
|
|
print $_; |
|
|
} |
|
|
|
|
搜索引擎友好 |
RewriteRule ^/products$ /content.php |
|
|
RewriteRule ^/products/([0-9]+)$ /content.php?id=$1 |
|
|
RewriteRule ^/products/([0-9]+),([ad]*),([0-9]{0,3}),([0-9]*),([0-9]*$) /marso/content.php?id=$1&sort=$2&order=$3&start=$4 |
|
狗尾续貂一把,把我在实际中用的apache+mod_jk+tomcat+mod_rewrite来重写含有.jsp的URL设置帖出来,其实就是简单地把前面学的用了一下,供参考。
假定你已经利用mod_jk把apache和tomcat协同起来工作了,并且已经开发了一个web应用叫mywebapp。对httpd.conf改动如下:
1. 两个模块的加载顺序
...
LoadModule rewrite_module modules/mod_rewrite.so
...
LoadModule jk_module modules/mod_jk.so
...
AddModule mod_jk.c
....
AddModule mod_rewrite.c
....
2. 两个模块的设置顺序
...
<IfModule mod_jk.c>
...
JkWorkersFile "/path/to/workers.properties"
JkLogFile "/path/to/mod_jk.log"
JkLogLevel info
...
JkMount /*.jsp ajp13
</IfModule>
...
<VirtualHost *>
...
ServerName hedong.3322.org
...
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteMap id2jsp txt:/path/to/id2jsp.txt #jsp文件名编码表
RewriteMap id2db txt:/path/to/id2db.txt #数据库名字编码表
RewriteRule ^/mywebapp/([0-9]*)/([0-9]*),([0-9]*)\.html ${id2jsp:$1}\?dbname=${id2db:$2}&r=$3 [PT] #参数r表示记录ID,PT允许mod_jk继续处理请求URL
RewriteRule ^/mywebapp$ /mywebapp/ [R] #被全目录名后被省略的/
RewriteRule ^/mywebapp/$ /mywebapp/index\.jsp [PT] #PT允许mod_jk继续处理请求URL
....
</IfModule>
JkMount /mywebapp ajp13
JkMount /mywebapp/* ajp13
....
</VirtualHost>
3. 一点感想
当用mod_rewrite在服务器范围内对请求URL进行重写时,如果涉及到URL中目录层次的变化(如本来是一层目录/a/page.html,现在被写成/jsps/part1/page.jsp)时,要小心,因为:HTML/jsp页面中很多链接是相对链接,尤其是图片链接尤为常见, 即如../images/1.jpg等。
当你重写了URL的目录层次时,页面中的相对链接不受重写的影响,而致出现显示问题。
接着上面的例子,假定/jsps/part1/page.jsp中有一个链接为../images/1.jpg,而且/jsps/images/1.jpg确实存在,当不用mod_rewrite重写时,/jsps/part1/page.jsp显示正常。而重写以后,page.jsp能运行,而1.jpg的URL变成了/images/1.jpg了,虽然你在重写是改动了目录,此1.jpg的URL还是相对于/a/的。