Nginx-rewrite模块详解与Location配置总结
一、rewrite模块介绍
- 官方README
1
2
3
4
5
6
7
8
9
10
11
12
13The ngx_http_rewrite_module module is used to change request URI using PCRE regular expressions, return redirects, and conditionally select configurations.
#将用户请求的URI基于PCRE regex所描述的模式进行检查,而后完成重定向替换
The break, if, return, rewrite, and set directives are processed in the following order:
#break、if、return、rewrite和set指令的处理顺序如下:
the directives of this module specified on the server level are executed sequentially;
#该模块在server段中以指定的指令顺序执行;
repeatedly:
a location is searched based on a request URI;
#根据请求URL搜索location配置;
the directives of this module specified inside the found location are executed sequentially;
#在指定的location中按照顺序执行模块指令;
the loop is repeated if a request URI was rewritten, but not more than 10 times.
#如果重写请求URI,则重复循环,但不超过10次;
二、rewrite模块指令
2.1、break指令
- 语法:
break
- 默认值:
none
- 作用域:
server
,location
,if
- 功能:完成当前设置的重写规则,停止执行其他的重写规则
Example:1
2
3
4if ($slow) {
limit_rate 10k;
break;
}
2.2、if指令
- 语法:
if (condition) { ... }
- 默认值:
none
- 作用域:
server, location
- 功能:对给定的条件condition进行判断。如果为真,大括号内的rewrite指令将被执行
- if条件(conditon)可以是如下任何内容:
- 一个变量名;false如果这个变量是空字符串或者以0开始的字符串;
- 使用
=
,!=
比较的一个变量和字符串 - 是用
~
,~*
与正则表达式匹配的变量,如果这个正则表达式中包含},;则整个表达式需要用” 或’ 包围 - 使用
-f
,!-f
检查一个文件是否存在 - 使用
-d
,!-d
检查一个目录是否存在 - 使用
-e
,!-e
检查一个文件、目录、符号链接是否存在 - 使用
-x
,!-x
检查一个文件是否可执行
Example One:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15if ($http_user_agent ~ MSIE) {
rewrite ^(.*)$ /msie/$1 break;
}
if ($http_cookie ~* "id=([^;]+)(?:;|$)") {
set $id $1;
}
if ($request_method = POST) {
return 405;
}
if ($slow) {
limit_rate 10k;
}
if ($invalid_referer) {
return 403;
}
2.3、return指令
- 语法:
return code [text];
return code URL;
return URL;
- 默认值:
none
- 作用域:
server
,location
,if
- 功能:停止处理并返回指定状态码(code)给客户端。非标准状态码444表示关闭连接且不给客户端发响应头。从0.8.42版本起,return支持响应URL重定向(对于301,302,303,307),或者文本响应(对于其他状态码).对于文本或者URL重定向可以包含变量
2.4、rewrite指令
- 语法:
rewrite regex replacement [flag];
- 默认值:
none
- 作用域:
server
,location
,if
- 功能:按照相关的正则表达式与字符串修改URI,指令按照在配置文件中出现的顺序执行。可以在重写指令后面添加标记。注意:如果替换的字符串以http://开头,请求将被重定向,并且不再执行多余的rewrite指令。
- 尾部的标记(flag)可以是以下的值:
- last - 停止处理重写模块指令,之后搜索location与更改后的URI匹配
- break - 完成重写指令
- redirect - 返回302临时重定向,如果替换字段用http://开头则被使用
- permanent - 返回301永久重定向
Example One:1
2
3
4
5
6
7
8
9
10
11
12
13#如果这些rewrite放到 “/download/” location如下所示, 那么应使用break而不是last , 使用last将循环10次匹配,然后返回 500错误
server {
...
rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 last;
rewrite ^(/download/.*)/audio/(.*)\..*$ $1/mp3/$2.ra last;
return 403;
...
}
location /download/ {
rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 break;
rewrite ^(/download/.*)/audio/(.*)\..*$ $1/mp3/$2.ra break;
return 403;
}
1 | #对于重写后的URL(replacement)包含原请求的请求参数,原URL的?后的内容。如果不想带原请求的参数 ,可以在replacement后加一个问号。如下,我们加了一个自定义的参数user=$1,然后在结尾处放了一个问号?,把原请的参数去掉 |
2.5、rewrite_log指令
- 语法:
rewrite_log on | off;
- 默认值:
rewrite_log off;
- 作用域:
http
,server
,location
,if
- 功能:开启或关闭以notice级别打印rewrite处理日志到error log文件。
Example One:1
2
3
4#打开rewrite_log on
#把error log的级别调整到 notice
rewrite_log on;
error_log logs/xxx.error.log notice;
2.6、set指令
- 语法:
set $variable value;
- 默认值:
none
- 作用域:
server
,location
,if
- 功能:定义一个变量并赋值,值可以是文本,变量或者文本变量混合体。
Example One:1
2
3
4
5#nginx日志打印cookie信息,在vhosts,server段中配置如下:
set $jvm_cookie "";
if ($http_cookie ~* "JSESSIONID=([[:alnum:]]*\.[[:alnum:]]*-[[:alnum:]]*)") {
set $jvm_cookie $1;
}
2.7、uninitialized_variable_warn指令
- 语法:
uninitialized_variable_warn on | off;
- 默认值:
uninitialized_variable_warn on;
- 作用域:
http
,server
,location
,if
- 功能:控制是否记录未初始化变量的警告信息
三、location配置
3.1、location正则表达式匹配
匹配顺序:
(location =) > (location 完整路径) > (location ^~ 路径) > (location ~,~* 正则顺序) > (location 部分起始路径) > (/)
-
~
为区分大小写匹配 -
~*
为不区分大小写匹配 -
!~
和!~*
分别为区分大小写不匹配及不区分大小写不匹配 -
^
以什么开头的匹配 -
$
以什么结尾的匹配 -
\
转义字符。可以转. * ?等 -
*
代表任意字符 -
=
开头表示精确匹配 -
^~
开头表示uri以某个常规字符串开头,不是正则匹配 -
/
通用匹配, 如果没有其它匹配,任何请求都会匹配到
3.2、location生产配置建议
1 | 所以实际使用中,个人觉得至少有三个匹配规则定义,如下: |
四、rewrite与flag标记配置
- rewrite功能就是,使用nginx提供的全局变量或自己设置的变量,结合正则表达式和标志位实现url重写以及重定向。rewrite只能放在server{},location{},if{}中,并且只能对域名后边的除去传递的参数外的字符串起作用,例如 http://seanlook.com/a/we/index.php?id=1&u=str 只对/a/we/index.php重写。语法rewrite regex replacement [flag];
- 如果相对域名或参数字符串起作用,可以使用全局变量匹配,也可以使用proxy_pass反向代理。
- 表明看rewrite和location功能有点像,都能实现跳转,主要区别在于rewrite是在同一域名内更改获取资源的路径,而location是对一类路径做控制访问或反向代理,可以proxy_pass到其他机器。很多情况下rewrite也会写在location里,
- 它们的执行顺序是:
1、执行server块的rewrite指令
2、执行location匹配
3、执行选定的location中的rewrite指令
4、如果其中某步URI被重写,则重新循环执行1-3,直到找到真实存在的文件;循环超过10次,则返回500 Internal Server Error错误。
提示:因为301和302不能简单的只返回状态码,还必须有重定向的URL,这就是return指令无法返回301,302的原因了。这里 last 和 break 区别有点难以理解:
last一般写在server和if中,而break一般使用在location中
last不终止重写后的url匹配,即新的url会再从server走一遍匹配流程,而break终止重写后的匹配
break和last都能组织继续执行后面的rewrite指令
Example One:1
2
3
4
5
6
7
8
9
10
11
12#对/images/bla_500x400.jpg的文件请求,重写到/resizer/bla.jpg?width=500&height=400地址,并会继续尝试匹配location
rewrite ^/images/(.*)_(\d+)x(\d+)\.(png|jpg|gif)$ /resizer/$1.$4?width=$2&height=$3? last;
location / {
# 重写规则信息
error_log logs/rewrite.log notice;
# 注意这里要用‘’单引号引起来,避免{}
rewrite '^/images/([a-z]{2})/([a-z0-9]{5})/(.*)\.(png|jpg|gif)$' /data?file=$3.$4;
# 注意不能在上面这条规则后面加上“last”参数,否则下面的set指令不会执行
set $image_file $3;
set $image_type $4;
}
Example Two:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31http {
# 定义image日志格式
log_format imagelog '[$time_local] ' $image_file ' ' $image_type ' ' $body_bytes_sent ' ' $status;
# 开启重写日志
rewrite_log on;
server {
root /home/www;
location / {
# 重写规则信息
error_log logs/rewrite.log notice;
# 注意这里要用‘’单引号引起来,避免{}
rewrite '^/images/([a-z]{2})/([a-z0-9]{5})/(.*)\.(png|jpg|gif)$' /data?file=$3.$4;
# 注意不能在上面这条规则后面加上“last”参数,否则下面的set指令不会执行
set $image_file $3;
set $image_type $4;
}
location /data {
# 指定针对图片的日志格式,来分析图片类型和大小
access_log logs/images.log mian;
root /data/images;
# 应用前面定义的变量。判断首先文件在不在,不在再判断目录在不在,如果还不在就跳转到最后一个url里
try_files /$arg_file /image404.html;
}
location = /image404.html {
# 图片不存在返回特定的信息
return 404 "image not found\n";
}
}
五、if指令与全局变量配置
- if判断指令,语法为if(condition){…},对给定的条件condition进行判断。如果为真,大括号内的rewrite指令将被执行。
- if条件(conditon)可以是如下任何内容:
- 当表达式只是一个变量时,如果值为空或任何以0开头的字符串都会当做false
- 直接比较变量和内容时,使用=或!=
-
~
正则表达式匹配,~*
不区分大小写的匹配,!~区分大小写的不匹配 -
-f
和!-f
用来判断是否存在文件 -
-d
和!-d
用来判断是否存在目录 -
-e
和!-e
用来判断是否存在文件或目录 -
-x
和!-x
用来判断文件是否可执行
提示:可以利用全局或自定义变量进行if,请移步NGINX内置变量速查表。
Example One:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31if ($http_user_agent ~ MSIE) {
rewrite ^(.*)$ /msie/$1 break;
} //如果UA包含"MSIE",rewrite请求到/msid/目录下
if ($http_cookie ~* "id=([^;]+)(?:;|$)") {
set $id $1;
} //如果cookie匹配正则,设置变量$id等于正则引用部分
if ($request_method = POST) {
return 405;
} //如果提交方法为POST,则返回状态405(Method not allowed)。return不能返回301,302
if ($slow) {
limit_rate 10k;
} //限速,$slow可以通过 set 指令设置
if (!-f $request_filename){
break;
proxy_pass http://127.0.0.1;
} //如果请求的文件名不存在,则反向代理到localhost 。这里的break也是停止rewrite检查
if ($args ~ post=140){
rewrite ^ http://example.com/ permanent;
} //如果query string中包含"post=140",永久重定向到example.com
location ~* \.(gif|jpg|png|swf|flv)$ {
valid_referers none blocked www.jefflei.com www.leizhenfang.com;
if ($invalid_referer) {
return 404;
} //防盗链
}
Example Two:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17#判断如果请求为/jfpt_common/logon!toAdminlogon.action?appid=(6|2760|5117|251|7259)$结尾,则rewrite为:https://$server_name/jfpt_common_sw/jfpt_common/logon!toAdminlogon.action?appid=(6|2760|5117|251|7259)
if ( $request_uri ~ /jfpt_common/logon!toAdminlogon.action\?appid=(6|2760|5117|251|7259)$ ) {
rewrite ^/jfpt_common/(.*) https://$server_name/jfpt_common_sw/$1 redirect;
}
#判断如果请求为/xxdkdf/login!toLogon.action,则rewrite为:https://$server_name/xxdkdf/login!toLogon.action
if ( $request_uri ~ /xxdkdf/logon\!toLogon\.action ) {
rewrite ^(.*) https://$server_name$1 redirect;
}
#永久重定向HTTPS
if ( $request_uri ~ /) {
rewrite ^(.*) https://$server_name$1 permanent;
}
六、nginx常用正则
- 分享:PCRE正则表达式全集
-
.
: 匹配除换行符以外的任意字符 -
?
: 重复0次或1次 -
+
: 重复1次或更多次 -
*
: 重复0次或更多次 -
\d
:匹配数字 -
^
: 匹配字符串的开始 -
$
: 匹配字符串的介绍 -
{n}
: 重复n次 -
{n,}
: 重复n次或更多次 -
[c]
: 匹配单个字符c -
[a-z]
: 匹配a-z小写字母的任意一个
提示:小括号()之间匹配的内容,可以在后面通过$1来引用,$2表示的是前面第二个()里的内容。正则里面容易让人困惑的是\转义特殊字符。
七、关于location 理解误区
- location 的匹配顺序是“先匹配正则,再匹配普通”。
矫正: location 的匹配顺序其实是“先匹配普通,再匹配正则”。我这么说,大家一定会反驳我,因为按“先匹配普通,再匹配正则”解释不了大家平时习惯的按“先匹配正则,再匹配普通”的实践经验。这里我只能暂时解释下,造成这种误解的原因是:正则匹配会覆盖普通匹配(实际的规则,比这复杂,后面会详细解释)。
- location 的执行逻辑跟 location 的编辑顺序无关。
矫正:这句话不全对,“普通 location ”的匹配规则是“最大前缀”,因此“普通 location ”的确与 location 编辑顺序无关;但是“正则 location ”的匹配规则是“顺序匹配,且只要匹配到第一个就停止后面的匹配”;“普通 location”与“正则 location ”之间的匹配顺序是?先匹配普通 location,再“考虑”匹配正则 location 。注意这里的“考虑”是“可能”的意思,也就是说匹配完“普通 location ”后,有的时候需要继续匹配“正则 location ”,有的时候则不需要继续匹配“正则 location ”。两种情况下,不需要继续匹配正则 location :( 1 )当普通 location 前面指定了“ ^~ ”,特别告诉 Nginx 本条普通 location 一旦匹配上,则不需要继续正则匹配;( 2 )当普通 location 恰好严格匹配上,不是最大前缀匹配,则不再继续匹配正则。
- 总结一句话:正则 location 匹配让步普通 location 的严格精确匹配结果;但覆盖普通 location 的最大前缀匹配结果