一、nginx rewrite  规则定义


使用 nginx 提供的全局变量或者自己设置的变量,结合正则表达式规则和标志位实现 url

重写以及重定向。


二、rewrite  作用域


rewrite 只能放在 sever{}, location {}, if{}中。


三、rewrite  作用对象


rewrite 规则只对域名后边的除去传递的参数外的字符串起作用。如果想对域名或者参

数字符串起作用,可以使用全局变量匹配。

例如:

http://www.laoseng.org/a/we/index.php?id=1&u=str 只对 /a/we/index.php 起作用。

针对域名使用全局变量$host.针对参数字符串$query_string.但都不是 rewrite 规则


if ($host ~* ^www\.(cafeneko\.info)) { 

 
set $host_without_www $1; 

 
rewrite ^(.*)$ http://$host_without_www$1 permanent; 

}

四、使用格式用法


rewrite 正则字符串 替换字符串 flag 标志位;


五、正则表达式详解

5.1.  正则表达式特殊字符含义详解


正则字符  含义

. 匹配除换行符以外的任意字符

\w 匹配字母或数字或下划线或汉字

\s 匹配任意的空白符

\d 匹配数字

\b 匹配单词的开始或结束

^ 匹配字符串的开始

$ 匹配字符串的结束

* 重复零次或更多次

+ 重复一次或更多次

? 重复零次或一次

{n} 重复 n 次

{n,} 重复 n 次或更多次

{n,m} 重复 n 到 m 次

*? 重复任意次,但尽可能少重复

+? 重复 1 次或更多次,但尽可能少重复

?? 重复 0 次或 1 次,但尽可能少重复

{n,m}? 重复 n 到 m 次,但尽可能少重复

{n,}? 重复 n 次以上,但尽可能少重复

\W

匹配任意不是字母,数字,下划线,汉字的

字符

\S 匹配任意不是空白符的字符

\D 匹配任意非数字的字符

\B 匹配不是单词开头或结束的位置


5.2.  正则表达式分类

5.2.1.  精确匹配


在匹配过程中匹配字符和要被匹配的字符能够划等号的。

例如:

(\d+)-abc-([0-9]+) 中的"-abc-"就是精确匹配。因为你匹配到的字符串必须包含"-abc-"

字符串。

而且你匹配成功的字符串中"-abc-"= 正则匹配中的"-abc-"字符串


5.2.2  模糊匹配


使用一些模糊代替的字符进行匹配。该字符能够代表很多字符。

例如:

. 表示任意字符可以匹配一个字母也可以匹配一个数字,或者其他的字符。

[0-9]表示匹配一个 0 到 9 之间任意一个数字。

[a-z]表示匹配一个 a 到 z 之间任意一个字母。


5.3.  反向引用


“rewrite 正则字符串 替换字符串 标志位“ 格式中,如果替换字符串重要用前边正则

表达式匹配到的字符,怎么处理?这时候就要用到反向引用。

正则字符串中模糊匹配到的字符串,可以在替换字符串中使用$n引用,其中$n 表示第

几个模糊匹配匹配到的字符串。

例如:

(\d+)-abc-([0-9]+)为例 在替代字符串中如果出现 $1 就表示 (\d+)匹配到的一串

数字,如果出现$2 表示([0-9]+)匹配到的一串数字,


六、flag  标志位


last 相当于 Apache 里的[L]标记,表示完成 rewrite

break 终止匹配, 不再匹配后面的规则

redirect 返回 302 临时重定向 地址栏会显示跳转后的地址

permanent 返回 301 永久重定向 地址栏会显示跳转后的地址

因为 301 和 302 不能简单的只单纯返回状态码,还必须有重定向的 URL,这就是 return 指令

无法返回 301,302 的原因了.

nginx 中分别使用 redirect 和 permanent 标志实现 301 和 302 状态以及跳转。

last 与 break 区别:

1.last 一般使用在非 location 中,break 一般使用在 location 中。

2.last 不终止重写后的 url 匹配,break 终止重写后的匹配

3.break 和 last 都能阻止继续执行后面的 rewrite 指令,

但是 last 如果在 location 下用的话,对于重写后的 URI 会重新匹配 location

但是 break 则不会重新匹配 location 。简单的说,break 终止的力度比 last 更加彻底


七、全局变量可以用做条件判断


$args #这个变量在读取时返回当前请求的 URL 参数串.

例如:

http://www.laoseng.org/index.php?id=1&user=laoseng,中 $arg 就等于 “id=1&user=laoseng”

$content_length 等于请求行的“Content_Length”的值。

content length 是指报头以外的内容长度。

一般的服务器实现中,超过这个长度的内容将被抛弃。 不会产生新 post。

$content_type 等同与请求头部的”Content_Type”的值

