Nginx Rewrite

Nginx服务器提供了Rewrite功能,用于实现URL的重写。该功能由ngx_http_rewrite_module模块提供,该模块默认开启。Nginx服务器的Rewrite功能依赖于PCRE(Perl Compatible Regular Expressions,Perl兼容的正则表达式),所以在编译安装Nginx的时候需要先安装PCRE库。

Rewrite功能就是,使用Nginx提供的全局变量或自己设置的变量,结合正则表达式和标志位实现url重写以及重定向。Rewrite只能对域名后边的除去传递的参数外的字符串起作用,例如https://mrbird.cc/page/2/search?type=1&value=nginx只对/page/2/search重写。

if指令

用于根据不同条件选择不同的Nginx配置,可在server和location块中配置。语法如下:

1
if (condition) { }

condition为判断条件(true/false):

  • 变量名。如果变量的值为空字符串或者以0开头的任意字符串,为false。其余为true;

  • 比较变量的内容时候,使用=!=

  • 使用正则表达式。~(大小写敏感),~*(大小写不敏感),!~!~*。正则表达式一般不加双引号,除非包含}或者分号;字符;

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

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

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

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

几个实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if ($request_method = POST) {
return 405; # 如果请求方法为POST,则直接返回405HTTP状态码。
}
if ($slow) {
limit_rate 10k;
}
if ($http_user_agent ~ MSIE) {
rewrite ^(.*)$ /msie/$1 break; # 如果UA包含"MSIE",rewrite请求到/msid/目录下
}
if ($http_cookie ~* "id=([^;]+)(?:;|$)") {
set $id $1; # 如果cookie匹配正则,设置变量$id等于正则引用部分
}
if (!-f $request_filename){
# 判断请求的文件是否不存在
}

break指令

该指令用于中断当前相同作用域的Nginx配置。Nginx遇到该指令时,回到上一层作用域继续向下读取配置。该指令可在server,location和if块中使用。语法如下:

1
break;

比如:

1
2
3
4
5
6
7
8
location / {
if ($slow) {
set $id $1;
break; # 跳出if作用域
limit_rate 10k; # 无效
}
... # 继续读取
}

return指令

该指令用于完成对请求的处理,直接向客户端返回响应状态码,处于该指令后的所有Nginx配置都是无效的。语法结构如下:

1
2
3
return [text];
return code URL;
return URL;

比如:

1
2
3
location = /404 {
return 404 "sorry page not found!\n"
}

rewrite指令

该指令通过正则表达式来改变URL,可以同时存在一个或多个指令,按照顺序依次对URL进行匹配处理。该指令可在server,location和if块中配置,语法如下:

1
rewrite regex replacement [flag];

  1. regex用于匹配URL的正则表达式,使用()标记要截取的内容。

  2. replacement匹配成功后用于替换URL中被截取的部分。默认情况下,如果replacement是由http://或者https://开头的字符串,则直接将重写后的URL返回客户端。

  3. flag标志位,其值有如下几种:

    • last,Nginx遇到含有该标志位的rewrite命令时,停止向下处理,直接使用该rewrite返回的新的URL去和所有的location块重新匹配。

    • break,将此处重写的URL作为一个新的URL,在本块中继续进行处理。该标志重写后的URL在当前location块中执行,不会转向到其他的location块。

    • redirect将重写后的URL返回给客户端,状态码为302,表示临时重定向URL。

    • permanent将重写后的URL返回给客户端,状态码为301,表示永久重定向URL。

举些例子:

