apache的rewrite非常强大,不过其官方提供的手册学习来并不大能让人理解(该手册我在我的站点导航上也列的有)。较之nginx的rewrite方面的学习,已有张宴的那本《实战Nginx:取代Apache的高性能Web服务器》的第七章对nginx的rewrite做了详细的讲解。其中虽然也对apache的rewrite做了部分对比。不过apache 的rewrite的总归没大多的底,偶然网上看到一篇示例,可以让人很快的对apache的rewrite进行理解、学习。于是顺手摘了过来。

原文是一个国外高手写的:http://www.sitepoint.com/apache-mod_rewrite-examples/

国内的那位高手翻译了一下,如下:

测试服务器安装

一些服务器没有开启mod_rewrite模块(服务器默认关闭),你可以键入一行PHP代码来确定你的服务器是否已经开启mod_rewrite模块:

1phpinfo();

在浏览器运行这段代码,找到Apache Modules section,如果mod_rewrite没有出现在其列表中,那么你就需要通知你的服务商开启mod_rewrite服务,或者..换另外一个好的服 务商。大多数服务商都会开启mod_rewrite模块,所以你很容易找到。

mod_rewrite的魔力

简单举例:创建三个文件,分别命名为 test.html,test.php和.htaccess

test.html 输入:

1<h1>This is the HTML file.</h1>
2test.php输入:
3<h1>This is the php file.</h1>
4.htaccess输入:
5RewriteEngine on
6RewriteRule ^/?test.html$ test.php [L]

将以上三个文件放test测试文件夹下,在浏览器录入:http://www.example.com/test/test.html 在浏览器中将 www.example.com替换成你自己的域名。如果运行结果显示“This is the PHP file”,那么运行成功,如果结果显示“This is the Html file”,那么肯定是哪里出了问题,请你再仔细检查下。

如果你测试成功,你是否发现了我们录入了test.html的文件名,确执行了test.php文件,是的,你已经初识了mod_rewrite的神奇。

mod_rewrite 正则表达式

现在我们可以重写URLs了!设想我们有一个显示城市信息的网站。根据URI选择城市:

1http://www.example.com/display.php?country=USA &state=California&city=San_Diego

这个URL太长并且对用户也不友好,我们更希望写成这样:http://www.example.com/USA/California/San_Diego

我们需要告诉Apache新的URL会根据一定的格式转化成这样,为了让display.php明白查询的字符,所以我们将用到正则表达式 告诉mod_rewrite匹配我们的URLs。如果你对正则表达式不太熟悉,许多网站提供了优秀的教程供你学习。在本文的末尾,我也会列举出比较好的参 考网址。如果你还是不能明白我所讲述的,那么我建议你看看后面链接中的前两篇。

一个最常用的正则就是(.)。它含有两个元素:一是“点”,表示任 意字符;二是“星”,表示以前的全部字符。所以(.)会匹配{REQUEST_URI}的所有字符。{REQUEST_URI}是URL中出去域名以及 “?”符号的所有查询字符,也是Apache 重写技术尝试匹配的字符。

包裹在正则表达式中的元素存放在“原子”内,它是在规则范围内允许被匹配的变量,所以以上正则存储了USA/California/San_Diego在“原子”中,为了解决我们的问题,我们需要三个“原子”,他们可以用左斜杠“/”进行分隔,所以正则表达式成了:

1(.*)/(.*)/(.*)

以上正则,在{REQUEST_URI}中通过两个“/”的分割存储了三个值,为了解决我们具体问题,我们得加一点限制――毕竟,第一个和最后一个原子可以匹配任何字符。
开始,我们可以添加一些特殊的字符,比如表示正则“开始”或者“结束”,“^”字符表示正则的开始而“$”表示正则的结束。

1^(.*)/(.*)/(.*)$

这个正则表示整个字符串将全部匹配,除去之前后者之后,没有任何例外。但是,这个方法仍然匹配的范围太广,我们将匹配的字符按照原子形式存放,然后通过他们形成查询字符串,所以我们必须信任我们所匹配的字符。用(.*)匹配字符串,由于允许了太多字符,所以会存在潜在的安全隐患,引用不当会使mod_rewrite运行出故障。

为了避免一些不必要的麻烦,让我们更改一下我们的原子正则,让其更加准确的匹配我们允许的字符。因为这些原子代表了地区地名,所以我们完全可以用A到Z的 大小写来表示他们,另外因为地名之间有空格,所以下划线“”也是被允许的。我们用中括弧明确我们匹配的正则,然后用短横线“-”表示连接的范围,所以被 我们允许的正则修改成了[a-zA-Z],因为我们还要避免匹配到空名字,所以用“+”来匹配在该字符之前的一个或者多个字符,所以我们的正则成了:

