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.
This commit is contained in:
Roman Arutyunyan 2016-12-22 14:25:34 +03:00
parent 42f3dd2b84
commit da2b2cf1e0
3 changed files with 93 additions and 21 deletions

View File

@ -27,7 +27,7 @@
#define NGX_HTTP_CACHE_ETAG_LEN 42 #define NGX_HTTP_CACHE_ETAG_LEN 42
#define NGX_HTTP_CACHE_VARY_LEN 42 #define NGX_HTTP_CACHE_VARY_LEN 42
#define NGX_HTTP_CACHE_VERSION 3 #define NGX_HTTP_CACHE_VERSION 4
typedef struct { typedef struct {
@ -71,6 +71,8 @@ struct ngx_http_cache_s {
ngx_file_uniq_t uniq; ngx_file_uniq_t uniq;
time_t valid_sec; time_t valid_sec;
time_t updating_sec;
time_t error_sec;
time_t last_modified; time_t last_modified;
time_t date; time_t date;
@ -114,12 +116,17 @@ struct ngx_http_cache_s {
unsigned purged:1; unsigned purged:1;
unsigned reading:1; unsigned reading:1;
unsigned secondary:1; unsigned secondary:1;
unsigned stale_updating:1;
unsigned stale_error:1;
}; };
typedef struct { typedef struct {
ngx_uint_t version; ngx_uint_t version;
time_t valid_sec; time_t valid_sec;
time_t updating_sec;
time_t error_sec;
time_t last_modified; time_t last_modified;
time_t date; time_t date;
uint32_t crc32; uint32_t crc32;

View File

@ -601,6 +601,8 @@ ngx_http_file_cache_read(ngx_http_request_t *r, ngx_http_cache_t *c)
c->buf->last += n; c->buf->last += n;
c->valid_sec = h->valid_sec; 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->last_modified = h->last_modified;
c->date = h->date; c->date = h->date;
c->valid_msec = h->valid_msec; c->valid_msec = h->valid_msec;
@ -632,6 +634,8 @@ ngx_http_file_cache_read(ngx_http_request_t *r, ngx_http_cache_t *c)
now = ngx_time(); now = ngx_time();
if (c->valid_sec < now) { 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); ngx_shmtx_lock(&cache->shpool->mutex);
@ -1252,6 +1256,8 @@ ngx_http_file_cache_set_header(ngx_http_request_t *r, u_char *buf)
h->version = NGX_HTTP_CACHE_VERSION; h->version = NGX_HTTP_CACHE_VERSION;
h->valid_sec = c->valid_sec; 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->last_modified = c->last_modified;
h->date = c->date; h->date = c->date;
h->crc32 = c->crc32; h->crc32 = c->crc32;
@ -1513,6 +1519,8 @@ ngx_http_file_cache_update_header(ngx_http_request_t *r)
h.version = NGX_HTTP_CACHE_VERSION; h.version = NGX_HTTP_CACHE_VERSION;
h.valid_sec = c->valid_sec; 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.last_modified = c->last_modified;
h.date = c->date; h.date = c->date;
h.crc32 = c->crc32; h.crc32 = c->crc32;

View File

@ -871,7 +871,9 @@ ngx_http_upstream_cache(ngx_http_request_t *r, ngx_http_upstream_t *u)
case NGX_HTTP_CACHE_UPDATING: 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; u->cache_status = rc;
rc = NGX_OK; rc = NGX_OK;
@ -894,6 +896,9 @@ ngx_http_upstream_cache(ngx_http_request_t *r, ngx_http_upstream_t *u)
case NGX_HTTP_CACHE_STALE: case NGX_HTTP_CACHE_STALE:
c->valid_sec = 0; c->valid_sec = 0;
c->updating_sec = 0;
c->error_sec = 0;
u->buffer.start = NULL; u->buffer.start = NULL;
u->cache_status = NGX_HTTP_CACHE_EXPIRED; u->cache_status = NGX_HTTP_CACHE_EXPIRED;
@ -2340,7 +2345,7 @@ ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u)
#if (NGX_HTTP_CACHE) #if (NGX_HTTP_CACHE)
if (u->cache_status == NGX_HTTP_CACHE_EXPIRED 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; ngx_int_t rc;
@ -2364,14 +2369,17 @@ ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u)
&& u->cache_status == NGX_HTTP_CACHE_EXPIRED && u->cache_status == NGX_HTTP_CACHE_EXPIRED
&& u->conf->cache_revalidate) && u->conf->cache_revalidate)
{ {
time_t now, valid; time_t now, valid, updating, error;
ngx_int_t rc; ngx_int_t rc;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http upstream not modified"); "http upstream not modified");
now = ngx_time(); now = ngx_time();
valid = r->cache->valid_sec; valid = r->cache->valid_sec;
updating = r->cache->updating_sec;
error = r->cache->error_sec;
rc = u->reinit_request(r); rc = u->reinit_request(r);
@ -2385,6 +2393,8 @@ ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u)
if (valid == 0) { if (valid == 0) {
valid = r->cache->valid_sec; valid = r->cache->valid_sec;
updating = r->cache->updating_sec;
error = r->cache->error_sec;
} }
if (valid == 0) { if (valid == 0) {
@ -2397,6 +2407,9 @@ ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u)
if (valid) { if (valid) {
r->cache->valid_sec = valid; r->cache->valid_sec = valid;
r->cache->updating_sec = updating;
r->cache->error_sec = error;
r->cache->date = now; r->cache->date = now;
ngx_http_file_cache_update_header(r); ngx_http_file_cache_update_header(r);
@ -4132,7 +4145,7 @@ ngx_http_upstream_next(ngx_http_request_t *r, ngx_http_upstream_t *u,
#if (NGX_HTTP_CACHE) #if (NGX_HTTP_CACHE)
if (u->cache_status == NGX_HTTP_CACHE_EXPIRED 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; ngx_int_t rc;
@ -4507,32 +4520,76 @@ ngx_http_upstream_process_cache_control(ngx_http_request_t *r,
offset = 8; offset = 8;
} }
if (p == NULL) { if (p) {
return NGX_OK; n = 0;
}
n = 0; for (p += offset; p < last; p++) {
if (*p == ',' || *p == ';' || *p == ' ') {
break;
}
for (p += offset; p < last; p++) { if (*p >= '0' && *p <= '9') {
if (*p == ',' || *p == ';' || *p == ' ') { n = n * 10 + *p - '0';
break; continue;
}
u->cacheable = 0;
return NGX_OK;
} }
if (*p >= '0' && *p <= '9') { if (n == 0) {
n = n * 10 + *p - '0'; u->cacheable = 0;
continue; return NGX_OK;
} }
u->cacheable = 0; r->cache->valid_sec = ngx_time() + n;
return NGX_OK;
} }
if (n == 0) { p = ngx_strlcasestrn(start, last, (u_char *) "stale-while-revalidate=",
u->cacheable = 0; 23 - 1);
return NGX_OK;
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;
} }
r->cache->valid_sec = ngx_time() + 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 #endif