Apache - mod_rewrite规则详解

一,简介:

Apache的rewrite模块,提供了一个基于正则表达式规则的重写引擎,用来(on the fly)实时修改传入的请求的 URL 。因功能极其强大,被称为URL重写的“瑞士军刀”。

它支持无限的规则,以及为每个规则附加条件,从而提供了一个真正灵活且强大的 URL 操作机制。URL 操作可以依赖于各种测试,例如服务器变量,环境变量,HTTP 头,时间戳,甚至外部数据库查询等,以便完成 URL 单元匹配。

这个模块可以操作完整的 URL (包含目录信息部分和查询字符串部分) ,在服务器上下文 (httpd.conf)、虚拟主机上下文 (<VirtualHost> 指令块)、目录上下文 (.htaccess 文件和 <Directory> 指令块) 都可以配置。重写的结果 URL,可以指向一个站内的处理程序、指向站外的重定向或者一个站内的代理。

既然 mod_rewrite 这么强大,它当然是相当复杂。因此,别指望一天之内就能看懂整个模块。

二,打开Apache的rewrite功能

1、LoadModule
1)在windows环境下:
打开您的apache安装目录“/apache/conf/” 下的 httpd.conf 文件,通过Ctrl+F查找到LoadModule rewrite_module modules/mod_rewrite.so,将前面的”#”号删除即可
2)在linux环境下:
在编译 apache 的时候记得加上带 rewrite 模块。

2、让apache服务器支持.htaccess
在服务器或者虚拟主机的<Directory>配置段里,把你的AllowOverride配置设置成All,表示允许所有指令在 .htaccess 生效。

3、检查rewrite模块是否开启
当rewrite模块已经成功加载时,在phpinfo()里可以看到load的模块列表里有rewrite的名字。

三,特殊字符

1)$N,引用RewriteRule模板中匹配的相关字串,N表示序号,N=0..9
2)%N,引用最后一个RewriteCond模板中匹配的数据,N表示序号
3)%{VARNAME},服务器变量
4)${mapname:key|default},映射函数调用

四,指令

Apache Rewrite 的重写规则的具体指令共有 RewriteBase, RewriteCond, RewriteEngine, RewriteLock, RewriteLog, RewriteLogLevel, RewriteMap, RewriteOptions, RewriteRule 九个指令。

下面我们就最常用的RewriteEngine, RewriteBase, RewriteCond, RewriteRule这四个指令重点讲解。

1、RewriteEngine指令

原文译文
描述(Description)Enables or disables runtime rewriting engine开启或关闭重写引擎
语法(Syntax)RewriteEngine on|off
默认(Default)RewriteEngine off
作用域/上下文(Context)server config, virtual host, directory, .htaccess

2、RewriterRule规则
一条RewriteRule指令,定义一条重写规则,规则间的顺序非常重要。对Apache1.2及以后的版本,模板(pattern)是一个 POSIX正则式,用以匹配当前的URL。当前的URL不一定是用记最初提交的URL,因为可能用一些规则在此规则前已经对URL进行了处理。

原文译文
描述(Description)Defines rules for the rewriting engine
语法(Syntax)RewriteRule Pattern Substitution [Flag1,Flag2,Flag3]
作用域/上下文(Context)server config, virtual host, directory, .htaccess

指令说明:匹配部分(Pattern) 是正则匹配URL的正则表达式(注意特殊字符需要转义处理), 可以在替换部分(Substitution)使用反向引用匹配部分的内容. 引用模式为: $N (N为1-9的整数)。

先说明一下一个比较特别的 Substitution 值: “-“, 如果Substitution是 “-” 的话, 那么被请求的URL不会被修改掉,只做匹配检查。

在URL重写的匹配部分中,服务器会把请求的URL的一部分删除掉再传递给Pattern部分进行匹配,重写结束后再添加上去。所以平常我们看到的匹配规则总是不带网址前面的那些域名的什么东西的,也不带什么目录什么的,这些 apache已经给删掉了,处理完后再加到前面。但是有个例外,就是如果 Substitution 部分是带 http:// 开头的话, 那就直接重定向了,服务器不会把先前删除的再给加上了,不然就出错了。

查看几个官方的例子:
例如请求地址为: http://thishost/somepath/pathinfo, 看下面几种结果

