博学笃行·盛德日新

Nginx的location和rewrite匹配顺序


技术

这是一篇转载加一些修改,原文地址 https://blog.csdn.net/xuejinliang/article/details/52847506

rewrite指令可以出现在server, location, if 下。这里主要讨论rewrite和location{}的匹配及匹配顺序问题。

对于出现在server{}下的rewrite指令,它会执行在location{}匹配之前,不管顺序如何。

对于出现在location{}下的 rewrite 指令,它的执行当然是在当前 location 匹配之后,但是由于 rewrite 导致 HTTP 请求的 URI 发生了变化,所以在执行完location{}下的 rewrite 后的 URI 又需要重新匹配 location{},就好比一个新的 HTTP 请求一样(注意由 location{} 内的 rewrite 导致的这样的循环匹配最多不超过 10 次,否则 nginx 会报 500 错误)。

总的来说,如果 server{}location{} 下都有 rewrite ,依然是先执行 server{} ,然后进行 location{} 匹配,如果被匹配的 location{} 之内还有 rewrite 指令,那么继续执行 rewrite ,同时因为 location{} 内的 rewrite 改变了 URI ,那么重写后的结果 URI 需要当做一个新的请求,重新进行location{}的匹配,不会再对server{}段的rewrite进行匹配了,但是在新匹配的的location{}段内的rewrite还是会匹配的。

last 和 break 的区别

关于 last flag 和 break flag 的区别,官方文档的描述是:

  • last - completes processing of rewrite directives, after which searches for corresponding URI and location

  • break - completes processing of rewrite directives

都有»不让继续执行后面的 rewrite 指令»的含义,两者的区别在于:如果匹配到包含last的rewrite规则后,不继续匹配后面的rewrite,并在内部重新发起一个新的URI请求,从server{}开始重新进行location{}的匹配;如果匹配到包含break的rewrite规则后,不继续匹配后面的rewrite,也不会再发起一个新的URI请求。

*** 下面将用6个实验进行说明 ***

实验准备工作

  1. 安装好nginx

  2. 在nginx安装目录的 html 子目录下创建6个文件, 分别叫:aaa.html, bbb.html, ccc.html, ddd.html, eee.html, fff.html,文件内容分别是各自的文件名(例 aaa.html 文件内容为: aaa html file )

  3. nginx配置文件初始化:

error_log  logs/error.log info;  #URL 重写模块的日志会写入此文件

server {
    listen       9090;
    server_name  localhost;
    root /data/www/html;
    rewrite_log on;   # 打开 URL 重写模块的日志开关,以便写入 error_log

    location  /aaa.html {
        rewrite "^/aaa\.html$"  /bbb.html;
        rewrite "^/bbb\.html$"  /ddd.html;
    }  

    location  /bbb.html {
        rewrite "^/bbb\.html$" /ccc.html;
    }  
}

上述配置注意两点:

  • 打开 rewrite 模块的日志开关,以便 rewrite 执行日志写入 error_log (注: rewrite 日志写入 error_log 的级别是 notice ,所以要注意 error_log 日志级别,此处用 info );

  • 定义了两个 location ,分别是 /aaa.html 和 /bbb.html ,但是在 /aaa.html 中,把 /aaa.html 重写成 /bbb.html ,接着又把 /bbb.html 重写成 /ddd.html ;在 /bbb.html 中,把 /bbb.html 重写成 /ccc.html 。

实验1:没有last和break标记时,请求 /aaa.html

  • 1.1 请求/aaa.html
> curl http://127.0.0.1:9090/aaa.html
ddd html file
  • 1.2 rewrite日志
2019/11/01 10:05:12 [notice] 88280#0: *1 "^/aaa\.html$" matches "/aaa.html", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 10:05:12 [notice] 88280#0: *1 rewritten data: "/bbb.html", args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 10:05:12 [notice] 88280#0: *1 "^/bbb\.html$" matches "/bbb.html", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 10:05:12 [notice] 88280#0: *1 rewritten data: "/ddd.html", args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 10:05:12 [info] 88280#0: *1 kevent() reported that client 127.0.0.1 closed keepalive connection
  • 1.3 结论

URL 重写模块的日志告诉我们:对于一个 HTTP 请求»GET /aaa.html»,重写过程是:先 /aaa.html 被重写为 /bbb.html ;然后 rewritten data: /bbb.html ,继续执行后面的 rewrite 指令,进而被重写为 /ddd.html ,然后 rewrittern data: /ddd.html 后面没有重写了(其实此时 /ddd.html 需要再次重新匹配 location 的,只是日志没有体现出来,接下来的测试 2 会体现这点),于是输出 /ddd.html 的内容。

实验2:使用last在location内,请求 /aaa.html

  • 2.1 修改nginx的配置
    server {
        listen       9090;
        server_name  localhost;
        root /data/www/html;
        rewrite_log on;   # 打开 URL 重写模块的日志开关,以便写入 error_log

        location  /aaa.html {
            rewrite "^/aaa\.html$"  /bbb.html last;
            rewrite "^/bbb\.html$"  /ddd.html;
        }

        location  /bbb.html {
            rewrite "^/bbb\.html$" /ccc.html;
        }
    }
  • 2.2 测试结果