HTML 中的 ContentType, Content-Type,连接类型,一般是指网页中存在的

Content-Type,用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、

什么编码读取这个文件。

$document_root 等同于当前请求的 root 指令指定的值 ==== 在配置文件中 root

/var/www

$document_uri 与$uri 一样

$host 与请求头部中“Host”行指定的值或是 request 到达的 server 的名字(没有 Host 行)

一样

$limit_rate 允许限制的连接速率

$request_method 等同于 request 的 method,通常是“GET”或“POST”

$remote_addr 客户端 ip

$remote_port 客户端 port

$remote_user 等同于用户名,由 ngx_http_auth_basic_module 认证

$request_filename 当前请求的文件的路径名,由 root或 alias 和 URI request 组合而成

$request_body_file 给客户的 body 存成文件,并返回文件名

$request_uri 含有参数的完整的初始 URI

$query_string 与$args 一样

$server_protocol 等同于 request 的协议,使用“HTTP/1.0”或“HTTP/1.1”

$server_addr request 到达的 server 的 ip,一般获得此变量的值的目的是进行系统调用。为

了避免系统调用,有必要在 listen 指令中指明 ip,并使用 bind 参数。

$server_name 请求到达的服务器名

$server_port 请求到达的服务器的端口号

$uri 等同于当前 request 中的 URI,可不同于初始值,例如内部重定向时或使用 index

参数举例:


if ($http_user_agent ~ MSIE) {
 rewrite ^(.*)$ /msie/$1 break;
 
}

 
if ($http_cookie ~* "id=([^;] +)(?:;|$)" ) {
 set $id $1;
 
}

 
if ($request_method = POST ) {
 return 405;
 
}

 
if ($args ~ post=140){
 rewrite ^ http://example.com/ permanent;
 }

八、使用的指令

1.set 作用详解

set 作用定义变量或者设置变量。


2.if 作用详解

if主要用来判断一些在rewrite语句中无法直接匹配的条件,比如检测文件存在与否,http

header,cookie 等。

用法: if(条件) {„}

- 当 if 表达式中的条件为 true,则执行 if 块中的语句

- 当表达式只是一个变量时,如果值为空或者任何以 0 开头的字符串都会当作 false

- 直接比较内容时,使用 = 和 !=

- 使用正则表达式匹配时,使用

~ 为区分大小写匹配

~* 为不区分大小写匹配

!~和!~*分别为区分大小写不匹配及不区分大小写不匹配

总结一句: ~为正则匹配, 后置*为大小写不敏感, 前置!为”非”操作

文件及目录判断

-f 和!-f 用来判断是否存在文件

-d 和!-d 用来判断是否存在目录

-e 和!-e 用来判断是否存在文件或目录

-x 和!-x 用来判断文件是否可执行

举例:

if ($http_user_agent ~ MSIE) {
 rewrite ^(.*)$ /msie/$1 break;
 
}//如果 UA 包含”MSIE”,rewrite 请求到/msie 目录下

 
if ($http_cookie ~* "id=([^;] +)(?:;|$)" ) {
 set $id $1;
 
}//如果 cookie 匹配正则,设置变量$id 等于正则引用部分

 
if ($request_method = POST ) {
 return 405;
 
}//如果提交方法为 POST,则返回状态 405 (Method not allowed)

 
if (!-f $request_filename) {
 break;
 proxy_pass http://127.0.0.1;
 }//如果请求文件名不存在,则反向代理 localhost

 if ($args ~ post=140){
 rewrite ^ http://example.com/ permanent;
 }//如果 query string 中包含”post=140″,永久重定向到 example.com


3.return 作用详解

return 设置 HTTP 返回状态,比如403,404 等(但是 301,302 不可用 return 返回,这个下面

会在 rewrite 提到)

(因为 301 和 302 不能简单的只单纯返回状态码,还必须有重定向的 URL,这就是 return

指令无法返回 301,302 的原因了. 作为替换,rewrite 可以更灵活的使用 redirect 和

permanent 标志实现 301 和302.)


4.break 作用详解

立即停止 rewrite 检测,跟下面讲到的 rewrite 的 break flag 功能是一样的,

区别在于前者是一个语句,后者是 rewrite 语句的 flag


九、location  优先级问题

locatoin 并非像 rewrite 那样逐条执行,而是有着匹配优先级的,

当一条请求同时满足几个 location 的匹配时,其只会选择其一的配置执行.

查找方法为:

1. 首先寻找所有的常量匹配,如 location /, location /av/, 以相对路径自左向右匹配,匹配长

度最高的会被使用,

2. 然后按照配置文件中出现的顺序依次测试正则表达式,如 location ~ download\/$,

location ~* \.wtf, 第一个匹配会被使用

3. 如果没有匹配的正则,则使用之前的常量匹配