Given Rule (重写规则)Resulting Substitution (替换结果)
^/somepath(.*) otherpath$1invalid, not supported(无效的重写语句,不支持)
^/somepath(.*) otherpath$1 [R]invalid, not supported
^/somepath(.*) otherpath$1 [P]invalid, not supported
^/somepath(.*) /otherpath$1/otherpath/pathinfo
^/somepath(.*) /otherpath$1 [R] http://thishost/otherpath/pathinfo via external redirection(外部重定向)
^/somepath(.*) /otherpath$1 [P]doesn’t make sense, not supported
^/somepath(.*) http://thishost/otherpath$1http://www.test.com/otherpath/pathinfo
^/somepath(.*) http://thishost/otherpath$1 [R] http://thishost/otherpath/pathinfo via external redirection(外部重定向)
^/somepath(.*) http://thishost/otherpath$1 [P]doesn’t make sense, not supported
^/somepath(.*) http://otherhost/otherpath$1 http://otherhost/otherpath/pathinfo via external redirection(外部重定向)
^/somepath(.*) http://otherhost/otherpath$1 [R] http://otherhost/otherpath/pathinfo via external redirection (the [R] flag is redundant)(外部重定向,[R] 标志是多余的)
^/somepath(.*) http://otherhost/otherpath$1 [P] http://otherhost/otherpath/pathinfo via internal proxy(内部网关重定向)

RewriteRule下常用的Flags参数(了解更多可查看文章末尾的“附录一”):
[R] 强制重定向,[R=code] code默认为302
[F] 禁用URL,返回HTTP 403 错误
[L] 这是最后一条规则,之后内容无用

:比如你要定义页面所有页面请求都重写到一个 index.php 文件

RewriteCond %{REQUEST_URI} !index\.php
RewriteRule ^(.*) index.php?req=$1 [L]

注意:使用[L]标志的时候, 一定要注意你的匹配条件, 不会非常容易让你的重写规则陷入死循环。你要定义页面所有页面请求都重写到一个 index.php 文件,那么一定要注意在匹配条件时确定当请求的脚本不是index.php时才执行重写规则。不然很明显当前页面请求的是 index.php,当然 这个请求被重写到 index.php 然后index.php又被重写到index.php… 这样反复执行,页面会报错,错误日志会记录报告你超出最大的重定向次数。

3、RewriteBase指令

原文译文
描述(Description)Sets the base URL for per-directory rewrites设置目录级重写的基准url
语法(Syntax)RewriteBase URL-path
默认(Default)RewriteBase physical-directory-path
作用域/上下文(Context)directory, .htaccess

通常默认的虚拟主机的网站在使用.htaccess 进行重写规则时不需要执行设置该指令. 因为 RewriteBase 默认值是该 .htaccess 文件所在的目录地址。但是如果使用目录别名的话就需要设置这个指令了。
假设一个网站目录使用了别名操作,如: Alias /xyz "/abc/def" 那么当客户端访问/xyz/index.html 文件时是相当于访问/abc/def/index.html。(注意:Alias的第二个参数是实际物理路径)
当然这个 .htaccess 文件在 /abc/def/.htaccess 位置,内容如下:

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase   /xxx
  RewriteRule   ^oldstuff.html$  newstuff.html
</IfModule>

假设访问服务器的: /xxx/oldstuff.html(比如: http://test.com/xxxtest/oldstuff.html 这样)
服务器处理流程大概是下面这样子:

1)把alias别名还原成真实的路径
/xyz/oldstuff.html -> /abc/def/oldstuff.html (per-server Alias)
2)rewrite前会去掉前缀(也就是.htaccess文件所在的目录部分这里是/abc/def/), 然后执行重写规则, 处理完之后再把前缀添加上去:
/abc/def/oldstuff.html -> /abc/def/newstuff.html (per-dir RewriteRule)
3)由于设定了RewriteBase值,所以路径最后还是被还原回去:
/abc/def/newstuff.html -> /xyz/newstuff.html (per-dir RewriteBase)
4)重写规则完成, 别名再次使用. 最后得到的结果:
/xyz/newstuff.html -> /abc/def/newstuff.html (per-server Alias)