1^([a-zA-Z_]+)/([a-zA-Z_]+)/([a-zA-Z_]+)$

{REQUEST_URI}是以“/”开头。Apache 在更改版本的时候会更改正则引擎,一代Apache要求有斜杠而二代Apache却不允许!但是我们可以用^/?(?表示匹配字符本身或者前一个字符)来兼容两个版本的Apache,所以我们的正则又成了:

1^/?([a-zA-Z_]+)/([a-zA-Z_]+)/([a-zA-Z_]+)$

正则在手,我们就可以将原子标识到URL上了:

1display.php?country=$1 &state=$2&city=$3

$1表示国家原子;$2表示省州原子;$3表示城市原子,这里可以加上9个原子,分别用$1到$9表示。现在我们要做的就是在该目录下创建一个新的.htaccess文件,录入一下代码:

1RewriteRule ^/?([a-zA-Z_]+)/([a-zA-Z_]+)/([a-zA-Z_]+)$ display.php?country=$1 &state=$2&city=$3 [L]

然后保存,重写规则必须写在一行并且用一个空格分开每一个参数,我们用[L]或者’last’表示匹配结束。(一会有更多flags介绍)我们的重写规则已经创建完成, URL请求字符上各原子的值将经过我们匹配的正则,加上查询变量到我们的重写URL上。display.php将从查询字符中解析这些值,然后将他们送入数据库查询或者进行其他数据库操作。

如果你的正则只允许有限的几个国家,为了避免数据库错误,你可以在正则中加入一下被允许条件,例子如下:

1^/?(USA|Canada|Mexico)/([a-zA-Z_]+)/([a-zA-Z_]+)$

如果你关心查询字符串的大小写问题,由于你数据库对大写有严格的限制,那么你可以在正则表达式后面加一个[NC]FLAG位来忽略大小写,但是不要忘记在你通过$_GET 获取传递值的时候,把他们转换成小写。

如果你想用数字(0,1…..9)来表示具体的地区,那么需要更改正则中的([a-zA-Z_]+)成([0-9])来匹配单个数字,([0-9]{1,2})匹配两位数字(0到99),([0-9]+)匹配多位数字,这个对匹配数据库ID之类的非常有用。

RewriteCond 指令

现在你已经学会了mod_rewrite的一些基本用法,现在我们来学习下怎样用RewirteCond指令来处理其他各类型的情况。当RewirteCond指令明确声明以后,mod_rewrite将根据它们做出相应的处理。

RewirteCond 指令的形式和RewriteRule有点类似,形式为:

RewirteCond 被匹配的字符 正则 FLAG标识。

逻辑FLAG标识 [OR],是非常有用的,记住所有RewirteCond 以及RewriteRule指令在[LAST]指令之前,所有的逻辑与关系都会被包含。

你可以用RewirteCond指令测试服务器变量,在this is the best list of server variables一文可以找到相关说明。

举一个列子,假设我们想将“www”放入你的域名中,首先你得测试你的服务器{HTTP_HOST}变量,看www.是否已经存在,如果没有那么定向到期望的主机名:

1RewriteCond %{HTTP_HOST} !^www.example.com$ [NC]
2RewriteRule .? http://www.example.com%{REQUEST_URI} [R=301,L]

这里{HTTP_HOST}是一个Apache服务器变量,我们必须加一个“%”字符再之前。正则表达式以“!”开始表示如果正则不匹配那么条件成立。我 们当然也要转义“.”字符,将其作为一个普通字面字符而不是表示所有字符。再最后我们还加了一个忽略大小写的[NC]FLAG。

RewriteRule匹配了零或者任意一个字符,并且定向到 http://www.example.com加上原来{REQUEST_URI}值。R=301向服务器提出301请求,表明这是一个永久转向,最后一个[L]表示已经完成这段正则匹配。

RewriteCond也可以创建原子,在RewriteRule中原子是以$1…..$9表示,但是在RewriteCond中是以%1….%9表示。你可以在稍后的例子中看到具体的原子操作。

