Apache模块 mod_rewrite

Apache模块 mod_rewrite

说明 一个基于一定规则的实时重写URL请求的引擎
状态 扩展(E)
模块名 rewrite_module
源文件 mod_rewrite.c
兼容性 仅在 Apache 1.3 及以后的版本中可用

概述

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

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

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

更多的讨论、细节、示例,请查看详细的URL重写文档

Apache模块 mod_rewrite

URL重写

学习 Apache mod_rewrite 13 例

Apache 以其极高的性价比让越来越多的公司组织选择它作为服务器。其中它有一个很有用的功能就是mod_rewrite模块,一个可将用户请求的URI根据特定规则转换的模块。
这篇文章将引领你学习rewrite 规则,正则表达,rewrite条件,以及提供了一系列的例子。
首先,我假设你已经懂得URI 重写对你网站的意义为前提,如果对这一方面你想了解得更多,这里我向你推荐 mod_rewrite: A Beginner’s Guide to URL Rewriting 这本书。你可以从书中找到关于这方面得更多信息。
测试服务器安装
一些服务器没有开启mod_rewrite模块(服务器默认关闭),你可以键入一行PHP代码来确定你的服务器是否已经开启mod_rewrite模块:
phpinfo();
在浏览器运行这段代码,找到Apache Modules section,如果mod_rewrite没有出现在其列表中,那么你就需要通知你的服务商开启mod_rewrite服务,或者..换另外一个好的服 务商。大多数服务商都会开启mod_rewrite模块,所以你很容易找到。