实际上相当于请求: /abc/def/newstuff.html
这里要注意的是即使RewriteBase /xyz这行被注释掉,服务器还是会执行上面的第一步和第二步,后面的不会被执行。服务器执行完第二步以后就发出一个内部重定向,按照上面的例子也就是 GET/abc/def/newstuff.html, 由于GET 模式获取到第一个是/开头的, 相当于请求DocumentRoot目录下的/abc/def/newstuff.html, 换成客户端请求的模式也就是http://www.test.com/xyz/oldstuff.html变成了http://www.test.com/abc/def/newstuff.html。如果/xyz只是目录的别名, DocumentRoot 目录下根本没有实际的物理目录abc/def这样最终会导致一个404 报错。

另外有一点就是在进行重写规则的时候. apache会去掉目录前缀。注意这个时候剩下的文件名或者相对目录第一个字符不是/开头的,而直接是文件名或者目录这样的模式。

4、RewriterCond指令

原文译文
描述(Description)Defines a condition under which rewriting will take place重写规则执行条件
语法(Syntax)RewriteCond TestString CondPattern [flags]TestStrng 为要被检查的内容, CondPattern 是进行匹配的规则
作用域/上下文(Context)server config, virtual host, directory, .htaccess

实际需要使用情况,比如要判断一个条件成真的时候才执行相关的重写操作,紧接着它下面的 RewriteRule 总是在RewriteCond 条件判断为真的时候才被执行。

特别的上面的 TestString,可提供反向引用。引用模式为: %N 其中N为(0 <= N <=9),引用当前若干RewriteCond条件中最后符合的条件中的分组成分,也就是括号里的内容,不过用到的不多。反向应用多在RewriteRule里常用。

它是一个兼容Perl风格的正则表达式和一些其他的特有字符属性. 这里介绍一下.

序号字符作用说明
1!(感叹号) 表示否的意思比如一个条件: 判断访问此页面的上一页URL是否包含 sex 字符的话可以用这样: RewriteCond %{HTTP_REFERER} !(sex)
2<就是小于的意思TestString <CondPattern
3>就是大于于的意思TestString >CondPattern
4=相等的意思<, >, = 三个和通常程序语言使用的 <, >, = 功能类似.
5-d是否是一个目录判断TestString是否不是一个目录可以这样: !-d
6-f是否是一个文件判断TestString是否不是一个文件可以这样: !-f
7-s是否是一个正常的有大小的文件判断TestString是否不是一个正常的有大小的文件可以这样: !-s
8-l是否是一个快捷方式文件判断TestString是否不是一个快捷方式文件可以这样: !-
9-x是否是一个文件并且有执行权限判断TestString是否不是一个文件并且有执行权限可以这样: !-x
10-F检查TestString是否是一个合法的文件而且通过服务器范围内的当前设置的访问控制进行访问。这个检查是通过一个内部subrequest完成的, 因此需要小心使用这个功能以降低服务器的性能。
11-U检查TestString是否是一个合法的URL而且通过服务器范围内的当前设置的访问控制进行访问。这个检查是通过一个内部subrequest完成的, 因此需要小心使用这个功能以降低服务器的性能。

查看完整的列表可以查看官方文档的rewritecond章节: http://httpd.apache.org/docs/2.4/zh-cn/mod/mod_rewrite.html#rewritecond

RewriteCond下的Flags参数:
[NC|nocase] 表示不区分大小写
[OR|ornext] 用于连接下一条规则
[NV|novary]

RewriteCond变量(了解更多可查看文章末尾的“附录二”):
REQUEST_URI,请求的URL值
REMOTE_ADDR,向服务器发送请求的IP地址

五,案例

1、URL根目录转移
从/转移到/e/www/

RewriteEngine on
RewriteRule ^/$ /e/www/ [R]

转移到其它服务器

RewriteEngine on
RewriteRule ^/~(.+) http://newserver/~$1 [R,L]

2、防盗链功能
如果来访问jpe jpg bmp png结尾的url 用户不是来自我们的网站,那么让他看一张我们网站的展示图片。

RewriteEngine On
RewriteCond %{HTTP_REFERER} !^http://(.+.)?mysite.com/ [NC]
RewriteCond %{HTTP_REFERER} !^$
RewriteRule .*.(jpe?g|gif|bmp|png)$ /images/nohotlink.jpg [L]

3、网址规范化
把所有二级域名都重定向到www.yourdomain.com