mod_rewrite Flags

  • mod_rewrite用”FLAGS”来建立重写条件以及其他属性。我们用中括弧将FLAGS包起来,放在条件或者是规则的末尾,用逗号将多个FLAGS分隔。以下列表是你需要熟悉的几个主要FLAGS:
  • last|L -[L]告诉Apache服务器一系列的条件或者是规则将在它出现后结束,换句话说就是[L]不出现,mod_rewrite将会一直执行。
  • nocase|NC -[NC]告诉Apache服务器忽略正则中的大小写,它经常被用到{HTTP_HOST}服务器参数上,因为域名里面是不会区分大小写的。
  • redirect|R -[R] 经常引用到触发可见的定向。默认情况下它是一个HTTP 302的临时重定向,但是你可以注明具体的HTTP 代码,比如你可以用[R=301]来表明这是一个永久重定向,这对搜索引擎抓取你重定向后的网页相当有用。
  • qsappend|QSA -[QSA] 用于添加新的查询参数。你可以在原查询参数后面定义新的查询参数,但命名时注意不要重复已存在的参数名。错误的引用[QSA]将会破坏原来的查询参数导致重定向错误。
  • forbidden|F -[F]告诉Apache响应请求时不提供页面。其原理就是Apache会发出一个403 HTTP相应,可以保护网站不被未经授权的或者其他盗链访问。
  • ornext|OR -[OR]作为默认值[AND]的反义词,可以通过逻辑关系将一系列重写条件组合起来。
  • next|N -[N]可以让你的重写条件循环匹配,当你不知道{REQUEST_URI}有多少字符进行匹配的时候很有用。你可以在 Apache.org’s mod_rewrite documentation page.了解到其他mod_rewrite FLAGS。

mod_rewrite注释

任何mod_rewrite代码之前都要加上RewriteEngine on这个状态,另外RewriteEngine on还可以用到其他地方。作为一个好的程序员,你知道注释对于程序来说是多么的重要。mod_rewrite允许在RewriteEngine off 与RewriteEngine on之间加上你的注释:

1RewriteEngine off
2RewriteCond %{HTTP_HOST} !^www.example.com$ [NC]
3RewriteRule .? http://www.example.com%{REQUEST_URI} [R=301,L]
4RewriteEngine on

以上所有的程序代码都不会被执行,RewriteEngine状态值的改变对新的mod_rewrite 代码开发非常有用。像你在PHP里面用/* … */注释一样,好好的运用他们。

mod_rewrite小技巧

作为站长,你要决定怎样提高你网页对访问者的辨识度以及在重写的URI地址里放入适当的信息。在创建新的URI规则的时候务必考虑详细周全一些。另外当你完成新的URI规则以后,必须回去更新以前老的链接来匹配新的规则。

当你在设计新的URI规则的时候,一定注意其唯一性。举一个先前的例子,我用了国家名,州省名,城市名作为URI的元素,因为他们在数据库里面都是唯一 的。但是如果建立一个让用户自己更新的数据库,我们没有理由让用户取的文章名字保持唯一性,所以文章一般在数据库里是以一个自动增长的ID作为唯一识别 码,这个唯一ID对URL重写规则相当友好,它可以使你的重写规则更加简洁,在URL里面可以用原子非常直接的将其值标识出来。

人们通常想映射数据库里面的值比如标题以及其他字符作为URL的标识,在mod_rewrite中有一个RewriteMap状态专门处理这种情况,但是 前提是你必须有修改Apache配置文件httpd.conf的权限。所以为了根本避免这个问题,还是直接用ID创建你的链接吧。

空格是以%20的形式展示在URL中的,所以你必须在PHP代码里面将其替换掉,PHP的str_replace函数完全可以胜任这项工作。你只需要 在$_GET获取查询值的时候,将其替换就可以了。但是在数据库中空格是难免的,所以我宁愿将空格替换成下划线,一下为PHP代码:

1$name = str_replace ( ‘ ‘, ‘_’, $name );

在添加新的URL规则的时候,小心不要打破了原先已存在的链接间的相对关系。开发人员通常会惊讶为什么有时候CSS,JAVASCRIPT,图片等文件出 现错误或者不启作用了。记住相对链接只匹配你当前URL的地址,所以你需要将这些相对链接更改成绝对链接地址,或者在你的静态网页加上HTML 标签。

13 个mod_rewrite 应用举例

先前我们举了一个给每个链接加一个www的列子,现在让我们看看用mod_rewrite还可以做哪些工作。

1.给子域名加www标记

1RewriteCond %{HTTP_HOST} ^([a-z.]+)?example.com$ [NC]
2RewriteCond %{HTTP_HOST} !^www. [NC]
3RewriteRule .? http://www.%1example.com%{REQUEST_URI} [R=301,L]

这个规则抓取二级域名的%1变量,如果不是以www开始,那么就加www,以前的域名以及{REQUEST_URI}会跟在其后。

2.去掉域名中的www标记

1RewriteCond %{HTTP_HOST} !^example.com$ [NC]
2RewriteRule .? http://example.com%{REQUEST_URI} [R=301,L]