mod_rewrite的魔力
简单举例:创建三个文件,分别命名为 test.html,test.php和.htaccess
test.html 输入:
<h1>This is the HTML file.</h1>
test.php输入:
<h1>This is the php file.</h1>
.htaccess输入:
RewriteEngine on
RewriteRule ^/?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选择城市:http://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在“原子”中,为了解决我们的问题,我们需要三个“原子”,他们可以用左斜杠“/”进行分隔,所以正则表达式成了:
(.*)/(.*)/(.*)
以上正则,在{REQUEST_URI}中通过两个“/”的分割存储了三个值,为了解决我们具体问题,我们得加一点限制――毕竟,第一个和最后一个原子可以匹配任何字符。
开始,我们可以添加一些特殊的字符,比如表示正则“开始”或者“结束”,“^”字符表示正则的开始而“$”表示正则的结束。
^(.*)/(.*)/(.*)$
这个正则表示整个字符串将全部匹配,除去之前后者之后,没有任何例外。
但是,这个方法仍然匹配的范围太广,我们将匹配的字符按照原子形式存放,然后通过他们形成查询字符串,所以我们必须信任我们所匹配的字符。用(.*)匹配字符串,由于允许了太多字符,所以会存在潜在的安全隐患,引用不当会使mod_rewrite运行出故障。
为了避免一些不必要的麻烦,让我们更改一下我们的原子正则,让其更加准确的匹配我们允许的字符。因为这些原子代表了地区地名,所以我们完全可以用A到Z的 大小写来表示他们,另外因为地名之间有空格,所以下划线“_”也是被允许的。我们用中括弧明确我们匹配的正则,然后用短横线“-”表示连接的范围,所以被 我们允许的正则修改成了[a-zA-Z_],因为我们还要避免匹配到空名字,所以用“+”来匹配在该字符之前的一个或者多个字符,所以我们的正则成了:
^([a-zA-Z_]+)/([a-zA-Z_]+)/([a-zA-Z_]+)$
{REQUEST_URI}是以“/”开头。Apache 在更改版本的时候会更改正则引擎,一代Apache要求有斜杠而二代Apache却不允许!但是我们可以用^/?(?表示匹配字符本身或者前一个字符)来兼容两个版本的Apache,所以我们的正则又成了:
^/?([a-zA-Z_]+)/([a-zA-Z_]+)/([a-zA-Z_]+)$
正则在手,我们就可以将原子标识到URL上了:
display.php?country=$1 &state=$2&city=$3
$1表示国家原子;$2表示省州原子;$3表示城市原子,这里可以加上9个原子,分别用$1到$9表示。
现在我们要做的就是在该目录下创建一个新的.htaccess文件,录入一下代码:
RewriteRule ^/?([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将从查询字符中解析这些值,然后将他们送入数据库查询或者进行其他数据库操作。
如果你的正则只允许有限的几个国家,为了避免数据库错误,你可以在正则中加入一下被允许条件,例子如下:
^/?(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.是否已经存在,如果没有那么定向到期望的主机名:
RewriteCond %{HTTP_HOST} !^www\.example\.com$ [NC]
RewriteRule .? 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之间加上你的注释:
RewriteEngine off
RewriteCond %{HTTP_HOST} !^www\.example\.com$ [NC]
RewriteRule .? http://www.example.com%{REQUEST_URI} [R=301,L]
RewriteEngine 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代码:
$name = str_replace ( ‘ ‘, ‘_’, $name );
在添加新的URL规则的时候,小心不要打破了原先已存在的链接间的相对关系。开发人员通常会惊讶为什么有时候CSS,JAVASCRIPT,图片等文件出 现错误或者不启作用了。记住相对链接只匹配你当前URL的地址,所以你需要将这些相对链接更改成绝对链接地址,或者在你的静态网页加上HTML <base>标签。
13 个mod_rewrite 应用举例
先前我们举了一个给每个链接加一个www的列子,现在让我们看看用mod_rewrite还可以做哪些工作。
1.给子域名加www标记 
RewriteCond %{HTTP_HOST} ^([a-z.]+)?example\.com$ [NC]
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule .? http://www.%1example.com%{REQUEST_URI} [R=301,L]
这个规则抓取二级域名的%1变量,如果不是以www开始,那么就加www,以前的域名以及{REQUEST_URI}会跟在其后。
2.去掉域名中的www标记 
RewriteCond %{HTTP_HOST} !^example\.com$ [NC]
RewriteRule .? http://example.com%{REQUEST_URI} [R=301,L]
3.去掉www标记,但是保存子域名 
RewriteCond %{HTTP_HOST} ^www\.(([a-z0-9_]+\.)?example\.com)$ [NC]
RewriteRule .? http://%1%{REQUEST_URI} [R=301,L]
这里,当匹配到1%变量以后,子域名才会在%2(内部原子)中抓取到,而我们需要的正是这个%1变量。
4.防止图片盗链 
一些站长不择手段的将你的图片盗链在他们网站上,耗费你的带宽。你可以加一下代码阻止这种行为。
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://(www\.)?example\.com/ [NC]
RewriteRule \.(gif|jpg|png)$ – [F]
如果{HTTP_REFERER}值不为空,或者不是来自你自己的域名,这个规则用[F]FLAG阻止以gif|jpg|png 结尾的URL
如果对这种盗链你是坚决鄙视的,你还可以改变图片,让访问盗链网站的用户知道该网站正在盗用你的图片。
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://(www\.)?example\.com/.*$ [NC]
RewriteRule \.(gif|jpg|png)$ http://www.example.com/hotlinked.gif [R=301,L]
除了阻止图片盗链链接,以上规则将其盗链的图片全部替换成了你设置的图片。
你还可以阻止特定域名盗链你的图片:
RewriteCond %{HTTP_REFERER} !^http://(www\.)?leech_site\.com/ [NC]
RewriteRule \.(gif|jpg|png)$ – [F,L]
这个规则将阻止域名黑名单上所有的图片链接请求。
当然以上这些规则都是以{HTTP_REFERER}获取域名为基础的,如果你想改用成IP地址,用{REMOTE_ADDR}就可以了。
5.如果文件不存在重定向到404页面 
如果你的主机没有提供404页面重定向服务,那么我们自己创建。
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .? /404.php [L]
这里-f匹配的是存在的文件名,-d匹配的存在的路径名。这段代码在进行404重定向之前,会判断你的文件名以及路径名是否存在。你还可以在404页面上加一个?url=$1参数:
RewriteRule ^/?(.*)$ /404.php?url=$1 [L]
这样,你的404页面就可以做一些其他的事情,例如默认信心,发一个邮件提醒,加一个搜索,等等。
6.重命名目录
如果你想在网站上重命名目录,试试这个:
RewriteRule ^/?old_directory/([a-z/.]+)$ new_directory/$1 [R=301,L]
在规则里我添加了一个“.”(注意不是代表得所有字符,前面有转义符)来匹配文件的后缀名。
7.将.html后缀名转换成.php
前提是.html文件能继续访问的情况下,更新你的网站链接。
RewriteRule ^/?([a-z/]+)\.html$ $1.php [L]
这不是一个网页重定向,所以访问者是不可见的。让他作为一个永久重定向(可见的),将FLAG修改[R=301,L]。
8.创建无文件后缀名链接
如果你想使你的PHP网站的链接更加简洁易记-或者隐藏文件的后缀名,试试这个:
RewriteRule ^/?([a-z]+)$ $1.php [L]
如果网站混有PHP以及HTML文件,你可以用RewriteCond先判断该后缀的文件是否存在,然后进行替换:
RewriteCond %{REQUEST_FILENAME}.php -f
RewriteRule ^/?([a-zA-Z0-9]+)$ $1.php [L]
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^/?([a-zA-Z0-9]+)$ $1.html [L]
如果文件是以.php为后缀,这条规则将被执行。
9.检查查询变量里的特定参数
如果在URL里面有一个特殊的参数,你可用RewriteCond鉴别其是否存在:
RewriteCond %{QUERY_STRING} !uniquekey=
RewriteRule ^/?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”标记。以下为实现的方式:
RewriteCond %{QUERY_STRING} !marker
RewriteCond %{QUERY_STRING} id=([-a-zA-Z0-9_+]+)
RewriteRule ^/?index\.php$ %1? [R=301,L]
RewriteRule ^/?([-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}变量:
RewriteCond %{REQUEST_URI} ^secure_page\.php$
RewriteCond %{HTTPS} !on
RewriteRule ^/?(secure_page\.php)$ https://www.example.com/$1 [R=301,L]
以上规则测试{REQUEST_URI}值是否等于我们的安全页代码,并且{HTTPS}不等于on。如果这两个条件同时满足,请求将被重定向到安全服务URI.另外你可用{SERVER_PORT}做同样的测试,443是常用的安全服务端口
RewriteCond %{REQUEST_URI} ^secure_page\.php$
RewriteCond %{SERVER_PORT} !^443$
RewriteRule ^/?(secure_page\.php)$ https://www.example.com/$1 [R=301,L]
13.在特定的页面上强制执行安全服务 
遇到同一个服务器根目录下分别有一个安全服务域名和一个非安全服务域名,所以你就需要用RewriteCond 判断安全服务端口是否占用,并且只将以下列表的页面要求为安全服务:
RewriteCond %{SERVER_PORT} !^443$
RewriteRule ^/?(page1|page2|page3|page4|page5)$ https://www.example.com/%1 [R=301,L]
以下是怎样将没有设置成安全服务的页面返回到80端口:
RewriteCond %{ SERVER_PORT } ^443$
RewriteRule !^/?(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.sitepoint.com/article/apache-mod_rewrite-examples/

原文作者:DK Lynn(DK Lynn is a former instructor pilot and “rocket scientist” now living in New Zealand where he operates a small business developing and hosting web sites. )

Apache mod_rewrite 实现301重定向

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

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

mod_rewrite 实现301重定向
1.只更换域名,后面的目录链接不变:

# BEGIN WP
Options +FollowSymLinks
RewriteEngine On
RewriteCond %{http_host} ^blog.woodfeed.com [NC]
RewriteRule ^(.*)$ http://woodfeed.com/$1 [L,R=301]
# END WP

# BEGIN WP
Options +FollowSymLinks
RewriteEngine On
RewriteCond %{http_host} ^blog\.woodfeed\.com$ [NC]
RewriteRule ^(.*)$ http://woodfeed.com%{REQUEST_URI} [L,R=301]
# END WP

2.更换域名的同时,将伪静态页面的.html去掉
本站blog.woodfeed.com就是用的下面代码实现的301定向。

# BEGIN WP
Options +FollowSymLinks
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([-a-zA-Z0-9_+]+).html$ http://woodfeed.com/$1 [L,R=301]
RewriteRule ^(.*)$ http://woodfeed.com%{REQUEST_URI} [L,R=301]
# END WP

mod_rewrite 正则表达式
现在我们可以重写URLs了!设想我们有一个显示城市信息的网站。根据URI选择城市:http://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在“原子”中,为了解决我们的问题,我们需要三个“原子”,他们可以用左斜杠“/”进行分隔,所以正则表达式成了:
(.*)/(.*)/(.*)
以上正则,在{REQUEST_URI}中通过两个“/”的分割存储了三个值,为了解决我们具体问题,我们得加一点限制――毕竟,第一个和最后一个原子可以匹配任何字符。
开始,我们可以添加一些特殊的字符,比如表示正则“开始”或者“结束”,“^”字符表示正则的开始而“$”表示正则的结束。
^(.*)/(.*)/(.*)$
这个正则表示整个字符串将全部匹配,除去之前后者之后,没有任何例外。
但是,这个方法仍然匹配的范围太广,我们将匹配的字符按照原子形式存放,然后通过他们形成查询字符串,所以我们必须信任我们所匹配的字符。用(.*)匹配字符串,由于允许了太多字符,所以会存在潜在的安全隐患,引用不当会使mod_rewrite运行出故障。
为了避免一些不必要的麻烦,让我们更改一下我们的原子正则,让其更加准确的匹配我们允许的字符。因为这些原子代表了地区地名,所以我们完全可以用A到Z的 大小写来表示他们,另外因为地名之间有空格,所以下划线“_”也是被允许的。我们用中括弧明确我们匹配的正则,然后用短横线“-”表示连接的范围,所以被 我们允许的正则修改成了[a-zA-Z_],因为我们还要避免匹配到空名字,所以用“+”来匹配在该字符之前的一个或者多个字符,所以我们的正则成了:
^([a-zA-Z_]+)/([a-zA-Z_]+)/([a-zA-Z_]+)$
{REQUEST_URI}是以“/”开头。Apache 在更改版本的时候会更改正则引擎,一代Apache要求有斜杠而二代Apache却不允许!但是我们可以用^/?(?表示匹配字符本身或者前一个字符)来兼容两个版本的Apache,所以我们的正则又成了:
^/?([a-zA-Z_]+)/([a-zA-Z_]+)/([a-zA-Z_]+)$正则在手,我们就可以将原子标识到URL上了:display.php?country=$1 &state=$2&city=$3
$1表示国家原子;$2表示省州原子;$3表示城市原子,这里可以加上9个原子,分别用$1到$9表示。
现在我们要做的就是在该目录下创建一个新的.htaccess文件,录入一下代码:
RewriteRule ^/?([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将从查询字符中解析这些值,然后将他们送入数据库查询或者进行其他数据库操作。
如果你的正则只允许有限的几个国家,为了避免数据库错误,你可以在正则中加入一下被允许条件,例子如下:
^/?(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 指令
当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.是否已经存在,如果没有那么定向到期望的主机名:
RewriteCond %{HTTP_HOST} !^www\.example\.com$ [NC]
RewriteRule .? 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注释
任何mod_rewrite代码之前都要加上RewriteEngine on这个状态,另外RewriteEngine on还可以用到其他地方。作为一个好的程序员,你知道注释对于程序来说是多么的重要。

mod_rewrite允许在RewriteEngine off 与RewriteEngine on之间加上你的注释:
RewriteEngine off
RewriteCond %{HTTP_HOST} !^www\.example\.com$ [NC]
RewriteRule .? http://www.example.com%{REQUEST_URI} [R=301,L]
RewriteEngine on
以上所有的程序代码都不会被执行,RewriteEngine状态值的改变对新的mod_rewrite 代码开发非常有用。像你在PHP里面用/* … */注释一样,好好的运用他们。

例子:用新的格式展示当前URI
如果这就是我们当前正在运行的URLs:/index.php?id=nnnn。我们非常希望将其更改成/nnnn并且让搜索引擎以新格式展现。首先,我 们为了让搜索引擎更新成新的,得将旧的URLs重定向到新的格式,但是,我们还得保证以前的index.php照样能够运行。是不是被我搞迷糊了?
实现以上功能,诀窍就在于在查询变量中加了一个访问者看不到的标记符“marker”。我们只将查询变量中没有出现“marker”标记的链接进行重定 向,然后将原有的链接替换成新的格式,并且通过[QSA]FLAG在已有的参数加一个“marker”标记。以下为实现的方式:
RewriteCond %{QUERY_STRING} !marker
RewriteCond %{QUERY_STRING} id=([-a-zA-Z0-9_+]+)
RewriteRule ^/?index\.php$ %1? [R=301,L]
RewriteRule ^/?([-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的一些扩展功能,所以如果你的网站放于在共享主机中会遇到很多障碍。
了解更多:http://lamp.linux.gov.cn/Apache/ApacheMenu/mod/mod_rewrite.html

Apache URL rewrite规则

1、Rewrite规则简介:
Rewirte主要的功能就是实现URL的跳转,它的正则表达式是基于Perl语言。可基于服务器级的(httpd.conf)和目录级的 (.htaccess)两种方式。如果要想用到rewrite模块,必须先安装或加载rewrite模块。方法有两种一种是编译apache的时候就直接安装rewrite模块,别一种是编译apache时以DSO模式安装apache,然后再利用源码和apxs来安装rewrite模块。
基于服务器级的(httpd.conf)有两种方法,一种是在httpd.conf的全局下直接利用RewriteEngine on来打开rewrite功能;另一种是在局部里利用RewriteEngine on来打开rewrite功能,下面将会举例说明,需要注意的是,必须在每个virtualhost里用RewriteEngine on来打开rewrite功能。否则virtualhost里没有RewriteEngine on它里面的规则也不会生效。
基于目录级的(.htaccess),要注意一点那就是必须打开此目录的FollowSymLinks属性且在.htaccess里要声明RewriteEngine on。

2、举例说明:
下面是在一个虚拟主机里定义的规则。功能是把client请求的主机前缀不是www.colorme.com和203.81.23.202都跳转到主机前缀为http://www.colorme.com.cn,避免当用户在地址栏写入http://colorme.com.cn时不能以会员方式登录网站。
NameVirtualHost 192.168.100.8:80
<VirtualHost 192.168.100.8:80>
ServerAdmin webmaster@colorme.com.cn
DocumentRoot “/web/webapp”
ServerName www.colorme.com.cn
ServerName colorme.com.cn
RewriteEngine on #打开rewirte功能
RewriteCond %{HTTP_HOST} !^www.colorme.com.cn [NC]#声明Client请求的主机中前缀不是www.colorme.com.cn,[NC]的意思是忽略大小写
RewriteCond %{HTTP_HOST} !^203.81.23.202 [NC]#声明Client请求的主机中前缀不是203.81.23.202,[NC]的意思是忽略大小写
RewriteCond %{HTTP_HOST} !^$#声明Client请求的主机中前缀不为空,[NC]的意思是忽略大小写
RewriteRule ^/(.*) http://www.colorme.com.cn/ [L]# 含义是如果Client请求的主机中的前缀符合上述条件,则直接进行跳转到http://www.colorme.com.cn/, [L]意味着立即停止重写操作,并不再应用其他重写规则。这里的.*是指匹配所有URL中不包含换行字符,()括号的功能是把所有的字符做一个标记,以便于后面的应用.就是引用前面里的(.*)字符。
</VirtualHost>

例二.将输入 folio.test.com 的域名时跳转到profile.test.com
listen 8080
NameVirtualHost 10.122.89.106:8080
<VirtualHost 10.122.89.106:8080>
ServerAdmin webmaster@colorme.com.cn
DocumentRoot “/usr/local/www/apache22/data1/”
ServerName profile.test.com
RewriteEngine on
RewriteCond %{HTTP_HOST} ^folio.test.com [NC]
RewriteRule ^/(.*) http://profile.test.com/ [L]
</VirtualHost>
3.Apache mod_rewrite规则重写的标志一览
1) R[=code](force redirect) 强制外部重定向
强制在替代字符串加上http://thishost[:thisport]/前缀重定向到外部的URL.如果code不指定,将用缺省的302 HTTP状态码。
2) F(force URL to be forbidden)禁用URL,返回403HTTP状态码。
3) G(force URL to be gone) 强制URL为GONE,返回410HTTP状态码。
4) P(force proxy) 强制使用代理转发。
5) L(last rule) 表明当前规则是最后一条规则,停止分析以后规则的重写。
6) N(next round) 重新从第一条规则开始运行重写过程。
7) C(chained with next rule) 与下一条规则关联
如果规则匹配则正常处理,该标志无效,如果不匹配,那么下面所有关联的规则都跳过。
8) T=MIME-type(force MIME type) 强制MIME类型
9) NS (used only if no internal sub-request) 只用于不是内部子请求
10) NC(no case) 不区分大小写
11) QSA(query string append) 追加请求字符串
12) NE(no URI escaping of output) 不在输出转义特殊字符
例如:RewriteRule /foo/(.*) /bar?arg=P1\%3d$1 [R,NE] 将能正确的将/foo/zoo转换成/bar?arg=P1=zed
13) PT(pass through to next handler) 传递给下一个处理
例如:
RewriteRule ^/abc(.*) /def$1 [PT] # 将会交给/def规则处理
Alias /def /ghi
14) S=num(skip next rule(s)) 跳过num条规则
15) E=VAR:VAL(set environment variable) 设置环境变量
4.