rewriteEngine on
rewriteCond %{http_host} ^yourdomain.com [NC]
rewriteRule ^(.*)$ http://www.yourdomain.com/$1 [R=301,L]

4、临时错误页面
当请求不存在的资源时, 统一定义到根目录下的 404.html

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^.*$ /404.html [L]

5、所有请求都定向到 index.php 脚本
注意要排除 index.php 本身,不然就进入死循环了.

RewriteRule !^index\.php$ index.php [L]

6、限制访问
如HTTP_REFERER 中包含 sex 字符, 则不允许访问

RewriteCond %{HTTP_REFERER} sex
RewriteRule ^.*$ - [F]

7、按照时间显示不同的页面
如访问 hello.html 页面时. 如果 在 8:00-19:00 的时候访问. 显示 hello.day.html 其他时间访问显示: hello.night.html

RewriteCond %{TIME_HOUR}%{TIME_MIN} >0700
RewriteCond %{TIME_HOUR}%{TIME_MIN} <1900
RewriteRule ^hello\.html$ hello.day.html
RewriteRule ^hello\.html$ hello.night.html

8、伪静态化
比如访问 /user20.html 则调用viewUser.php 显示用户ID为20的用户资料

RewriteRule ^user([0-9]*)\.html$ viewUser.php?userid=$1

9、二级域名指向目录
喜欢用二级域名的比较实用了。比如网站目录下有 user, upload 等几个目录, 可以通过 http://www.test.com/user 这样的模式访问. 但是如果想做成统一用二级域名模式访问: http://user.test.com , 但是不允许 http://www.test.com/user 这样访问. 那么就像下面这样来限制.

RewriteCond %{REQUEST_URI} ^/user
RewriteRule ^.*$ http://user.test.com" [L]

10、把老的域名转向新域名

RewriteEngine On
RewriteRule ^(.*)$ http://www.yourdomain.com/$1 [R=301,L]

附录一 RewriteRule Flags

FlagDescriptionExample
C|chain表示该行重写规则外还要有其他的重写规则, 相当于通常程序语言的与符号 ‘&’ , 如果第一条规则条匹配的话进行下一项条件匹配. 如果第一条或者中间一条匹配不成功. 在其后的都会被跳过。
F|forbidden表示禁止访问,Apache服务器会返回403禁止访问状态码给客户端获取或者下载 exe程序文件显示禁止访问:RewriteRule \.exe - [F]
G|gone表示服务器响应状态码为:410,通常使用该标志的时候 target 目标值设置成 “-“,被请求的资源是有效的表示旧的资源是有效的,并且不在乎大小写:RewriteRule oldproduct - [G,NC]
L|last表示当前规则是最后一条规则,停止分析以后规则的重写。该标志的使用频率非常高
N|next重新回到规则顶部重复执行,一般在极端情况下用这个标志。相当于一个while循环, 直到匹配失败时返回如把请求地址中的所有A字符替换成B字符:RewriteRule (.*)A(.*) $1B$2 [N]
NC|nocase表示请求的规则部分不区分大小写,类似正则式里的/xxx/i 模式
P|proxy需要模块 mod_proxy 支持, 类似一个分发器网关的作用。使用[P]标志,意味着使用了[L]标志,因为使用该标志后马上就重定向到新地址了,后面的重写规则会被忽略掉如网站的所有图片想用单独的一台服务器来运行:RewriteRule (.*)\.(jpg|gif|png) http://images.example.com$1.$2 [P]
R|redirect表示进行重定向, 状态码在300-399里随机出, 默认是 302 重定向。通常和标志L一起使用,使用模式: [R[=302]]
S|skip表示跳过执行下面的几个重写规则,又点类似goto

完整列表可查看官方文档:RewriteRule Flags

附录二 服务器变量Variables

