conditional "Expires" header config bug

Maxim Dounin mdounin at mdounin.ru
Wed Dec 8 02:23:01 MSK 2010


Hello!

On Tue, Dec 07, 2010 at 01:43:16PM -0800, ghazel at gmail.com wrote:

> On Tue, Dec 7, 2010 at 1:36 PM, Maxim Dounin <mdounin at mdounin.ru> wrote:
> > On Tue, Dec 07, 2010 at 01:09:54PM -0800, ghazel at gmail.com wrote:
> >>
> >> I'm trying to conditionally set the "Expires" header. This tiny
> >> snippet does not work as expected:
> >>
> >>     location ~* testing/.*\.png$ {
> >>       if ($request_uri ~* "\.png\?[0-9]+$") {
> >>         expires max;
> >>       }
> >>       try_files $uri /testing/oops.png;
> >>     }
> >>
> >> The basic problem is that if the png does not exist and there is a
> >> query string, I get a 404 instead of oops.png. All other combinations
> >
> > This is expected - try_files isn't inherited into implicit
> > location created by "if".
> 
> I would very much like some more examples of what you mean here. It's
> not clear to me why IfIsEvil occurs, or how to think about what the if
> statement is actually doing.

What "if" statement (inside location) actually doing is creating 
implicit nested location (conditional one), with some hacks to 
make handler modules (proxy_pass, fastcgi_pass, ...) to be 
inherited into this location (normally they aren't).  Basically 
there are two problems:

1. People expect

location / {
    set $true 1;

    if ($true) {
        add_header X-Header-One 1;
    }

    if ($true) {
        add_header X-Header-Two 2;
    }
}

to return both headers.  Though this isn't something going to 
happen, as actually the above config means something like

location / {
    location ...implicit-location-1 {
        add_header X-Header-One 1;
    }

    location ...implicit-location-2 {
        add_header X-Header-Two 2;
    }
}

And as usual nginx will select only one location to process 
request.  Hence only X-Header-Two will be returned.

2. Mentioned hacks are incomplete / broken and may cause various 
problems.  As in your case - they don't handle try_files (which 
isn't normally inherited, too).  E.g. something like

location / {
    try_files /file @fallback;
 
    set $true 1;
 
    if ($true) {
        # nothing
    }
}

is actually means for nginx something like

location / {
    try_files /file @fallback;

    location ...implicit-location-which-matches-everything {
        # no try_files here
    }
}

and try_files will not work - as there is no try_files in location 
which actually used to process request.

More cases are listed on http://wiki.nginx.org/IfIsEvil.

> >> seem to work fine. I have read IfIsEvil, but the alternatives
> >> presented (try_files, return, redirect) do not cover the case of
> >> setting headers.
> >
> > Not "redirect" but "rewrite ... last".  Anyway, they are cover the
> > case in question.
> 
> Er yes, sorry.
> 
> > Generally safe parttern using only "return" looks like the
> > following:
> >
> >    location ~ \.png$ {
> >        recursive_error_pages on;
> >        error_page 418 = @expires;
> >
> >        if ($args ~ "^[0-9]+$") {
> >            return 418;
> >        }
> >
> >        try_files $uri /testing/oops.png;
> >    }
> >
> >    location @expires {
> >        expires max;
> >        try_files $uri /testing/oops.png;
> >    }
> >
> >    location = /testing/oops.png {
> >        # we don't want expires max on oops
> >    }
> 
> This is clever! I didn't know you could set an internal location with
> error_page. Not that it isn't documented, just that I never even
> considered looking there for that sort of thing since the case I'm
> using it for is not an error.

One more hint: using "error_page fallback" like

   log_not_found off;
   error_page 404 = @fallback;

is actually better than try_files in simple cases.  It a) saves 
one stat() syscall and b) isn't racy (try_files aproach has race 
between stat() and then open() in static handler, which makes it 
possible to generate 404 anyway - if you happen to delete file 
between stat() and open()).

On the other hand - try_files is easier to use, especially 
in complex cases.

Maxim Dounin



More information about the nginx mailing list