Apache rewrite例子集合
在 httpd 中将一个域名转发到另一个域名
虚拟主机世界近期更换了域名,新域名为 www.wbhw.com, 更加简短好记。这时需要将原来的域名 webhosting-world.com, 以及论坛所在地址 webhosting-world.com/forums/ 定向到新的域名,以便用户可以找到,并且使原来的论坛 URL 继续有效而不出现 404 未找到,比如原来的 http://www. webhosting-world.com/forums/-f60.html, 让它在新的域名下继续有效,点击后转发到 http://bbs.wbhw.com/-f60.html, 这就需要用 apache 的 Mod_rewrite 功能来实现。

在< virtualhost> 中添加下面的重定向规则:

RewriteEngine On
# Redirect webhosting-world.com/forums to bbs.wbhw.com
RewriteCond %{REQUEST_URI} ^/forums/
RewriteRule /forums/(.*) http://bbs.wbhw.com/$1 [R=permanent,L]

# Redirect webhosting-world.com to wbhw.com
RewriteCond %{REQUEST_URI} !^/forums/
RewriteRule /(.*) http://www.wbhw.com/$1 [R=permanent,L]

添加了上面的规则以后, 里的全部内容如下:
< virtualhost *:80>
ServerAlias webhosting-world.com
ServerAdmin admin@webhosting-world.com
DocumentRoot /path/to/webhosting-world/root
ServerName www.webhosting-world.com