3.去掉www标记,但是保存子域名

1RewriteCond %{HTTP_HOST} ^www.(([a-z0-9_]+.)?example.com)$ [NC]
2RewriteRule .? http://%1%{REQUEST_URI} [R=301,L]

这里,当匹配到1%变量以后,子域名才会在%2(内部原子)中抓取到,而我们需要的正是这个%1变量。

4.防止图片盗链

一些站长不择手段的将你的图片盗链在他们网站上,耗费你的带宽。你可以加一下代码阻止这种行为。

1RewriteCond %{HTTP_REFERER} !^$
2RewriteCond %{HTTP_REFERER} !^http://(www.)?example.com/ [NC]
3RewriteRule .(gif|jpg|png)$ – [F]

如果{HTTP_REFERER}值不为空,或者不是来自你自己的域名,这个规则用[F]FLAG阻止以gif|jpg|png 结尾的URL如果对这种盗链你是坚决鄙视的,你还可以改变图片,让访问盗链网站的用户知道该网站正在盗用你的图片。

1RewriteCond %{HTTP_REFERER} !^$
2RewriteCond %{HTTP_REFERER} !^http://(www.)?example.com/.*$ [NC]
3RewriteRule .(gif|jpg|png)$ http://www.example.com/hotlinked.gif [R=301,L]

除了阻止图片盗链链接,以上规则将其盗链的图片全部替换成了你设置的图片。你还可以阻止特定域名盗链你的图片:

1RewriteCond %{HTTP_REFERER} !^http://(www.)?leech_site.com/ [NC]
2RewriteRule .(gif|jpg|png)$ – [F,L]

这个规则将阻止域名黑名单上所有的图片链接请求。当然以上这些规则都是以{HTTP_REFERER}获取域名为基础的,如果你想改用成IP地址,用{REMOTE_ADDR}就可以了。

5.如果文件不存在重定向到404页面

如果你的主机没有提供404页面重定向服务,那么我们自己创建

1RewriteCond %{REQUEST_FILENAME} !-f
2RewriteCond %{REQUEST_FILENAME} !-d
3RewriteRule .? /404.php [L]

这里-f匹配的是存在的文件名,-d匹配的存在的路径名。这段代码在进行404重定向之前,会判断你的文件名以及路径名是否存在。你还可以在404页面上加一个?url=$1参数:

1RewriteRule ^/?(.*)$ /404.php?url=$1 [L]

这样,你的404页面就可以做一些其他的事情,例如默认信心,发一个邮件提醒,加一个搜索,等等。

6.重命名目录

如果你想在网站上重命名目录,试试这个:

1RewriteRule ^/?old_directory/([a-z/.]+)$ new_directory/$1 [R=301,L]

在规则里我添加了一个“.”(注意不是代表得所有字符,前面有转义符)来匹配文件的后缀名。

7.将.html后缀名转换成.php

前提是.html文件能继续访问的情况下,更新你的网站链接。

1RewriteRule ^/?([a-z/]+).html$ $1.php [L]

这不是一个网页重定向,所以访问者是不可见的。让他作为一个永久重定向(可见的),将FLAG修改[R=301,L]。

8.创建无文件后缀名链接

如果你想使你的PHP网站的链接更加简洁易记-或者隐藏文件的后缀名,试试这个:

1RewriteRule ^/?([a-z]+)$ $1.php [L]

如果网站混有PHP以及HTML文件,你可以用RewriteCond先判断该后缀的文件是否存在,然后进行替换:

1RewriteCond %{REQUEST_FILENAME}.php -f
2RewriteRule ^/?([a-zA-Z0-9]+)$ $1.php [L]
3RewriteCond %{REQUEST_FILENAME}.html -f
4RewriteRule ^/?([a-zA-Z0-9]+)$ $1.html [L]

如果文件是以.php为后缀,这条规则将被执行。

9.检查查询变量里的特定参数

如果在URL里面有一个特殊的参数,你可用RewriteCond鉴别其是否存在:

1RewriteCond %{QUERY_STRING} !uniquekey=
2RewriteRule ^/?script_that_requires_uniquekey.php$ other_script.php [QSA,L]

以上规则将检查{QUERY_STRING}里面的uniquekey参数是否存在,如果{REQUEST_URI}值为script_that_requires_uniquekey,将会定向到新的URL。

10.删除查询变量

Apache的mod_rewrite模块会自动辨识查询变量,除非你做了以下改动:

