# HG changeset patch # User Hiroaki Nakamura <hnaka...@gmail.com> # Date 1718345844 -32400 # Fri Jun 14 15:17:24 2024 +0900 # Branch correct_age # Node ID 493817cf38580a71430f9c1293e29bdfbf243075 # Parent 2f636ec9c71aaa7129f3f79fa1e584e02f8748f0 Correctly calculate and set Age header. Implement the calculation of the Age header as specified in "RFC 9111: HTTP Caching" https://www.rfc-editor.org/rfc/rfc9111.html
diff -r 2f636ec9c71a -r 493817cf3858 src/http/ngx_http_cache.h --- a/src/http/ngx_http_cache.h Fri Jun 14 15:17:14 2024 +0900 +++ b/src/http/ngx_http_cache.h Fri Jun 14 15:17:24 2024 +0900 @@ -59,6 +59,8 @@ size_t body_start; off_t fs_size; ngx_msec_t lock_time; + time_t response_time; + time_t corrected_initial_age; } ngx_http_file_cache_node_t; @@ -75,6 +77,8 @@ time_t error_sec; time_t last_modified; time_t date; + time_t response_time; + time_t corrected_initial_age; ngx_str_t etag; ngx_str_t vary; diff -r 2f636ec9c71a -r 493817cf3858 src/http/ngx_http_file_cache.c --- a/src/http/ngx_http_file_cache.c Fri Jun 14 15:17:14 2024 +0900 +++ b/src/http/ngx_http_file_cache.c Fri Jun 14 15:17:24 2024 +0900 @@ -971,6 +971,8 @@ fcn->uniq = 0; fcn->body_start = 0; fcn->fs_size = 0; + fcn->response_time = 0; + fcn->corrected_initial_age = 0; done: @@ -980,6 +982,8 @@ c->uniq = fcn->uniq; c->error = fcn->error; + c->response_time = fcn->response_time; + c->corrected_initial_age = fcn->corrected_initial_age; c->node = fcn; failed: @@ -1624,6 +1628,7 @@ ngx_int_t ngx_http_cache_send(ngx_http_request_t *r) { + time_t resident_time, current_age; ngx_int_t rc; ngx_buf_t *b; ngx_chain_t out; @@ -1646,6 +1651,17 @@ return NGX_HTTP_INTERNAL_SERVER_ERROR; } + /* + * Update age response header. + * https://www.rfc-editor.org/rfc/rfc9111.html#name-calculating-age + */ + resident_time = ngx_time() - c->response_time; + current_age = c->corrected_initial_age + resident_time; + r->headers_out.age_n = current_age; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http file cache send, resp:%O, resident:%d, age:%d", + c->response_time, resident_time, current_age); + rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { diff -r 2f636ec9c71a -r 493817cf3858 src/http/ngx_http_header_filter_module.c --- a/src/http/ngx_http_header_filter_module.c Fri Jun 14 15:17:14 2024 +0900 +++ b/src/http/ngx_http_header_filter_module.c Fri Jun 14 15:17:24 2024 +0900 @@ -322,6 +322,10 @@ len += sizeof("Last-Modified: Mon, 28 Sep 1970 06:00:00 GMT" CRLF) - 1; } + if (r->headers_out.age_n != -1) { + len += sizeof("Age: ") - 1 + NGX_OFF_T_LEN + 2; + } + c = r->connection; if (r->headers_out.location @@ -518,6 +522,10 @@ *b->last++ = CR; *b->last++ = LF; } + if (r->headers_out.age_n != -1) { + b->last = ngx_sprintf(b->last, "Age: %O" CRLF, r->headers_out.age_n); + } + if (host.data) { p = b->last + sizeof("Location: ") - 1; diff -r 2f636ec9c71a -r 493817cf3858 src/http/ngx_http_request.c --- a/src/http/ngx_http_request.c Fri Jun 14 15:17:14 2024 +0900 +++ b/src/http/ngx_http_request.c Fri Jun 14 15:17:24 2024 +0900 @@ -646,6 +646,7 @@ r->headers_in.keep_alive_n = -1; r->headers_out.content_length_n = -1; r->headers_out.last_modified_time = -1; + r->headers_out.age_n = -1; r->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1; r->subrequests = NGX_HTTP_MAX_SUBREQUESTS + 1; diff -r 2f636ec9c71a -r 493817cf3858 src/http/ngx_http_request.h --- a/src/http/ngx_http_request.h Fri Jun 14 15:17:14 2024 +0900 +++ b/src/http/ngx_http_request.h Fri Jun 14 15:17:24 2024 +0900 @@ -290,6 +290,7 @@ off_t content_offset; time_t date_time; time_t last_modified_time; + off_t age_n; } ngx_http_headers_out_t; diff -r 2f636ec9c71a -r 493817cf3858 src/http/ngx_http_special_response.c --- a/src/http/ngx_http_special_response.c Fri Jun 14 15:17:14 2024 +0900 +++ b/src/http/ngx_http_special_response.c Fri Jun 14 15:17:24 2024 +0900 @@ -581,6 +581,7 @@ r->headers_out.content_length_n = -1; r->headers_out.last_modified_time = -1; + r->headers_out.age_n = -1; } diff -r 2f636ec9c71a -r 493817cf3858 src/http/ngx_http_upstream.c --- a/src/http/ngx_http_upstream.c Fri Jun 14 15:17:14 2024 +0900 +++ b/src/http/ngx_http_upstream.c Fri Jun 14 15:17:24 2024 +0900 @@ -50,6 +50,8 @@ ngx_http_upstream_t *u); static ngx_int_t ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u); +static void ngx_http_upstream_update_age(ngx_http_request_t *r, + ngx_http_upstream_t *u, time_t now); static ngx_int_t ngx_http_upstream_intercept_errors(ngx_http_request_t *r, ngx_http_upstream_t *u); static ngx_int_t ngx_http_upstream_test_connect(ngx_connection_t *c); @@ -132,6 +134,8 @@ 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 @@ -319,6 +323,10 @@ ngx_http_upstream_copy_header_line, offsetof(ngx_http_headers_out_t, content_encoding), 0 }, + { ngx_string("Age"), + ngx_http_upstream_process_age, 0, + ngx_http_upstream_ignore_header_line, 0, 0 }, + { ngx_null_string, NULL, 0, NULL, 0, 0 } }; @@ -499,6 +507,7 @@ u->headers_in.content_length_n = -1; u->headers_in.last_modified_time = -1; + u->headers_in.age_n = -1; return NGX_OK; } @@ -1068,6 +1077,7 @@ ngx_memzero(&u->headers_in, sizeof(ngx_http_upstream_headers_in_t)); u->headers_in.content_length_n = -1; u->headers_in.last_modified_time = -1; + u->headers_in.age_n = -1; if (ngx_list_init(&u->headers_in.headers, r->pool, 8, sizeof(ngx_table_elt_t)) @@ -1547,6 +1557,7 @@ ngx_memzero(u->state, sizeof(ngx_http_upstream_state_t)); u->start_time = ngx_current_msec; + u->request_time = ngx_time(); u->state->response_time = (ngx_msec_t) -1; u->state->connect_time = (ngx_msec_t) -1; @@ -2006,6 +2017,7 @@ ngx_memzero(&u->headers_in, sizeof(ngx_http_upstream_headers_in_t)); u->headers_in.content_length_n = -1; u->headers_in.last_modified_time = -1; + u->headers_in.age_n = -1; if (ngx_list_init(&u->headers_in.headers, r->pool, 8, sizeof(ngx_table_elt_t)) @@ -2520,6 +2532,8 @@ return; } + ngx_http_upstream_update_age(r, u, ngx_time()); + ngx_http_upstream_send_response(r, u); } @@ -2606,6 +2620,7 @@ "http upstream not modified"); now = ngx_time(); + ngx_http_upstream_update_age(r, u, now); valid = r->cache->valid_sec; updating = r->cache->updating_sec; @@ -2639,7 +2654,12 @@ valid = ngx_http_file_cache_valid(u->conf->cache_valid, u->headers_in.status_n); if (valid) { - valid = now + valid; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "adjust cache valid_sec:%O, " + "valid:%O, init_age:%d for 304", + now + valid - r->cache->corrected_initial_age, + valid, r->cache->corrected_initial_age); + valid = now + valid - r->cache->corrected_initial_age; } } @@ -2663,6 +2683,59 @@ } +static void +ngx_http_upstream_update_age(ngx_http_request_t *r, ngx_http_upstream_t *u, + time_t now) +{ + time_t response_time, date, apparent_age, response_delay, age_value, + corrected_age_value, corrected_initial_age; + + /* + * Update age response header. + * https://www.rfc-editor.org/rfc/rfc9111.html#name-calculating-age + */ + response_time = now; + if (u->headers_in.date != NULL) { + date = ngx_parse_http_time(u->headers_in.date->value.data, + u->headers_in.date->value.len); + if (date == NGX_ERROR) { + date = now; + } + } else { + date = now; + } + apparent_age = ngx_max(0, response_time - date); + + response_delay = response_time - u->request_time; + age_value = u->headers_in.age_n != -1 ? u->headers_in.age_n : 0; + corrected_age_value = age_value + response_delay; + + corrected_initial_age = ngx_max(apparent_age, corrected_age_value); + r->headers_out.age_n = corrected_initial_age; + + ngx_log_debug8(NGX_LOG_DEBUG_HTTP, u->peer.connection->log, 0, + "http upstream set age:%O, req:%O, resp:%O, date:%O, " + "a_age:%O, resp_delay:%O, u_age:%O, c_age:%O", + corrected_initial_age, u->request_time, response_time, date, + apparent_age, response_delay, u->headers_in.age_n, + corrected_age_value); + +#if (NGX_HTTP_CACHE) + if (r->cache) { + r->cache->response_time = response_time; + r->cache->corrected_initial_age = corrected_initial_age; + if (u->headers_in.adjusting_valid_sec) { + r->cache->valid_sec -= corrected_initial_age; + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, u->peer.connection->log, 0, + "http upstream adjusted cache " + "valid_sec:%O, init_age:%O", + r->cache->valid_sec, corrected_initial_age); + } + } +#endif +} + + static ngx_int_t ngx_http_upstream_intercept_errors(ngx_http_request_t *r, ngx_http_upstream_t *u) @@ -2738,6 +2811,7 @@ status); if (valid) { r->cache->valid_sec = ngx_time() + valid; + u->headers_in.adjusting_valid_sec = 1; } } @@ -2945,6 +3019,7 @@ r->headers_out.status_line = u->headers_in.status_line; r->headers_out.content_length_n = u->headers_in.content_length_n; + r->headers_out.age_n = u->headers_in.age_n; r->disable_not_modified = !u->cacheable; @@ -4609,6 +4684,7 @@ if (valid) { r->cache->valid_sec = ngx_time() + valid; + u->headers_in.adjusting_valid_sec = 1; r->cache->error = rc; } } @@ -4884,6 +4960,7 @@ } r->cache->valid_sec = ngx_time() + n; + u->headers_in.adjusting_valid_sec = 1; u->headers_in.expired = 0; } @@ -5046,6 +5123,7 @@ default: r->cache->valid_sec = ngx_time() + n; + u->headers_in.adjusting_valid_sec = 1; u->headers_in.no_cache = 0; u->headers_in.expired = 0; return NGX_OK; @@ -5312,6 +5390,39 @@ 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_ERR, r->connection->log, 0, + "upstream sent duplicate header line: "%V: %V", " + "previous value: "%V: %V"", + &h->key, &h->value, + &u->headers_in.age->key, + &u->headers_in.age->value); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + h->next = NULL; + u->headers_in.age = h; + u->headers_in.age_n = ngx_atoof(h->value.data, h->value.len); + + if (u->headers_in.age_n == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid "Age" header: " + ""%V: %V"", &h->key, &h->value); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + 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) { diff -r 2f636ec9c71a -r 493817cf3858 src/http/ngx_http_upstream.h --- a/src/http/ngx_http_upstream.h Fri Jun 14 15:17:14 2024 +0900 +++ b/src/http/ngx_http_upstream.h Fri Jun 14 15:17:24 2024 +0900 @@ -287,14 +287,17 @@ 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; + off_t age_n; unsigned connection_close:1; unsigned chunked:1; unsigned no_cache:1; unsigned expired:1; + unsigned adjusting_valid_sec:1; } ngx_http_upstream_headers_in_t; @@ -369,6 +372,7 @@ ngx_table_elt_t *h); ngx_msec_t start_time; + time_t request_time; ngx_http_upstream_state_t *state; diff -r 2f636ec9c71a -r 493817cf3858 src/http/v2/ngx_http_v2.h --- a/src/http/v2/ngx_http_v2.h Fri Jun 14 15:17:14 2024 +0900 +++ b/src/http/v2/ngx_http_v2.h Fri Jun 14 15:17:24 2024 +0900 @@ -398,6 +398,7 @@ #define NGX_HTTP_V2_STATUS_404_INDEX 13 #define NGX_HTTP_V2_STATUS_500_INDEX 14 +#define NGX_HTTP_V2_AGE_INDEX 21 #define NGX_HTTP_V2_CONTENT_LENGTH_INDEX 28 #define NGX_HTTP_V2_CONTENT_TYPE_INDEX 31 #define NGX_HTTP_V2_DATE_INDEX 33 diff -r 2f636ec9c71a -r 493817cf3858 src/http/v2/ngx_http_v2_filter_module.c --- a/src/http/v2/ngx_http_v2_filter_module.c Fri Jun 14 15:17:14 2024 +0900 +++ b/src/http/v2/ngx_http_v2_filter_module.c Fri Jun 14 15:17:24 2024 +0900 @@ -256,6 +256,10 @@ len += 1 + ngx_http_v2_literal_size("Wed, 31 Dec 1986 18:00:00 GMT"); } + if (r->headers_out.age_n != -1) { + len += 1 + ngx_http_v2_integer_octets(NGX_OFF_T_LEN) + NGX_OFF_T_LEN; + } + if (r->headers_out.location && r->headers_out.location->value.len) { if (r->headers_out.location->value.data[0] == '/' @@ -543,6 +547,18 @@ pos = ngx_http_v2_write_value(pos, pos, len, tmp); } + if (r->headers_out.age_n != -1) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 output header: "age: %O"", + r->headers_out.age_n); + + *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_AGE_INDEX); + + p = pos; + pos = ngx_sprintf(pos + 1, "%O", r->headers_out.age_n); + *p = NGX_HTTP_V2_ENCODE_RAW | (u_char) (pos - p - 1); + } + if (r->headers_out.location && r->headers_out.location->value.len) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 output header: "location: %V"", diff -r 2f636ec9c71a -r 493817cf3858 src/http/v3/ngx_http_v3_filter_module.c --- a/src/http/v3/ngx_http_v3_filter_module.c Fri Jun 14 15:17:14 2024 +0900 +++ b/src/http/v3/ngx_http_v3_filter_module.c Fri Jun 14 15:17:24 2024 +0900 @@ -13,6 +13,7 @@ /* static table indices */ #define NGX_HTTP_V3_HEADER_AUTHORITY 0 #define NGX_HTTP_V3_HEADER_PATH_ROOT 1 +#define NGX_HTTP_V3_HEADER_AGE_ZERO 2 #define NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO 4 #define NGX_HTTP_V3_HEADER_DATE 6 #define NGX_HTTP_V3_HEADER_LAST_MODIFIED 10 @@ -213,6 +214,15 @@ sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); } + if (r->headers_out.age_n > 0) { + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_AGE_ZERO, + NULL, NGX_OFF_T_LEN); + } else if (r->headers_out.age_n == 0) { + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_AGE_ZERO); + } + if (r->headers_out.location && r->headers_out.location->value.len) { if (r->headers_out.location->value.data[0] == '/' @@ -452,6 +462,27 @@ p, n); } + if (r->headers_out.age_n != -1) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: "age: %O"", + r->headers_out.age_n); + + if (r->headers_out.age_n > 0) { + p = ngx_sprintf(b->last, "%O", r->headers_out.age_n); + n = p - b->last; + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_AGE_ZERO, + NULL, n); + + b->last = ngx_sprintf(b->last, "%O", r->headers_out.age_n); + + } else { + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_AGE_ZERO); + } + } + if (r->headers_out.location && r->headers_out.location->value.len) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 output header: "location: %V"", _______________________________________________ nginx-devel mailing list nginx-devel@nginx.org https://mailman.nginx.org/mailman/listinfo/nginx-devel