RewriteEngine On
# Redirect webhosting-world.com/forums to bbs.wbhw.com
RewriteCond %{REQUEST_URI} ^/forums/
RewriteRule /forums/(.*) http://bbs.wbhw.com/$1 [R=permanent,L]

# Redirect webhosting-world.com to wbhw.com
RewriteCond %{REQUEST_URI} !^/forums/
RewriteRule /(.*) http://www.wbhw.com/$1 [R=permanent,L]
< /virtualhost>

URL重定向例子一:
1.http://www.zzz.com/xxx.php-> http://www.zzz.com/xxx/
2.http://yyy.zzz.com-> http://www.zzz.com/user.php?username=yyy 的功能

RewriteEngine On
RewriteCond %{HTTP_HOST} ^www.zzz.com
RewriteCond %{REQUEST_URI} !^user\.php$
RewriteCond %{REQUEST_URI} \.php$
RewriteRule (.*)\.php$ http://www.zzz.com/$1/ [R]

RewriteCond %{HTTP_HOST} !^www.zzz.com
RewriteRule ^(.+) %{HTTP_HOST} [C]
RewriteRule ^([^\.]+)\.zzz\.com http://www.zzz.com/user.php?username=$1

例子二:
/type.php?typeid=* –> /type*.html
/type.php?typeid=*&page=* –> /type*page*.html

RewriteRule ^/type([0-9]+).html$ /type.php?typeid=$1 [PT]
RewriteRule ^/type([0-9]+)page([0-9]+).html$ /type.php?typeid=$1&page=$2 [PT]

5.使用Apache的URL Rewrite配置多用户虚拟服务器
要实现这个功能,首先要在DNS服务器上打开域名的泛域名解析(自己做或者找域名服务商做)。比如,我就把 *.semcase.com和 *.semcase.cn全部解析到了我的这台Linux Server上。

然后,看一下我的Apache中关于*.semcase.com的虚拟主机的设定。

#*.com,*.osall.net
<VirtualHost *:80>
ServerAdmin webmaster@semcase.com
DocumentRoot /home/www/www.semcase.com
ServerName dns.semcase.com
ServerAlias dns.semcase.com semcase.com semcase.net *.semcase.com *.semcase.net
CustomLog /var/log/httpd/osa/access_log.log” common
ErrorLog /var/log/httpd/osa/error_log.log”
<Directory /home/www/www.semcase.com>
AllowOverride None
Order deny,allow
#AddDefaultCharset GB2312
</Directory>
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{HTTP_HOST} ^[^.]+\.osall\.(com|net)$
RewriteRule ^(.+) %{HTTP_HOST}$1 [C]
RewriteRule ^([^.]+)\.osall\.(com|net)(.*)$ /home/www/www.semcase.com/sylvan$3?un=$1&%{QUERY_STRING} [L] </IfModule>
</VirtualHost>
在这段设定中,我把*.semcase.net和*.semcase.com 的Document Root都设定到了 /home/www/www.semcase.com
但是,继续看下去,看到<IfModule mod_rewrite.c>…</IfModule>配置了吗?在这里我就配置了URL Rewrite规则。
RewriteEngine on#打开URL Rewrite功能
RewriteCond %{HTTP_HOST} ^[^.]+.osall.(com|net)$#匹配条件,如果用户输入的URL中主机名是类似 xxxx.semcase.com 或者 xxxx.semcase.cn 就执行下面一句
RewriteRule ^(.+) %{HTTP_HOST}$1 [C]#把用户输入完整的地址(GET方式的参数除外)作为参数传给下一个规则,[C]是Chain串联下一个规则的意思
RewriteRule ^([^.]+).osall.(com|net)(.*)$ /home/www/dev.semcase.com/sylvan$3?un=$1&%{QUERY_STRING} [L]# 最关键的是这一句,使用证则表达式解析用户输入的URL地址,把主机名中的用户名信息作为名为un的参数传给 /home/www/dev.semcase.com目录下的脚本,并在后面跟上用户输入的GET方式的传入参数。并指明这是最后一条规则([L]规则)。注意,在这一句中指明的重写后的地址用的是服务器上的绝对路径,这是内部跳转。如果使用http://xxxx这样的URL格式,则被称为外部跳转。使用外部跳转的话,浏览着的浏览器中的URL地址会改变成新的地址,而使用内部跳转则浏览器中的地址不发生改变,看上去更像实际的二级域名虚拟服务器。

深入理解Apache的mod_rewrite,即Url重写

原文:A Deeper Look at mod_rewrite for Apache

人们一提到.htaccess配置文件,首先映入他们脑海的就是用mod_rewrite进行URL地址重定向。对mod_rewrite的看法各不相同,为了就人们对mod_rewrite是怎么认识的有一个快速的看法,我在twitter上搜索了一下”mod_rewrite”,并且将我写这篇文章时的前几个搜索页面的结果找出来:

midk:啊!.hatccess和mod_rewrite是如此的痛苦……

basterzenbach:我喜欢mod_rewrite。在我的有生之年,我都可以用它工作,并且还是不能精通它——太强大了。

mikemackay:仍然喜欢mod_rewrite的灵活性——又得到了拯救。这往往容易被忽略……并且要比你想想的要简单!

hostpc:我讨厌mod_rewrite。无法用它正常工作。

awanderingmind:噢,Wordpress 和apache,你们带给了我烦恼。该死的mod_rewrite!

danielishiding:为什么mod_rewrite不工作了!该死!

我注意到人们清楚的认识到了mod_rewrite的强大,但是往往在语法面前望而却步。考虑到Apache的mod_rewrite文档在前面几页说了同样的问题,这并不奇怪:

“mod_rewrite例子和文档的数量,尽管可以以吨来计算,但是它是巫术。该死的冷漠的巫术,但仍然是巫术。“——-布莱恩摩尔