a).分配一个新的查询参数(你可以用[QSA,L]FLAG保存最初的查询变量)
b).在文件名后面加一个“?”(比如index.php?)。符号“?”不会在浏览器的地址栏里显示。

11.用新的格式展示当前URI

如果这就是我们当前正在运行的URLs:/index.php?id=nnnn。我们非常希望将其更改成/nnnn并且让搜索引擎以新格式展现。首先,我 们为了让搜索引擎更新成新的,得将旧的URLs重定向到新的格式,但是,我们还得保证以前的index.php照样能够运行。是不是被我搞迷糊了?

实现以上功能,诀窍就在于在查询变量中加了一个访问者看不到的标记符“marker”。我们只将查询变量中没有出现“marker”标记的链接进行重定 向,然后将原有的链接替换成新的格式,并且通过[QSA]FLAG在已有的参数加一个“marker”标记。以下为实现的方式:

1RewriteCond %{QUERY_STRING} !marker
2RewriteCond %{QUERY_STRING} id=([-a-zA-Z0-9_+]+)
3RewriteRule ^/?index.php$ %1? [R=301,L]
4RewriteRule ^/?([-a-zA-Z0-9_+]+)$ index.php?marker &id=$1 [L]

这里,原先的URL:http://www.example.com/index.php?id=nnnn,不包含marker,所以被第一个规则永久重 定向到http://www.example.com/nnnn,第二个规则将http://www.example.com/nnnn反定向到 http://www.example.com/index.php?marker &id=nnnn,并且加了marker以及id=nnnn两个变量,最后mod_rewrite就开始进行处理过程。
第二次匹配,marker被匹配,所以忽略第一条规则,这里有一个“.”字符会出现在http://www.example.com/index.php?marker &id=nnnn中,所以第二条规则也会被忽略,这样我们就完成了。
注意,这个解决方案要求Apache的一些扩展功能,所以如果你的网站放于在共享主机中会遇到很多障碍。

12.保证安全服务启用

Apache可以用两种方法辨别你是否开启了安全服务,分别引用{HTTPS}和{SERVER_PORT}变量:

1RewriteCond %{REQUEST_URI} ^secure_page.php$
2RewriteCond %{HTTPS} !on
3RewriteRule ^/?(secure_page.php)$ https://www.example.com/$1 [R=301,L]

以上规则测试{REQUEST_URI}值是否等于我们的安全页代码,并且{HTTPS}不等于on。如果这两个条件同时满足,请求将被重定向到安全服务URI.另外你可用{SERVER_PORT}做同样的测试,443是常用的安全服务端口

1RewriteCond %{REQUEST_URI} ^secure_page.php$
2RewriteCond %{SERVER_PORT} !^443$
3RewriteRule ^/?(secure_page.php)$ https://www.example.com/$1 [R=301,L]

13.在特定的页面上强制执行安全服务

遇到同一个服务器根目录下分别有一个安全服务域名和一个非安全服务域名,所以你就需要用RewriteCond 判断安全服务端口是否占用,并且只将以下列表的页面要求为安全服务:

1RewriteCond %{SERVER_PORT} !^443$
2RewriteRule ^/?(page1|page2|page3|page4|page5)$ https://www.example.com/%1 [R=301,L]

以下是怎样将没有设置成安全服务的页面返回到80端口:

1RewriteCond %{ SERVER_PORT } ^443$
2RewriteRule !^/?(page6|page7|page8|page9)$ http://www.example.com%{REQUEST_URI} [R=301,L]

总结

Apache的mod_rewrite模块,不仅会用在SEO以及URLs用户友好方面,还会用到某些重要的重定向工作中,如果你想学习到更多,以下是我找到的一些网上资源:
正则表达:
Great tutorial: http://gnosis.cx/publish/programming/regular_expressions.html
Cheat sheet: http://regexlib.com/CheatSheet.aspx
A regex-capable text editor: http://www.editpadpro.com/
Regex Coach: http://weitz.de/regex-coach/
mod_rewrite
Cheat sheet: http://www.ilovejackdaniels.com/cheat-sheets/mod_rewrite-cheat-sheet/

最后,吃水不忘挖井人,附下原文的地址:http://www.tsingfeng.com/?p=357 (尽管我不是从这个地址取得的,呵呵……)

在此也做个链接:http://www.path8.net/tn/?s=apache+mod_rewrite (从这位仁兄那里看到的,把这哥们的几管关于apache rewrite的也做个链接吧!)

最后,对于rewrite也发表下自己的观点:rewrite的关键是正则的用法,其实所有的正则大致上是其同的。如perl 、php……,另外在理论看不懂时,最好能找一些例子先看下,看完了再去看理论的东西。