> curl http://127.0.0.1:9090/aaa.html
ccc html file
  • 2.3 rewrite日志
2019/11/01 10:13:52 [notice] 4530#0: *1 "^/aaa\.html$" matches "/aaa.html", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 10:13:52 [notice] 4530#0: *1 rewritten data: "/bbb.html", args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 10:13:52 [notice] 4530#0: *1 "^/bbb\.html$" matches "/bbb.html", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 10:13:52 [notice] 4530#0: *1 rewritten data: "/ccc.html", args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 10:13:52 [info] 4530#0: *1 kevent() reported that client 127.0.0.1 closed keepalive connection
  • 2.4 结论

首先 /aaa.html 匹配了 location /aaa.html {} ,于是执行 rewrite "^/aaa\.html$" /bbb.html last;, 把 /aaa.html 重写为 /bbb.html ,同时由于 last flag 的使用,后面的 rewrite 指令(指的是 rewrite "^/bbb\.html$" /ddd.html; )不会被执行。似乎此时应该输出“ bbb html file ”才对,但是我们看看 nginx 官方解释:last - completes processing of rewrite directives, after which searches for corresponding URI and location, 意思是说 last 不再匹配后面的 rewrite 指令,但是紧接着需要对重写后的 URI 重新匹配 location 。让我们再看看官方的“ If the directives of this module are given at the server level, then they are carried out before the location of the request is determined. If in that selected location there are further rewrite directives, then they also are carried out. If the URI changed as a result of the execution of directives inside location, then location is again determined for the new URI. This cycle can be repeated up to 10 times, after which Nginx returns a 500 error. ”因此,重新匹配的时候,匹配到了新的 location /bbb.html {} ,执行“ rewrite «^/bbb.html$» /ccc.html ”,最后的内容是»ccc html file»。

  • 2.5 疑问

在location中的rewrite匹配到使用last标签后,再次新的请求,还会重新开始匹配server{}区间内的rewrite么?还是说只会匹配location{}了?请继续看实验3

实验3:使用last在location内,请求 /aaa.html

  • 3.1 修改nginx的配置
server {
    listen       9090;
    server_name  localhost;
    root /data/www/html;
    rewrite_log on;

    rewrite "^/bbb\.html$" /eee.html;

    location  /aaa.html {
        rewrite "^/aaa\.html$"  /bbb.html last;
        rewrite "^/bbb\.html$"  /ddd.html;
    }

    location  /bbb.html {
        rewrite "^/bbb\.html$" /ccc.html;
    }

}
  • 3.2 测试结果
> curl http://127.0.0.1:9090/aaa.html
ccc html file
  • 3.3 rewrite日志
2019/11/01 10:38:40 [notice] 52579#0: *1 "^/bbb.html$" does not match "/aaa.html", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 10:38:40 [notice] 52579#0: *1 "^/aaa\.html$" matches "/aaa.html", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 10:38:40 [notice] 52579#0: *1 rewritten data: "/bbb.html", args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 10:38:40 [notice] 52579#0: *1 "^/bbb\.html$" matches "/bbb.html", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 10:38:40 [notice] 52579#0: *1 rewritten data: "/ccc.html", args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 10:38:40 [info] 52579#0: *1 kevent() reported that client 127.0.0.1 closed keepalive connection
  • 3.4 结论

匹配了location{}段内的rewrite包含有last flag的话,新的URI匹配不会再对server{}段内的rewrite进行匹配了,直接进行location{}段的匹配。

  • 3.5 疑问

如果匹配到新的location{}段内的rewrite没有last或者break flag的话,这一次新的URI的请求还会匹配server{}里的rewrite么?

实验4:请求 /aaa.html

  • 4.1 修改nginx的配置
server {
    listen       9090;
    server_name  localhost;
    root /data/www/html;
    rewrite_log on;

    rewrite "^/bbb\.html$" /eee.html;
    rewrite "^/ccc\.html$" /fff.html;

    location  /aaa.html {
        rewrite "^/aaa\.html$"  /bbb.html last;
        rewrite "^/bbb\.html$"  /ddd.html;
    }

    location  /bbb.html {
        rewrite "^/bbb\.html$" /ccc.html;
    }

    location /ccc.html {
        rewrite "^/ccc\.html$" /ddd.html;
    }
}
  • 4.2 测试结果
> curl http://127.0.0.1:9090/aaa.html
ddd html file
  • 4.3 rewrite日志
