diff --git a/src/http/modules/ngx_http_headers_filter_module.c b/src/http/modules/ngx_http_headers_filter_module.c index f72044097..acfdb273f 100644 --- a/src/http/modules/ngx_http_headers_filter_module.c +++ b/src/http/modules/ngx_http_headers_filter_module.c @@ -100,6 +100,10 @@ static ngx_http_set_header_t ngx_http_set_headers[] = { offsetof(ngx_http_headers_out_t, etag), ngx_http_set_response_header }, + { ngx_string("Age"), + offsetof(ngx_http_headers_out_t, age), + ngx_http_set_response_header }, + { ngx_null_string, 0, NULL } }; @@ -383,6 +387,11 @@ ngx_http_set_expires(ngx_http_request_t *r, ngx_http_headers_conf_t *conf) } } + if (r->headers_out.age) { + r->headers_out.age->hash = 0; + r->headers_out.age = NULL; + } + e = r->headers_out.expires; if (e == NULL) { diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index 6d18cc3c6..33363907a 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -282,6 +282,7 @@ typedef struct { ngx_table_elt_t *cache_control; ngx_table_elt_t *link; + ngx_table_elt_t *age; ngx_str_t *override_charset; diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index bb8964add..af5fcda5a 100644 --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -27,6 +27,8 @@ static ngx_int_t ngx_http_upstream_cache_last_modified(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_upstream_cache_etag(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_upstream_cache_age(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); #endif static void ngx_http_upstream_init_request(ngx_http_request_t *r); @@ -139,6 +141,8 @@ static ngx_int_t ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_upstream_process_vary(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_upstream_process_age(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_upstream_copy_header_line(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t @@ -300,6 +304,11 @@ static ngx_http_upstream_header_t ngx_http_upstream_headers_in[] = { ngx_http_upstream_copy_multi_header_lines, offsetof(ngx_http_headers_out_t, link), 0 }, + { ngx_string("Age"), + ngx_http_upstream_process_age, 0, + ngx_http_upstream_copy_header_line, + offsetof(ngx_http_headers_out_t, age), 0 }, + { ngx_string("X-Accel-Expires"), ngx_http_upstream_process_accel_expires, 0, ngx_http_upstream_copy_header_line, 0, 0 }, @@ -451,6 +460,10 @@ static ngx_http_variable_t ngx_http_upstream_vars[] = { ngx_http_upstream_cache_etag, 0, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 }, + { ngx_string("upstream_cache_age"), NULL, + ngx_http_upstream_cache_age, 0, + NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 }, + #endif { ngx_string("upstream_http_"), NULL, ngx_http_upstream_header_variable, @@ -496,6 +509,7 @@ ngx_conf_bitmask_t ngx_http_upstream_ignore_headers_masks[] = { { ngx_string("Cache-Control"), NGX_HTTP_UPSTREAM_IGN_CACHE_CONTROL }, { ngx_string("Set-Cookie"), NGX_HTTP_UPSTREAM_IGN_SET_COOKIE }, { ngx_string("Vary"), NGX_HTTP_UPSTREAM_IGN_VARY }, + { ngx_string("Age"), NGX_HTTP_UPSTREAM_IGN_AGE }, { ngx_null_string, 0 } }; @@ -5129,14 +5143,16 @@ ngx_http_upstream_process_cache_control(ngx_http_request_t *r, return NGX_OK; } - if (n == 0) { + if (n <= u->headers_in.age_n) { u->headers_in.no_cache = 1; return NGX_OK; } - r->cache->valid_sec = ngx_min((ngx_uint_t) ngx_time() + n, + r->cache->valid_sec = ngx_min((ngx_uint_t) ngx_time() + + n - u->headers_in.age_n, NGX_MAX_INT_T_VALUE); u->headers_in.expired = 0; + u->headers_in.max_age = 1; } extensions: @@ -5317,6 +5333,7 @@ ngx_http_upstream_process_accel_expires(ngx_http_request_t *r, r->cache->valid_sec = ngx_time() + n; u->headers_in.no_cache = 0; u->headers_in.expired = 0; + u->headers_in.max_age = 0; return NGX_OK; } } @@ -5330,6 +5347,7 @@ ngx_http_upstream_process_accel_expires(ngx_http_request_t *r, r->cache->valid_sec = n; u->headers_in.no_cache = 0; u->headers_in.expired = 0; + u->headers_in.max_age = 0; } } #endif @@ -5580,6 +5598,61 @@ ngx_http_upstream_process_vary(ngx_http_request_t *r, } +static ngx_int_t +ngx_http_upstream_process_age(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset) +{ + ngx_http_upstream_t *u; + + u = r->upstream; + + if (u->headers_in.age) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "upstream sent duplicate header line: \"%V: %V\", " + "previous value: \"%V: %V\", ignored", + &h->key, &h->value, + &u->headers_in.age->key, + &u->headers_in.age->value); + h->hash = 0; + return NGX_OK; + } + + u->headers_in.age = h; + h->next = NULL; + +#if (NGX_HTTP_CACHE) + { + ngx_int_t n; + + if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_AGE) { + return NGX_OK; + } + + if (r->cache == NULL || !u->cacheable) { + return NGX_OK; + } + + n = ngx_atoi(h->value.data, h->value.len); + + if (n != NGX_ERROR) { + u->headers_in.age_n = n; + + if (u->headers_in.max_age) { + if (n >= r->cache->valid_sec - ngx_time()) { + u->headers_in.no_cache = 1; + return NGX_OK; + } + + r->cache->valid_sec -= n; + } + } + } +#endif + + return NGX_OK; +} + + static ngx_int_t ngx_http_upstream_copy_header_line(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) @@ -6311,6 +6384,60 @@ ngx_http_upstream_cache_etag(ngx_http_request_t *r, return NGX_OK; } + +static ngx_int_t +ngx_http_upstream_cache_age(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + time_t now, age; + + if (r->upstream == NULL) { + v->not_found = 1; + return NGX_OK; + } + + if (!r->cached + || r->cache == NULL + || r->upstream->cache_status == NGX_HTTP_CACHE_REVALIDATED) + { + if (r->upstream->headers_in.age == NULL) { + v->not_found = 1; + return NGX_OK; + } + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->len = r->upstream->headers_in.age->value.len; + v->data = r->upstream->headers_in.age->value.data; + + return NGX_OK; + } + + p = ngx_pnalloc(r->pool, NGX_TIME_T_LEN); + if (p == NULL) { + return NGX_ERROR; + } + + now = ngx_time(); + age = now - r->cache->date; + + if (r->cache->date > now) { + age = 0; + } + + age += r->upstream->headers_in.age_n; + + v->len = ngx_sprintf(p, "%T", age) - p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; +} + #endif diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h index 4560bbe9a..acb84878a 100644 --- a/src/http/ngx_http_upstream.h +++ b/src/http/ngx_http_upstream.h @@ -55,6 +55,7 @@ #define NGX_HTTP_UPSTREAM_IGN_XA_BUFFERING 0x00000080 #define NGX_HTTP_UPSTREAM_IGN_XA_CHARSET 0x00000100 #define NGX_HTTP_UPSTREAM_IGN_VARY 0x00000200 +#define NGX_HTTP_UPSTREAM_IGN_AGE 0x00000400 #define NGX_HTTP_UPSTREAM_NOTIFY_HEADER 0x1 @@ -303,14 +304,17 @@ typedef struct { ngx_table_elt_t *cache_control; ngx_table_elt_t *set_cookie; + ngx_table_elt_t *age; off_t content_length_n; time_t last_modified_time; + time_t age_n; unsigned connection_close:1; unsigned chunked:1; unsigned no_cache:1; unsigned expired:1; + unsigned max_age:1; } ngx_http_upstream_headers_in_t;