而下面几种方法当匹配时会立即终止其他 location 的尝试

1. = 完全匹配,location = /download/

2. ^~ 终止正则匹配,如 location ^~ /download/ 如果这条是最长匹配,则终止正则匹配,这个

符号只能匹配常量

3. 在没有=或者^~的情况下,如果常量完全匹配,也会立即终止测试,比如请求为

/download/ 会完全命中 location /download/而不继续其他的正则测试

总结:

1. 如果完全匹配(不管有没有=),尝试会立即终止

2. 以最长匹配测试各个常量,如果常量匹配并有 ^~, 尝试会终止

3. 按在配置文件中出现的顺序测试各个正则表达式

4. 如果第 3 步有命中,则使用其匹配 location,否则使用第 2 步的 location

例子来看看

location = / {
 ....配置 A
 
}

 
location / {
 ....配置 B
 
}

 
location ^~ /images/ {
 ....配置 C
 
}

 
location ~* \.(gif|jpg|jpeg)$ {
 ....配置 D
 }

访问 / 会使用配置 A -> 完全命中

访问 /documents/document.html 会使用配置 B -> 匹配常量 B,不匹配正则 C 和 D,所以

用 B

访问 /images/1.gif 会使用配置C -> 匹配常量B,匹配正则C,使用首个命中的正则,所以用

C

访问 /documents/1.jpg 会使用配置 D -> 匹配常量 B,不匹配正则 C,匹配正则 D,使用首个

命中的正则,所以用 D

为了简化问题最简单的方法就是把 rewrite 规则放在比 location 先执行的 server 里。


十、实战实例详例

1.网站目录首页处理

1.1.访问 index.html 统一转发到 /shouye/index.html 中

rewrite ^/index\.(html|htm)$ /shouye/index.html break;

1.2.不存在的文件 404 的全部跳转首页配置--固定目录访问指定该目录下的首页文件。

location / {
 index index.html index.htm index.php;
 
root /application/www/laoseng;

 
if (-f $request_filename/index.html){ #某一个目录下的 html 可以访问。
 rewrite (.*) $1/index.html break;
 
}

 
if (-f $request_filename/index.php){ #某个目录下的 php
 rewrite (.*) $1/index.php;
 
}

 
if (!-f $request_filename){ #如果不存在转向访问首页 index.php
 rewrite (.*) /index.php;
 }
 }



2.访问/abcjob/a.php 或者 a.xxx 转发访问 /area/abc/a.xxx permanent

3.多目录转成参数

www.laoseng.com/sort/2 => www.laoseng.com/index.php?act=sort&name=abc&id=2
 if ($host ~* (.*)\.laoseng\.com) {
 set $sub_name $1;
 rewrite ^/sort/(\d+)/?$ /index.php?act=sort&cid=$sub_name&id=$1 last;
 }

4.目录对换

/123456/xxxx -> /xxxx?id=123456

rewrite ^/(\d+)/(.+) /$2?id=$1 last;


5.根据浏览器类型访问不同目录

例如下面设定 nginx 在用户使用 ie 的使用重定向到/nginx-ie 目录下:

if ($http_user_agent ~ MSIE) {
 rewrite ^(.*)$ /nginx-ie/$1 break;
 }
 firefox 的用户访问 /nginx-firefox 下的文件
 if ($http_user_agent ~ Firefox) {
 rewrite ^(.*)$ /nginx-firefox/$1 break;
 }

说明 ~ 意思是包含的意思.


6.目录自动加“/” 例如访问 http://www.laoseng.com/22 22是该域名下 22 目录。

if (-d $request_filename){
 rewrite ^/(.*)([^/])$ http://$host/$1$2/ permanent;
 }

7.安全或者特殊要求关于禁止访问那些文件或者禁止那些 ip 访问。

7.1 禁止访问某个目录下的单个文件

location ~ ^/dirname/file.html {
 deny all;

 break;
 }

7.2 禁止访问某个或者多个目录

location ~ ^/(cron|templates)/ {
 deny all;
 break;
 }

7.3.禁止访问某个目录下的可执行文件。

location ~ ^/dirname/(.*)\.(php|jsp|exe)$ {
 deny all;
 break;
 }

8.给 favicon.ico 和 robots.txt设置过期时间;

这里为 favicon.ico 为 99 天,robots.txt 为 7 天并不记录 404 错误日志

location ~(favicon.ico) {
 log_not_found off;
 #access_log off;
 expires 99d;
 break;
 }

9.将多级目录下的文件转成一个文件,增强 seo 效果

/job-123-456-789.html 指向/job/123/456/789.html

rewrite ^/job-([0-9]+)-([0-9]+)-([0-9]+)\.html$ /job/$1/$2/jobshow_$3.html last;

