nginx的rewrite指令的可选参数break和last的差异

更新于 2019-11-29  约 15 分钟

被翻译的文章

<nginx url rewriting:difference between break and last>此提问的第二回答,即有例子的那一篇。

前置条件

安装 echo-nginx-module 模块到 nginx 上,安装方法可见我的这篇文章:为已经安装好的nginx动态添加新模块echo-nginx-module

阅读本文者应当对如何修改 nginx 的配置文件以及如何重启 nginx 使其应用新的配置文件有所了解,并且对 server 和 location 这两个基本的模块有所了解。

相关资料

官方对rewrite相关模块的说明

If the specified regular expression matches a request URI, URI is changed as specified in the replacement string. The rewrite directives are executed sequentially in order of their appearance in the configuration file. It is possible to terminate further processing of the directives using flags. If a replacement string starts with “http://”, “https://”, or “$scheme”, the processing stops and the redirect is returned to a client.

last
stops processing the current set of ngx_http_rewrite_module directives and starts a search for a new location matching the changed URI;

break
stops processing the current set of ngx_http_rewrite_module directives as with the breakdirective;

开始

Example 1: No (break or last) flags

code

server {
    server_name example.com;
    root 'path/to/somewhere';

    location / {
        echo 'finally matched location /';
    }

    location /notes {
        echo 'finally matched location /notes';
    }

    location /documents {
        echo 'finally matched location /documents';
    }

    rewrite ^/(.*\.txt)$ /notes/$1;
    rewrite ^/notes/(.*\.txt)$ /documents/$1;
}

Result:

# curl example.com/test.txt
finally matched location /documents

Explanation:

For rewrite, the flags are optional!

翻译和解读

第一个例子里breaklast一个不用,考察在这种情况下程序能否正常运行。

从结果来看,输入的/test.txt被最终被rewrite为了/documents/test.txt,其内部的过程其实是/test.txt->/notes/test.txt->/documents/test.txt,与官方文档中说的

The rewrite directives are executed sequentially in order of their appearance in the configuration file.

rewrite指令会按写下的顺序执行相符合。证明了breaklast参数可以没有。

Example 2: Outside location block (break or last)

code

server {
    server_name example.com;
    root 'path/to/somewhere';

    location / {
        echo 'finally matched location /';
    }

    location /notes {
        echo 'finally matched location /notes';
    }

    location /documents {
        echo 'finally matched location /documents';
    }

    rewrite ^/(.*\.txt)$ /notes/$1 break; # or last
    rewrite ^/notes/(.*\.txt)$ /documents/$1; # this is not parsed
}

Result:

# curl example.com/test.txt
finally matched location /notes

Explanation:

Outside the location block, both break and last behave in the exact manner...

  • no more parsing of rewrite conditions
  • Nginx internal engine goes to the next phase (searching for location match)

翻译和解读

此处测试了当rewrite指令在server块内,但在location块外的情景下,breaklast参数带来的影响。官方文档对此没有做特别说明,原文作者的测试时发现,使用breaklast造成的最终效果一样。都是阻止了第二条rewrite指令的发生,然后进入下一个阶段——rewrite指令改写的结果的基础上搜寻对应的location块。

Example 3: Inside location block - "break"

code

server {
    server_name example.com;
    root 'path/to/somewhere';

    location / {
        rewrite ^/(.*\.txt)$ /notes/$1 break;
        rewrite ^/notes/(.*\.txt)$ /documents/$1; # this is not parsed
        echo 'finally matched location /';
    }

    location /notes {
        echo 'finally matched location /notes';
    }

    location /documents {
        echo 'finally matched location /documents';
    }
}

Result:

# curl example.com/test.txt
finally matched location /

Explanation:

Inside a location block,break flag would do the following...

  • no more parsing of rewrite conditions
  • Nginx internal engine continues to parse the current location block

翻译和解读

前面的逻辑是很简单的:/test.txt在进入后,因为server块中没有rewrite指令,开始location块匹配,进入location /块,会被rewrite ^/(.*\.txt)$ /notes/$1 break;抓住。

然后发生了什么呢?结合输出结果。输出的结果为finally matched location /。这说明被抓住之后,值被改了,但是仍然在继续执行当前location块内的事情,并不会拿着被改掉之后的值,重新进入寻找适配location块的阶段。

另外,这里没有再单独测试什么参数都不带的情况。我自己测试之后的话最终输出会是finally matched location /documents。说明正常情况下和在外部并没有区别。

Example 4: Inside location block - "last"

code

server {
    server_name example.com;
    root 'path/to/somewhere';

    location / {
        echo 'finally matched location /';
        rewrite ^/(.*\.txt)$ /notes/$1 last;
        rewrite ^/notes/([^/]+.txt)$ /documents/$1;  # this is not parsed
    }

    location /notes {
        echo 'finally matched location /notes';
        rewrite ^/notes/(.*\.txt)$ /documents/$1;  # this is not parsed, either!
    }

    location /documents {
        echo 'finally matched location /documents';
    }
}

Result:

# curl example.com/test.txt
finally matched location /notes

Explanation:

Inside a location block,last flag would do the following...

  • no more parsing of rewrite conditions
  • Nginx internal engine starts to look for another location match based on the result of the rewrite result.
  • no more parsing of rewrite conditions, even on the next location match!

翻译和解读

前面被抓住的推理部分一模一样。

被抓住然后做了些什么?这次输出结果不一样了,变成了finally matched location /notes,说明抓住之后被改了之后,又以被改变之后的值,再次进入寻找合适的location块的阶段。所以跑到了location /notes那儿去,造成了那里的echo。

在此基础上有另一件事情,那就是在此进入寻找合适的location块的阶段中,会不会被外部的rewrite指令处理到呢?
比如

server {
    server_name example.com;
    root 'path/to/somewhere';

    location / {
        echo 'finally matched location /';
        rewrite ^/(.*\.txt)$ /notes/$1 last;
        rewrite ^/notes/([^/]+.txt)$ /documents/$1;  # this is not parsed
    }

    location /notes {
        echo 'finally matched location /notes';
        rewrite ^/notes/(.*\.txt)$ /documents/$1;  # this is not parsed, either!
    }

    location /documents {
        echo 'finally matched location /documents';
    }
    
    rewrite ^/notes/(.*\.txt)$ /documents/$1; //新增的外部的rewrite指令
}

我这里经过测试,答案是不会。

Summary:

原文

  • When a rewrite condition with the flag break or last matches, Nginx stops parsing any more rewrites!
  • Outside a location block, with break or last, Nginx does the same job (stops processing anymore rewrite conditions).
  • Inside a location block, with break, Nginx only stops processing anymore rewrite conditions
  • Inside a location block, with last, Nginx stops processing anymore rewrite conditions and then starts to look for a new matching of location block! Nginx also ignores any rewrites in the new location block!

翻译

  • breaklast参数放在rewrite指令后,无论如何一定会产生使这条rewrite指令之后的rewrite指令无效的效果。
  • location块外使用breaklast,对rewrite指令造成的效果一样,除了第一条的打断效果外,都会让rewrite指令的结果进入搜寻适配的location块的阶段。
  • location块内使用break,打断rewrite指令流后,继续执行当前location块中的其它(模块的)任务。
  • location块内使用last,打断rewrite指令流后,会以rewrite指令的结果为新被适配者去搜寻自己合适的location块并进入,rewrite指令所在的location块内其它(模块的)任务被放弃。但不会受外部rewrite指令的影响。
阅读 302更新于 2019-11-29

推荐阅读
目录