[PATCH 1 of 3] Correctly calculate and set Age header

Hiroaki Nakamura hnakamur at gmail.com
Wed Jun 26 11:44:56 UTC 2024


Hello,

* I have deleted ngx_http_upstream_update_age and changed to
   update age header in ngx_http_upstream_process_headers.
* I have changed to use ngx_atotm for converting age header
  string to time_t.

Thank you,
Hiroaki Nakamura

# HG changeset patch
# User Hiroaki Nakamura <hnakamur at gmail.com>
# Date 1719401770 -32400
#      Wed Jun 26 20:36:10 2024 +0900
# Node ID 57facb7deaca2a323e71cfee28a087534b3ed1c5
# Parent  ed8a5f82e23aa8bd0a4b3ab56d414e12145717ab
Cache: added calculation of the Age header.
* Implement the modified calculation of the Age header as specified in
  RFC 9111 "HTTP Caching", https://www.rfc-editor.org/rfc/rfc9111.html

  Calculation initial age as specified in RFC 9111:

  apparent_age = max(0, response_time - date_value);

  response_delay = response_time - request_time;
  corrected_age_value = age_value + response_delay;

  corrected_initial_age = max(apparent_age, corrected_age_value);

  However, if the upstream date is too old, then apparant_age
  becomes too big.

  Therefore, we use the following calculation:

  if upstream date is between request_time and response_time, we assume
  the upstream's clock is reasonably well synchronized and use

  corrected_initial_age = age_value + (response_time - date);

  if upstream date is not present or not between request_time
  and response_time, we do not rely on the upstream date and use

  corrected_initial_age = age_value + (response_time - u->request_time);

  When sending a cached response, we calculate the Age
  as specified in RFC 9111:

  resident_time = now - response_time;
  current_age = corrected_initial_age + resident_time;

  where response_time is cache_creation_time.

* Also ignore Expires and X-Accel-Expires when Cache-Control max-age
  or s-maxage is set as specified in
  https://www.rfc-editor.org/rfc/rfc9111#name-expires:

  "If a response includes a Cache-Control header field with the
  max-age directive (Section 5.2.2.1), a recipient MUST ignore
  the Expires header field. Likewise, if a response includes the
  s-maxage directive (Section 5.2.2.10), a shared cache recipient
  MUST ignore the Expires header field."

diff -r ed8a5f82e23a -r 57facb7deaca src/http/ngx_http_cache.h
--- a/src/http/ngx_http_cache.h Wed Jun 26 20:36:03 2024 +0900
+++ b/src/http/ngx_http_cache.h Wed Jun 26 20:36:10 2024 +0900
@@ -27,7 +27,7 @@
 #define NGX_HTTP_CACHE_ETAG_LEN      128
 #define NGX_HTTP_CACHE_VARY_LEN      128