其中的$n 就是指正则表达中模糊匹配的第 n 个匹配的字符串。

以上例子中$1 表示第一个([0-9]+)匹配的数字串,$2 表示第二个([0-9]+)匹配的数字串....


10.域名跳转


server
 {
 listen 80;
 server_name jump.c1gstudio.com;
 index index.html index.htm index.php;
 root /opt/lampp/htdocs/www;
 rewrite ^/ http://www.c1gstudio.com/;
 access_log off;
 }

11.多域名转向 www.c1gstudio.org www.c1gstudio.net 都转向 www.c1gstudio.com 域名

变化时候,使用老域名转向新域名

server_name www.c1gstudio.com www.c1gstudio.net;
 index index.html index.htm index.php;
 root /opt/lampp/htdocs;
 if ($host ~ "c1gstudio/.net") {
 rewrite ^(.*) http://www.c1gstudio.com$1 permanent;
 }

12.三级域名跳转

if ($http_host ~* "^(.*)/.i/.c1gstudio/.com$") {
 rewrite ^(.*) http://top.yingjiesheng.com$1;
 break;
 }

13.域名镜向

server
 {
 listen 80;
 server_name www.abc.com;
 index index.html index.htm index.php;
 root /opt/lampp/htdocs/www;
 rewrite ^/(.*) http://www.laoseng.com/$1 last;
 access_log off;
 }


14.文件反盗链并设置过期时间(其中域名是 www.laoseng.org)

这里的 return 490 为自定义的 http 状态码,默认为 403,方便找出正确的盗链的请求

“rewrite ^/ http://leech.c1gstudio.com/leech.gif;”显示一张防盗链图片

“access_log off;”不记录访问日志,减轻压力

“expires 3d”所有文件 3 天的浏览器缓存

location ~* ^.+\.(jpg|jpeg|gif|png|swf|rar|zip|css|js)$ {
 valid_referers none blocked *.laoseng.org *.laoseng.net localhost 208.97.167.194;
 if ($invalid_referer) {
 rewrite ^/ http://www.laoseng.org/logo.gif;
 return 490;
 break;
 }
 access_log off;
 root /var/www/laoseng;
 expires 3d;
 break;
 }


15.针对传递参数的 rewrite 规则

动态参数 rewrite

以 discuz7.2 到 discuzx1.5 为例

if ($query_string ~* tid=([0-9]+)) {
 set $id $1;
 rewrite  "^(.*)/viewthread.php$"
 $1/forum.php?mod=viewthread&tid=$id&extra=page%3D&page=1 last;
 }
 if ($query_string ~* gid=([0-9]+)) {
 set $id $1;
 rewrite "^(.*)/index.php$" $1/forum.php?gid=$id last;
 }

16.针对访问 nginx 嵌套 if 变相实现方法

nginx 不支持 if and 和多层嵌套 if,需要通过其它

方法实现.

下面是把访问镜像网站 blog.laoseng.com 的爬虫转到 www 站.

set $needrewrite ''; #随便定义的一个变量
 if  ($http_user_agent  ~*
 (baiduspider|googlebot|soso|bing|sogou|yahoo|sohu-search|yodao|YoudaoBot|robozil
 la|msnbot|MJ12bot|NHN|Twiceler)) {
 set $needrewrite 'o';
 }
 if ($host ~ blog\.laoseng\.com) {
 set $needrewrite "${needrewrite}k";
 }
 if ($needrewrite = ok) {
 #return 403;
 rewrite ^(.*) http://www.laoseng.com$1 permanent;
 }


17.完整正确的 Discuz!在 Nginx 下的 Rewrite 如下:

rewrite ^/archiver/((fid|tid)-[\w\-]+\.html)$ /archiver/index.php?$1 last;
 rewrite ^/forum-([0-9]+)-([0-9]+)\.html$ /forumdisplay.php?fid=$1&page=$2 last;
 rewrite
 ^/thread-([0-9]+)-([0-9]+)-([0-9]+)\.html$ /viewthread.php?tid=$1&extra=page%3D$3&page
 =$2 last;
 rewrite ^/space-(username|uid)-(.+)\.html$ /space.php?$1=$2 last;
 rewrite ^/tag-(.+)\.html$ /tag.php?name=$1 last;
 break;

十一、学习建议以及总结

做了 20 多条rewrite 规则,那么总结最终的想法以及学习 rewrite 规则的时候建议:

1.学习正则表达式。

2.学习一下 nginx 全局变量,以及自定义变量,if 语句等

3.实战配置一些例子,配置 10-20 种不同类型后,看看效果。

4.总结一下,思考一下.


十二、参考文献

http://wiki.nginx.org/HttpRewriteModule

http://blog.hexu.org/archives/1052.shtml

http://blog.c1gstudio.com/archives/434