2019/11/01 11:11:10 [notice] 9817#0: *1 "^/bbb\.html$" does not match "/aaa.html", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 11:11:10 [notice] 9817#0: *1 "^/ccc\.html$" does not match "/aaa.html", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 11:11:10 [notice] 9817#0: *1 "^/aaa\.html$" matches "/aaa.html", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 11:11:10 [notice] 9817#0: *1 rewritten data: "/bbb.html", args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 11:11:10 [notice] 9817#0: *1 "^/bbb\.html$" matches "/bbb.html", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 11:11:10 [notice] 9817#0: *1 rewritten data: "/ccc.html", args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 11:11:10 [notice] 9817#0: *1 "^/ccc\.html$" matches "/ccc.html", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 11:11:10 [notice] 9817#0: *1 rewritten data: "/ddd.html", args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 11:11:10 [info] 9817#0: *1 kevent() reported that client 127.0.0.1 closed keepalive connection
  • 4.4 结论

如果匹配到的新的location{}段内的rewrite没有last或者break flag的话,这一次新的URI的请求不会再去匹配server{}里的rewrite了。但是还是会去匹配location{}段内的其他rewrite。 比如我们这里访问 /aaa.html ,首先匹配到 location /aaa.html, 然后匹配到该location{}段内的 rewrite "^/aaa\.html$" /bbb.html last;,此时内部以新的URI的请求,因为有last flag,所以不会再匹配server{}段内的rewrite "^/bbb\.html$" /eee.html;,而是进行location /bbb.html 的匹配, 然后进入到 location /bbb.html 段进行 rewrite "^/bbb\.html$" /ccc.html;的匹配,rewrite到 /ccc.html,但是因为之前有last标签,所有并不会匹配server{}段内的rewrite "^/ccc\.html$" /fff.html;, 而是进行location /ccc.html 匹配,然后又再次匹配到rewrite "^/ccc\.html$" /ddd.html;, 再次发起内部的新URI请求,得到 ddd html file的内容。

  • 4.5 疑问

把上述last flag换成break flag会有何影响呢?

实验5:请求 /aaa.html

  • 5.1 修改nginx的配置
server {
    listen       9090;
    server_name  localhost;
    root /data/www/html;
    rewrite_log on;

    rewrite "^/bbb\.html$" /eee.html;
    rewrite "^/ccc\.html$" /fff.html;

    location  /aaa.html {
        rewrite "^/aaa\.html$"  /bbb.html break;
        rewrite "^/bbb\.html$"  /ddd.html;
    }

    location  /bbb.html {
        rewrite "^/bbb\.html$" /ccc.html;
    }

    location /ccc.html {
        rewrite "^/ccc\.html$" /ddd.html;
    }
}
  • 5.2 测试结果
> curl http://127.0.0.1:9090/aaa.html
bbb html file
  • 5.3 rewrite日志
2019/11/01 11:33:20 [notice] 49879#0: *1 "^/bbb\.html$" does not match "/aaa.html", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 11:33:20 [notice] 49879#0: *1 "^/ccc\.html$" does not match "/aaa.html", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 11:33:20 [notice] 49879#0: *1 "^/aaa\.html$" matches "/aaa.html", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 11:33:20 [notice] 49879#0: *1 rewritten data: "/bbb.html", args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 11:33:20 [info] 49879#0: *1 kevent() reported that client 127.0.0.1 closed keepalive connection
  • 5.4 结论

从日志及结果可以看出,break条件满足的时候,不在匹配任何location和rewrite。

实验6:请求 /aaa.html

  • 6.1 修改nginx的配置
server {
    listen       9090;
    server_name  localhost;
    root /data/www/html;
    rewrite_log on;

    rewrite "^/bbb\.html$" /eee.html;
    rewrite "^/ccc\.html$" /fff.html;

    location  /aaa.html {
        rewrite "^/aaa\.html$"  /bbb.html;
        rewrite "^/eee\.html$"  /ddd.html;
    }

    location  /bbb.html {
        rewrite "^/bbb\.html$" /ccc.html;
    }

    location /ccc.html {
        rewrite "^/ccc\.html$" /ddd.html;
    }
}
  • 6.2 测试结果
> curl http://127.0.0.1:9090/aaa.html
ddd html file
  • 6.3 rewrite日志
2019/11/01 12:09:22 [notice] 20194#0: *8 "^/bbb\.html$" does not match "/aaa.html", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 12:09:22 [notice] 20194#0: *8 "^/ccc\.html$" does not match "/aaa.html", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 12:09:22 [notice] 20194#0: *8 "^/aaa\.html$" matches "/aaa.html", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 12:09:22 [notice] 20194#0: *8 rewritten data: "/bbb.html", args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 12:09:22 [notice] 20194#0: *8 "^/eee\.html$" does not match "/bbb.html", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 12:09:22 [notice] 20194#0: *8 "^/bbb\.html$" matches "/bbb.html", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 12:09:22 [notice] 20194#0: *8 rewritten data: "/ccc.html", args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 12:09:22 [notice] 20194#0: *8 "^/ccc\.html$" matches "/ccc.html", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 12:09:22 [notice] 20194#0: *8 rewritten data: "/ddd.html", args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "127.0.0.1:9090"
2019/11/01 12:09:22 [info] 20194#0: *8 kevent() reported that client 127.0.0.1 closed keepalive connection
  • 6.4 结论

从日志及结果可以看出,只要匹配了location下的rewrite,就再也不会去匹配server{}下的rewrite配置了。

评论