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"
评论区