看了一些关于.htaccess,apache重写url的规则,一般都写得很清楚。但是对于我一个小白,感觉要挖的东西挺多的,并不能满足我的需求。于是就追着apache的文档中的mod_rewrite查看,讲得很清楚。
当然,除了在.htaccess中配置相关的重写规则,也可以在apache的httpd.conf或者虚拟主机配置httpd-vhosts.conf中配置,参考官方例子如下:
边截图边解释一下,基于我自己的英文翻译和理解,如有不同之处请按照你们自己的理解。
1. apache mod_rewrite 模块介绍
对于重写模块,相信都不陌生,一般的web项目都会用到url重定向,这就需要使用mod_rewrite的模块,在.htaccess文件来完成。
首先,开启apache的mod_rewrite的模块,注释httpd.conf中的下列文字即可开启。
LoadModule rewrite_module modules/mod_rewrite.so
官方文档介绍:
2.正则表达式
mod_rewrite使用的是Perl Compatible Regular Expression的词汇,这里讲解简单的正则表达式帮助理解,详细的正则表达式移至http://perldoc.perl.org/perlre.html或http://shop.oreilly.com/product/9780596528126.do。
1)需要的最小正则表达式
mod_rewrite中经常会使用到 ! 字符,一般用在正则表达式前表示否定。
2)官方给出一个简单的重写url的公式例子,比较容易理解,就不翻译了。
3 ) RewriteCond
RewriteCond指令定义一个规则条件。一个或多个RewriteCond可以在一个RewriteRule指令之前。只有当URI的当前状态与其模式相匹配时才会使用下面的规则,并且如果满足这些条件的话。TestString是一个字符串,除了纯文件以外,它还可以包含以下扩展结构:
- RewriteRule反向引用:这些是形式$N(0 <= N <= 9)的反向引用。$1到$9提供对正则分组部分(括号)的访问。从属于RewriteRule就是当前的RewriteCond条件设置。$0提供了与该模式匹配的整个字符串的访问权。
- RewriteCond反向引用:这些是由%N(0 <= N <= 9)组成的反向引用。%1到%9提供对正则的分组部分的访问(在括号中),从当前条件集的最后匹配RewriteCond。$0提供对整个字符串的访问,该字符串与该模式匹配。
- RewriteMap扩展:这些由样式 ${mapname:key|default} 组成的扩展。
- 服务器变量:这些变量是由样式 %{NAME_OF_VARIALBE}组成的,NAME_OF_VARIABLE可以是如下例表中的字符串:
这些变量都对应相似的命名HTTP MIME-headers,APACHE HTTP服务器的C 变量,或者Unix系统中的tm结构体。大多数文档在手册或CGI规范的其他地方被记录。
SERVER_NAME和SERVER_PORT分别依赖于UseCanonicalName和UseCanonicalPhysicalPort。
那些对于 mod_rewrite很特别的部分包括下面这些 :
其他应该注意的:
- 变量SCRIPT_FILENAME和REQUEST_FILENAME包含相同的值——Apache HTTP服务器内部REQUEST_REC结构的文件名字段的值。第一个名称是众所周知的CGI变量名,而第二个名称是REQUEST_URI的相应对应物(它包含request_rec的uri字段的值)。
如果发生了替换,并且重写继续,那么两个变量的值将相应地更新。
如果在每个服务器环境中使用(例如在请求被映射到文件系统之前),SCRIPT_FILENAME和REQUEST_FILENAME不能包含完整的本地文件系统路径,因为在处理的这个阶段路径未知。在这种情况下,两个变量都将包含REQUEST_URI的值。为了在每个服务器上下文中获得请求的完整的本地文件系统路径,可以使用基于url的查询指令%{LA-U:REQUEST_FILENAME}来确定REQUEST_FILENAME的最终值。
- %{ENV:variable} 中variable可以是任何环境变量,都是可以使用的。这是通过Apache httpd服务器进程的 getenv() ,如果没有找到话,就从 Apache httpd结构进行查找。
- %{SSL:variable} 中variable的名字是一个SSL环境变量的名称,不管mod_ssl是否加载都可以使用。但是如果没有加载mod_ssl的话,会扩展为空字符串。例如: %{SSL:SSL_CIPHER_USEKEYSIZE}可能扩展到128。
- %{HTTP:header},header 可以是任何一个HTTP MIME-header的名称,总是可以用来获取HTTP请求中发送的关部信息。例:%{HTTP:Proxy-Connection}是HTTP头部信息中{Proxy-Connection}的值 。如果在一个条件 中使用了HTTP头,当请求的条件评估为真时,这个头部信息将被添加到响应的变化头部信息中。如果请求条件评估为假时,则不添加。对于适应的调整缓存,需要将HTTP头添加到响应的不同头部中。
必须记住,在'ornext|OR'的标志情况下,条件会遵循一个短路逻辑,这样就不会对某些条件进行评估。
- %{LA-U:variable}可用于执行一个内部(URL-based)的子请求,以确定变量的最终值。这可以用来访问重写的变量,而这在当前阶段是不可用的,但是将在后面的阶段中设置。
例如,要根据每个服务器上下文中REMOTE_USER变量(httpd.conf 文件)重写,你必须使用 %{LA-U:REMOTE_USER}- 这个变量由认证阶段设置,该阶段是在URL转换阶段之后(在mod_rewrite操作期间)。
另一方面,因为mod_rewrite实现了它的每个目录上下文(.htaccess文件)通过API的Fixup阶段,由于认证阶段在此阶段 之前,您可以在该上下文中使用%{REMOTE_USER}。***
- %{LA-F:variable} 可用于执行内部的子请求(基于文件名),以确定变量的最终值。大多数时候,这和上面的LA-U是一样的。
CondPattern是条件模式,它是一个正则表达式,应用于TestString的当前实例。在匹配CondPattern之前,先对TestString进行评估。
CondPattern通常是一个perl兼容的正则表达式,但是还有一些附加语法可以用来对TestString执行其他有用的测试:
- 你可以在模式字符串之前加上 '!' 字符( 感叹号 ) 来指定一个不匹配的模式。
- 你可以执行词法字符串比较:
'
把CondPattern当作普通字符串,并将它与TestString进行比较。如果在CondPattern之前,TestString是正确的。
'>CondPattern' (词法之后)
把CondPattern当作普通字符串,并将它与TestString进行比较。如果在CondPattern之后,TestString是正确的。
'=CondPattern'(等于词法)
把CondPattern当作普通字符串,并将它与TestString进行比较。如果TestString等于CondPattern就是正确的。(两个字符串必须绝对相等,字符是字符)。如果CondPattern是“”(两个引号)这将比作TestString为空字符串。
'<=CondPattern(小于或等于词法)'
把CondPattern当作普通字符串,并将它与TestString进行比较。如果TestString在词法之前或者等于词法就是真。(两个字符串必须绝对相等,字符是字符)。
'>=CondPattern(大于或等于词法)'
把CondPattern当作普通字符串,并将它与TestString进行比较。如果TestString在词法之后或者等于词法就为真。(两个字符串必须绝对相等,字符是字符)。
- 你可以执行整数比较:
'-eq' (数值相等):
TestString当作一个整数,并且与CondPattern进行数值比较。两个数值相等就为真。
'-ge' (数值大于或等于):
TestString当作一个整数,并且与CondPattern进行数值比较。TestString数值上大于或等于CondPattern就为真。
'-gt'(数值大于):
TestString当作一个整数,并且与CondPattern进行数值比较。它的数值大于CondPattern那就为真。
'-le'(数值小于或等于):
TestString当作一个整数,并且与CondPattern进行数值比较。它的数值小于或CondPattern就为真。使用-L或者-h 变量避免与 -l混淆。
'-lt'(数值小于):
TestString当作一个整数,并且与CondPattern进行数值比较。它的数值小于CondPattern就为真。使用-L或者-h 变量避免与 -l混淆。***
- 你可以执行各种文件属性测试:
'-d'(是目录)
TestString当作一个路径名称,测试它是否存在且是一个路径。
'-f'(是一个普通文件)
TestString当作一个路径名称,测试它是否存在且是一个普通文件。
'-F'(是通过子请求的现有文件)
检查TestString是否是有效的文件,通过所有服务器的当前配置的访问控制来访问该路径。这将使用内部 子请求来执行检查,所以要小心使用它---它会影响服务器性能。
'-H'(是符号链接,bash约定)
详见 -I
'-I'(是符号链接)
TestString当作一个路径名称,测试它是否存在且为一个符号链接。当使用lt或-le测试时如果可能出现混淆,也可使用-L或者-h的bash约定。
'-L'(是符号链接,bash约定)
详见-I
'-s'(是普通文件,有大小)
TestString被当作一个路径名称,测试它是否存在且一个文件大小于0的常规文件。
'-U'(是通过子请求,存在的URL)
检查TestString是否是一个有效的URL,通过所有服务器的当前配置的访问控制来访问该路径。这将使用内部子请求来执行检查,所以要小心使用它---它会影响服务器的性能。
'-x'(有可执行的权限)
TestString当作一个路径名称,测试它是否存在且有可执行的权限。这些权限是根据底层操作系统来确定的。
注意:所有这些测试也可用一个感叹号(!)来否定它们的含义。
- 如果TestString有一些特殊的表达式,CondPattern将把它们看作一个ap_expr.
在如下的例子中,-strmatch用于比较REFERER和站点主机名,以阻止不必要的热链接。
RewriteCond expr "! %{HTTP_REFERER} -strmatch '://%{HTTP_HOST}/' "
RewriteRule ^/images - [F]
也可为CondPattern后设置特殊的标志,[flags]作为RewriteCond指令的第三个参数,其中的标志是一个以逗号分隔的下列任何标记的列表:
'nocase|NC'(no case):
这使得测试不区分大小写——“A-Z” 和 “a-z”之间的区别被忽略了,无论是在扩展的TestString还是CondPattern中。该标记仅有效于TestString和CondPattern。它对文件系统和子请求检查没有影响。
'ornext|OR'(or next condition)
用这个来将规则条件与局部或隐式结合起来。典型的例子:
RewriteCond %{REMOTE_HOST} ^host1 [OR]
RewriteCond %{REMOTE_HOST} ^host2 [OR]
RewriteCond %{REMOTE_HOST} ^host3
RewriteRule ...some special stuff for any of these hosts...
没有这个标识,你需要写三遍condition/rule对。
'novary|NV'(没有变化)
如果一个HTTP头部在条件中使用,这个标识阻止在回应的变化头部中添加这个头部信息。
如果响应的表示因该头部信息的值而变化,那么使用此标志可能会破坏响应的适当缓存。因此,只有在理解了变化头的含义时,才应用使用这个标志。
Example:
为了根据"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]
解释:如果你使用一个它定义为'Mozilla'的浏览器(包括 Netscape Navigator,Mozilla etc),你将取得最大的主页(有可能会包括框架,或者其他特殊特性)。如果你使用Lynx浏览器(这是基于终端),你将取得最小的主页(有可能是一个专为轻变,只显示文本的浏览器设计的版本)。如果既不是上面条件中适用的浏览器(你使用任何浏览器,或者你的浏览器定义它自己为不标准版的),你将取得std(标准)的主页。
4) 重写规则由三个部分组成
-> 样式(pattern): 请求的url要满足此正则表达式;
-> 替换(substitution): 满足正则表达式的请求url被重写的路径;
-> [flags] : 影响重写请求的选项;
举例:
A. 系统文件路径转移至另一个路径
RewriteRule ^/games /usr/local/games/web
这有点像别名设置。如访问/games/xxx.jpg会直接指向/usr/local/games/web/xxx.jpg路径。
B. 网络路径到资源
RewriteRule ^/foo$ /bar
如果文件根目录设置为 /usr/local/apache2/htdocs,那么它将映射 http://example.com/foo 的请求到 路径 /usr/local/apache2/htdocs/bar。
C. 完整的url重写
RewriteRule ^/product/view$ http://site2.example.com/seeproduct.html [R]
这将引导用户生成一个新的请求到具体的URL。
替换也可以包含请求中正则表达式匹配的部分追加至其后
RewriteRule ^/product/(.*)/view$ /var/web/productdb/$1
变量$1将被样式中的圆括号中的正则表达式匹配部分替换
举个例子:
请求为 http://example.com/product/r14df/view,将映射至路径 /var/web/productdb/r14df。
3. RewriteRule Flags(重写规则中的[flags])
apache的重写规则可以被一个或者多个标识(flags)修改。标识包含在规则最后的方括号中,多个标识用逗号>隔开。
RewriteRule pattern target [Flag1,Flag2,Flag3]
所有的标识有短的术语,也有长的术语(极少部分的除外),如长术语cookie,短的术语就是co。flags不区分大小写。
挑几个例子说吧,其他更多请参照官方文档。
B(跳过回溯引用 escape backreferences)
[B]标识指令重写规则在应用转换之前跳过非字母数字字符。
mod_rewrite在映射url之前不会跳过它们,所以回溯引用在他们被使用的同时也不会被跳过。使用B标识,在回溯引用中非字母数字字符会被跳过。举例:
RewriteRule ^search/(.*)/$ /search.php?term=$1
搜索表达式'x & y/z',浏览器会加密它成为'x%20%26%20y%2Fz',请求就成为了search/x%20%26%20y%2Fz。没有B标识,重写规则会映射成为一个无效的URL‘search.php?term=x & y/z’,于是会被加密成search.php?term=x%20&y%2Fz=,当然这并不是所期望的那样。如果在其后面加上[B]标识,参数会在传给输入的URL前再次加密。结果就是一个正确的映射 /search.php?term=x%20%26%20y%2Fz。
**注意:你同时需要设置AllowEncodeSlashes为On,这样才能让这种特殊情况正常工作,httpd不允许加密URLs中的斜杠,如果碰见了就会返回404。
F 禁止
使用[F]标识导致服务器返回403禁止状态码给客户端。直接使用Deny也能完成这样的效果,但是[F]在返回禁止状态时更加灵活。
接下来的规则会禁止执行.exe文件从你的服务器中导出。
RewriteRule .exe - [F]
例子中使用"-"语法作为重写的目标,意思就是请求的URI没有被修改。如果你要禁止此请求,没有理由重写成其他的URI。
当使用[F]时,[L]也隐藏使用了。这就是,回应立即返回,并不会使用进一步的规则。
L最后的
[L]标识促使mod_rewrite阻止继续执行规则制定。在大多数的内容中,它意味着如果这个规则匹配,没有进一步的规则会执行。这跟Perl中的last命令或者C中的break命令是一致的。使用这个标识表明当前的规则应该立即执行且无需考虑进一步的规则。在这里举的例子,会重写任何请求到index.php,最初的请求作为一个请求字符串参数添加到index.php中。
但是,RewriteCond确保当请求已经给到index.php时,RewriteRule会被跳过。
RewriteBase /
RewriteCond %{REQUEST_URI} != /index.php
RewriteRule ^(.*) /index.php?req=$1 [L,PT]
PT转移
4. 一些access例子(这些例子不一定有作用,理解这些规则更重要)
- 1)禁止图片热链接
描述:
下面的技术禁止在页面中插入其他站点,包括你的图像。这种做法通常被称为“热链接”,并导致你的带宽被用于其他人的站点提供内容。
解决:
这个技术依赖于变量HTTP_REFERER的值,这是可选的。因此,有些人有可能绕开这一限制。但是,大多数用户将体验到失败的请求,随着时间的推移,这将导致图片从其他站点中删除。
有很多方面可以解决这个情况。
在第一个例子中,如果它没有从我们网站上的一个页面发起请求,那我们只是拒绝请求。为了本例的目的,我们假设我们网站是 www.example.com
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !www.example.com [NC]
RewriteRule .(gif|jpg|png)$ - [F,NC]
在第二个例子中,我们显示另一个镜像替代失败请求
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !www.example.com [NC]
RewriteRule .(gif|jpg|png)$ /images/go-away.png [R,NC]
在第三个例子中,我们直接跳转请求到当前图片所在的其他网站
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !www.example.com [NC]
RewriteRule .(gif|jpg|png)$ http://other.example.com/image.gif [R,NC]
在这些技术中,最后两种方法最有效地让人们停止热链接你的图片,因为他们不会看到他们期望看到的
图像。
讨论:
如果你想要做的就是拒绝访问资源,而不是将请求重定向到其他地方,那么可以在不使用mod_rewrite的情况下完成此任务:
SetEnvIf Referer example .com localreferer
Require env localreferer
- 2) 拒绝黑名单中的主机
RewriteEngine on
RewriteMap hosts-deny txt:/path/to/hosts.deny
RewriteCond ${hosts-deny:%{REMOTE_ADDR}|NOT-FOUND} !=NOT-FOUND [OR]
RewriteCond ${hosts-deny:%{REMOTE_HOST}|NOT-FOUND} !=NOT-FOUND
RewriteRule ^ - [F]hosts.deny
注意!这是一个映射,不是一个名单,即使我们就是这样希望它的。
mod_rewrite 解析它作为 键/值 对,所以至少每个条目必须有虚拟的值"-"。
193.102.180.41 -
bsdtil.sdm.de -
192.76.162.40 -
3) 基于referer的引流
描述:
根据请求的Referer重定向请求,每个referer的目标不同。
解决:
RewriteMap deflector txt:/path/to/deflector.mapRewriteCond %{HTTP_REFERER} !=""
RewriteCond ${deflector:%{HTTP_REFERER} } =-
RewriteRule ^ %{HTTP_REFERER} [R,L]RewriteCond %{HTTP_REFERER} != ""
RewriteCond ${deflector:%{HTTP_REFERER}|NOT-FOUND} !=NOT-FOUND
RewriteRule ^ ${deflector:%{HTTP_REFERER}} [R,L]注意:map文件列出了每个referer的重定向目标,或者,如果我们只是希望重定向到它们来自哪里,那么“-”就需要放在map里。
deflector.map
http://badguys.example.com/bad/index.html -
http://badguys.example.com/bad/index2.html -
http://badguys.example.com/bad/index3.html http://somewhere.example.com
4) mod_rewrtite重定向和重映射
从旧到新(内部)
描述:
假设我们最近重新命名了页面foo.html为bar.html,现在想向后兼容旧的URL。但是,我们想使用旧URL的用户不会察觉这些页面被重命名。那么,我们不想更改他们浏览器中的地址。
解决:
RewriteEngine on
RewriteRule ^/old.html$ /new.html [PT]
重写旧到新(外部)
描述:
再次假设我们最近重命名foo.html为bar.html,现在想向后说兼容旧的URL。但是这次我们想将用户的旧URL得到新的地址的提示,例,他们的浏览器地址栏也应该改变。
解决:
RewriteEngine on
RewriteRule ^/foo.html$ bar.html [R]
资源移至其他的服务器
描述:
如果一项资源已经移至其他的服务器,当用户更新他们的书签的时候,你可能希望旧服务器上的URLs继续运行一断时间。
解决:
你可以用mod_rewrite跳转到这些URLs到新的服务器上,但是你也许也考虑到使用Redirect或者RedirectMatch指令。
``#With mod_rewrite
RewriteEngine on
RewriteRule ^/docs/(.+) http://new.example.com/docs/$1 [R,L]``#With RedirectMatch
RedirectMatch ^/docs/(.*) http://new.example.com/docs/$1``#With Redirect
Redirect /docs/ http://new.example.com/docs/
从静态到动态
描述:
如何用一种无缝的方式转换静态页面foo.html到动态变量 foo.cgi,例,没有浏览器或用户注意到。
解决:
我们只需要将URL重写为CGI-script,并强制处理程序成为CGI-script,以便将其作为CGI程序执行。这是对/quux/foo.html的请求内部对/quux/foo.cgi的调用。
RewriteEngine on
RewriteBase /~quux/
RewriteRule ^foo.html$ foo.cgi [H=cgi-script]
文件扩展的向后兼容性
描述:
在合并document.YYYY到document.XXXX时,我们该如何做到URLs向后兼容(依然存在虚拟的)?例,在传输完一堆.html文件到.php?
解决:
我们将名称改写为basename,并测试新的扩展存在。如果它存在,我们就取那个名字,否则我们重写URL到它的初始状态。backward compatibility ruleset for rewriting document.html to document.php
when and only when document.php exists
RewriteEngine on
RewriteBase /var/www/htdocs
RewriteCond $1.php -f
RewriteCond $1.html !-f
RewriteRule ^(.*).html$ $1.php
讨论:
这个例子使用了一个经常被忽略的mod重写特性,它利用了规则集的执行顺序。特别是,mod重写在对rewriterecond指令进行评估之前,对其左边的部分进行评估。因此,$1已经RewriteCond指令被评估的时候被定义。这允许我们使用相同的基本文件名来测试原始文件(document.html)和target(document.php)文件的存在。
该规则集被设计为在每个目录上下文中使用(在一个目录中或在.htaccess文件中),以便-f检查查找正确的目录路径。您可能需要设置一个RewriteBase指令来指定您正在工作的目录基础。