太糟糕了!因此,在本文中。我真的试图使mod_rewrite的难度降低一个档次。我不仅要去尝试解决mod_rewrite的的语法,还要设法提供一个工作流程,使你可以通过它调试和解决你的mod_rewrite问题。我也会给你一些有用的现实世界中的例子。

然而,在开始之前,我还要做一个警告。许多学科,尤其是这个,除非你自己动手尝试,否则你是不会学会的!这就是为何我会更专注于教授一个调试工作流程。像往常一样,如果你还没有加载模块,我会告诉你如何安装好你的系统。我敦促你们在你们自己服务器上做这些例子,如果是测试环境,则更好。你的经验和成功次数越多,你就会越容易将这种知识扩展到更高级的例子和应用。享受吧。

mod_rewrite的是什么?

mod_rewrite的是一个Apache模块,可使服务器操纵请求的网址。根据一系列规则对传入的网址进行检查,规则中包含一个正则表达式来检测特定的格式。如果在地址中发现了一个格式,并且满足适当的条件,该格式就会被一个替代的字符串或者是动作取代。这一过程一直在进行着,直到没有更多的规则或是程序被明确告诉停止。

上面的内容可以总结为以下3点:

*有一个按顺序排列的处理规则列表。

*如果有一个规则相匹配,它会检查那条规则满足的条件。

*如果一切都匹配,它会替代或这是做出一个动作。

mod_rewrite的优点

用这样的一个地址重定向工具有很明显的优点,但是有一些东西也不是很明显。

人们用mod_rewrite的主要原因是为了将丑陋的、神秘的网址转化为所谓的“友好的地址”或者是“干净的地址”。新网址通过多种方式变的友好,而不是仅仅一种。它们是用户友好的,表现在可更容易为人类所理解,瞥一眼就可以,并且用户可能自己来操纵网址。作为额外的奖励,这些网址对搜索引擎来说也是友好的。创建友好的网址是一个搜索引擎优化技术,网址是一种有效描述他链接的内容的方式。看看下面的例子:

不是很友好: http://example.com/user.Php?id=4512

比较友好: http://example.com/user/4512/

甚至更好:     http://example.com/user/Joe/

最后的链接不仅仅是看上去变的简单了,它还可以使搜索引擎从中提取语义。这种基本的URL重写机制是使用mod_rewrite的一种方式。然而,正如你将要看到的一样,除了这些简单的转换,它还可以作很多的事情。

将同一个例子扩展一下,一些人声称通过用mod_rewrite改变你的网址可以获得安全效益。给出同一个例子,想像,考虑一下下面这个对用户id的攻击:

http://example.com/user.php?id=AHHHHHH

http://example.com/user/AHHHHHH/

第一个例子是明确的PHP脚本调用,并且必须得处理无效的ID号。写得不好的脚本可能会失败,更极端的情况是(写得不好的Web应用程序)错误的输入可能导致数据损坏。然而,如果只给用户显示友好的网址,也就是说他们甚至不知道user.php网页的存在,他们可能只知道友好的URL结构。试图在这种情况下进行的攻击可能在读取PHP脚本之前就已经失败了。这是因为mod_rewrite的核心是正则表达式的格式匹配。在上面的例子中,你的地址中可能有一个数字,比如( d +),而不是字符,像a-z,当重写模块找到的是字母而不是数字时,重写就会失败。

从安全的角度讲,这种额外的抽象功能是不错的。如果你愿意,你甚至可以防止直接访问原始PHP脚。不过,我们决不能使用mod_rewrite来替换一般的安全措施,你的脚本应当在服务器端进行验证。

在服务器上启用mod_rewrite模块

就像启用.htaccess支持一样,启用mod_rewrite或者是其他apache模块必须修改全局配置文件(httpd.conf)。就像前面说的一样,由于mod_rewrite用的是如此广泛,主机提供商几乎总是启用这个模块的。然而,如果你怀疑你的主机提供商没有启用它(我们会在下面测试),你应当联系他们,并且他们很乐意启用它。

如果你是自己安装的Apache,毫无疑问,当编译Apache的时候,要将Mod_rewrite模块包括进来,因为默认情况下是不包括它的。然而,它是用的如此普遍,几乎所有的安装指南,包括Apache的安装文档都会在他们的示例中指出如何将它编译进来。然而,预先包装的版本已经将它启用了。如果你正在读这篇文章,那么你的Apache有99%的可能已经将mod_rewrite模块编译进来了,所以你只须进入下一个步骤。

如果你是你们网络的网络管理员,并且你想确认一下你已经加载了这个模块,你应当检查一下httpd.conf文件。在配置文件有很大一部分用于加载那一大堆模块。下面的行可能会出现在文件中,如果是,好极了!如果它被注释掉了,或者说是在它前面有一个#号,哪么你只需将#号删除掉,留下下面的这一部分:

1、LoadModule rewrite_module modules/mod_rewrite.so

老版本的Apache1.3,可能需要你在LoadModule目录中加上以下目录:

# Only in Apache 1.3
AddModule mod_rewrite.c

然而,这好像在Apache 2及以后的版本中消失了,只需要LoadModule指令。

如果你不得不修改配置文件,那么你必须重启你的apache服务。你要记住备份你的原始文件,以防万一你需要将它还原回以前的版本。

测试mod_rewrite模块

你可以通过多种方式测试mod_rewrite模块是否启用(或者是工作)了,最简单的方法是查看PHP的phpinfo函数的输出。创建下面的这个非常简单的PHP页面,在你的浏览器中打开它,并且在输出结果中找一下”mod_rewrite”。

<?php phpinfo(); ?>

mod_rewrite应该会显示在网页的“Loaded Modules”部分中,就像这样:

点击浏览下一页

图片看不清楚?请点击这里查看原图(大图)。

然而,如果你用的不是Php(虽然在接下来的教程中我会用它),还有很多方式来测试。apache有许多命令行工具。

在我的基本身份验证的第一个教程中,我提到了在htpasswd的工具。你可以使用诸如apachectl或者httpd的其他工具直接对模块进行测试。有命令行开关可以使你检查现有的已经安装加载的模块。您可以执行下面的命令来得到一个所有已加载的模块的列表。

shell> apachectl -t -D DUMP_MODULES

这里我展示的是这个命令的帮组页面。然后,我运行了这个命令,并在结果中查找了“rewrite”,有一行输出与之相匹配。

点击浏览下一页

最后,如果你还是不能确定它是否启用了,像以前一样将它注释掉,看看会发生什么!之后,我会介绍语法,但这里仅仅是一个测试,看看他是否工作了。下面的.htaccess文件将重定向任何给定的文件夹请求到good.html文件,这意味着如果你的mod_rewrite工作了,你应该看到good.html。如果mod_rewrite不工作,那么你会看到一个带警告的index.html。

# Redirect everything in this directory to ”good.html”
RewriteEngine on
RewriteRule .* good.html

下面是正确的和错误的页面:

点击浏览下一页

点击浏览下一页

.htaccess的内容

通常情况下,你可以写在.htaccess文件中的内容也可以写到全局配置文档中。在mod_rewrite中,如果你将一条规则放的文件不同,会有一点儿小差异。最明显的是:

如果你将【……】规则放到了.htaccess文件中,目录的前缀(/)在REQUEST_URI变量中会被去掉,因为所有的请求会被自动假设是现在目录的相对地址。——apache文档

