Nginx-rewrite模块详解与Location配置总结

Nginx-rewrite模块详解与Location配置总结

一、rewrite模块介绍

  • 官方README
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    The 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
4
if ($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
15
if ($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
2
3
#对于重写后的URL(replacement)包含原请求的请求参数,原URL的?后的内容。如果不想带原请求的参数 ,可以在replacement后加一个问号。如下,我们加了一个自定义的参数user=$1,然后在结尾处放了一个问号?,把原请的参数去掉
rewrite ^/users/(.*)$ /show?user=$1? last;
#如果正则表达regex式中包含 “}” 或 “;”, 那么整个表达式需要用双引号或单引号包围

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
所以实际使用中,个人觉得至少有三个匹配规则定义,如下:
#直接匹配网站根,通过域名访问网站首页比较频繁,使用这个会加速处理,官网如是说。
#这里是直接转发给后端应用服务器了,也可以是一个静态首页
# 第一个必选规则
location = / {
proxy_pass http://tomcat:8080/index
}
# 第二个必选规则是处理静态文件请求,这是nginx作为http服务器的强项
# 有两种配置模式,目录匹配或后缀匹配,任选其一或搭配使用
location ^~ /static/ {
root /webroot/static/;
}
location ~* \.(gif|jpg|jpeg|png|css|js|ico)$ {
root /webroot/res/;
}
#第三个规则就是通用规则,用来转发动态请求到后端应用服务器
#非静态文件请求就默认是动态请求,自己根据实际把握
#毕竟目前的一些框架的流行,带.php,.jsp后缀的情况很少了
location / {
proxy_pass http://tomcat:8080/
}

四、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
31
http {
# 定义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
31
if ($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 的最大前缀匹配结果
-------------本文结束感谢您的阅读-------------
LiGuanCheng wechat
如有问题,请与我微信交流或通过右下角“daovoice”与我联系~。
请我喝一杯咖啡~