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

Eugaia ngx.eugaia at gmail.com
Tue Feb 15 02:48:26 MSK 2011


Hi,

On 15/02/2011 00:22, António P. P. Almeida wrote:
> On 14 Fev 2011 05h27 WET, agentzh at gmail.com wrote:
...
>> Disclaimer: There may be other corner cases that I've missed here,
>> and other more knowledgeable people can correct me wherever I'm
>> wrong :)
agentzh - well done on bringing this topic up - I'm sure doing so will 
help enlighten many who aren't so familiar with Nginx internals.
> 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.
I would say that inheritance of configuration options is generally the 
rule rather than the exception, but there are a lot of configuration 
options that just aren't permitted inside if blocks.  However, 
inheritance of the content handler (which is what you're really talking 
about) is a subset of directive inheritence in general.

Most, though not all, modules that generate content ('upstream' modules 
like fastcgi, proxy etc can be considered as 'generating' content) use 
the same content handler system.  The zip module is another one.  (For 
anyone familiar with C but not with Nginx internals: each location has a 
core location configuration on which a pointer to the content handler is 
saved, and this pointer is copied to the inherited if blocks unless 
another content handler directive is set inside the if block).

Modules that deal with checking authentication (e.g. Maxim's 
auth_request module) or filtering the response (e.g. the SSI module) 
don't inherit the content handler, but the configuration options may 
(and usually are) inherited into the if block.  The only times they are 
not merged is if there is no merging defined for the value of a 
particular directive (but in most cases there is, for all types of 
configuration).
> 2. As soon as there's a matching location, the rewrite phase starts
>     and runs.
This depends on what you mean by 'matching'.  There are rules which 
governing which location will be matched - see 
http://wiki.nginx.org/HttpCoreModule#location for details.  Which 
location is matched may not be the first location listed that matches 
the URL.  Matching for nested locations is handled before rewrite-phase 
handling.
> 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.
As soon as a 'break' or 'return' directive is reached in the rewrite 
phase, rewrite processing is stopped in the current block (returns will 
normally stop all processing, but may not depending on settings for 
error_page etc - which is probably a little beyond the scope of 
discussion here).  If 'rewrite' is reached with a 'last' or 'break', 
then processing also stops.
> 3. This differs only on the content phase. There it can happen that
>     inner if blocks can inherit content handlers from outer blocks.
All the scripted rewrite directives (if, set, rewrite, break and return) 
are processed during the rewrite phase, which happens before the content 
phase.  What happens is that if an 'if' block is reached, i.e. the 
conditions in the if conditional equate to true, then the 'location' for 
the content is set to the contents of that if block (unless a subsequent 
if block is also true - in which case that block is used instead and the 
previously true if block, which is then completely ignored - or the 
request reaches a different location - e.g. by a rewrite).

The content is created from the directives that apply in one and only 
one 'location' - which can be a standard location or an if block.
> 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.
All content handlers that use the standard mechanism for setting the 
content handler (I don't know if there are any that don't, but I don't 
know any that don't use the standard way) are inherited in the same way.
> 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.
Yes - the value of 76 is echoed by the subrequest
> Case 2: There's a content handler inside the if block.
Yes
> The echo module
>          doesn't provide inheritance of directives in outer
>          blocks.
This is irrelevant.  The 'echo' content handler overrides the content 
handler from the proxy module in the 'outer' location.  Inheritance of 
content handlers is not handled on a per-module basis, it's handled in 
the core http module.
> Hence the value is 76 not because of the proxy_pass
>          directive but because of the echo "a = $a" directive.
Yes
> Case 3: The if block has a break that interrupts the control flow. So
>          the set $a 76 directive is never processed.
Yes.
> We get 56 because
>          of the content handler provided by the echo module inside the
>          if block.
Yes.
> Case 4: (You named it 5, but it's a typo)
>
>          The return directive inside the if block provides a special
>          response: 404.
Yes.
> The output filter that the headers_more module
>          provides is inherited by the if and gets sent in the reply.
Yes, and the value of 32 is used and not 76 because the set $a 76 is not 
processed because the 'return' is reached first.
> Now I see why in the http://wiki.nginx.org/IfIsEvil page the only
> *safe* directives are return and rewrites with a last flag.
The 'set' directive, as well as any set_xxx directives that use the 
Nginx Development Kit (NDK) (e.g. those in the set_misc module) are also 
fine, since they plug into the same rewrite mechanism.

The main problem is that most of the directives in Nginx are 
declarative, and the order in which you place them in the config file 
(within the same block) isn't important.  Most of the time these 
directives are inherited into sub-locations (real locations or if 
blocks), and it's probably an unintentional mistake by the developer if 
they aren't inherited.  The rewrite directives, however, are procedural, 
and the order can be (and often is) important.  Within a location (that 
is any 'normal' locations and any 'if' blocks inside), all the scripted 
rewrite directives (if, set, break, return, rewrite and any set_xxx 
directives that use the NDK - including set_by_lua) are processed in the 
order they are written - regardless of whether they appear inside an if 
block or not (if's can't appear inside other if's).  With respect to 
parsing rewrite directives, locations and the if blocks location inside 
those locations are considered as one (but locations within locations 
are not).

Also, it's important to realise that the 'if' conditions and block 
branching only happens at the rewrite stage.  It is safe to mix 'set', 
'set_by_lua', 'set_md5' etc. directives, and they will probably do what 
you expect, because they are processed in order - all at the rewrite 
stage.  If you start mixing 'set' with content handlers (e.g. 
content_by_lua), then it's probably a good idea to put all your 
rewrite-phase directives before your content-phase directives in your 
config files.

> Either one
> of them "break" the control flow in the rewrite phase.
Correct.
> Therefore are
> not susceptible to inheritance issues.
See above.

Hope that helps,

Marcus.



More information about the nginx mailing list