首 页   |  资 讯   |   日 志   |   相 册   |   音 乐   |   下 载   |   朋友圈   |   论 坛   |   帮 助   |  
娱乐八封  |   灌水天堂  |   帖图专区  |   黑客攻防  |   IT业界新闻  |   网站安全讨论  |   网站推广  |   站长常用软件  |   信息发布专区  |   站长赚钱  |   域名交易与投资  |   主机质量大家评  |   网站建设源码  |   站长俱乐部  |   建站宝典  |   硬件专版  |   有问必答  |   电脑网络  |   设计天地  |   WAP建站技术交流  |   网络编程  |   网页设计  |   图形图象  |   数据库技术  |   服务器技术  |  
作者: clubadmin   发表日期: 2006-08-22 02:56   复制链接

Apache模块 mod_rewrite

说明:提供了一个基于规则的实时转向URL请求的引擎
状态:Extension
模块名:rewrite_module
源文件:mod_rewrite.c
兼容性:包含在Apache 1.3及其更新版本中

概要

``The great thing about mod_rewrite is it gives you all the configurability and flexibility of Sendmail. The downside to mod_rewrite is that it gives you all the configurability and flexibility of Sendmail.''

-- Brian Behlendorf
Apache Group

`` Despite the tons of examples and docs, mod_rewrite is voodoo. Damned cool voodoo, but still voodoo. ''

-- Brian Moore
bem@news.cmc.net

欢迎来到mod_rewrite, URL操作的瑞士军刀!

此模块提供了一个基于规则的(使用正则表达式分析器的)实时转向URL请求的引擎。 支持每个规则可以拥有不限数量的规则以及附加条件规则的灵活而且强大的URL操作机制。 此URL操作可以取决于各种测试,比如服务器变量、环境变量、HTTP头、时间标记, 甚至各种格式的用于匹配URL组成部分的查找数据库。

此模块可以操作URL的所有部分(包括路径信息部分), 在服务器级的(httpd.conf)和目录级的(.htaccess)配置都有效, 还可以生成最终请求串。此重写操作的结果可以是内部子处理,也可以是外部请求的转向, 甚至还可以是内部代理处理。

但是,所有这些功能和灵活性带来一个问题,那就是复杂性, 因此,不要指望一天之内就能看懂整个模块。

此模块从1997年7月起为Apache Group所专用,由以下这些人创建于1996年4月

http://www.uplinux.com/download/doc/apache/ApacheManual/mod/mod_rewrite.html

  共12条回复
clubadmin 发表于 2006-08-22 03:03 #1

此模块的内部处理极为复杂,但是,为了使一般用户避免犯低级错误, 也让管理员能充分利用其功能,在此仍然做一下说明。

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唯一的实现方法。 记住以下两点,会有助于更好地理解:

  1. 虽然mod_rewrite可以重写URL为URL,重写URL为文件名, 甚至重写文件名为文件名,但是目前API只提供一个URL到文件名的hook。 在Apache 2.0 中,增加了两个丢失hook以使处理过程更清晰。 但是,这样做并没有给用户带来麻烦,只需记住这样一个事实: Apache借助URL到文件名的hook而比API设计的目标功能更强大。
  2. 难以置信的是,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。

[Needs graphics capability to display]
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来调用(见下述), 并且,在SubstitutionTestString中都有效。 Figure 2 说明了反向引用被转换扩展的位置。

[Needs graphics capability to display]
Figure 2: The back-reference flow through a rule.

虽然mod_rewrite内部处理的这个过程是比较杂乱的, 但是了解这些可以帮助你阅读下文中指令的讲述。


返回
clubadmin 发表于 2006-08-22 03:14 #4

说明:设置目录级重写的基准URL
语法:RewriteBase URL-path
默认值:参见使用方法.
上下文:目录, .htaccess
覆盖项:FileInfo
状态:Extension
模块:mod_rewrite

RewriteBase指令显式地设置了目录级重写的基准URL。 在下文中,你可以看见RewriteRule可以用于目录级的配置文件中(.htaccess), 并在局部范围内起作用,即,规则实际处理的只是剥离了本地路径前缀的一部分。 处理结束后,这个路径会被自动地附着回去。 默认值是,RewriteBase physical-directory-path