-#define NGX_HTTP_CACHE_VERSION       5
+#define NGX_HTTP_CACHE_VERSION       6


 typedef struct {
@@ -59,6 +59,8 @@ typedef struct {
     size_t                           body_start;
     off_t                            fs_size;
     ngx_msec_t                       lock_time;
+    time_t                           creation_time;
+    time_t                           corrected_initial_age;
 } ngx_http_file_cache_node_t;


@@ -75,6 +77,8 @@ struct ngx_http_cache_s {
     time_t                           error_sec;
     time_t                           last_modified;
     time_t                           date;
+    time_t                           creation_time;
+    time_t                           corrected_initial_age;

     ngx_str_t                        etag;
     ngx_str_t                        vary;
@@ -141,6 +145,8 @@ typedef struct {
     u_char                           vary_len;
     u_char                           vary[NGX_HTTP_CACHE_VARY_LEN];
     u_char                           variant[NGX_HTTP_CACHE_KEY_LEN];
+    time_t                           creation_time;
+    time_t                           corrected_initial_age;
 } ngx_http_file_cache_header_t;


diff -r ed8a5f82e23a -r 57facb7deaca src/http/ngx_http_file_cache.c
--- a/src/http/ngx_http_file_cache.c Wed Jun 26 20:36:03 2024 +0900
+++ b/src/http/ngx_http_file_cache.c Wed Jun 26 20:36:10 2024 +0900
@@ -627,6 +627,8 @@ ngx_http_file_cache_read(ngx_http_reques
     c->body_start = h->body_start;
     c->etag.len = h->etag_len;
     c->etag.data = h->etag;
+    c->creation_time = h->creation_time;
+    c->corrected_initial_age = h->corrected_initial_age;

     r->cached = 1;

@@ -971,6 +973,8 @@ renew:
     fcn->uniq = 0;
     fcn->body_start = 0;
     fcn->fs_size = 0;
+    fcn->creation_time = 0;
+    fcn->corrected_initial_age = 0;

 done:

@@ -980,6 +984,8 @@ done:

     c->uniq = fcn->uniq;
     c->error = fcn->error;
+    c->creation_time = fcn->creation_time;
+    c->corrected_initial_age = fcn->corrected_initial_age;
     c->node = fcn;

 failed:
@@ -1326,6 +1332,8 @@ ngx_http_file_cache_set_header(ngx_http_
     h->valid_msec = (u_short) c->valid_msec;
     h->header_start = (u_short) c->header_start;
     h->body_start = (u_short) c->body_start;
+    h->creation_time = c->creation_time;
+    h->corrected_initial_age = c->corrected_initial_age;

     if (c->etag.len <= NGX_HTTP_CACHE_ETAG_LEN) {
         h->etag_len = (u_char) c->etag.len;
@@ -1590,6 +1598,8 @@ ngx_http_file_cache_update_header(ngx_ht
     h.valid_msec = (u_short) c->valid_msec;
     h.header_start = (u_short) c->header_start;
     h.body_start = (u_short) c->body_start;
+    h.creation_time = c->creation_time;
+    h.corrected_initial_age = c->corrected_initial_age;

     if (c->etag.len <= NGX_HTTP_CACHE_ETAG_LEN) {
         h.etag_len = (u_char) c->etag.len;
diff -r ed8a5f82e23a -r 57facb7deaca src/http/ngx_http_header_filter_module.c
--- a/src/http/ngx_http_header_filter_module.c Wed Jun 26 20:36:03 2024 +0900
+++ b/src/http/ngx_http_header_filter_module.c Wed Jun 26 20:36:10 2024 +0900
@@ -322,6 +322,10 @@ ngx_http_header_filter(ngx_http_request_
         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_TIME_T_LEN + 2;
+    }
+
     c = r->connection;

     if (r->headers_out.location
@@ -518,6 +522,10 @@ ngx_http_header_filter(ngx_http_request_
         *b->last++ = CR; *b->last++ = LF;
     }

+    if (r->headers_out.age_n != -1) {
+        b->last = ngx_sprintf(b->last, "Age: %T" CRLF, r->headers_out.age_n);
+    }
+
     if (host.data) {

         p = b->last + sizeof("Location: ") - 1;
diff -r ed8a5f82e23a -r 57facb7deaca src/http/ngx_http_request.c
--- a/src/http/ngx_http_request.c Wed Jun 26 20:36:03 2024 +0900
+++ b/src/http/ngx_http_request.c Wed Jun 26 20:36:10 2024 +0900
@@ -646,6 +646,7 @@ ngx_http_alloc_request(ngx_connection_t
     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 ed8a5f82e23a -r 57facb7deaca src/http/ngx_http_request.h
--- a/src/http/ngx_http_request.h Wed Jun 26 20:36:03 2024 +0900
+++ b/src/http/ngx_http_request.h Wed Jun 26 20:36:10 2024 +0900
@@ -291,6 +291,7 @@ typedef struct {
     off_t                             content_offset;
     time_t                            date_time;
     time_t                            last_modified_time;
+    time_t                            age_n;
 } ngx_http_headers_out_t;


diff -r ed8a5f82e23a -r 57facb7deaca src/http/ngx_http_special_response.c
--- a/src/http/ngx_http_special_response.c Wed Jun 26 20:36:03 2024 +0900
+++ b/src/http/ngx_http_special_response.c Wed Jun 26 20:36:10 2024 +0900
@@ -581,6 +581,7 @@ ngx_http_clean_header(ngx_http_request_t

     r->headers_out.content_length_n = -1;
     r->headers_out.last_modified_time = -1;
+    r->headers_out.age_n = -1;
 }


diff -r ed8a5f82e23a -r 57facb7deaca src/http/ngx_http_upstream.c
--- a/src/http/ngx_http_upstream.c Wed Jun 26 20:36:03 2024 +0900
+++ b/src/http/ngx_http_upstream.c Wed Jun 26 20:36:10 2024 +0900
@@ -136,6 +136,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
@@ -323,6 +325,10 @@ static ngx_http_upstream_header_t  ngx_h
                  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 }
 };

@@ -507,6 +513,7 @@ ngx_http_upstream_create(ngx_http_reques

     u->headers_in.content_length_n = -1;
     u->headers_in.last_modified_time = -1;
+    u->headers_in.age_n = -1;

     return NGX_OK;
 }
@@ -961,6 +968,7 @@ ngx_http_upstream_cache(ngx_http_request
         c->updating_sec = 0;
         c->error_sec = 0;

+        u->headers_in.relative_freshness = 0;
         u->buffer.start = NULL;
         u->cache_status = NGX_HTTP_CACHE_EXPIRED;

@@ -1057,6 +1065,7 @@ ngx_http_upstream_cache_get(ngx_http_req
 static ngx_int_t
 ngx_http_upstream_cache_send(ngx_http_request_t *r, ngx_http_upstream_t *u)
 {
+    time_t             date_when_revalidated, age_when_revalidated;
     ngx_int_t          rc;
     ngx_http_cache_t  *c;

@@ -1068,6 +1077,9 @@ ngx_http_upstream_cache_send(ngx_http_re
         return ngx_http_cache_send(r);
     }

+    date_when_revalidated = u->headers_in.date_when_revalidated;
+    age_when_revalidated = u->headers_in.age_n;
+
     /* TODO: cache stack */

     u->buffer = *c->buf;
@@ -1076,6 +1088,7 @@ ngx_http_upstream_cache_send(ngx_http_re
     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))
@@ -1094,6 +1107,11 @@ ngx_http_upstream_cache_send(ngx_http_re
     rc = u->process_header(r);

     if (rc == NGX_OK) {
+        if (u->cache_status == NGX_HTTP_CACHE_REVALIDATED) {
+            /* use date and age from upstream when revalidated */
+            u->headers_in.date_when_revalidated = date_when_revalidated;
+            u->headers_in.age_n = age_when_revalidated;
+        }

         rc = ngx_http_upstream_process_headers(r, u);

@@ -1557,6 +1575,7 @@ ngx_http_upstream_connect(ngx_http_reque
     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;
@@ -2016,6 +2035,7 @@ ngx_http_upstream_reinit(ngx_http_reques
     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))
@@ -2648,6 +2668,10 @@ ngx_http_upstream_test_next(ngx_http_req
         }

         u->cache_status = NGX_HTTP_CACHE_REVALIDATED;
+        u->headers_in.date_when_revalidated = u->headers_in.date == NULL ? -1
+                                              : ngx_parse_http_time(
+
u->headers_in.date->value.data,
+
u->headers_in.date->value.len);
         rc = ngx_http_upstream_cache_send(r, u);

         if (rc == NGX_DONE) {
@@ -2670,7 +2694,12 @@ ngx_http_upstream_test_next(ngx_http_req
                 valid = ngx_http_file_cache_valid(u->conf->cache_valid,
                                                   u->headers_in.status_n);
                 if (valid) {
-                    valid = now + valid;
+                    time_t age = r->cache->corrected_initial_age;
+                    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                                   "adjust cache valid_sec:%T, "
+                                   "valid:%T, init_age:%d for 304",
+                                   now + valid - age, valid, age);
+                    valid = now + valid - age;
                 }
             }

@@ -2772,6 +2801,7 @@ ngx_http_upstream_intercept_errors(ngx_h
                                                           status);
                         if (valid) {
                             r->cache->valid_sec = ngx_time() + valid;
+                            u->headers_in.relative_freshness = 1;
                         }
                     }

@@ -2977,6 +3007,109 @@ ngx_http_upstream_process_headers(ngx_ht
     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;
+
+#if (NGX_HTTP_CACHE)
+    if (r->cache) {
+        ngx_http_cache_t  *c;
+
+        c = r->cache;
+
+        if (u->cache_status == NGX_HTTP_CACHE_HIT
+            || u->cache_status == NGX_HTTP_CACHE_STALE)
+        {
+            time_t  resident_time, current_age;
+
+            /*
+             * Update age response header.
+             * https://www.rfc-editor.org/rfc/rfc9111.html#name-calculating-age
+             *
+             * resident_time = now - response_time;
+             * current_age = corrected_initial_age + resident_time;
+             */
+            resident_time = ngx_time() - c->creation_time;
+            current_age = c->corrected_initial_age + resident_time;
+            r->headers_out.age_n = current_age ? current_age : -1;
+            ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http upstream process headers, cache_status:%d,"
+                           " created:%T, resident:%T, age:%T",
+                           u->cache_status, c->creation_time, resident_time,
+                           current_age);
+
+        } else if (u->cache_status == NGX_HTTP_CACHE_MISS
+                   || u->cache_status == NGX_HTTP_CACHE_REVALIDATED)
+        {
+            time_t  response_time, age_value, date, corrected_initial_age;
+
+            /*
+             * Update age response header.
+             * https://www.rfc-editor.org/rfc/rfc9111.html#name-calculating-age
+             *
+             * apparent_age = max(0, response_time - date_value);
+             *
+             * response_delay = response_time - request_time;
+             * corrected_age_value = age_value + response_delay;
+             *
+             * corrected_initial_age = max(apparent_age, corrected_age_value);
+             *
+             * However, if the upstream date is too old, then apparant_age
+             * becomes too big.
+             *
+             * Therefore, we use the following calculation:
+             *
+             * if upstream date is between request_time and response_time, we
+             * assume the upstream's clock is reasonably well synchronized and
+             * use
+             *
+             * corrected_initial_age = age_value + (response_time - date);
+             *
+             * if upstream date is not present or not between request_time
+             * and response_time, we do not rely on the upstream date and use
+             *
+             * corrected_initial_age = age_value
+             *                         + (response_time - u->request_time);
+             */
+            response_time = ngx_time();
+
+            age_value = u->headers_in.age_n != -1 ? u->headers_in.age_n : 0;
+
+            if (u->cache_status == NGX_HTTP_CACHE_REVALIDATED) {
+                date = u->headers_in.date_when_revalidated;
+            } else {
+                date = u->headers_in.date == NULL ? -1
+                       : ngx_parse_http_time(u->headers_in.date->value.data,
+                                             u->headers_in.date->value.len);
+            }
+
+            if (date >= u->request_time && date <= response_time) {
+                corrected_initial_age = age_value + (response_time - date);
+                ngx_log_debug2(NGX_LOG_DEBUG_HTTP, u->peer.connection->log, 0,
+                               "http upstream process headers,
cache_status:%d,"
+                               " age:%T, upstream date ok",
+                               u->cache_status, corrected_initial_age);
+            } else {
+                corrected_initial_age = age_value +
+                                        (response_time - u->request_time);
+                ngx_log_debug2(NGX_LOG_DEBUG_HTTP, u->peer.connection->log, 0,
+                               "http upstream process headers,
cache_status:%d,"
+                               " age:%T, upstream date not ok",
+                               u->cache_status, corrected_initial_age);
+            }
+
+            r->headers_out.age_n = corrected_initial_age
+                                   ? corrected_initial_age : -1;
+
+            c->creation_time = response_time;
+            c->corrected_initial_age = corrected_initial_age;
+            if (u->headers_in.relative_freshness) {
+                c->valid_sec -= corrected_initial_age;
+                ngx_log_debug1(NGX_LOG_DEBUG_HTTP, u->peer.connection->log, 0,
+                               "http upstream adjusted cache valid_sec:%T",
+                               c->valid_sec);
+            }
+        }
+    }
+#endif

     r->disable_not_modified = !u->cacheable;

@@ -3188,6 +3321,7 @@ ngx_http_upstream_send_response(ngx_http
                                               u->headers_in.status_n);
             if (valid) {
                 r->cache->valid_sec = now + valid;
+                u->headers_in.relative_freshness = 1;
             }
         }

@@ -4670,6 +4804,7 @@ ngx_http_upstream_finalize_request(ngx_h

                 if (valid) {
                     r->cache->valid_sec = ngx_time() + valid;
+                    u->headers_in.relative_freshness = 1;
                     r->cache->error = rc;
                 }
             }
@@ -4945,6 +5080,7 @@ ngx_http_upstream_process_cache_control(
         }

         r->cache->valid_sec = ngx_time() + n;
+        u->headers_in.relative_freshness = 1;
         u->headers_in.expired = 0;
     }

@@ -5047,7 +5183,18 @@ ngx_http_upstream_process_expires(ngx_ht
         return NGX_OK;
     }

-    r->cache->valid_sec = expires;
+    /*
+     * https://www.rfc-editor.org/rfc/rfc9111#name-expires
+     *
+     * If a response includes a Cache-Control header field with the max-age
+     * directive (Section 5.2.2.1), a recipient MUST ignore the Expires
+     * header field. Likewise, if a response includes the s-maxage directive
+     * (Section 5.2.2.10), a shared cache recipient MUST ignore the Expires
+     * header field.
+     */
+    if (!u->headers_in.relative_freshness) {
+        r->cache->valid_sec = expires;
+    }
     }
 #endif

@@ -5107,6 +5254,7 @@ ngx_http_upstream_process_accel_expires(

         default:
             r->cache->valid_sec = ngx_time() + n;
+            u->headers_in.relative_freshness = 1;
             u->headers_in.no_cache = 0;
             u->headers_in.expired = 0;
             return NGX_OK;
@@ -5119,7 +5267,18 @@ ngx_http_upstream_process_accel_expires(
     n = ngx_atoi(p, len);

     if (n != NGX_ERROR) {
-        r->cache->valid_sec = n;
+        /*
+        * https://www.rfc-editor.org/rfc/rfc9111#name-expires
+        *
+        * If a response includes a Cache-Control header field with the max-age
+        * directive (Section 5.2.2.1), a recipient MUST ignore the Expires
+        * header field. Likewise, if a response includes the s-maxage directive
+        * (Section 5.2.2.10), a shared cache recipient MUST ignore the Expires
+        * header field.
+        */
+        if (!u->headers_in.relative_freshness) {
+            r->cache->valid_sec = n;
+        }
         u->headers_in.no_cache = 0;
         u->headers_in.expired = 0;
     }
@@ -5373,6 +5532,38 @@ ngx_http_upstream_process_vary(ngx_http_


 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,
+                      "ignore duplicate age header from upstream: \"%V: %V\", "
+                      "previous value: \"%V: %V\"",
+                      &h->key, &h->value,
+                      &u->headers_in.age->key,
+                      &u->headers_in.age->value);
+        return NGX_OK;
+    }
+
+    h->next = NULL;
+    u->headers_in.age = h;
+    u->headers_in.age_n = ngx_atotm(h->value.data, h->value.len);
+
+    if (u->headers_in.age_n == NGX_ERROR) {
+        ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
+                      "ignore invalid \"Age\" header from upstream: "
+                      "\"%V: %V\"", &h->key, &h->value);
+    }
+
+    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 ed8a5f82e23a -r 57facb7deaca src/http/ngx_http_upstream.h
--- a/src/http/ngx_http_upstream.h Wed Jun 26 20:36:03 2024 +0900
+++ b/src/http/ngx_http_upstream.h Wed Jun 26 20:36:10 2024 +0900
@@ -287,14 +287,18 @@ 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;
+    time_t                           date_when_revalidated;

     unsigned                         connection_close:1;
     unsigned                         chunked:1;
     unsigned                         no_cache:1;
     unsigned                         expired:1;
+    unsigned                         relative_freshness:1;
 } ngx_http_upstream_headers_in_t;


@@ -369,6 +373,7 @@ struct ngx_http_upstream_s {
                                          ngx_table_elt_t *h);

     ngx_msec_t                       start_time;
+    time_t                           request_time;

     ngx_http_upstream_state_t       *state;

diff -r ed8a5f82e23a -r 57facb7deaca src/http/v2/ngx_http_v2.h
--- a/src/http/v2/ngx_http_v2.h Wed Jun 26 20:36:03 2024 +0900
+++ b/src/http/v2/ngx_http_v2.h Wed Jun 26 20:36:10 2024 +0900
@@ -398,6 +398,7 @@ ngx_int_t ngx_http_v2_table_size(ngx_htt
 #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 ed8a5f82e23a -r 57facb7deaca src/http/v2/ngx_http_v2_filter_module.c
--- a/src/http/v2/ngx_http_v2_filter_module.c Wed Jun 26 20:36:03 2024 +0900
+++ b/src/http/v2/ngx_http_v2_filter_module.c Wed Jun 26 20:36:10 2024 +0900
@@ -258,6 +258,10 @@ ngx_http_v2_header_filter(ngx_http_reque
         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_TIME_T_LEN) + NGX_TIME_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 @@ ngx_http_v2_header_filter(ngx_http_reque
         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: %T\"",
+                       r->headers_out.age_n);
+
+        *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_AGE_INDEX);
+
+        p = pos;
+        pos = ngx_sprintf(pos + 1, "%T", 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 ed8a5f82e23a -r 57facb7deaca src/http/v3/ngx_http_v3_filter_module.c
--- a/src/http/v3/ngx_http_v3_filter_module.c Wed Jun 26 20:36:03 2024 +0900
+++ b/src/http/v3/ngx_http_v3_filter_module.c Wed Jun 26 20:36:10 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 @@ ngx_http_v3_header_filter(ngx_http_reque
                                   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_TIME_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 @@ ngx_http_v3_header_filter(ngx_http_reque
                                               p, n);
     }

+    if (r->headers_out.age_n != -1) {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "http3 output header: \"age: %T\"",
+                       r->headers_out.age_n);
+
+        if (r->headers_out.age_n > 0) {
+            p = ngx_sprintf(b->last, "%T", 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, "%T", 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