有一点要记住,如果你在网上看例子或者是你自己在测试一个实例,要注意前面的斜线!当我将一些例子放到一起的时候,我将在下面试图澄清这些问题。

正则表达式

本教程不打算教你正则表达式。对于那些你知道的正则表达式,mod_rewrite中用到的正则表达式会根据Apache版本的不同而有所改变。在Apache 2.0中,他们似乎是与Perl兼容(pcre)的正则表达式。这意味着许多你所使用的简写,例如w的意思是[A-Za-z0-9],d的意思是 [0-9],以及更多不存在的简写。但是,我的公司使用的是Apache 1.3,并且Apache1.3的正则表达式是比较有限的。

如果你不知道正则表达式,下面这些有用的教程会让你快速入门:

Nettuts very own Jeffrey’s Crash Course
The Absolute Bare Minimum Every Programmer Should Know About Regular Expressions
Quick And Practical Tutorial
Smashing Magazine Links on Regular Expressions

还有每个人都应该知道的一些引用:

Popular Added Bytes Cheatsheet For Regular Expressions
Added Bytes Cheatsheet for mod_rewrite
Explain Regular Expressions

如果有还没有花时间去学习正则表达式,我强烈建议你花点时间学习一下。因为通常情况下,他们没有你想象的那么复杂。我从多年的经验中选择了上面的那些关于正则表达式的链接,我觉得这些指南对于学习最基础的东西来说,写的很好。如果你想有效的利用mod_rewrite,正则表达式是至关重要的,在其他方面,了解他们也很有用,如在你最喜爱的代码编辑器中使用“查找/替换”。

初次体验

好了,你等待的耐心已经足够大了,让我们快速的看一个例子。这个例子在链接的源代码中有。这里只给出.htaccess文件的代码:

# Enable Rewriting
RewriteEngine on

# Rewrite user URLs
#   Input:  user/NAME/
#   Output: user.Php?id=NAME
RewriteRule ^user/(w+)/?{$selection}nbsp;user.php?id=$1

在我对它做任何解释之前,我会先讲解一下目录中的另外一个文件。

目录中包含两个文件:index.php和user.php。index.php中有一些指向user页面的链接或者是各种各样的格式。php代码用来显示页面被请求了,并检查传过来的”id”参数。下面是user.php的代码:

<?php

// Get the username from the url
$id =
{$selection}
GET[‘id’];

?><!DOCTYPE html PUBLIC ”-//W3C//DTD XHTML 1.0 Strict//EN”
“http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd”>
<html xmlns=”http://www.w3.org/1999/xhtml” xml:lang=”en” lang=”en”>
<head>
<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8″/>
<title>Simple mod\_rewrite example</title>
<style type=”text/css”> .green { color: green; } </style>
</head>
<body>
<h1>You Are on user.php!</h1>
<p>Welcome: <span class=”green”><?php echo $id; ?></span></p>
</body>
</html>

这个例子有一些不同的地方。首先,请注意URL重写必须通过 RewriteEngine指令启用!如果你的htaccess文件要使用重写规则,应始终包括这行,否则你不能确定它是否启用了!作为一个经验法则,总是将它包括进去并确保每个.htaccess文件中你只包含了一个。字符串“on”不区分大小写,因此,当你在网上看到其他的例子用的是“On”,这是可以接受的。

第一个重写规则是用来处理user.php页面的。就像这些注释说的一样,我们正在将友好的网址重写为正常的URL格式。为了做到这一点,当输入友好的网址时,事实上,我们将它转化成了标准的查询字符串URL。将它分解开,我们就得到了:

T规则:

RewriteRule ^user/(w+)/?$ user.php?id=$1

匹配模式:

^             输入的开头

user/          以“user/“开始的请求地址

(w+)        提取所有的字母,并将提取的结果传给$1

/?             可选的斜线 “/”

$              输入结束

替换为:

user.php?id=   要用到的字符串.

$1             上面第一个提取到的字符串。

下面是一些例子及对上面每行话的解释:

User.php

输入 匹配 提取 输出 结果
user.php?id=joe No user.php?id=joe Normal
user/joe Yes joe user.php?id=joe Good
user/joe/ Yes joe user.php?id=joe Good
user/joe/x No user/joe/x Fail

因此,第一个例子不会受到重写规则的影响,并且可以正常访问。第二个和第三个例子与重写规则相匹配,会根据重写规则被改写,可以正常访问,最后一个例子不符合规则且无法访问。服务器没有用户目录,不能试图找到它。这是预期的结果,因为user/joe/ x是一个无法访问的网址!

这个例子比较容易理解。然而,为了澄清任何更复杂的事情,就像我现在做的一样,我必须要花好几分钟去注意细节。在下一节中,我们将举一个更复杂的例子,这个例子涉及所有重写的核心内容。

注意:如果这个例子不能在你的机器上运行,可能是由于你的Apache或mod_rewrite 版本与PCRE不兼容。请尝试着将^user/(w+)/?$改为 ^user/([a-z]+)/?$。 请注意,我没有使用w的缩写。如果此版本可以在你的机器上正确运行,那么你不要使用正则表达式的缩写,要使用较长的当量(见上面的正则表达式节)。

执行流程详情

重写规则的执行流程比较简单,但不是完全明了。因此,我将叙述一下细节。这一切都开始于用于向你的服务器提出请求的时候。他们在浏览器地址栏中键入网址,他们的浏览器将之转换成一个HTTP请求并发送到服务器,Apache收到这一请求,并将之解析成片断。下面是一个例子:

请注意,每当我提到apache的变量,我使用了一种奇怪的语法:%{APACHE_VAR}。这是因为它类似于mod_rewrite访问变量的语法。不过,括号内名字是重要的。

那么,mod_rewrites是如何工作的?如果你用的是.htaccess文件,那么你只需输入REMOTE_URI部分,但没有开始的斜线!我之前提到过这个,对大多数刚刚开始用它的人来说,这显得很混乱。如果你是将它添加到了全局配置文件里,那么你应当加上斜线。

为了说的更具体一点儿,下面是Apache的文档中对mod_rewrite中“URL部分”的描述:

该模式始终是对请求的URL路径进行正则表达式匹配(主机名后面的那部分,但在任何以问号为标志的显示查询字符串的前面)。 Apache文档

为了消除大家的模糊不清的认识,下面用黄色高亮显示的两个网址是mod_rewrite在.htaccess文件中的“部分网址”:

点击浏览下一页

在本节接下来的部分我将利用这两个网址来描述执行的流程。我将把第一个网址称为“绿色”网址,第二个称为“蓝色”网址。在整个分析中,我还将使用“URL部分”来表示开始处没有斜线的REMOTE_URI。

对于那些想要100%的区分开这两中教法的人,我这里说的URL其实是URI。一个统一资源标识符(URI)的定义有别于统一资源定位符(URL)。一个 URI只是标识资源在哪里,这意味着存在多个URl可以指向相同的资源,但是他们是不同的地址。一个URI可能在找到资源之前经过了数次跳动和重定向。然而,URL却是标识资源的确切位置。这种细微的差别随着时间的推移,变得越来月模糊,以至于没有人关心它们的差异。我将继续使用术语URL,因为人们用它更舒服一些。

