Rewrite简介
Rewrite是Nginx服务器提供的一个重要基本功能,是Web服务器产品中几乎必备的功能。主要的作用是用来实现URL的重写。
注意:Nginx服务器的Rewrite功能的实现依赖于PCRE的支持,因此在编译安装Nginx服务器之前,需要安装PCRE库。Nginx使用的是ngx_http_rewrite_module模块来解析和处理Rewrite功能的相关配置。
- “地址重写"与"地址转发”
重写和转发的区别:
- 地址重写浏览器地址会发生变化而地址转发则不变
- 一次地址重写会产生两次请求而一次地址转发只会产生一次请求
- 地址重写到的页面必须是一个完整的路径而地址转发则不需要
- 地址重写因为是两次请求所以request范围内属性不能传递给新页面,地址转发因为是一次请求所以可以传递值
- 地址转发速度快于地址重写
Rewrite规则
Rewrite常用全局变量
变量 | 说明 | 案例 |
---|---|---|
$args | 变量中存放了请求URL中的请求指令。 | |
功能和$query_string一样 | http://172.41.100.15:8088/args?a=1&b=3 | |
$args=a=1&b=3 | ||
$http_user_agent | 变量存储的是用户访问服务的代理信息 | |
(如果通过浏览器访问,记录的是浏览器的相关版本信息) | http_user_agent = Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 | |
$host | 变量存储的是访问服务器的server_name值 | host =172.41.100.15 |
$document_uri | 变量存储的是当前访问地址的URI。 | document_uri =/args |
$document_root | 变量存储的是当前请求对应location的root值,如果未设置,默认指向Nginx自带html目录所在位置 | document_root=/home/nginx/html |
$content_length | 变量存储的是请求头中的Content-Length的值 | |
$content_type | 变量存储的是请求头中的Content-Type的值 | |
$http_cookie | 变量存储的是客户端的cookie信息,可以通过add_header Set-Cookie 'cookieName=cookieValue’来添加cookie数据 | |
$limit_rate | 变量中存储的是Nginx服务器对网络连接速率的限制,也就是Nginx配置中对limit_rate指令设置的值,默认是0,不限制。 | |
$remote_addr | 变量中存储的是客户端的IP地址 | remote_addr=172.41.100.13 |
$remote_port | 变量中存储了客户端与服务端建立连接的端口号 | remote_port=50360 |
$remote_user | 变量中存储了客户端的用户名,需要有认证模块才能获取 | |
$scheme | 变量中存储了访问协议 | scheme =http |
$server_addr | 变量中存储了服务端的地址 | server_addr=172.41.100.15 |
$server_name | 变量中存储了客户端请求到达的服务器的名称 | server_name=localhost |
$server_port | 变量中存储了客户端请求到达服务器的端口号 | server_port=8088 |
$server_protocol | 变量中存储了客户端请求协议的版本,比如"HTTP/1.1" | server_protocol=HTTP/1.1 |
$request_body_file | 变量中存储了发给后端服务器的本地文件资源的名称 | |
$request_method | 变量中存储了客户端的请求方式,比如"GET","POST"等 | request_method =GET |
$request_filename | 变量中存储了当前请求的资源文件的路径名 | request_filename =/home/nginx/html/args |
$request_uri | 变量中存储了当前请求的URI,并且携带请求参数 | request_uri=/args?a=1&b=3 |
set指令
该指令用来设置一个新的变量。
语法 | set $variable value; |
---|---|
默认值 | — |
位置 | server、location、if |
variable:变量的名称,变量名称要用"$"作为变量的第一个字符,且不能与Nginx服务器预设的全局变量同名。
value:变量的值,可以是字符串、其他变量或者变量的组合等。
if指令
该指令用来支持条件判断,并根据条件判断结果选择不同的Nginx配置。
语法 | if (condition){…} |
---|---|
默认值 | — |
位置 | server、location |
condition为判定条件,可以支持以下写法:
- 变量名。如果变量名对应的值为空或者是0,if都判断为false,其他条件为true。
if ($param){
}
- 使用"=“和”!="比较变量和字符串是否相等,满足条件为true,不满足为false
if ($request_method = POST){
return 405;
}
注意:此处和Java不太一样的地方是字符串不需要添加引号。
- 使用正则表达式对变量进行匹配,匹配成功返回true,否则返回false。变量与正则表达式之间使用"“、”“、”!“、”!"来连接
- "~"代表匹配正则表达式过程中区分大小写
- "~*"代表匹配正则表达式过程中不区分大小写
- "!“和”!*"刚好和上面取相反值,如果匹配上返回false,匹配不上返回true
if ($http_user_agent ~ MSIE){
#$http_user_agent的值中是否包含MSIE字符串,如果包含返回true
}
注意:正则表达式字符串一般不需要加引号,但是如果字符串中包含"}“或者是”;"等字符时,就需要把引号加上。
- 判断请求的文件是否存在使用"-f"和"!-f",
当使用"-f"时,如果请求的文件存在返回true,不存在返回false。
当使用"!-f"时,如果请求文件不存在,但该文件所在目录存在返回true,文件和目录都不存在返回false,如果文件存在返回false
if (-f $request_filename){
#判断请求的文件是否存在
}
if (!-f $request_filename){
#判断请求的文件是否不存在
}
- 判断请求的目录是否存在使用"-d"和"!-d",
当使用"-d"时,如果请求的目录存在,if返回true,如果目录不存在则返回false
当使用"!-d"时,如果请求的目录不存在但该目录的上级目录存在则返回true,该目录和它上级目录都不存在则返回false,如果请求目录存在也返回false. - 判断请求的目录或者文件是否存在使用"-e"和"!-e"
当使用"-e",如果请求的目录或者文件存在时,if返回true,否则返回false.
当使用"!-e",如果请求的文件和文件所在路径上的目录都不存在返回true,否则返回false - 判断请求的文件是否可执行使用"-x"和"!-x"
当使用"-x",如果请求的文件可执行,if返回true,否则返回false
当使用"!-x",如果请求文件不可执行,返回true,否则返回false
break指令
用于中断当前相同作用域中的其他Nginx配置。与该指令处于同一作用域的Nginx配置中,位于它前面的指令配置生效,位于后面的指令配置无效。
语法 | break; |
---|---|
默认值 | — |
位置 | server、location、if |
location /{
if ($param){
set $id $1;
break;
#不生效,限制nginx向客户端每秒传输速率
limit_rate 10k;
}
}
return指令
用于完成对请求的处理,直接向客户端返回响应状态代码。在return后的所有Nginx配置都是无效的。
| 语法 | return code [text];
return code URL;
return URL; |
| — | — |
| 默认值 | — |
| 位置 | server、location、if |
- code:为返回给客户端的HTTP状态代理。可以返回的状态代码为0~999的任意HTTP状态代理
- text:为返回给客户端的响应体内容,支持变量的使用
- URL:为返回给客户端的URL地址
rewrite指令
该指令通过正则表达式的使用来改变URI。可以同时存在一个或者多个指令,按照顺序依次对URL进行匹配和处理。
语法 | rewrite regex replacement [flag]; |
---|---|
默认值 | — |
位置 | server、location、if |
regex:用来匹配URI的正则表达式
replacement:匹配成功后,用于替换URI中被截取内容的字符串。
如果该字符串是以"http://"或者"https://"开头的,则不会继续向下对URI进行其他处理,而是直接返回重写后的URI给客户端。
flag:用来设置rewrite对URI的处理行为,可选值有如下:
- last: 终止执行rewrite模块指令集,并开始搜寻重写url后匹配的location
- break:终止执行rewrite模块指令集
- redirect:临时重定向
- permanent:永久重定向
- 案例
server{
listen 8077;
server_name localhost;
location / {
rewrite ^/test1 /test3 last;
rewrite ^/test2 /test4 break;
rewrite ^/test5 /test6 redirect;
rewrite ^/test7 /test6 permanent;
}
location /break/ {
rewrite ^/break/(.*) /test$1 break;
# echo需要安装第三方模块 echo-nginx-module
# git clone https://codechina.csdn.net/mirrors/agentzh/echo-nginx-module.git
echo "break page";
}
location /test3 {
return 200 "/test3";
}
location /test4 {
return 200 "/test4";
}
location /test6 {
return 200 "/test6";
}
}
- 访问请求
- 访问
[http://172.41.100.15:8077/test1](http://172.41.100.15:8077/test1)
返回/test3
访问/test1被重写为/test3,因为flag为last,所以发起一次location匹配,匹配到location /test3 {},所以最终返回结果为http200及/test3;
- 访问
[http://172.41.100.15:8077/test2](http://172.41.100.15:8077/test2)
返回 404
访问/test2,虽然/test2被重定向到/test4,但是break指令不会重新开启一个新的请求继续匹配,所以nginx是不会匹配到下面的/test/这个location,nginx尝试找/test4这个html页面并输出起内容,事实上,这个页面不存在,所以会报404的错误。可以看error.log
2021/08/18 10:15:19 [error] 29936#0: *7 open() "/home/nginx/html/test4" failed (2: No such file or directory), client: 172.41.100.13, server: localhost, request: "GET /test2 HTTP/1.1", host: "172.41.100.15:8077"
- 访问
http://172.41.100.15:8077/test5
返回/test6
访问/test1被重写为/test6,因为flag为redirect,地址栏被重写为http://172.41.100.15:8077/test6
http状态码为302 临时重定向,浏览器发起两次请求,一次/test5,一次/test6
- 访问
[http://172.41.100.15:8077/test7](http://172.41.100.15:8077/test5)
返回/test6
访问/test1被重写为/test7,因为flag为permanent,地址栏被重写为[http://172.41.100.15:8077/test6](http://172.41.100.15:8077/test6)
http状态码为301 永久重定向,浏览器发起两次请求,一次/test7,一次/test6
- 访问
http://172.41.100.15:8077/break/6
返回break page
break是跳过当前请求的rewrite阶段,并继续执行本请求的其他阶段,对于/break/
对应的content阶段的输出为 echo “break page”;
content阶段,可以简单理解为产生数据输出的阶段,如返回静态页面内容也是在content阶段;echo指令也是运行在content阶段,一般情况下content阶段只能对应一个输出指令,如同一个location配置两个echo,最终只会有一个echo指令被执行;
如果把/break/里的echo 指令注释,然后再次访问/break/xx会报404
虽然/break/xx被重定向到/test/xx,但是break指令不会重新开启一个新的请求继续匹配,所以nginx是不会匹配到下面的/test/这个location;在echo指令被注释的情况下,/break/ 这location里只能执行nginx默认的content指令,即尝试找/test/xx这个html页面并输出起内容,事实上,这个页面不存在,所以会报404的错误。
- URL和URI的区别:
URI:统一资源标识符
URL:统一资源定位符
rewrite_log指令
配置是否开启URL重写日志的输出功能。
语法 | rewrite_log on|off; |
---|---|
默认值 | rewrite_log off; |
位置 | http、server、location、if |
开启后,URL重写的相关日志将以notice级别输出到error_log指令配置的日志文件汇总。
- 案例
server{
rewrite_log on;
# 重定向error_log地址
error_log /home/rewrite.log info;
listen 8077;
server_name localhost;
}
日志输出,notice都是重定向日志
配合防盗链使用
使用rewrite将盗链请求转发到自定义的一张图片和页面,给用户比较好的提示信息。
- 添加一张图片或者页面做提示展示
在172.41.100.14的nginx目录中添加403图片
- 根据防盗链条件重定向盗链请求
server{
listen 9088;
server_name localhost;
location /images {
valid_referers none blocked server_names 127.0.0.1;
if ($invalid_referer){
# 重定向到指定图片
rewrite ^/ http://172.41.100.14:9088/403/403.jpg;
}
root /usr/local/nginx/html;
}
}
- 效果展示
原请求被重定向到403页面了。
目录合并
网站中有一个资源文件的访问路径时 /server/11/22/33/44/20.html,也就是说20.html存在于第5级目录下,如果想要访问该资源文件,客户端的URL地址就要写成 http://172.41.100.15/server/11/22/33/44/20.html
,这个是非常不利于SEO搜索引擎优化的,同时客户端也不好记,使用rewrite可以进行如下配置
server {
listen 80;
server_name localhost;
location /server{
rewrite ^/server-([0-9]+)-([0-9]+)-([0-9]+)-([0-9]+)\.html$ /server/$1/$2/$3/$4/$5.html last;
}
}
客户端只需要输入http://172.41.100.15/server-11-22-33-44-20.html
就可以访问到20.html页面了。这里也充分利用了rewrite指令支持正则表达式的特性。