[PATCH] Upstream: prioritise Cache-Control over Expires

Sergey Kandaurov pluknet at nginx.com
Mon Jun 6 14:41:21 UTC 2022


> On 6 Jun 2022, at 17:42, Sergey Kandaurov <pluknet at nginx.com> wrote:
> 
> On Sun, Apr 24, 2022 at 06:42:47PM +0300, Maxim Dounin wrote:
>> Hello!
>> 
>> On Sun, Apr 24, 2022 at 07:55:17AM +0300, Maxim Dounin wrote:
>> 
>> [...]
>> 
>>> As far as I can tell, proper behaviour, assuming we parse cache 
>>> control extensions independently of X-Accel-Expires, can be 
>>> implemented by using just one flag.
>> 
>> No, that's wrong, as with just one flag it wouldn't be possible to 
>> correctly disable caching of responses with:
>> 
>> Cache-Control: private
>> Cache-Control: max-age=10
>> 
>> So it needs at least two flags.  Updated patch below, review and 
>> testing appreciated.
>> 
>> # HG changeset patch
>> # User Maxim Dounin <mdounin at mdounin.ru>
>> # Date 1650814681 -10800
>> #      Sun Apr 24 18:38:01 2022 +0300
>> # Node ID 940ba4317a97c72d1ee6700cbf58a543fee04c7a
>> # Parent  a736a7a613ea6e182ff86fbadcb98bb0f8891c0b
>> Upstream: fixed X-Accel-Expires/Cache-Control/Expires handling.
>> 
>> Previously, if caching was disabled due to Expires in the past, nginx
>> failed to cache the response even if it was cacheable as per subsequently
>> parsed Cache-Control header (ticket #964).
>> 
>> Similarly, if caching was disabled due to Expires in the past,
>> "Cache-Control: no-cache" or "Cache-Control: max-age=0", caching was not
>> used if it was cacheable as per subsequently parsed X-Accel-Expires header.
>> 
>> Fix is to avoid disabling caching immediately after parsing Expires in
>> the past or Cache-Control, but rather set flags which are later checked by
>> ngx_http_upstream_process_headers() (and cleared by "Cache-Control: max-age"
>> and X-Accel-Expires).
>> 
>> Additionally, now X-Accel-Expires does not prevent parsing of cache control
>> extensions, notably stale-while-revalidate and stale-if-error.  This
>> ensures that order of the X-Accel-Expires and Cache-Control headers is not
>> important.
>> 
>> Prodded by Vadim Fedorenko and Yugo Horie.
>> 
>> diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c
>> --- a/src/http/ngx_http_upstream.c
>> +++ b/src/http/ngx_http_upstream.c
>> @@ -2697,6 +2697,10 @@ ngx_http_upstream_intercept_errors(ngx_h
>> 
>>             if (r->cache) {
>> 
>> +                if (u->headers_in.no_cache || u->headers_in.expired) {
>> +                    u->cacheable = 0;
>> +                }
>> +
>>                 if (u->cacheable) {
>>                     time_t  valid;
>> 
>> @@ -2791,6 +2795,10 @@ ngx_http_upstream_process_headers(ngx_ht
>> 
>>     umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);
>> 
>> +    if (u->headers_in.no_cache || u->headers_in.expired) {
>> +        u->cacheable = 0;
>> +    }
>> +
>>     if (u->headers_in.x_accel_redirect
>>         && !(u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT))
>>     {
>> @@ -4735,18 +4743,18 @@ ngx_http_upstream_process_cache_control(
>>         return NGX_OK;
>>     }
>> 
>> -    if (r->cache->valid_sec != 0 && u->headers_in.x_accel_expires != NULL) {
>> -        return NGX_OK;
>> -    }
>> -
>>     start = h->value.data;
>>     last = start + h->value.len;
>> 
>> +    if (r->cache->valid_sec != 0 && u->headers_in.x_accel_expires != NULL) {
>> +        goto extensions;
>> +    }
>> +
>>     if (ngx_strlcasestrn(start, last, (u_char *) "no-cache", 8 - 1) != NULL
>>         || ngx_strlcasestrn(start, last, (u_char *) "no-store", 8 - 1) != NULL
>>         || ngx_strlcasestrn(start, last, (u_char *) "private", 7 - 1) != NULL)
>>     {
>> -        u->cacheable = 0;
>> +        u->headers_in.no_cache = 1;
>>         return NGX_OK;
>>     }
>> 
>> @@ -4776,12 +4784,15 @@ ngx_http_upstream_process_cache_control(
>>         }
>> 
>>         if (n == 0) {
>> -            u->cacheable = 0;
>> +            u->headers_in.no_cache = 1;
>>             return NGX_OK;
>>         }
>> 
>>         r->cache->valid_sec = ngx_time() + n;
>> -    }
>> +        u->headers_in.expired = 0;
>> +    }
>> +
>> +extensions:
>> 
>>     p = ngx_strlcasestrn(start, last, (u_char *) "stale-while-revalidate=",
>>                          23 - 1);
>> @@ -4863,7 +4874,7 @@ ngx_http_upstream_process_expires(ngx_ht
>>     expires = ngx_parse_http_time(h->value.data, h->value.len);
>> 
>>     if (expires == NGX_ERROR || expires < ngx_time()) {
>> -        u->cacheable = 0;
>> +        u->headers_in.expired = 1;
>>         return NGX_OK;
>>     }
>> 
>> @@ -4914,6 +4925,8 @@ ngx_http_upstream_process_accel_expires(
>> 
>>         default:
>>             r->cache->valid_sec = ngx_time() + n;
>> +            u->headers_in.no_cache = 0;
>> +            u->headers_in.expired = 0;
>>             return NGX_OK;
>>         }
>>     }
>> @@ -4925,6 +4938,8 @@ ngx_http_upstream_process_accel_expires(
>> 
>>     if (n != NGX_ERROR) {
>>         r->cache->valid_sec = n;
>> +        u->headers_in.no_cache = 0;
>> +        u->headers_in.expired = 0;
> 
> Handling of "X-Accel-Redirect: @0" is still missed.  Per the docs,
> it should set caching time to Epoch resulting in the expired response.
> Instead, such header is handled different, and it can be overridden
> with Cache-Control/Expires headers.  Probably, it should be handled
> separately and disable caching, similar to "X-Accel-Redirect: 0".
> 

Of course, this should be read as "X-Accel-Expires", bad copy'n'paste.

> As the above note covers a corner case of X-Accel-Expires,
> I believe it's fine to commit as is.
> 
>>     }
>>     }
>> #endif
>> diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h
>> --- a/src/http/ngx_http_upstream.h
>> +++ b/src/http/ngx_http_upstream.h
>> @@ -297,6 +297,8 @@ typedef struct {
>> 
>>     unsigned                         connection_close:1;
>>     unsigned                         chunked:1;
>> +    unsigned                         no_cache:1;
>> +    unsigned                         expired:1;
>> } ngx_http_upstream_headers_in_t;
>> 
>> 
>> 
> _______________________________________________
> nginx-devel mailing list -- nginx-devel at nginx.org
> To unsubscribe send an email to nginx-devel-leave at nginx.org

-- 
Sergey Kandaurov



More information about the nginx-devel mailing list