所以,现在我们知道重写规则将要采取行动了。一旦Apache已解析出请求,它就会将它翻译成它认为的文件,并去读取该文件。在这个过程中,他会搜索.htaccess文件。假设,.htaccess文件起用了RewriteEngine,那么任何重写规则都可以更改网址。地址的急剧变化(如 Apache将某个网址原来指向的目录替换为另外一个目录)将促发Apache发出子请求,进而获取新的文件。

在大多数情况下,你是可以看到子请求的。这些实现细节对于了解你写的或使用的大多数简单的重写规则来说并不重要。更重要的是知道Apache如何处理.htaccess文件中的重写规则。

.htaccess文件中的规则会以它们出现的顺序被处理。请注意,每个重写规则都是“部分网址”,也就是说类似于REMOTE_URI。当一个规则促发替换的时候,修改后的“部分网址”将被移交给下一个规则。这意味着,正在处理的网址可能已经被前面的规则修改过了,网址会被每个相匹配的规则更新。这一点很重要!

下面是一个流程图,它试图提供URL在通过含有多个规则的.htaccess文件时的执行过程:

点击浏览下一页

请注意,流程图的顶部的将会与重写规则进行匹配的数据是“网址部分”,如果替换成功,则修改过的网址会与下一条规则继续匹配。

前面,我介绍了重写条件,但是没有详谈。每个重写过程都与一条重写规则相关联。条件出现在与它们有联系的规则之前,但是只有与规则相匹配了,网址才会得到评估。正如流程图所示,如果与一个重写规则相匹配了,Apache会检查这条规则有什么条件(即做出替换是否需要其他条件)。如果没有条件,那么将进行替代并进入下一步。如果需要条件,那么只有所有的条件都成立的时候,才会进行替换。举一个具体的例子。

我用的网址实际上是我放在”profile_example”目录中的源代码的一部分。这和前面的例子user.Php一样,但现在有一个profile.php页面,一个附加的重写规则,和一个条件!让我们看一下这段代码和它在Apache中的执行过程:

点击浏览下一页

这里有两个规则。规则#1和我们前面看到的user例子一样。规则#2是新加的,注意它有一个条件。在“网址部分”我们已经讨论过会从上到下遍历每一条规则。因此,必须先经过规则#1,然后才是规则#2。

理解这个例子的关键是首先要了解目标。在这个例子中,我允许友好网址,但实际上,我要明确地禁止直接访问Php页面。请注意,有些人可能会说这是一个坏主意。他们可能会说,作为开发者,这个调试起来会更难。是这样的,事实上我不推荐做这样的小把戏,但是作为一个例子,这很好。更实际的使用mod_rewrite的例子会在本教程后面的部分看到。

因此,在这一点的基础上,让我们看看我绿色网址发生了什么。这次,我们希望取得成功。

点击浏览下一页

在最上面,可以看到apache的THE_REQUEST变量。我把它放在上方是因为它不像我们要处理的其他Apache变量,在请求期间这个变量的值不会改变。这就是规则#2使用%{THE_REQUEST}的原因之一。在THE_REQUEST下面,我们看到绿色的“网址部分”开始进入第一个规则了:

URL匹配成功。

没有任何条件,因此继续。

进行替换。

没有任何标志,因而继续。

通过第一条规则后,该网址已经更改。网址已被重写成了profile.php?id=joe,这时,Apache会听下来更新它的大多数变量。我们看不到?id=joe,新的“网址部分”会进入下一条规则。这是我们第一次遇到条件:

URL匹配成功。

还有条件,我们将检查条件。

THE_REQUEST不包含profile.php,因此条件检查失败。

因为不满足条件,所以我们忽略替换和标志。

这条规则没有改变URL。

这一次,我们通过了所有的重写规则,并且profile.php?id=joe 页会被正确的提取。

下面介绍关于如何执行蓝色的URL,这一次,我们要失败:

点击浏览下一页

我再次将THE_REQUEST的值放在了最上面,蓝色的“网址部分”进入规则#1:

URL匹配失败。

其他的一切都被忽略,网址没有改变,进入下一步。

第一个规则很容易。通常情况下,如果URL匹配失败,那么它会原样进入下一步。现在进入规则#2:

URL匹配成功。

有条件需要比较,因此会先测试条件。

请求包含 profile.php,因此条件测试通过。

通过所有的条件,我们可以替换网址了。

”-”是一个特殊的替换,这一为着任何东西都不会改变了。

规则中有标志,因此我们处理标志。

有一个F标志,意思是返回一个禁止访问响应。

一个403 Forbidden响应发送到了客户端。

有几件事情值得再重复一次。为了使替换发生,所有条件都必须检查通过。在上面这种情况下只有一个条件,并且检查通过了,所以,可以对网址进行替换。注意,有一种特殊的替换,不改变任何东西。当你想用标志做点儿什么的时候,这种方法相当有用,在这种情况下,我们就会这样做(指的是,替换后什么都不改变)。

下面是一个URL例子的分解和它们的返回值表:

Profile.php

