# HG changeset patch # User Roman Arutyunyan # Date 1482405934 -10800 # Node ID 9a9e136868694a5c13a37f71d6763d873bd520b8 # Parent 5e2423bce8839bf6ddbca26f7b41ccb6459eefb7 Cache: support for stale-while-revalidate and stale-if-error. Previously, there was no way to enable the proxy_cache_use_stale behavior by reading the backend response. Now, stale-while-revalidate and stale-if-error Cache-Control extensions (RFC 5861) are supported. They specify, how long a stale response can be used when a cache entry is being updated, or in case of an error. diff --git a/src/http/ngx_http_cache.h b/src/http/ngx_http_cache.h --- a/src/http/ngx_http_cache.h +++ b/src/http/ngx_http_cache.h @@ -27,7 +27,7 @@ #define NGX_HTTP_CACHE_ETAG_LEN 42 #define NGX_HTTP_CACHE_VARY_LEN 42 -#define NGX_HTTP_CACHE_VERSION 3 +#define NGX_HTTP_CACHE_VERSION 4 typedef struct { @@ -71,6 +71,8 @@ struct ngx_http_cache_s { ngx_file_uniq_t uniq; time_t valid_sec; + time_t updating_sec; + time_t error_sec; time_t last_modified; time_t date; @@ -114,12 +116,17 @@ struct ngx_http_cache_s { unsigned purged:1; unsigned reading:1; unsigned secondary:1; + + unsigned stale_updating:1; + unsigned stale_error:1; }; typedef struct { ngx_uint_t version; time_t valid_sec; + time_t updating_sec; + time_t error_sec; time_t last_modified; time_t date; uint32_t crc32; diff --git a/src/http/ngx_http_file_cache.c b/src/http/ngx_http_file_cache.c --- a/src/http/ngx_http_file_cache.c +++ b/src/http/ngx_http_file_cache.c @@ -601,6 +601,8 @@ ngx_http_file_cache_read(ngx_http_reques c->buf->last += n; c->valid_sec = h->valid_sec; + c->updating_sec = h->updating_sec; + c->error_sec = h->error_sec; c->last_modified = h->last_modified; c->date = h->date; c->valid_msec = h->valid_msec; @@ -632,6 +634,8 @@ ngx_http_file_cache_read(ngx_http_reques now = ngx_time(); if (c->valid_sec < now) { + c->stale_updating = c->valid_sec + c->updating_sec >= now; + c->stale_error = c->valid_sec + c->error_sec >= now; ngx_shmtx_lock(&cache->shpool->mutex); @@ -1252,6 +1256,8 @@ ngx_http_file_cache_set_header(ngx_http_ h->version = NGX_HTTP_CACHE_VERSION; h->valid_sec = c->valid_sec; + h->updating_sec = c->updating_sec; + h->error_sec = c->error_sec; h->last_modified = c->last_modified; h->date = c->date; h->crc32 = c->crc32; @@ -1513,6 +1519,8 @@ ngx_http_file_cache_update_header(ngx_ht h.version = NGX_HTTP_CACHE_VERSION; h.valid_sec = c->valid_sec; + h.updating_sec = c->updating_sec; + h.error_sec = c->error_sec; h.last_modified = c->last_modified; h.date = c->date; h.crc32 = c->crc32; 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 @@ -871,7 +871,9 @@ ngx_http_upstream_cache(ngx_http_request case NGX_HTTP_CACHE_UPDATING: - if (u->conf->cache_use_stale & NGX_HTTP_UPSTREAM_FT_UPDATING) { + if ((u->conf->cache_use_stale & NGX_HTTP_UPSTREAM_FT_UPDATING) + || c->stale_updating) + { u->cache_status = rc; rc = NGX_OK; @@ -894,6 +896,9 @@ ngx_http_upstream_cache(ngx_http_request case NGX_HTTP_CACHE_STALE: c->valid_sec = 0; + c->updating_sec = 0; + c->error_sec = 0; + u->buffer.start = NULL; u->cache_status = NGX_HTTP_CACHE_EXPIRED; @@ -2340,7 +2345,7 @@ ngx_http_upstream_test_next(ngx_http_req #if (NGX_HTTP_CACHE) if (u->cache_status == NGX_HTTP_CACHE_EXPIRED - && (u->conf->cache_use_stale & un->mask)) + && ((u->conf->cache_use_stale & un->mask) || r->cache->stale_error)) { ngx_int_t rc; @@ -2364,14 +2369,17 @@ ngx_http_upstream_test_next(ngx_http_req && u->cache_status == NGX_HTTP_CACHE_EXPIRED && u->conf->cache_revalidate) { - time_t now, valid; + time_t now, valid, updating, error; ngx_int_t rc; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http upstream not modified"); now = ngx_time(); + valid = r->cache->valid_sec; + updating = r->cache->updating_sec; + error = r->cache->error_sec; rc = u->reinit_request(r); @@ -2385,6 +2393,8 @@ ngx_http_upstream_test_next(ngx_http_req if (valid == 0) { valid = r->cache->valid_sec; + updating = r->cache->updating_sec; + error = r->cache->error_sec; } if (valid == 0) { @@ -2397,6 +2407,9 @@ ngx_http_upstream_test_next(ngx_http_req if (valid) { r->cache->valid_sec = valid; + r->cache->updating_sec = updating; + r->cache->error_sec = error; + r->cache->date = now; ngx_http_file_cache_update_header(r); @@ -4132,7 +4145,7 @@ ngx_http_upstream_next(ngx_http_request_ #if (NGX_HTTP_CACHE) if (u->cache_status == NGX_HTTP_CACHE_EXPIRED - && (u->conf->cache_use_stale & ft_type)) + && ((u->conf->cache_use_stale & ft_type) || r->cache->stale_error)) { ngx_int_t rc; @@ -4507,32 +4520,76 @@ ngx_http_upstream_process_cache_control( offset = 8; } - if (p == NULL) { - return NGX_OK; - } - - n = 0; - - for (p += offset; p < last; p++) { - if (*p == ',' || *p == ';' || *p == ' ') { - break; - } - - if (*p >= '0' && *p <= '9') { - n = n * 10 + *p - '0'; - continue; - } - - u->cacheable = 0; - return NGX_OK; - } - - if (n == 0) { - u->cacheable = 0; - return NGX_OK; - } - - r->cache->valid_sec = ngx_time() + n; + if (p) { + n = 0; + + for (p += offset; p < last; p++) { + if (*p == ',' || *p == ';' || *p == ' ') { + break; + } + + if (*p >= '0' && *p <= '9') { + n = n * 10 + *p - '0'; + continue; + } + + u->cacheable = 0; + return NGX_OK; + } + + if (n == 0) { + u->cacheable = 0; + return NGX_OK; + } + + r->cache->valid_sec = ngx_time() + n; + } + + p = ngx_strlcasestrn(start, last, (u_char *) "stale-while-revalidate=", + 23 - 1); + + if (p) { + n = 0; + + for (p += 23; p < last; p++) { + if (*p == ',' || *p == ';' || *p == ' ') { + break; + } + + if (*p >= '0' && *p <= '9') { + n = n * 10 + *p - '0'; + continue; + } + + u->cacheable = 0; + return NGX_OK; + } + + r->cache->updating_sec = n; + r->cache->error_sec = n; + } + + p = ngx_strlcasestrn(start, last, (u_char *) "stale-if-error=", 15 - 1); + + if (p) { + n = 0; + + for (p += 15; p < last; p++) { + if (*p == ',' || *p == ';' || *p == ' ') { + break; + } + + if (*p >= '0' && *p <= '9') { + n = n * 10 + *p - '0'; + continue; + } + + u->cacheable = 0; + return NGX_OK; + } + + r->cache->error_sec = n; + } } #endif