Eric
Eric

Reputation: 24954

nginx - How rewrite directive works on location matching together with flags?

Should following nginx config be dead cycle?

location / {
    # url rewrites
    rewrite ^/(.*).jsp$ /$1.html last;

    try_files $uri $uri/ =404;
}

According to doc it seems should, but in test it seems works the same as:

rewrite ^/(.*).jsp$ /$1.html break;

@Update

About last, nginx doc says:

it start a search for a new location that matches the rewritten URL.

The questions are:

Upvotes: 4

Views: 2420

Answers (1)

Ivan Tsirulev
Ivan Tsirulev

Reputation: 2811

To explain this, let's take a look at this example configuration:

server {
    listen 80;
    server_name localhost;

    location / {                   #  Location #1
        root /some/path;
    }

    location ~ ^/(foo|bar|baz) {   #  Location #2

        rewrite ^/foo /bar;        #  Rewrite rule #1
        rewrite ^/bar /baz break;  #  Rewrite rule #2
        rewrite ^/baz /qux last;   #  Rewrite rule #3
        rewrite ^/qux /foo;        #  Rewrite rule #4

        root /another/path;
    }
}
  1. What happens if we are trying to open http://localhost/foo in a browser is this:

    Nginx parses the request, gets the URI part (/foo) and then looks for all the locations that match this requested URI. In this case both locations match the URI, but since Location #2 is defined with a regular expression, Nginx will choose it over Location #1, or in other words, will make this location the request context. You can find some info on the location priorities in the documentation.

    Now that the location is determined, Nginx starts executing all the rewrite directives found in it one by one.

    After the Rewrite rule #1 is executed the URI is changed from /foo to /bar and Nginx proceeds with execution of the next rewrite directive, which is the Rewrite rule #2.

    After the Rewrite rule #2 is executed the URI is once again changed, this time from /bar to /baz. And since the break flag is used Nginx won't execute the rest rewrite directives in this location. So neither the Rewrite rule #3 nor Rewrite rule #4 will be executed this time.

    As a result, /another/path/baz will be shown in the browser.

    The same file will be served if we request http://localhost/bar directly. Only in that case Rewrite rule #1 will not be executed (because /bar does not match ^/foo).

  2. Now if we try to open http://localhost/baz the process will go like this:

    The Location #2 will be chosen as the context because it is of a higher priority and matches the request. Both the Rewrite rule #1 and Rewrite rule #2 will be ignored, because /baz does not match either ^/foo or ^/bar.

    The break flag of Rewrite rule #2 will be ignored along with the rule itself, because flags affect the process only when URI is being rewritten by the rule to which the flag belongs.

    The Rewrite rule #3 matches the URI and so it will be executed. The rule changes the URI from /baz to /qux and then the whole process is interrupted because the last flag kicks in. What it does is basically tells Nginx to stop processing the request and start from the very beginning but this time using the rewritten URI.

    So Nginx starts looking for a suitable location once again, but this time the URI is /qux, not /baz. And since Location #2 does not match /qux, Location #1 is chosen as the request context. And so /some/path/qux is served (pay attention to the path).

    The most interesting part here is that we started in the context of the Location #2 and ended up in Location #1 because of the last flag.

This is how it all works. And yes, if there is only one location, it will be used again (providing it matches the rewritten URI), but this time with the new (rewritten) URI. And yes, it is possible to cause an infinite loop with an unfortunate configuration. Although Nginx will detect and interrupt it after 10 cycles or so.

P.S. If you are wondering about Rewrite rule #4, in this configuration it is a "dead directive". It will never be executed.

Upvotes: 15

Related Questions