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