bastiat
bastiat

Reputation: 2071

Nginx two locations executed on one request

With below nginx configuration

server {
  listen 2022;
  location /STFlow/ {
    rewrite ^/STFlow(.*)$ $1 last;
    proxy_pass http://zuul-proxy:8080; 
    proxy_set_header Host      $host;
    proxy_set_header X-Real-IP $http_x_forwarded_for;
    proxy_set_header X-Forwarded-For $http_x_forwarded_for;
  }

  location / {
    set $realip $remote_addr;
    if ($http_x_forwarded_for ~ "^(\d+\.\d+\.\d+\.\d+)") {
        set $realip $1;
    }
    proxy_pass http://zuul-proxy:8080; 
    proxy_set_header Host      $host;
    proxy_set_header X-Real-IP $http_x_forwarded_for;
    proxy_set_header X-Forwarded-For $http_x_forwarded_for;
    client_max_body_size 50M;
  }

with executing curl http://okd-dev-route.internal.com/STFlow/dashboard

I get following logs:

2020/07/16 07:38:39 [notice] 31#31: *1 "^/STFlow(.*)$" matches "/STFlow/dashboard", client: 10.129.4.1, server: , request: "GET /STFlow/dashboard HTTP/1.1", host: "okd-dev-route.internal.com"
2020/07/16 07:38:39 [notice] 31#31: *1 rewritten data: "/dashboard", args: "", client: 10.129.4.1, server: , request: "GET /STFlow/dashboard HTTP/1.1", host: "okd-dev-route.internal.com"
2020/07/16 07:38:39 [notice] 31#31: *1 "^(\d+\.\d+\.\d+\.\d+)" matches "10.221.196.254", client: 10.129.4.1, server: , request: "GET /STFlow/dashboard HTTP/1.1", host: "okd-dev-route.internal.com"
2020/07/16 07:38:39 [info] 31#31: *1 client 10.129.4.1 closed keepalive connection (104: Connection reset by peer)

I'm a noob to nginx and okd. It looks for me as if rewrites from both locations /STFlow/ and / are executed!

As far as I understood nginx documentation only one location should be selected and executed.

But here we can see two matches:

"^/STFlow(.*)$" matches "/STFlow/dashboard"

which seems to come from location /STFlow/ and later

"^(\d+.\d+.\d+.\d+)" matches "10.221.196.254" which seems to come from if condition in location /

How is it possible?

What's going on here?

Is it possible to put some custom debug logs in both locations to see which lines are executed and in what order?

Upvotes: 1

Views: 1122

Answers (2)

bastiat
bastiat

Reputation: 2071

Here is the explanation of my problem:

The break, if, return, rewrite, and set directives are processed in the following order:

the directives of ngx_http_rewrite_module module specified on the server level are executed sequentially;

repeatedly:

  • a location is searched based on a request URI;
  • the directives of this module specified inside the found location are executed sequentially;
  • the loop is repeated if a request URI was rewritten, but not more than 10 times.

Upvotes: 0

Danila Vershinin
Danila Vershinin

Reputation: 9895

As far as I understood nginx documentation only one location should be selected and executed

Only a single location is indeed selected for serving a request. It does not mean that there is no "jumping" done between the locations (contexts) while reaching the final location.

This is what the rewrite module is actually responsible for: jumping between contexts by rewriting current URI / repeating NGINX's location search based on the rewritten URI.

NGINX first finds the location with longest prefix for serving request. Specific to your example, this is location /STFlow/ {. Now it selected it for evaluation.

It sees rewrite ^/STFlow(.*)$ $1 last; which modifies current URI to /dashboard. The last keyword to rewrite directive triggers location search all over again based on the now current /dashboard URI. Worth noting that while jumping location /STFlow/ { to location / { the directives from the former are not going to apply. After the "jump", now location / { is the one currently selected for serving the request, etc.

The location / { has no more directives (no try_files, rewrite, etc.) that can modify the current URI and trigger searching all over again. Thus it is the final location that will be used by NGINX for constructing the response.

The directives/configuration that will finally apply to serving response is the ones from the final location or its parent, if the final location is a nested location. Even then, it depends on specific directives. Some directives will be inherited from parent location and some would not. It is a major topic of its own. For example:

location / {
    expires max;
    location /foo/ {
       index index.php;   
    }
}

The expires will apply for request to /foo/bar because it belongs to the nested location /foo/. But if you were to make it non-nested:

location / {
   expires max;
}
location /foo/ {
   index index.php;   
}

The expires will not apply to URI /foo/bar.

Array-like directives, e.g. fastcgi_param, add_header, etc. are counter-intuitive in how they inherit from parent location. They are inherited only if they are not present in the nested location.

location / {
    add_header X-Foo Bar;
    location /foo/ {
        add_header X-Something 1; 
    }
}

With this config, only X-Something: 1; is set, but if you were to comment out its line, you will have parent header apply and see X-Foo: Bar in the output headers.

Some may call it "bad design" because it leads to a lot of confusion at the beginning.

Upvotes: 2

Related Questions