在对一个新的URL进行替换时,此模块必须把这个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内部也为其他许多操作所使用。 所以,你可以充分信任其设计和实现是正确的。


返回
clubadmin 发表于 2006-08-22 03:15 #5

说明:定义重写发生的条件
语法:RewriteCond TestString CondPattern
上下文:服务器配置, 虚拟主机, 目录, .htaccess
覆盖项:FileInfo
状态:Extension
模块:mod_rewrite

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中。此模块版本对应于正在使用的Apache的版本 (比如,在Apache 1.3.14的发行版中,这个值是19990320:10)。 通常,对它感兴趣的是模块的作者。
    THE_REQUEST
    这是由浏览器发送给服务器的完整的HTTP请求行。(比如, "GET /index.html HTTP/1.1"). 它不包含任何浏览器发送的附加头信息。
    REQUEST_URI
    这是在HTTP请求行中所请求的资源。(比如上述例子中的"/index.html".)
    REQUEST_FILENAME
    这是与请求相匹配的完整的本地文件系统的文件路径名或描述.

特别注意事项:

  1. SCRIPT_FILENAME和REQUEST_FILENAME包含的值是相同的,, Apache服务器的内部request_rec结构中的filename字段。 第一个其实就是大家都知道的CGI变量名,而第二个则是( 包含了request_rec结构中的uri字段的)REQUEST_URI的一个副本,
  2. 特殊形式: %{ENV:variable} 其中的variable可以是任何环境变量。 它是通过查找Apache内部结构得到的, 或者(如果没找到的话)是由Apache服务器进程通过getenv()得到的。
  3. 特殊形式: %{HTTP:header} 其中的header可以是任何HTTP MIME头的名称。 它是通过查找HTTP请求得到的。比如: %{HTTP:Proxy-Connection}就是HTTP头 ``Proxy-Connection:''的值.
  4. 特殊形式 %{LA-U:variable} 它是一个预设的值, variable的最终值在执行一个内部的(基于URL的)子请求后决定。 在重写需要使用一个尚未有效的但是会在之后的API程序段中设置的变量的时候,就会使用这个方法。 比如,需要在服务器级配置(httpd.conf文件)中重写REMOTE_USER变量, 则,必须使用%{LA-U:REMOTE_USER},因为此变量是由认证程序段设置的, 而这个程序段是在mod_rewrite所在的URL转译程序段之后才执行的。 但是,因为mod_rewrite是通过API修正程序段来实现目录级(.htaccess file)配置的, 而这个程序段在认证程序段之前就执行了,所以用%{REMOTE_USER}就可以了。
  5. 特殊形式: %{LA-F:variable} 它是一个预设的值, variable的最终值在执行一个内部的(基于文件名的)子请求后决定。 大多数情况下和上述的LA-U是相同的.

CondPattern是条件pattern, , 一个应用于当前实例TestString的正则表达式, , TestString将会被计算然后与CondPattern匹配.

谨记: CondPattern是一个兼容perl的正则表达式, 但是还有若干增补:

  1. 可以在pattern串中使用'!' 字符(惊叹号)来实现匹配的反转
  2. 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)
    它使测试忽略大小写, , 扩展后的TestStringCondPattern中, 'A-Z' 和'a-z'是没有区别的。此标记仅作用于TestStringCondPattern的比较, 而对文件系统和子请求的测试不起作用。
  • '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等等; 如果你使用的是其他的浏览器,则你得到的是一个标准的主页。


返回
clubadmin 发表于 2006-08-22 03:18 #10

说明:定义用于关键词查找的映射函数
语法:RewriteMap MapName MapType:MapSource
上下文:服务器配置, 虚拟主机
状态:Extension
模块:mod_rewrite
兼容性:Apache 2.0.41及其更新版本中可以使用不同的dbm类型。

RewriteMap定义一个映射表, 由映射函数用于查找关键词来插入/替换字段。此查找操作的源可以是多种类型。

MapName是映射表的名称, 指定了一个映射函数,用于重写规则的字符串替换,它可以是下列形式之一:

${ MapName : LookupKey }
${
MapName : LookupKey | DefaultValue }

如果使用了这样的形式,则会在MapName中查找关键词LookupKey。 如果找到了,则被替换成SubstValue; 如果没有找到,则被替换成DefaultValue, 如果没有指定DefaultValue,则被替换成空字符串。

