侧边栏壁纸
博主头像
张种恩的技术小栈博主等级

行动起来,活在当下

  • 累计撰写 748 篇文章
  • 累计创建 65 个标签
  • 累计收到 39 条评论

目 录CONTENT

文章目录

Nginx(14)之URL重写

zze
zze
2020-03-13 / 0 评论 / 0 点赞 / 861 阅读 / 12155 字

Nginx 的 rewrite 指令可以实现路径重写、重定向等功能,它的使用规则如下:

语法: rewrite regex replacement [flag];
默认: —
可使用的上下文: server, location, if

其中:
	regex:匹配的正则表达式;
	replacement:重写后的 URL;
	flag:对重写后路径的具体处理动作;

上面前俩参数 regexreplacement 很明了,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,发现 flaglast,准备重新以 /www/1.html 进行匹配;
  • 以 URL /www/1.html 重新开始匹配第一条 rewrite 规则,不符合规则不可以被匹配到,继续向下执行;
  • 以 URL /www/1.html 开始匹配第二条 rewrite 规则,符合规则可以被匹配到,replacement 中的 $1 是对 regex 中的分组 (.*\.html) 的后向引用,即 /www/1.html 会被重写为 /hdoc/1.html,发现 flaglast,准备重新以 /hdoc/1.html 进行匹配;
  • 以 URL /hdoc/1.html 重新开始匹配第一条 rewrite 规则,不符合规则不可以被匹配到,继续向下执行;
  • 以 URL /hdoc/1.html 重新开始匹配第二条 rewrite 规则,不符合规则不可以被匹配到,继续向下执行;
  • 发现后续没有内容了,此时就开始寻找 URL /hdoc/1.html 对应的资源响应,即 /nginx/hdoc/1.html

下面使用浏览器访问一下验证一下上述推理:

image.png

即访问 /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,发现 flagbreak,则立马寻找 URL /www/1.html 对应的资源响应,即 /nginx/www/1.html

下面使用浏览器访问一下验证一下上述推理:

image.png

即访问 /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,发现 flaglast,准备重新以 /www/1.html 进行匹配;
  • 以 URL /www/1.html 重新开始匹配第一条 rewrite 规则,不符合规则不可以被匹配到,继续向下执行;
  • 以 URL /www/1.html 开始匹配第二条 rewrite 规则,符合规则可以被匹配到,replacement 中的 $1 是对 regex 中的分组 (.*\.html) 的后向引用,即 /www/1.html 会被重写为 /hdoc/1.html,发现 flagbreak,则立马寻找 URL /hdoc/1.html 对应的资源响应,即 /nginx/hdoc/1.html

下面使用浏览器访问一下验证一下上述推理:

image.png

即访问 /html/1.html 最终返回了 /nginx/hdoc/1.html 的内容。

现在你明白了“使用 break 时 URL 一旦被 rewrite 规则重写完成后,会立马寻找重写后的 URL 对应的资源并返回”这句话的含义了吧~~~

我们会发现,此种方式和上面「测试 last」中使用两个 last 的结果一样,但是它却少几个步骤,所以我们在使用 lastbreak 是最好要细细斟酌一下如何匹配效率最优哦!

测试 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,使用浏览器验证一下:
rewrite_redirect

测试 permanent

修改虚拟主机配置如下:

server {
    listen 80;
    root /nginx/;

    location / {
        rewrite ^/html/(.*\.html)$ /www/$1 permanent;
        rewrite ^/www/(.*\.html)$ /hdoc/$1 last;
    }
}

permanentredirect 唯一的区别就是 permanent 是永久重定向地址会被浏览器缓存,响应状态码为 301
redirect,请求的 URL 只要被使用 permanent 的规则匹配到了就立马响应浏览器重定向,后续的规则都不会执行。

即此时我们请求 10.0.1.200/html/1.html 会被第一条规则匹配到并重定向到 10.0.1.200/www/1.html,使用浏览器验证一下:

rewrite_redirect


到此,四个 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 的内容,那就验证一下:

image.png

翻车了,,,我们发现此时返回的竟然是第一条 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 页面,那我们测试一下咯。。

image.png

会发现依旧是返回第一条 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 对应的页面,即:

image.png

我们此时应该可以暂且下一个结论,当有多条 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"
0

评论区