1
2
3
4
location / {
rewrite ^(/myweb/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 last;
rewrite ^(/myweb/.*)/audio/(.*)\..*$ $1/mp4/$2.ra last;
}

如果某个请求URL在第2行被匹配成功,Nginx不会继续使用第3行配置处理新的URL,而是让所有的location重新匹配新的URL。

1
2
3
4
location /myweb/ {
rewrite ^(/myweb/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 break;
rewrite ^(/myweb/.*)/audio/(.*)\..*$ $1/mp4/$2.ra break;
}

如果某个请求URL在第2行被匹配成功,Nginx服务器将新的URL继续使用本块中的第3行配置进行处理,不会将新的URL发送到其他location块。

如果这里将break替换为last的话,新的URL包含/myweb/串,本location块可能继续捕获到该新的URL,这样便造成了死循环。Nginx在尝试10次循环之后,返回500 Internal Server Error错误。

rewrite_log指令

该指令配置是否开启重写URL日志的输出功能,语法如下:

1
rewrite_log on | off

默认值为off。日志将以notice级别输出到error_log配置的日志文件中。

set指令

该指令用于设置一个新的变量,语法如下:

1
set variable value

变量名称必须以$开头,且不能和Nginx服务器预设的全局变量同名,变量的作用域为全局。

uninitialized_variable_warn指令

该指令用于配置实用未初始化的变量时,是否记录警告日志,语法如下:

1
uninitialized_variable_warn on | off

默认值为on。

rewrite常用全局变量

下表列出了在rewrite配置过程中可能会使用到的Nginx全局变量:

变量 说明
$args 请求URL中的请求参数串,如arg1=value1&arg2=value2
$content_length 请求头中的Content-length字段
$content_type 请求头重的Content-type片段
$document_root 当前请求的根路径
$document_uri 当前请求的URI,比如https://mrbird.cc/page/2/search?type=1&value=nginx中的/page/2/search
$host 当前URL的主机部分字段,比如https://mrbird.cc/page/2/search?type=1&value=nginx中的mrbird.cc。如果为空,则为server块中server_name指令的配置值
$http_user_agent 客户端的代理信息
$http_cookie 客户端的cookie信息
$limit_rate Nginx服务器对网络连接速率的限制,即Nginx配置中的limit_rate指令的配置值
$remote_addr 客户端的IP地址
$remote_port 客户端的端口号
$request_body_file 发给后端服务器的本地文件资源名称
$request_method 客户端的请求方式,如get,post等
$request_filename 当前请求的文件路径,由root或alias指令与URI请求生成。
$request_uri 包含请求参数的原始URI,不包含主机名,如:”/foo/bar.php?arg=baz”
$query_string 和$args相同
$scheme HTTP方法(如http,https,ftp)
$server_protocol 请求使用的协议,通常是HTTP/1.0或HTTP/1.1
$server_addr 服务器的地址
$server_name 服务器名称
$server_port 请求到达服务器的端口号
$uri 同$document_uri

rewrite实例

域名跳转

1
2
3
4
5
6
server {
listen 80;
server_name jump.myweb.name;
rewrite ^/ http://www.myweb.info/;
...
}

比如客户端访问http://jump.myweb.name时,URL将被重写为http://jump.myweb.info

1
2
3
4
5
6
7
8
server {
listen 80;
server_name jump.myweb.name jump.myweb.info
if ($host ~ myweb\.info) {
rewrite ^(.*) http://jump.myweb.name$1 permanent;
}
...
}

当客户端访问http://jump.myweb.info/reqsource时,URL将被重写为http://jump.myweb.name/reqsource

1
2
3
4
5
6
7
8
9
server {
listen 80;
server_name jump1.myweb.name jump2.myweb.name;
if ($http_host ~* ^(.*)\.myweb\.name$) {
rewrite ^(.*) http://jump.myweb.name$1;
break;
}
...
}

当客户端访问http://jump1.myweb.name/reponse或者http://jump2.myweb.name/reponse时,URL都将被Nginx重写为http://jump.myweb.name/reponse

目录合并

1
2
3
4
5
6
7
8
9
server {
listen 80;
server_name www.myweb.name;
location ^~ /server {
rewrite ^/server-([0-9]+)-([0-9]+)-([0-9]+)-([0-9]+)-([0-9]+)\.htm$ /server/$1/$2/$3/$4/$5.html last;
break;
}
}
...

此时如果客户端输入http://www.myweb.name/server-12-34-56-78-9.htm即可访问到http://www.myweb.name/server/12/34/56/78/9.html。这样做将多级目录下的资源文件请求转换为了目录计数少的资源请求,有利于SEO。

防盗链

防盗链的实现原理:通过HTTP协议中的请求头中的Referer头域,我们可以检测到访问目标资源的源地址,如果该地址不是自己站点的URL,就采取组织措施。

Nginx配置中有一个指令valid_referers,如果Referer头域没有符合valid_referers配置的值,$invalid_referer变量的值将被赋值为1。valid_referers语法如下:

1
valid_referers none | blocked | server_names | string ...;

  • none检测Referer头域不存在的情况。

  • blocked检测Referer头域的值被防火墙或者代理服务器删除或伪装的情况。

  • server_names设置URL。

1
2
3
4
5
6
7
8
9
10
11
server {
listen 80;
server_name www.myweb.name;
location ~* ^.+\.(gif|jpg|png|swf|flv|rar|zip)$ {
...
valid_referers none blocked server_names *.myweb.com;
if ($invalid_referer) {
rewrite ^/ http://www.myweb.com/images/forbidden.png;
}
}
...

TOP