Welcome! Log In Create A New Profile

Advanced

Re: How nginx's "location if" works (Was Re: Setting cache parameters via if directives)

António P. P. Almeida
February 14, 2011 05:32PM
On 14 Fev 2011 05h27 WET, agentzh@gmail.com wrote:

> Well, ngx_proxy module's directive inheritance is in action here,
> which gives you nice side effects that you want :)
>
> I'll analyze some examples here such that people *may* get some
> light.
>
> [Case 1]
>
> location /proxy {
> set $a 32;
> if ($a = 32) {
> set $a 56;
> }
> set $a 76;
> proxy_pass http://127.0.0.1:$server_port/$a;
> }
>
> location ~ /(\d+) {
> echo $1;
> }
>
> Calling /proxy gives 76 because it works in the following steps:
>
> 1. Nginx runs all the rewrite phase directives in the order that
> they're in the config file, i.e.,
>
> set $a 32;
> if ($a = 32) {
> set $a 56;
> }
> set $a 76;
>
> and $a gets the final value of 76.
>
> 2. Nginx traps into the "if" inner block because its condition $a =
> 32
> was met in step 1.
>
> 3. The inner block does not has any content handler, ngx_proxy
> inherits the content handler (that of ngx_proxy) in the outer scope
> (see src/http/modules/ngx_http_proxy_module.c:2025).
>
> 4. Also the config specified by proxy_pass also gets inherited by
> the
> inner "if" block (see src/http/modules/ngx_http_proxy_module.c:2015)
>
> 5. Request terminates (and the control flow never goes outside of
> the
> "if" block).
>
> That is, the proxy_pass directive in the outer scope will never run
> in this example. It is "if" inner block that actually serves you.
>
> Let's see what happens when we override the inner "if" block's
> content handler with out own:
>
> [Case 2]
>
> location /proxy {
> set $a 32;
> if ($a = 32) {
> set $a 56;
> echo "a = $a";
> }
> set $a 76;
> proxy_pass http://127.0.0.1:$server_port/$a;
> }
> location ~ /(\d+) {
> echo $1;
> }
>
> You will get this while accessing /proxy:
>
> a = 76
>
> Looks counter-intuitive? Oh, well, let's see what's happening this
> time:
>
> 1. Nginx runs all the rewrite phase directives in the order that
> they're in the config file, i.e.,
>
> set $a 32;
> if ($a = 32) {
> set $a 56;
> }
> set $a 76;
>
> and $a gets the final value of 76.
>
> 2. Nginx traps into the "if" inner block because its condition $a =
> 32
> was met in step 1.
>
> 3. The inner block *does* has a content handler specified by "echo",
> then the value of $a (76) gets emitted to the client side.
>
> 4. Request terminates (and the control flow never goes outside of
> the
> "if" block), as in Case 1.
>
> We do have a choice to make Case 2 work as we like:
>
> [Case 3]
>
> location /proxy {
> set $a 32;
> if ($a = 32) {
> set $a 56;
> break;
>
> echo "a = $a";
> }
> set $a 76;
> proxy_pass http://127.0.0.1:$server_port/$a;
> }
> location ~ /(\d+) {
> echo $1;
> }
>
> This time, we just add a "break" directive inside the if block. This
> will stop nginx from running the rest ngx_rewrite directives. So we
> get
>
> a = 56
>
> So this time, nginx works this way:
>
> 1. Nginx runs all the rewrite phase directives in the order that
> they're in the config file, i.e.,
>
> set $a 32;
> if ($a = 32) {
> set $a 56;
> break;
> }
>
> and $a gets the final value of 56.
>
> 2. Nginx traps into the "if" inner block because its condition $a =
> 32
> was met in step 1.
>
> 3. The inner block *does* has a content handler specified by "echo",
> then the value of $a (56) gets emitted to the client side.
>
> 4. Request terminates (and the control flow never goes outside of
> the
> "if" block), just as in Case 1.
>
> Okay, you see how ngx_proxy module's config inheritance among nested
> locations take the key role here, and make you *believe* it works
> the way that you want. But other modules (like "echo" mentioned in
> one of my earlier emails) may not inherit content handlers in nested
> locations (in fact, most content handler modules, including upstream
> ones, don't).
>
> And one must be careful about bad side effects of config inheritance
> of "if" blocks in other cases, consider the following example:
>
> [Case 5]
>
> location /proxy {
> set $a 32;
> if ($a = 32) {
> return 404;
> }
> set $a 76;
> proxy_pass http://127.0.0.1:$server_port/$a;
> more_set_headers "X-Foo: $a";
> }
> location ~ /(\d+) {
> echo $1;
> }
>
> Here, ngx_header_more's "more_set_headers" will also be inherited by
> the implicit location created by the "if" block. So you will get:
>
> curl localhost/proxy
> HTTP/1.1 404 Not Found
> Server: nginx/0.8.54 (without pool)
> Date: Mon, 14 Feb 2011 05:24:00 GMT
> Content-Type: text/html
> Content-Length: 184
> Connection: keep-alive
> X-Foo: 32
>
> which may or may not what you want :)
>
> BTW, the "add_header" directive will not emit a "X-Foo" header in
> this case, and it does not mean no directive inheritance happens
> here, but add_header's header filter will skip 404 responses.
>
> You see, how tricky it is behind the scene! No wonder people keep
> saying "nginx's if is evil".
>
> Cheers,
> -agentzh
>
> Disclaimer: There may be other corner cases that I've missed here,
> and other more knowledgeable people can correct me wherever I'm
> wrong :)

