[PATCH 1 of 3] Correctly calculate and set Age header
Hiroaki Nakamura
hnakamur at gmail.com
Thu Jun 20 11:39:43 UTC 2024
# HG changeset patch
# User Hiroaki Nakamura <hnakamur at gmail.com>
# Date 1718882801 -32400
# Thu Jun 20 20:26:41 2024 +0900
# Branch correct_age
# Node ID c81df54e3d0333c26d4296792dc0df767b386f91
# Parent 73929a4f3447d558747623884b5ba281c13332d8
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 73929a4f3447 -r c81df54e3d03 src/http/ngx_http_cache.h
--- a/src/http/ngx_http_cache.h Thu Jun 20 20:26:24 2024 +0900
+++ b/src/http/ngx_http_cache.h Thu Jun 20 20:26:41 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 73929a4f3447 -r c81df54e3d03 src/http/ngx_http_file_cache.c
--- a/src/http/ngx_http_file_cache.c Thu Jun 20 20:26:24 2024 +0900
+++ b/src/http/ngx_http_file_cache.c Thu Jun 20 20:26:41 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 73929a4f3447 -r c81df54e3d03 src/http/ngx_http_header_filter_module.c
--- a/src/http/ngx_http_header_filter_module.c Thu Jun 20 20:26:24 2024 +0900
+++ b/src/http/ngx_http_header_filter_module.c Thu Jun 20 20:26:41 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 73929a4f3447 -r c81df54e3d03 src/http/ngx_http_request.c
--- a/src/http/ngx_http_request.c Thu Jun 20 20:26:24 2024 +0900
+++ b/src/http/ngx_http_request.c Thu Jun 20 20:26:41 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 73929a4f3447 -r c81df54e3d03 src/http/ngx_http_request.h
--- a/src/http/ngx_http_request.h Thu Jun 20 20:26:24 2024 +0900
+++ b/src/http/ngx_http_request.h Thu Jun 20 20:26:41 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 73929a4f3447 -r c81df54e3d03 src/http/ngx_http_special_response.c
--- a/src/http/ngx_http_special_response.c Thu Jun 20 20:26:24 2024 +0900
+++ b/src/http/ngx_http_special_response.c Thu Jun 20 20:26:41 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 73929a4f3447 -r c81df54e3d03 src/http/ngx_http_upstream.c
--- a/src/http/ngx_http_upstream.c Thu Jun 20 20:26:24 2024 +0900
+++ b/src/http/ngx_http_upstream.c Thu Jun 20 20:26:41 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))
@@ -1549,6 +1559,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;
@@ -2008,6 +2019,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))
@@ -2529,6 +2541,8 @@
return;
}
+ ngx_http_upstream_update_age(r, u, ngx_time());
+
ngx_http_upstream_send_response(r, u);
}
@@ -2615,6 +2629,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;
@@ -2648,7 +2663,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;
}
}
@@ -2672,6 +2692,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)
@@ -2747,6 +2820,7 @@
status);
if (valid) {
r->cache->valid_sec = ngx_time() + valid;
+ u->headers_in.adjusting_valid_sec = 1;
}
}
@@ -2952,6 +3026,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;
@@ -4616,6 +4691,7 @@
if (valid) {
r->cache->valid_sec = ngx_time() + valid;
+ u->headers_in.adjusting_valid_sec = 1;
r->cache->error = rc;
}
}
@@ -4891,6 +4967,7 @@
}
r->cache->valid_sec = ngx_time() + n;
+ u->headers_in.adjusting_valid_sec = 1;
u->headers_in.expired = 0;
}
@@ -5053,6 +5130,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;
@@ -5319,6 +5397,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 73929a4f3447 -r c81df54e3d03 src/http/ngx_http_upstream.h
--- a/src/http/ngx_http_upstream.h Thu Jun 20 20:26:24 2024 +0900
+++ b/src/http/ngx_http_upstream.h Thu Jun 20 20:26:41 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 73929a4f3447 -r c81df54e3d03 src/http/v2/ngx_http_v2.h
--- a/src/http/v2/ngx_http_v2.h Thu Jun 20 20:26:24 2024 +0900
+++ b/src/http/v2/ngx_http_v2.h Thu Jun 20 20:26:41 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 73929a4f3447 -r c81df54e3d03 src/http/v2/ngx_http_v2_filter_module.c
--- a/src/http/v2/ngx_http_v2_filter_module.c Thu Jun 20 20:26:24 2024 +0900
+++ b/src/http/v2/ngx_http_v2_filter_module.c Thu Jun 20 20:26:41 2024 +0900
@@ -258,6 +258,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] == '/'
@@ -552,6 +556,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 73929a4f3447 -r c81df54e3d03 src/http/v3/ngx_http_v3_filter_module.c
--- a/src/http/v3/ngx_http_v3_filter_module.c Thu Jun 20 20:26:24 2024 +0900
+++ b/src/http/v3/ngx_http_v3_filter_module.c Thu Jun 20 20:26:41 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"",
More information about the nginx-devel
mailing list