Nginx 的 rewrite
指令可以实现路径重写、重定向等功能,它的使用规则如下:
语法: rewrite regex replacement [flag];
默认: —
可使用的上下文: server, location, if
其中:
regex:匹配的正则表达式;
replacement:重写后的 URL;
flag:对重写后路径的具体处理动作;
上面前俩参数 regex
和 replacement
很明了,regex
的作用就是使用正则表达式对当前请求 URL 进行匹配,如果匹配成功,就将路径重写为 replacement
指定的值,那么 flag
到底是啥意思呢?
flag
有如下几个常用取值:
last
:URL 一旦被rewrite
规则重写完成后,重写后的 URL 会以类似一个新请求的方式被从头重新开始匹配;break
:URL 一旦被rewrite
规则重写完成后,会立马寻找重写后的 URL 对应的资源并返回;redirect
:以302
响应码(临时重定向)返回重写后的 URL;permanent
:以301
响应码 (永久重定向)返回重写后的 URL;
看了上述的描述可能你还是似懂非懂,那就一起来动手试验一下吧~~~
下面我以 IP 为
10.0.1.200
的主机做演示。
环境准备
配置如下虚拟主机:
server {
listen 80;
root /nginx/;
}
创建对应测试目录:
$ mkdir /nginx/{www,hdoc} -p
$ echo 'page in www' > /nginx/www/1.html
$ echo 'page in hdoc' > /nginx/hdoc/1.html
测试访问:
$ curl 10.0.1.200/www/1.html
<h1>page www</h1>
$ curl 10.0.1.200/hdoc/1.html
<h1>page hdoc</h1>
测试 last
修改虚拟主机配置如下:
server {
listen 80;
root /nginx/;
location / {
rewrite ^/html/(.*\.html)$ /www/$1 last;
rewrite ^/www/(.*\.html)$ /hdoc/$1 last;
}
}
思考一下:如果我们此时访问 10.0.1.200/html/1.html
会返回什么?
- 以 URL
/html/1.html
开始匹配第一条rewrite
规则,符合规则可以被匹配到,replacement
中的$1
是对regex
中的分组(.*\.html)
的后向引用,即/html/1.html
会被重写为/www/1.html
,发现flag
为last
,准备重新以/www/1.html
进行匹配; - 以 URL
/www/1.html
重新开始匹配第一条rewrite
规则,不符合规则不可以被匹配到,继续向下执行; - 以 URL
/www/1.html
开始匹配第二条rewrite
规则,符合规则可以被匹配到,replacement
中的$1
是对regex
中的分组(.*\.html)
的后向引用,即/www/1.html
会被重写为/hdoc/1.html
,发现flag
为last
,准备重新以/hdoc/1.html
进行匹配; - 以 URL
/hdoc/1.html
重新开始匹配第一条rewrite
规则,不符合规则不可以被匹配到,继续向下执行; - 以 URL
/hdoc/1.html
重新开始匹配第二条rewrite
规则,不符合规则不可以被匹配到,继续向下执行; - 发现后续没有内容了,此时就开始寻找 URL
/hdoc/1.html
对应的资源响应,即/nginx/hdoc/1.html
;
下面使用浏览器访问一下验证一下上述推理:
即访问 /html/1.html
最终返回了 /nginx/hdoc/1.html
的内容。
现在你理解了“使用 last
时 URL 一旦被 rewrite
规则重写完成后,重写后的 URL 会以类似一个新请求的方式被从头重新开始匹配”这句话的含义吧~~~
测试 break
修改虚拟主机配置如下:
server {
listen 80;
root /nginx/;
location / {
rewrite ^/html/(.*\.html)$ /www/$1 break;
rewrite ^/www/(.*\.html)$ /hdoc/$1 last;
}
}
思考一下:如果我们此时访问 10.0.1.200/html/1.html
会返回什么?
- 以 URL
/html/1.html
开始匹配第一条rewrite
规则,符合规则可以被匹配到,replacement
中的$1
是对regex
中的分组(.*\.html)
的后向引用,即/html/1.html
会被重写为/www/1.html
,发现flag
为break
,则立马寻找 URL/www/1.html
对应的资源响应,即/nginx/www/1.html
;
下面使用浏览器访问一下验证一下上述推理:
即访问 /html/1.html
最终返回了 /nginx/www/1.html
的内容。
如果你还木有明白,那就再修改虚拟主机配置如下:
server {
listen 80;
root /nginx/;
location / {
rewrite ^/html/(.*\.html)$ /www/$1 last;
rewrite ^/www/(.*\.html)$ /hdoc/$1 break;
}
}
思考一下:这次我们访问 10.0.1.200/html/1.html
会返回什么?
- 以 URL
/html/1.html
开始匹配第一条rewrite
规则,符合规则可以被匹配到,replacement
中的$1
是对regex
中的分组(.*\.html)
的后向引用,即/html/1.html
会被重写为/www/1.html
,发现flag
为last
,准备重新以/www/1.html
进行匹配; - 以 URL
/www/1.html
重新开始匹配第一条rewrite
规则,不符合规则不可以被匹配到,继续向下执行; - 以 URL
/www/1.html
开始匹配第二条rewrite
规则,符合规则可以被匹配到,replacement
中的$1
是对regex
中的分组(.*\.html)
的后向引用,即/www/1.html
会被重写为/hdoc/1.html
,发现flag
为break
,则立马寻找 URL/hdoc/1.html
对应的资源响应,即/nginx/hdoc/1.html
;
下面使用浏览器访问一下验证一下上述推理:
即访问 /html/1.html
最终返回了 /nginx/hdoc/1.html
的内容。
现在你明白了“使用 break
时 URL 一旦被 rewrite
规则重写完成后,会立马寻找重写后的 URL 对应的资源并返回”这句话的含义了吧~~~
我们会发现,此种方式和上面「测试 last」中使用两个 last
的结果一样,但是它却少几个步骤,所以我们在使用 last
和 break
是最好要细细斟酌一下如何匹配效率最优哦!
测试 redirect
修改虚拟主机配置如下:
server {
listen 80;
root /nginx/;
location / {
rewrite ^/html/(.*\.html)$ /www/$1 redirect;
rewrite ^/www/(.*\.html)$ /hdoc/$1 last;
}
}
思考一下:如果我们此时访问 10.0.1.200/html/1.html
会返回什么?
这个就不用一步步的推理啦,因为这里的 redirect
就是我们熟悉的 302 临时重定向啦~~
请求的 URL 只要被使用 redirect
的规则匹配到了就立马响应浏览器重定向,后续的规则都不会执行。
即此时我们请求 10.0.1.200/html/1.html
会被第一条规则匹配到并重定向到 10.0.1.200/www/1.html
,使用浏览器验证一下:
测试 permanent
修改虚拟主机配置如下:
server {
listen 80;
root /nginx/;
location / {
rewrite ^/html/(.*\.html)$ /www/$1 permanent;
rewrite ^/www/(.*\.html)$ /hdoc/$1 last;
}
}
permanent
与 redirect
唯一的区别就是 permanent
是永久重定向地址会被浏览器缓存,响应状态码为 301
。
同 redirect
,请求的 URL 只要被使用 permanent
的规则匹配到了就立马响应浏览器重定向,后续的规则都不会执行。
即此时我们请求 10.0.1.200/html/1.html
会被第一条规则匹配到并重定向到 10.0.1.200/www/1.html
,使用浏览器验证一下:
到此,四个 flag
的使用我们已经都了解了,本该松一口气我们又学会新知识。。。但是我测试的时候又发现了一个大坑。。继续往下看。
大坑
在上面的测试中,我们的 rewrite
规则都是写在 location
节下,但是它其实还可以写在 server
下,我们把虚拟主机配置修改为如下:
server {
listen 80;
root /nginx/;
rewrite ^/html/(.*\.html)$ /www/$1 last;
rewrite ^/www/(.*\.html)$ /hdoc/$1 last;
}
思考一下:如果我们此时访问 10.0.1.200/html/1.html
会返回什么?
按之前的对 last
的结论来说,URL 会被两条规则所匹配,最后重写后的 URL 为 /hdoc/1.html
,所以返回的结果应该是 /nginx/hdoc/1.html
的内容,那就验证一下:
翻车了,,,我们发现此时返回的竟然是第一条 rewrite
指令重写后的 URL 对应的页面。
继续,那我们再把虚拟主机配置改为如下:
server {
listen 80;
root /nginx/;
rewrite ^/html/(.*\.html)$ /www/$1 break;
rewrite ^/www/(.*\.html)$ /hdoc/$1 break;
}
此时访问 10.0.1.200/html/1.html
会返回什么呢?
按之前对 last
的结论来说,URL 被第一条规则匹配到重写后会立即寻找重写后 URL 对应的资源并返回。
第一条规则重写后的 URL 是 /www/1.html
,即应该返回 /nginx/www/1.html
页面,那我们测试一下咯。。
会发现依旧是返回第一条 rewrite
指令重写后的 URL 对应的页面。
在随后我也测试了如下几种情况:
# last,break
server {
...
rewrite ^/html/(.*\.html)$ /www/$1 last;
rewrite ^/www/(.*\.html)$ /hdoc/$1 break;
}
# break,last
server {
...
rewrite ^/html/(.*\.html)$ /www/$1 break;
rewrite ^/www/(.*\.html)$ /hdoc/$1 last;
}
# last,redirect
server {
...
rewrite ^/html/(.*\.html)$ /www/$1 last;
rewrite ^/www/(.*\.html)$ /hdoc/$1 redirect;
}
# last,permanent
server {
...
rewrite ^/html/(.*\.html)$ /www/$1 last;
rewrite ^/www/(.*\.html)$ /hdoc/$1 permanent;
}
这几种配置下访问 10.0.1.200/html/1.html
的结果都是第一条规则匹配到重写后 URL 对应的页面,即:
我们此时应该可以暂且下一个结论,当有多条 rewrite
指令用在 server
节中时,仅第一条 rewrite
指令会生效。
这里我使用的 Nginx 版本为 1.16.1
,如果有不同结论欢迎拆台。。。
rewrite
指令由 ngx_http_rewrite_module
模块提供,更多细节可参阅官方文档。
重写日志
Nginx 对于被 rewrite
匹配到的请求可单独配置其重写日志,使用指令为 rewrite_log
,其使用规则如下:
语法: rewrite_log on | off;
默认: rewrite_log off;
可使用的上下文: http, server, location, if
rewrite_log
指令只可以设定是否启用重写日志功能,它还需要与 error_log
指令一起搭配使用,使用 error_log
可指定重写日志的保存位置。
示例:
server {
listen 80;
root /nginx/;
rewrite_log on;
error_log /var/log/nginx/rewrite_log notice;
location / {
rewrite ^/html/(.*\.html)$ /www/$1 last;
rewrite ^/www/(.*\.html)$ /hdoc/$1 permanent;
}
}
这里注意设定
error_log
的日志级别参数哦,因为它默认是记录错误日志的,如果不降低其记录的日志级别,重写日志是不会记录的。
其访问日志格式如下:
$ tailf /var/log/nginx/rewrite_log
2020/03/13 21:12:56 [notice] 5930#0: *334 "^/html/(.*\.html)$" matches "/html/1.html", client: 10.0.1.201, server: , request: "GET /html/1.html HTTP/1.1", host: "10.0.1.200"
2020/03/13 21:12:56 [notice] 5930#0: *334 rewritten data: "/www/1.html", args: "", client: 10.0.1.201, server: , request: "GET /html/1.html HTTP/1.1", host: "10.0.1.200"
2020/03/13 21:12:56 [notice] 5930#0: *334 "^/html/(.*\.html)$" does not match "/www/1.html", client: 10.0.1.201, server: , request: "GET /html/1.html HTTP/1.1", host: "10.0.1.200"
2020/03/13 21:12:56 [notice] 5930#0: *334 "^/www/(.*\.html)$" matches "/www/1.html", client: 10.0.1.201, server: , request: "GET /html/1.html HTTP/1.1", host: "10.0.1.200"
2020/03/13 21:12:56 [notice] 5930#0: *334 rewritten data: "/hdoc/1.html", args: "", client: 10.0.1.201, server: , request: "GET /html/1.html HTTP/1.1", host: "10.0.1.200"
2020/03/13 21:12:56 [notice] 5930#0: *334 "^/html/(.*\.html)$" does not match "/hdoc/1.html", client: 10.0.1.201, server: , request: "GET /html/1.html HTTP/1.1", host: "10.0.1.200"
2020/03/13 21:12:56 [notice] 5930#0: *334 "^/www/(.*\.html)$" does not match "/hdoc/1.html", client: 10.0.1.201, server: , request: "GET /html/1.html HTTP/1.1", host: "10.0.1.200"
评论区