输入 匹配 获取 输出 结果
profile.php?id=joe Yes (#2) profile.php?id=joe Forbidden
profile/joe Yes (#1) joe profile.php?id=joe Good
profile/joe/ Yes (#1) joe profile.php?id=joe Good
profile/joe/x No profile/joe/x Fail

语法

在介绍重写规则(RewriteRule)和重写条件(RewriteCond)的语法之前,我建议你先下载theAddedBytes Cheatsheet。这是因为cheatsheet表列出了最有用的服务器变量,标志,并有正则表达式技巧,甚至还有几个例子。在那里面有这么多的内容,将它们关联起来是很难的。

让我们从重写规则开始。如果你想做一些特殊的事,你可以随时查看apache的关于重写规则的文档。下面是我的概述:

点击浏览下一页

这个表显示了什么类型的标志是可用的。许多指南涵盖了flags的详细讲解,我会通过通过下面的例子介绍一下我认为的用的最多的flag。

下面是Apache的RewriteCond文档和我的概述:

点击浏览下一页

调试流程

当你使用mod_rewrite制定新规则的时候,总是以一个简单的规则开始,并且逐步发展为最后的版本。从来不要试图一下子将所有的事情办好。对于重写条件的编写,这个道理同样适用。一次添加规则和条件,多次测试!

我正在介绍的这种方法的关键之处是它可以让你知道是否你的一个改变不能正常工作或者是使某个地方运行不正常。当一次做得太多的时候,你会不可避免的遇到错误,并且你将不得不恢复你所做的一切更改来找出问题到底是出在那儿了。这是一项非常艰难的工作,可能会导致你的失望。不过,如果你总是稳步推进,并且在每一步都可以到达一个可以正常运行的点,你的处境就会稍好一点儿。

人们往往忽略这条建议,创建了一个复杂的规则,最终却不能工作。几个小时后,他们发现问题没有出现在复杂的部分,反而只是简单的正则表达式错误,如果他们按我上面解释的构造规则的换,问题可能早已经被发现了。在反向工程拆解规则上,这种方法也适用。这种做法将极大降低人们的失望!

例子

在下面的例子中,我总是会假设网站的域名是example.com。此域名很重要,因为它会影响HTTP_HOST变量以及在你的网站上将指定的URL 重定向到另一个文件。如果你打算修改你的任何一个例子,以便它可以在你的网站上工作,请记住这一点。如果是这样,只需用你的域名替换 “example.com”。例如,Nettuts会将“example.com”改为“nettuts.com”。

删除www

这是最经典的重写规则。这将使得每个通过http://www.example.com访问你网站的人会得到一个硬性的重定向,从而其浏览器的地址栏中也将进行相应更新。

RewriteEngine on
RewriteCond %{HTTP_HOST} ^www.example.com{$selection}nbsp;[NC]
RewriteRule ^(.*){$selection}nbsp;http://example.com/$1 [R=301,L]

这条规则与任何输入的地址都匹配,并将所有的地址保存为$1。本例中的重要组成部分是条件语句,这个条件会检查HTTP_HOST变量,看它是否以“www”开始。如果是这样,重写就会发生:

替代的是一个完整的URL (它以http://开始)

替代中包含早期抓取的 $1。

[R=301]标志将浏览器重定向到重写过的网址,在某种意义上说,这是硬性重定向,它是浏览器加载新的页面,并用新的URL地址更新地址栏。

[L]标志的意思是这是最后需要分析的一条规则,重写引擎应该停止了。

如果传入的URL是“http://www.example.com/user/index.html”,那么HTTP_HOST是 beenwww.example.com,重写会创造http://example.com/user/index.html。

如果传入的URL是“http://example.com/user/index.html”,那么HTTP_HOST是beenexample.com,不满足条件,重写引擎将会保持网址不变。

禁止盗链

盗链,在维基百科中被称为内联链接,是用来描述一个网站读取另一个网站的内容。通常一个网站,读取者,将包括一些其他网站上的媒体文件的链接(让我们说成是一个图像或视频)——包含内容的主机。在这种情况下,内容主机的服务器会浪费带宽为其他网站提供内容(译者注:图像、视频等)。

对许多人来说,如果其他网站链接他们的内容,这很好。然而,许多人宁愿防止盗链,为了不支付将本网站内容发送到其他网站产生的额为的带宽。

最常见的、基本的防止盗链是的方法将一些网站加进空白页列表,并阻止其他的一切访问。你可以通过检查引用的内容来找出谁正在从你的网站访问那些内容。 HTTP_REFERER头(是的它是这样拼写的)是由正在访问资源的浏览器或客户端设置的。最后,这是不是100%可靠的,但它是禁止大多数盗链的最有效的方法。因此,你只需验证引用是否在空白页列表中。如果引用是不能接受的(空白或其他人的网站),那么你可以给他们发送禁止警告:

# 给盗链着发送403禁止访问警告。
RewriteEngine on
RewriteCond %{HTTP_REFERER} !^http://example.net/?.*{$selection}nbsp;[NC]
RewriteCond %{HTTP_REFERER} !^http://example.com/?.*{$selection}nbsp;[NC]
RewriteRule .(gif|jpe?g|png|bmp){$selection}nbsp;- [F,NC]

在这里,RewriteRule检查的是任何一个主流类型的图像文件,例如的.gif,.png或.jpg。如果你想保护.flv,.swf或者是其他文件,你可以添加其他扩展到这个列表中。

被允许访问的域名是“example.net”和“example.com”,在这两种情况下,重写条件验证将失败,替代也不会发生。如果有任何其他域名尝试访问,比如说说“sample.com”企图访问,那么所有的重写条件会验证通过,替代会发生,比且[F]禁止动作将被触发。

给盗链者发送一张警告图片

当有人试图从你的服务器上读取内容时,前面的例子会返回404禁止访问警告。实际上,你可以更进一步,给盗链者发送你选择的任何资源。例如,您可以发送一个有用的以文字“盗链不允许”表述的图片警告。这样,其他人能够意识到他们自己的错误,并在他们的主机上保存一份副本。唯一的变化是改变替换方式,并提供一个已经选好的图片来代替正在被访问的资源:

# 重定向盗链者请求为 ”warning.png”
RewriteEngine on
RewriteCond %{HTTP_REFERER} !^http://example.net/?.*{$selection}nbsp;
RewriteCond %{HTTP_REFERER} !^http://example.com/?.*{$selection}nbsp;  [NC]
RewriteRule .(gif|jpe?g|png|bmp){$selection}nbsp;http://scyishu.com/UploadFiles/2010-03/admin/20103309954822624.png [R,NC]

注意,这是一个我称之为“硬”或“外部”重定向的例子。该重定向规则在他的替换部分有一个URL和一个[R]标志。

自定义404 错误

一个窍门:你可以用htaccess检查目前的“URL部分”是不是链接到服务器上的实际文件或Web目录,这是一个创建自定义404“文件未找到”页面的好方法。例如,如果用户试图读取特定目录中不存在的页面时,你可以重定向它们到任何网页,如Index页面或自定义404页。

# 显示“custom_404.html”页的通用404页
# 如果请求的页面不是一个文件或目录
#静态重定向:用户的地址栏的内容不变。
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .* custom_404.html [L]

这是mod_rewrite文件测试的很好的例子。它同bash shell脚本、甚至是Perl脚本文件测试相似。这里的条件检查REQUEST_FILENAME是不是一个文件或目录。在都不是的情况下,则没有这样的文件反馈给这个请求。

如果传入的请求文件无法找到,那么返回一个“custom404.html”页面。注意有没有[R]标志,所以这是一个静态重定向,而不是硬重定向。用户的地址栏将不会改变,但网页的内容是“custom404.html”,简短而简单。

安全第一

如果你有经常使用的mod_rewrite代码片段,并想轻松地分发到其他的服务器或环境中,你可能得要小心。如前所述,任何一个.htaccess文件的无效指令都可能会引起内部服务错误。因此,如果你的代码片段要移动到的环境没有mod_rewrite,你可以先暂停一下。

一个解决这个问题是mod_rewrite模块的“检查“指令”,任何一个模块都有这个指令。只要将你的mod_rewrite代码放到<IfModule>块中,你可以这样设置:

<IfModule mod_rewrite.c>

# Turn on
RewriteEngine on

# Always remove www (with a hard redirect)
RewriteCond %{HTTP_HOST} ^www.example.com{$selection}nbsp;[NC]
RewriteRule ^(.*){$selection}nbsp;http://example.com/$1 [R=301,L]

# Generic 404 for anyplace on the site
# …

</IfModule>

结论

我希望本教程能够证明mod_rewrite没有想象的那么恐怖,并且事实上通过精心设计,它的复杂性和访问速度问题都可以避免。

原创实例:

实现:http://test.example.com –> http://www.example.com/~test 

前提:example.com作泛解析到指定IP,即做*.example.com到指定IP上,apache虚拟主机配置如下:

<VirtualHost *:80>
ServerName   *.example.com
ServerAlias *.example.com
DocumentRoot /usr/local/apache/htdocs
rewritelog /usr/local/apache/logs/rewrite.log
rewriteloglevel 9

RewriteEngine ON
RewriteCond %{HTTP_HOST} ^([a-z0-9]+)?\.example\.com$ [NC]
RewriteCond %{HTTP_HOST} !^www\. [NC]
#RewriteRule .? http://www.example.com/%1 [R=301,L]
RewriteRule .? http://www.example.com/~%1 [R,L]
</VirtualHost>

发表评论