Thank you for elaborating on this issue further. Lets see if I can
summarize my understanding, right now.

1. Inheritance in if blocks (which are in fact implicit locations) can
happen *only* for a few modules, like proxy, fastcgi and your
headers_more. Inheritance is the exception and not the rule.
Most modules don't provide content phase handlers inheritance.

2. As soon as there's a matching location, the rewrite phase starts
and runs. As long as all conditions on the ifs are true the rewrite
phase directives are always processed, no matter what they are,
rewrites or variable assignments or break or returns.

3. This differs only on the content phase. There it can happen that
inner if blocks can inherit content handlers from outer blocks.

4. If there's content handler inside the if block that's the one
that's used. It may happen that the module that provides this
handler can inherit content handlers from the outer blocks.

Analyzing your examples:

Case 1: There's no content handler inside the if block, but the proxy
module provides inheritance hence we get the value 76.

Case 2: There's a content handler inside the if block. The echo module
doesn't provide inheritance of directives in outer
blocks. Hence the value is 76 not because of the proxy_pass
directive but because of the echo "a = $a" directive.

Case 3: The if block has a break that interrupts the control flow. So
the set $a 76 directive is never processed. We get 56 because
of the content handler provided by the echo module inside the
if block.

Case 4: (You named it 5, but it's a typo)

The return directive inside the if block provides a special
response: 404. The output filter that the headers_more module
provides is inherited by the if and gets sent in the reply.

Now I see why in the http://wiki.nginx.org/IfIsEvil page the only
*safe* directives are return and rewrites with a last flag. Either one
of them "break" the control flow in the rewrite phase. Therefore are
not susceptible to inheritance issues.

Is this correct?

Thanks,
--- appa

_______________________________________________
nginx mailing list
nginx@nginx.org
http://nginx.org/mailman/listinfo/nginx
Subject Author Posted

How nginx's "location if" works (Was Re: Setting cache parameters via if directives)

agentzh February 14, 2011 12:30AM

Re: How nginx's "location if" works (Was Re: Setting cache parameters via if directives)

António P. P. Almeida February 14, 2011 05:32PM

Re: How nginx's "location if" works (Was Re: Setting cache parameters via if directives)

Eugaia February 14, 2011 06:52PM

Re: How nginx's "location if" works (Was Re: Setting cache parameters via if directives)

Ryan Malayter February 15, 2011 02:04PM

Re: How nginx's "location if" works (Was Re: Setting cache parameters via if directives)

Dayo February 15, 2011 02:23PM

Re: How nginx's "location if" works (Was Re: Setting cache parameters via if directives)

Dayo February 15, 2011 02:27PM

Re: How nginx's "location if" works (Was Re: Setting cache parameters via if directives)

agentzh February 15, 2011 11:28PM

Re: How nginx's "location if" works (Was Re: Setting cache parameter via if directives)

Alexander Kunz February 18, 2011 06:22AM

Re: How nginx's "location if" works (Was Re: Setting cache parameter via if directives)

agentzh February 18, 2011 06:58AM

Re: How nginx's "location if" works (Was Re: Setting cache parameter via if directives)

agentzh February 18, 2011 06:58AM

Re: How nginx's "location if" works (Was Re: Setting cache parameter via if directives)

Alexander Kunz February 18, 2011 07:34AM

Re: How nginx's "location if" works (Was Re: Setting cache parameter via if directives)

Justin Cormack February 18, 2011 08:26AM

Re: How nginx's "location if" works (Was Re: Setting cache parameter via if directives)

agentzh February 19, 2011 01:10AM

Re: How nginx's "location if" works (Was Re: Setting cache parameter via if directives)

Alexander Kunz February 19, 2011 05:46AM

Re: How nginx's "location if" works (Was Re: Setting cache parameters via if directives)

seekvn September 28, 2021 04:57AM



Sorry, only registered users may post in this forum.

Click here to login

Online Users

Guests: 310
Record Number of Users: 8 on April 13, 2023
Record Number of Guests: 421 on December 02, 2018
Powered by nginx      Powered by FreeBSD      PHP Powered      Powered by MariaDB      ipv6 ready