HTTP headers 部分
HTTP_USER_AGENT相当于PHP中的服务器参数: $_SERVER[“HTTP_USER_AGENT”]Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.8) Gecko/2009032609 Firefox/3.0.8
HTTP_REFERER相当于PHP中的服务器参数: _SERVER[“HTTP_REFERER”]http://www.test.com/test.php
HTTP_COOKIE相当于PHP中的服务器参数: $_SERVER[“HTTP_COOKIE”]ZDEDebuggerPresent=php,phtml,php3
HTTP_FORWARDED相当于PHP中的服务器参数: $_SERVER[“HTTP_FORWARDED”]代理服务器的IP地址
HTTP_HOST相当于PHP中的服务器参数: $_SERVER[“HTTP_HOST”]www.test.com
HTTP_PROXY_CONNECTIONPHP中貌似未提供这样的服务器信息值. 如果有的话可能等值于: $_SERVER[“HTTP_PROXY_CONNECTION”]网络连接代理方面的信息,和HTTP_FORWARDED参数一样
HTTP_ACCEPT相当于PHP中的服务器参数: $_SERVER[“HTTP_ACCEPT”]text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
server internals 部分
DOCUMENT_ROOT相当于PHP中的服务器参数: $_SERVER[“DOCUMENT_ROOT”]C:/webRoot/t
SERVER_ADMIN相当于PHP中的服务器参数: $_SERVER[“SERVER_ADMIN”]youemailaddress@gmail.com
SERVER_NAME相当于PHP中的服务器参数: $_SERVER[“SERVER_NAME”]www.test.com
SERVER_ADDR相当于PHP中的服务器参数: $_SERVER[“SERVER_ADDR”]127.0.0.1
SERVER_PORT相当于PHP中的服务器参数: $_SERVER[“SERVER_PORT”]80
SERVER_PROTOCOL相当于PHP中的服务器参数: $_SERVER[“SERVER_PROTOCOL”]HTTP/1.1
SERVER_SOFTWARE相当于PHP中的服务器参数: $_SERVER[“SERVER_SOFTWARE”]Apache/2.2.11 (Win32) PHP/5.2.9-1
connection & request 部分
REMOTE_ADDR相当于PHP中的服务器参数: $_SERVER[“REMOTE_ADDR”]127.0.0.1 正在浏览当前页面用户的 IP 地址。
REMOTE_HOST相当于PHP中的服务器参数: $_SERVER[“REMOTE_HOST”]127.0.0.1 正在浏览当前页面用户的主机名。反向域名解析基于该用户的 REMOTE_ADDR
REMOTE_PORT相当于PHP中的服务器参数: $_SERVER[“REMOTE_PORT”]2574 (变化的值)用户连接到服务器时所使用的端口
REMOTE_USERPHP 好像未提供相关的$_SERVER值.
REMOTE_IDENTPHP 好像未提供相关的$_SERVER值
REQUEST_METHOD相当于PHP中的服务器参数: $_SERVER[“REQUEST_METHOD”]GET
SCRIPT_FILENAME相当于PHP中的服务器参数: $_SERVER[“SCRIPT_FILENAME”]C:/webRoot/t/share77.html
PATH_INFO相当于PHP中的服务器参数: $_REQUEST[“PATH_INFO”]
QUERY_STRING相当于PHP中的服务器参数: $_SERVER[“QUERY_STRING”]a=b&c=d&e=f
AUTH_TYPE相当于PHP中的服务器参数: $_SERVER[“AUTH_TYPE”]认证的类型
date and time 部分
TIME_YEAR服务器获取当前的年份值2009
TIME_MON服务器获取当前的月份值04
TIME_DAY服务器获取当前的日值21
TIME_HOUR服务器获取当前时间的小时15
TIME_MIN服务器获取当前时间的分钟22
TIME_SEC服务器获取当前时间的秒32
TIME_WDAY服务器获取当天是星期几, 从星期日-星期六, 数字从 0-63
TIME服务器获取当前的时间, 格式为: 年月日时分秒20090422162634
specials 部分
API_VERSIONapache 的 API 版本信息20051115:21
THE_REQUEST浏览器发给服务器的请求值. 不包括其他的头信息GET /share77.html HTTP/1.1
REQUEST_URI浏览器请求的资源信息/share77.html
REQUEST_FILENAME被请求的资源的在磁盘的物理地址C:/webRoot/t/share77.html
IS_SUBREQ如果是 sub-request 则显示为 true, 否则为 falsefalse
HTTPS如果连接使用 SSL/TLS 模式, 则值为on , 否则值为off, 这个参数比较安全, 即使未载入 mod_ssl 模块时off

查看官方文档:Variables

参考

官方文档 – Apache Module mod_rewriteApache mod_rewrite
个人博客 – https://blog.csdn.net/bjash/article/details/8310040

    原文作者:Nosee123
    原文地址: https://www.jianshu.com/p/83ce523cac7a
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