十
9
2009
深入理解Apache的mod_rewrite
发布者: seasun人们一提到.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/
将同一个例子扩展一下,一些人声称通过用mod_rewrite改变你的网址可以获得安全效益。给出同一个例子,想像,考虑一下下面这个对用户id的攻击:
- http://example.com/user.php?id=AHHHHHH
- http://example.com/user/AHHHHHH/
从安全的角度讲,这种额外的抽象功能是不错的。如果你愿意,你甚至可以防止直接访问原始PHP脚。不过,我们决不能使用mod_rewrite来替换一般的安全措施,你的脚本应当在服务器端进行验证。
1、LoadModule rewrite_module modules/mod\_rewrite.so
- # Only in Apache 1.3
- AddModule mod\_rewrite.c


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


- Popular Added Bytes Cheatsheet For Regular Expressions
- Added Bytes Cheatsheet for mod_rewrite
- Explain Regular Expressions
- # Enable Rewriting
- RewriteEngine on
- # Rewrite user URLs
- # Input: user/NAME/
- # Output: user.php?id=NAME
- RewriteRule ^user/(\w+)/?$ user.php?id=$1
- <?php
- // Get the username from the url
- $id = $_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>
- T规则:
- RewriteRule ^user/(\w+)/?$ user.php?id=$1
- 匹配模式:
- ^ 输入的开头
- user/ 以“user/“开始的请求地址
- (\w+) 提取所有的字母,并将提取的结果传给$1
- /? 可选的斜线 “/”
- $ 输入结束
- 替换为:
- user.php?id= 要用到的字符串.
- $1 上面第一个提取到的字符串。
| 输入 | 匹配 | 提取 | 输出 | 结果 |
|---|---|---|---|---|
| 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是一个无法访问的网址!
|
| 这个例子比较容易理解。然而,为了澄清任何更复杂的事情,就像我现在做的一样,我必须要花好几分钟去注意细节。在下一节中,我们将举一个更复杂的例子,这个例子涉及所有重写的核心内容。 |
^user/([a-z]+)/?$。 请注意,我没有使用\w的缩写。如果此版本可以在你的机器上正确运行,那么你不要使用正则表达式的缩写,要使用较长的当量(见上面的正则表达式节)。
|
请注意,每当我提到Apache的变量,我使用了一种奇怪的语法:%{APACHE_VAR}。这是因为它类似于mod_rewrite访问变量的语法。不过,括号内名字是重要的。
|
||
|
|
该模式始终是对请求的URL路径进行正则表达式匹配(主机名后面的那部分,但在任何以问号为标志的显示查询字符串的前面)。 Apache文档
|




- URL匹配成功。
- 没有任何条件,因此继续。
- 进行替换。
- 没有任何标志,因而继续。
- URL匹配成功。
- 还有条件,我们将检查条件。
- THE_REQUEST不包含profile.php,因此条件检查失败。
- 因为不满足条件,所以我们忽略替换和标志。
- 这条规则没有改变URL。
profile.php?id=joe 页会被正确的提取。
- URL匹配失败。
- 其他的一切都被忽略,网址没有改变,进入下一步。
- URL匹配成功。
- 有条件需要比较,因此会先测试条件。
- 请求包含
profile.php,因此条件测试通过。 - 通过所有的条件,我们可以替换网址了。
- ”-”是一个特殊的替换,这一为着任何东西都不会改变了。
- 规则中有标志,因此我们处理标志。
- 有一个F标志,意思是返回一个禁止访问响应。
一个403 Forbidden响应发送到了客户端。
| 输入 | 匹配 | 获取 | 输出 | 结果 |
|---|---|---|---|---|
| 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 |
让我们从重写规则开始。如果你想做一些特殊的事,你可以随时查看Apache的关于重写规则的文档。下面是我的概述:


我正在介绍的这种方法的关键之处是它可以让你知道是否你的一个改变不能正常工作或者是使某个地方运行不正常。当一次做得太多的时候,你会不可避免的遇到 错误,并且你将不得不恢复你所做的一切更改来找出问题到底是出在那儿了。这是一项非常艰难的 工作,可能会导致你的失望。不过,如果你总是稳步推进,并且在每一步都可以到达一个可以正常运行的点,你的处境就会稍好一点儿。
在下面的例子中,我总是会假设网站的域名是example.com。此域名很重要,因为它会影响HTTP_HOST变量以及在你的网站上将指定的URL 重定向到另一个文件。如果你打算修改你的任何一个例子,以便它可以在你的网站上工作,请记住这一点。如果是这样,只需用你的域名替换 “example.com”。例如,Nettuts会将“example.com”改为“nettuts.com”。
这是最经典的重写规则。这将使得每个通过http://www.example.com访问你网站的人会得到一个硬性的重定向,从而其浏览器的地址栏中也将进行相应更新。
- RewriteEngine on
- RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
- RewriteRule ^(.*)$ http://example.com/$1 [R=301,L]
- 替代的是一个完整的URL (它以http://开始)
- 替代中包含早期抓取的 $1。
- [R=301]标志将浏览器重定向到重写过的网址,在某种意义上说,这是硬性重定向,它是浏览器加载新的页面,并用新的URL地址更新地址栏。
- [L]标志的意思是这是最后需要分析的一条规则,重写引擎应该停止了。
如果传入的URL是“http://example.com/user/index.html”,那么HTTP_HOST是beenexample.com,不满足条件,重写引擎将会保持网址不变。
禁止盗链
对许多人来说,如果其他网站链接他们的内容,这很好。然而,许多人宁愿防止盗链,为了不支付将本网站内容发送到其他网站产生的额为的带宽。
最常见的、基本的防止盗链是的方法将一些网站加进空白页列表,并阻止其他的一切访问。你可以通过检查引用的内容来找出谁正在从你的网站访问那些内容。 HTTP_REFERER头(是的它是这样拼写的)是由正在访问资源的浏览器或客户端设置的。最后,这是不是100%可靠的,但它是禁止大多数盗链的最有 效的方法。因此,你只需验证引用是否在空白页列表中。如果引用是不能接受的(空白或其他人的网站),那么你可以给他们发送禁止警告:
- # 给盗链着发送403禁止访问警告。
- RewriteEngine on
- RewriteCond %{HTTP_REFERER} !^http://example\.net/?.*$ [NC]
- RewriteCond %{HTTP_REFERER} !^http://example\.com/?.*$ [NC]
- RewriteRule \.(gif|jpe?g|png|bmp)$ – [F,NC]
被允许访问的域名是“example.net”和“example.com”,在这两种情况下,重写条件验证将失败,替代也不会发生。如果有任何其他域 名尝试访问,比如说说“sample.com”企图访问,那么所有的重写条件会验证通过,替代会发生,比且[F]禁止动作将被触发。
给盗链者发送一张警告图片
- # 重定向盗链者请求为 “warning.png”
- RewriteEngine on
- RewriteCond %{HTTP_REFERER} !^http://example\.net/?.*$
- RewriteCond %{HTTP_REFERER} !^http://example\.com/?.*$ [NC]
- RewriteRule \.(gif|jpe?g|png|bmp)$ http://example.com/warning.png [R,NC]
一个窍门:你可以用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$ [NC]
- RewriteRule ^(.*)$ http://example.com/$1 [R=301,L]
- # Generic 404 for anyplace on the site
- # …
- </IfModule>
结论
我希望本教程能够证明mod_rewrite没有想象的那么恐怖,并且事实上通过精心设计,它的复杂性和访问速度问题都可以避免。