可以使用下列MapTypeMapSource的组合:

  • 标准纯文本
    MapType: txt, MapSource: 有效的Unix文件系统文件名

    这是重写映射表的标准形式,即, MapSource是一个纯文本文件,包含空行、注释行(以字符'#'打头), 以及每行一个的替换对,如下。

    MatchingKey SubstValue

    Example

    ##
    
    ## 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

  • 随机纯文本
    MapType: rnd, MapSource: 有效的Unix文件系统文件名

    这个与上述的标准纯文本很相似,但它有一个特殊的后处理特性: 查找完毕后,会解析其中包含的含义为``or''和``|''符号。 也就是说,会随机地选择其中之一作为实际的返回值。 虽然这看似毫无意义,但它的设计意图是, 在一个查找值是服务器名称的反向代理环境中,实现负载平衡。如:

    ##
    
    ## map.txt -- rewriting map
    ##

    static www1|www2|www3|www4
    dynamic www5|www6

    RewriteMap servers rnd:/path/to/file/map.txt

  • 散列文件
    MapType: dbm[=type], MapSource: 有效的Unix文件系统文件名

    这里的源是一个二进制格式的DBM文件,包含了与纯文本相同的内容, 但是因为它有优化的特殊表现形式,使它的查找速度明显快得多。 此类型可以是sdbm, gdbm, ndbm或db,由compile-time settings所决定。如果省略type,则使用编译时选择的缺省设置。 你可以使用任何DBM工具或者下列Perl脚本来建立这个文件,但必须保证DBM的类型正确。 建立NDBM文件的例子:

    #!/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 (<TXT>) {
    next if (/^\s*#/ or /^\s*$/);
    $DB{$1} = $2 if (/^\s*(\S+)\s+(\S+)/);
    }

    untie %DB;
    close(TXT);

    $ txt2dbm map.txt map.db

  • 内部函数
    MapType: int, MapSource: 内部的Apache函数

    这里的源是一个内部的Apache函数。 目前,还不能由你自己建立,只能使用下列已经存在的函数:

    • toupper:
      转换查找关键词为大写.
    • tolower:
      转换查找关键词为小写.
    • escape:
      转换查找关键词中的特殊字符为十六进制编码.
    • unescape:
      转换查找关键词中的十六进制编码为特殊字符.
  • 外部的重写程序
    MapType: prg, MapSource: 有效的Unix文件系统文件名

    这里的源是一个程序,而不是一个映射表文件。 程序的编制语言可以随意选择,但最终结果必须是可执行的 (, 或者是目标代码,或者是首行为'#!/path/to/interpreter'的脚本).

    此程序仅在Apache服务器启动时启动一次, 随后通过stdinstdout文件句柄与重写引擎交互。 对每个映射函数的查找操作,它从stdin接收以回车结束的查找关键词, 然后把查找结果以回车结束反馈到stdout, 如果查找失败,则返回四个字符的``NULL'' (, 对给定的关键词没有对应的值)。 此程序的最简单形式是一个1:1的映射(,key == value),如:

    #!/usr/bin/perl
    
    $| = 1;
    while (<STDIN>) {
    # ...put here any transformations or lookups...
    print $_;
    }

    但是必须注意:

    1. ``即使它看来简单而愚蠢,只要正确,就保持原样(Keep it simple, stupid)'' (KISS), 因为,在规则起作用时,此程序的崩溃会直接导致Apache服务器的崩溃。
    2. 避免犯一个常见的错误: 绝不要对stdout做缓冲I/O! 它会导致死循环! 所以上述例子中才会有``$|=1''...
    3. 使用RewriteLock指令定义一个加锁文件, 用于同步mod_rewrite和此程序之间的通讯。缺省时是没有同步操作的。

RewriteMap指令允许多次出现。 对每个映射函数都可以使用一个RewriteMap指令来定义其重写映射表。 虽然不能在目录的上下文中定义映射表, 但是,完全可以在其中使用映射表。

注意

对于纯文本和DBM格式的文件,已经查找过的关键词会被缓存在内核中,直到映射表的mtime改变了或者服务器重启了。这样,你可以把每个请求都会用到的映射函数放在规则中,这是没有问题的,因为外部查找只进行一次!

返回
最新文章