details:   
https://github.com/nginx/nginx/commit/662c1dd2a97afd6c7ca09b8f5a74347ee017b86b
branches:  master
commit:    662c1dd2a97afd6c7ca09b8f5a74347ee017b86b
user:      Roman Arutyunyan <a...@nginx.com>
date:      Fri, 15 Nov 2024 08:23:53 +0400
description:
Upstream: early hints support.

The change implements processing upstream early hints response in
ngx_http_proxy_module and ngx_http_grpc_module.  A new directive
"early_hints" enables sending early hints to the client.  By default,
sending early hints is disabled.

Example:

    map $http_sec_fetch_mode $early_hints {
        navigate $http2$http3;
    }

    early_hints $early_hints;

    proxy_pass http://example.com;

---
 src/http/modules/ngx_http_grpc_module.c  |   8 +-
 src/http/modules/ngx_http_proxy_module.c |  27 ++++-
 src/http/ngx_http.c                      |   1 +
 src/http/ngx_http.h                      |   2 +
 src/http/ngx_http_core_module.c          |  41 +++++++
 src/http/ngx_http_core_module.h          |   2 +
 src/http/ngx_http_header_filter_module.c | 107 +++++++++++++++++
 src/http/ngx_http_request.h              |   1 +
 src/http/ngx_http_upstream.c             | 163 +++++++++++++++++++++++++
 src/http/ngx_http_upstream.h             |   2 +
 src/http/v2/ngx_http_v2.h                |   1 +
 src/http/v2/ngx_http_v2_filter_module.c  | 200 ++++++++++++++++++++++++++++++-
 src/http/v3/ngx_http_v3_filter_module.c  | 153 +++++++++++++++++++++++
 13 files changed, 698 insertions(+), 10 deletions(-)

diff --git a/src/http/modules/ngx_http_grpc_module.c 
b/src/http/modules/ngx_http_grpc_module.c
index 80046d6a4..d74b17708 100644
--- a/src/http/modules/ngx_http_grpc_module.c
+++ b/src/http/modules/ngx_http_grpc_module.c
@@ -1869,7 +1869,8 @@ ngx_http_grpc_process_header(ngx_http_request_t *r)
                         return NGX_HTTP_UPSTREAM_INVALID_HEADER;
                     }
 
-                    if (status < NGX_HTTP_OK) {
+                    if (status < NGX_HTTP_OK && status != NGX_HTTP_EARLY_HINTS)
+                    {
                         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                                       "upstream sent unexpected :status 
\"%V\"",
                                       status_line);
@@ -1902,6 +1903,10 @@ ngx_http_grpc_process_header(ngx_http_request_t *r)
                 h->lowcase_key = h->key.data;
                 h->hash = ngx_hash_key(h->key.data, h->key.len);
 
+                if (u->headers_in.status_n == NGX_HTTP_EARLY_HINTS) {
+                    continue;
+                }
+
                 hh = ngx_hash_find(&umcf->headers_in_hash, h->hash,
                                    h->lowcase_key, h->key.len);
 
@@ -4413,6 +4418,7 @@ ngx_http_grpc_create_loc_conf(ngx_conf_t *cf)
     conf->upstream.pass_request_body = 1;
     conf->upstream.force_ranges = 0;
     conf->upstream.pass_trailers = 1;
+    conf->upstream.pass_early_hints = 1;
     conf->upstream.preserve_output = 1;
 
     conf->headers_source = NGX_CONF_UNSET_PTR;
diff --git a/src/http/modules/ngx_http_proxy_module.c 
b/src/http/modules/ngx_http_proxy_module.c
index d4c5abf62..dbe998b13 100644
--- a/src/http/modules/ngx_http_proxy_module.c
+++ b/src/http/modules/ngx_http_proxy_module.c
@@ -1888,6 +1888,13 @@ ngx_http_proxy_process_status_line(ngx_http_request_t *r)
                    u->headers_in.status_n, &u->headers_in.status_line);
 
     if (ctx->status.http_version < NGX_HTTP_VERSION_11) {
+
+        if (ctx->status.code == NGX_HTTP_EARLY_HINTS) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "upstream sent HTTP/1.0 response with early hints");
+            return NGX_HTTP_UPSTREAM_INVALID_HEADER;
+        }
+
         u->headers_in.connection_close = 1;
     }
 
@@ -1949,6 +1956,14 @@ ngx_http_proxy_process_header(ngx_http_request_t *r)
                 ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
             }
 
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http proxy header: \"%V: %V\"",
+                           &h->key, &h->value);
+
+            if (r->upstream->headers_in.status_n == NGX_HTTP_EARLY_HINTS) {
+                continue;
+            }
+
             hh = ngx_hash_find(&umcf->headers_in_hash, h->hash,
                                h->lowcase_key, h->key.len);
 
@@ -1960,10 +1975,6 @@ ngx_http_proxy_process_header(ngx_http_request_t *r)
                 }
             }
 
-            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
-                           "http proxy header: \"%V: %V\"",
-                           &h->key, &h->value);
-
             continue;
         }
 
@@ -1974,6 +1985,10 @@ ngx_http_proxy_process_header(ngx_http_request_t *r)
             ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                            "http proxy header done");
 
+            if (r->upstream->headers_in.status_n == NGX_HTTP_EARLY_HINTS) {
+                return NGX_OK;
+            }
+
             /*
              * if no "Server" and "Date" in header line,
              * then add the special empty headers
@@ -3628,10 +3643,10 @@ ngx_http_proxy_create_loc_conf(ngx_conf_t *cf)
     conf->ssl_conf_commands = NGX_CONF_UNSET_PTR;
 #endif
 
-    /* "proxy_cyclic_temp_file" is disabled */
+    /* the hardcoded values */
     conf->upstream.cyclic_temp_file = 0;
-
     conf->upstream.change_buffering = 1;
+    conf->upstream.pass_early_hints = 1;
 
     conf->headers_source = NGX_CONF_UNSET_PTR;
 
diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c
index d835f896e..7f2b4225a 100644
--- a/src/http/ngx_http.c
+++ b/src/http/ngx_http.c
@@ -72,6 +72,7 @@ ngx_uint_t   ngx_http_max_module;
 
 
 ngx_http_output_header_filter_pt  ngx_http_top_header_filter;
+ngx_http_output_header_filter_pt  ngx_http_top_early_hints_filter;
 ngx_http_output_body_filter_pt    ngx_http_top_body_filter;
 ngx_http_request_body_filter_pt   ngx_http_top_request_body_filter;
 
diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h
index cb4a1e68a..7d98f5cd7 100644
--- a/src/http/ngx_http.h
+++ b/src/http/ngx_http.h
@@ -152,6 +152,7 @@ ngx_int_t 
ngx_http_read_client_request_body(ngx_http_request_t *r,
 ngx_int_t ngx_http_read_unbuffered_request_body(ngx_http_request_t *r);
 
 ngx_int_t ngx_http_send_header(ngx_http_request_t *r);
+ngx_int_t ngx_http_send_early_hints(ngx_http_request_t *r);
 ngx_int_t ngx_http_special_response_handler(ngx_http_request_t *r,
     ngx_int_t error);
 ngx_int_t ngx_http_filter_finalize_request(ngx_http_request_t *r,
@@ -191,6 +192,7 @@ extern ngx_str_t  ngx_http_html_default_types[];
 
 
 extern ngx_http_output_header_filter_pt  ngx_http_top_header_filter;
+extern ngx_http_output_header_filter_pt  ngx_http_top_early_hints_filter;
 extern ngx_http_output_body_filter_pt    ngx_http_top_body_filter;
 extern ngx_http_request_body_filter_pt   ngx_http_top_request_body_filter;
 
diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c
index 92c3eae8a..2d62674d9 100644
--- a/src/http/ngx_http_core_module.c
+++ b/src/http/ngx_http_core_module.c
@@ -670,6 +670,13 @@ static ngx_command_t  ngx_http_core_commands[] = {
       offsetof(ngx_http_core_loc_conf_t, etag),
       NULL },
 
+    { ngx_string("early_hints"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_set_predicate_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, early_hints),
+      NULL },
+
     { ngx_string("error_page"),
       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
                         |NGX_CONF_2MORE,
@@ -1857,6 +1864,37 @@ ngx_http_send_header(ngx_http_request_t *r)
 }
 
 
+ngx_int_t
+ngx_http_send_early_hints(ngx_http_request_t *r)
+{
+    ngx_int_t                  rc;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    if (r->post_action) {
+        return NGX_OK;
+    }
+
+    if (r->header_sent) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                      "header already sent");
+        return NGX_ERROR;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    rc = ngx_http_test_predicates(r, clcf->early_hints);
+
+    if (rc != NGX_DECLINED) {
+        return rc;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http send early hints \"%V?%V\"", &r->uri, &r->args);
+
+    return ngx_http_top_early_hints_filter(r);
+}
+
+
 ngx_int_t
 ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
 {
@@ -3637,6 +3675,7 @@ ngx_http_core_create_loc_conf(ngx_conf_t *cf)
     clcf->chunked_transfer_encoding = NGX_CONF_UNSET;
     clcf->etag = NGX_CONF_UNSET;
     clcf->server_tokens = NGX_CONF_UNSET_UINT;
+    clcf->early_hints = NGX_CONF_UNSET_PTR;
     clcf->types_hash_max_size = NGX_CONF_UNSET_UINT;
     clcf->types_hash_bucket_size = NGX_CONF_UNSET_UINT;
 
@@ -3917,6 +3956,8 @@ ngx_http_core_merge_loc_conf(ngx_conf_t *cf, void 
*parent, void *child)
     ngx_conf_merge_uint_value(conf->server_tokens, prev->server_tokens,
                               NGX_HTTP_SERVER_TOKENS_ON);
 
+    ngx_conf_merge_ptr_value(conf->early_hints, prev->early_hints, NULL);
+
     ngx_conf_merge_ptr_value(conf->open_file_cache,
                               prev->open_file_cache, NULL);
 
diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h
index e7e266bf8..a794144aa 100644
--- a/src/http/ngx_http_core_module.h
+++ b/src/http/ngx_http_core_module.h
@@ -430,6 +430,8 @@ struct ngx_http_core_loc_conf_s {
     ngx_http_complex_value_t  *disable_symlinks_from;
 #endif
 
+    ngx_array_t  *early_hints;             /* early_hints */
+
     ngx_array_t  *error_pages;             /* error_page */
 
     ngx_path_t   *client_body_temp_path;   /* client_body_temp_path */
diff --git a/src/http/ngx_http_header_filter_module.c 
b/src/http/ngx_http_header_filter_module.c
index 76f6e9629..789938329 100644
--- a/src/http/ngx_http_header_filter_module.c
+++ b/src/http/ngx_http_header_filter_module.c
@@ -13,6 +13,7 @@
 
 static ngx_int_t ngx_http_header_filter_init(ngx_conf_t *cf);
 static ngx_int_t ngx_http_header_filter(ngx_http_request_t *r);
+static ngx_int_t ngx_http_early_hints_filter(ngx_http_request_t *r);
 
 
 static ngx_http_module_t  ngx_http_header_filter_module_ctx = {
@@ -50,6 +51,9 @@ static u_char ngx_http_server_string[] = "Server: nginx" CRLF;
 static u_char ngx_http_server_full_string[] = "Server: " NGINX_VER CRLF;
 static u_char ngx_http_server_build_string[] = "Server: " NGINX_VER_BUILD CRLF;
 
+static ngx_str_t ngx_http_early_hints_status_line =
+    ngx_string("HTTP/1.1 103 Early Hints" CRLF);
+
 
 static ngx_str_t ngx_http_status_lines[] = {
 
@@ -625,10 +629,113 @@ ngx_http_header_filter(ngx_http_request_t *r)
 }
 
 
+static ngx_int_t
+ngx_http_early_hints_filter(ngx_http_request_t *r)
+{
+    size_t            len;
+    ngx_buf_t        *b;
+    ngx_uint_t        i;
+    ngx_chain_t       out;
+    ngx_list_part_t  *part;
+    ngx_table_elt_t  *header;
+
+    if (r != r->main) {
+        return NGX_OK;
+    }
+
+    if (r->http_version < NGX_HTTP_VERSION_11) {
+        return NGX_OK;
+    }
+
+    len = 0;
+
+    part = &r->headers_out.headers.part;
+    header = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (header[i].hash == 0) {
+            continue;
+        }
+
+        len += header[i].key.len + sizeof(": ") - 1 + header[i].value.len
+               + sizeof(CRLF) - 1;
+    }
+
+    if (len == 0) {
+        return NGX_OK;
+    }
+
+    len += ngx_http_early_hints_status_line.len
+           /* the end of the early hints */
+           + sizeof(CRLF) - 1;
+
+    b = ngx_create_temp_buf(r->pool, len);
+    if (b == NULL) {
+        return NGX_ERROR;
+    }
+
+    b->last = ngx_copy(b->last, ngx_http_early_hints_status_line.data,
+                       ngx_http_early_hints_status_line.len);
+
+    part = &r->headers_out.headers.part;
+    header = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (header[i].hash == 0) {
+            continue;
+        }
+
+        b->last = ngx_copy(b->last, header[i].key.data, header[i].key.len);
+        *b->last++ = ':'; *b->last++ = ' ';
+
+        b->last = ngx_copy(b->last, header[i].value.data, header[i].value.len);
+        *b->last++ = CR; *b->last++ = LF;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "%*s", (size_t) (b->last - b->pos), b->pos);
+
+    /* the end of HTTP early hints */
+    *b->last++ = CR; *b->last++ = LF;
+
+    r->header_size = b->last - b->pos;
+
+    b->flush = 1;
+
+    out.buf = b;
+    out.next = NULL;
+
+    return ngx_http_write_filter(r, &out);
+}
+
+
 static ngx_int_t
 ngx_http_header_filter_init(ngx_conf_t *cf)
 {
     ngx_http_top_header_filter = ngx_http_header_filter;
+    ngx_http_top_early_hints_filter = ngx_http_early_hints_filter;
 
     return NGX_OK;
 }
diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h
index 9407f46ae..ad11f147f 100644
--- a/src/http/ngx_http_request.h
+++ b/src/http/ngx_http_request.h
@@ -74,6 +74,7 @@
 #define NGX_HTTP_CONTINUE                  100
 #define NGX_HTTP_SWITCHING_PROTOCOLS       101
 #define NGX_HTTP_PROCESSING                102
+#define NGX_HTTP_EARLY_HINTS               103
 
 #define NGX_HTTP_OK                        200
 #define NGX_HTTP_CREATED                   201
diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c
index d4cf1b7fe..d1bcdbbe0 100644
--- a/src/http/ngx_http_upstream.c
+++ b/src/http/ngx_http_upstream.c
@@ -48,6 +48,9 @@ static void 
ngx_http_upstream_send_request_handler(ngx_http_request_t *r,
 static void ngx_http_upstream_read_request_handler(ngx_http_request_t *r);
 static void ngx_http_upstream_process_header(ngx_http_request_t *r,
     ngx_http_upstream_t *u);
+static ngx_int_t ngx_http_upstream_process_early_hints(ngx_http_request_t *r,
+    ngx_http_upstream_t *u);
+static void ngx_http_upstream_early_hints_writer(ngx_http_request_t *r);
 static ngx_int_t ngx_http_upstream_test_next(ngx_http_request_t *r,
     ngx_http_upstream_t *u);
 static ngx_int_t ngx_http_upstream_intercept_errors(ngx_http_request_t *r,
@@ -2530,6 +2533,20 @@ ngx_http_upstream_process_header(ngx_http_request_t *r, 
ngx_http_upstream_t *u)
             continue;
         }
 
+        if (rc == NGX_OK
+            && u->headers_in.status_n == NGX_HTTP_EARLY_HINTS)
+        {
+            rc = ngx_http_upstream_process_early_hints(r, u);
+
+            if (rc == NGX_OK) {
+                rc = u->process_header(r);
+
+                if (rc == NGX_AGAIN) {
+                    continue;
+                }
+            }
+        }
+
         break;
     }
 
@@ -2567,6 +2584,152 @@ ngx_http_upstream_process_header(ngx_http_request_t *r, 
ngx_http_upstream_t *u)
 }
 
 
+static ngx_int_t
+ngx_http_upstream_process_early_hints(ngx_http_request_t *r,
+    ngx_http_upstream_t *u)
+{
+    u_char            *p;
+    ngx_uint_t         i;
+    ngx_list_part_t   *part;
+    ngx_table_elt_t   *h, *ho;
+    ngx_connection_t  *c;
+
+    c = r->connection;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream early hints");
+
+    if (u->conf->pass_early_hints) {
+
+        u->early_hints_length += u->buffer.pos - u->buffer.start;
+
+        if (u->early_hints_length <= (off_t) u->conf->buffer_size) {
+
+            part = &u->headers_in.headers.part;
+            h = part->elts;
+
+            for (i = 0; /* void */; i++) {
+
+                if (i >= part->nelts) {
+                    if (part->next == NULL) {
+                        break;
+                    }
+
+                    part = part->next;
+                    h = part->elts;
+                    i = 0;
+                }
+
+                if (ngx_hash_find(&u->conf->hide_headers_hash, h[i].hash,
+                                  h[i].lowcase_key, h[i].key.len))
+                {
+                    continue;
+                }
+
+                ho = ngx_list_push(&r->headers_out.headers);
+                if (ho == NULL) {
+                    return NGX_ERROR;
+                }
+
+                *ho = h[i];
+            }
+
+            if (ngx_http_send_early_hints(r) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            if (c->buffered) {
+                if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+                    return NGX_ERROR;
+                }
+
+                r->write_event_handler = ngx_http_upstream_early_hints_writer;
+            }
+
+        } else {
+            ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                          "upstream sent too big early hints");
+        }
+    }
+
+    if (u->reinit_request(r) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    ngx_http_clean_header(r);
+
+    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;
+
+    if (ngx_list_init(&u->headers_in.headers, r->pool, 8,
+                      sizeof(ngx_table_elt_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (ngx_list_init(&u->headers_in.trailers, r->pool, 2,
+                      sizeof(ngx_table_elt_t))
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    p = u->buffer.pos;
+
+    u->buffer.pos = u->buffer.start;
+
+#if (NGX_HTTP_CACHE)
+
+    if (r->cache) {
+        u->buffer.pos += r->cache->header_start;
+    }
+
+#endif
+
+    u->buffer.last = ngx_movemem(u->buffer.pos, p, u->buffer.last - p);
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_upstream_early_hints_writer(ngx_http_request_t *r)
+{
+    ngx_connection_t     *c;
+    ngx_http_upstream_t  *u;
+
+    c = r->connection;
+    u = r->upstream;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http upstream early hints writer");
+
+    c->log->action = "sending early hints to client";
+
+    if (ngx_http_write_filter(r, NULL) == NGX_ERROR) {
+        ngx_http_upstream_finalize_request(r, u,
+                                           NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    if (!c->buffered) {
+        if (!u->store && !r->post_action && !u->conf->ignore_client_abort) {
+            r->write_event_handler =
+                                  ngx_http_upstream_wr_check_broken_connection;
+
+        } else {
+            r->write_event_handler = ngx_http_request_empty_handler;
+        }
+    }
+
+    if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+        ngx_http_upstream_finalize_request(r, u,
+                                           NGX_HTTP_INTERNAL_SERVER_ERROR);
+    }
+}
+
+
 static ngx_int_t
 ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u)
 {
diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h
index e0a903669..7a47675eb 100644
--- a/src/http/ngx_http_upstream.h
+++ b/src/http/ngx_http_upstream.h
@@ -185,6 +185,7 @@ typedef struct {
     ngx_flag_t                       pass_request_headers;
     ngx_flag_t                       pass_request_body;
     ngx_flag_t                       pass_trailers;
+    ngx_flag_t                       pass_early_hints;
 
     ngx_flag_t                       ignore_client_abort;
     ngx_flag_t                       intercept_errors;
@@ -354,6 +355,7 @@ struct ngx_http_upstream_s {
 
     ngx_buf_t                        buffer;
     off_t                            length;
+    off_t                            early_hints_length;
 
     ngx_chain_t                     *out_bufs;
     ngx_chain_t                     *busy_bufs;
diff --git a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h
index 6751b3026..9605c8a23 100644
--- a/src/http/v2/ngx_http_v2.h
+++ b/src/http/v2/ngx_http_v2.h
@@ -213,6 +213,7 @@ struct ngx_http_v2_stream_s {
 
     ngx_pool_t                      *pool;
 
+    unsigned                         initialized:1;
     unsigned                         waiting:1;
     unsigned                         blocked:1;
     unsigned                         exhausted:1;
diff --git a/src/http/v2/ngx_http_v2_filter_module.c 
b/src/http/v2/ngx_http_v2_filter_module.c
index 556446ed2..907906a88 100644
--- a/src/http/v2/ngx_http_v2_filter_module.c
+++ b/src/http/v2/ngx_http_v2_filter_module.c
@@ -28,6 +28,8 @@
 
 
 static ngx_int_t ngx_http_v2_header_filter(ngx_http_request_t *r);
+static ngx_int_t ngx_http_v2_early_hints_filter(ngx_http_request_t *r);
+static ngx_int_t ngx_http_v2_init_stream(ngx_http_request_t *r);
 
 static ngx_http_v2_out_frame_t *ngx_http_v2_create_headers_frame(
     ngx_http_request_t *r, u_char *pos, u_char *end, ngx_uint_t fin);
@@ -97,6 +99,7 @@ ngx_module_t  ngx_http_v2_filter_module = {
 
 
 static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+static ngx_http_output_header_filter_pt  ngx_http_next_early_hints_filter;
 
 
 static ngx_int_t
@@ -109,7 +112,6 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
     ngx_list_part_t           *part;
     ngx_table_elt_t           *header;
     ngx_connection_t          *fc;
-    ngx_http_cleanup_t        *cln;
     ngx_http_v2_stream_t      *stream;
     ngx_http_v2_out_frame_t   *frame;
     ngx_http_v2_connection_t  *h2c;
@@ -614,7 +616,196 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
 
     ngx_http_v2_queue_blocked_frame(h2c, frame);
 
-    stream->queued = 1;
+    stream->queued++;
+
+    if (ngx_http_v2_init_stream(r) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    return ngx_http_v2_filter_send(fc, stream);
+}
+
+
+static ngx_int_t
+ngx_http_v2_early_hints_filter(ngx_http_request_t *r)
+{
+    u_char                    *pos, *start, *tmp;
+    size_t                     len, tmp_len;
+    ngx_uint_t                 i;
+    ngx_list_part_t           *part;
+    ngx_table_elt_t           *header;
+    ngx_connection_t          *fc;
+    ngx_http_v2_stream_t      *stream;
+    ngx_http_v2_out_frame_t   *frame;
+    ngx_http_v2_connection_t  *h2c;
+
+    stream = r->stream;
+
+    if (!stream) {
+        return ngx_http_next_early_hints_filter(r);
+    }
+
+    if (r != r->main) {
+        return NGX_OK;
+    }
+
+    fc = r->connection;
+
+    if (fc->error) {
+        return NGX_ERROR;
+    }
+
+    len = 0;
+    tmp_len = 0;
+
+    part = &r->headers_out.headers.part;
+    header = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (header[i].hash == 0) {
+            continue;
+        }
+
+        if (header[i].key.len > NGX_HTTP_V2_MAX_FIELD) {
+            ngx_log_error(NGX_LOG_CRIT, fc->log, 0,
+                          "too long response header name: \"%V\"",
+                          &header[i].key);
+            return NGX_ERROR;
+        }
+
+        if (header[i].value.len > NGX_HTTP_V2_MAX_FIELD) {
+            ngx_log_error(NGX_LOG_CRIT, fc->log, 0,
+                          "too long response header value: \"%V: %V\"",
+                          &header[i].key, &header[i].value);
+            return NGX_ERROR;
+        }
+
+        len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len
+                 + NGX_HTTP_V2_INT_OCTETS + header[i].value.len;
+
+        if (header[i].key.len > tmp_len) {
+            tmp_len = header[i].key.len;
+        }
+
+        if (header[i].value.len > tmp_len) {
+            tmp_len = header[i].value.len;
+        }
+    }
+
+    if (len == 0) {
+        return NGX_OK;
+    }
+
+    h2c = stream->connection;
+
+    len += h2c->table_update ? 1 : 0;
+    len += 1 + ngx_http_v2_literal_size("418");
+
+    tmp = ngx_palloc(r->pool, tmp_len);
+    pos = ngx_pnalloc(r->pool, len);
+
+    if (pos == NULL || tmp == NULL) {
+        return NGX_ERROR;
+    }
+
+    start = pos;
+
+    if (h2c->table_update) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                       "http2 table size update: 0");
+        *pos++ = (1 << 5) | 0;
+        h2c->table_update = 0;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                   "http2 output header: \":status: %03ui\"",
+                   (ngx_uint_t) NGX_HTTP_EARLY_HINTS);
+
+    *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_STATUS_INDEX);
+    *pos++ = NGX_HTTP_V2_ENCODE_RAW | 3;
+    pos = ngx_sprintf(pos, "%03ui", (ngx_uint_t) NGX_HTTP_EARLY_HINTS);
+
+    part = &r->headers_out.headers.part;
+    header = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (header[i].hash == 0) {
+            continue;
+        }
+
+#if (NGX_DEBUG)
+        if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) {
+            ngx_strlow(tmp, header[i].key.data, header[i].key.len);
+
+            ngx_log_debug3(NGX_LOG_DEBUG_HTTP, fc->log, 0,
+                           "http2 output header: \"%*s: %V\"",
+                           header[i].key.len, tmp, &header[i].value);
+        }
+#endif
+
+        *pos++ = 0;
+
+        pos = ngx_http_v2_write_name(pos, header[i].key.data,
+                                     header[i].key.len, tmp);
+
+        pos = ngx_http_v2_write_value(pos, header[i].value.data,
+                                      header[i].value.len, tmp);
+    }
+
+    frame = ngx_http_v2_create_headers_frame(r, start, pos, 0);
+    if (frame == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_http_v2_queue_blocked_frame(h2c, frame);
+
+    stream->queued++;
+
+    if (ngx_http_v2_init_stream(r) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    return ngx_http_v2_filter_send(fc, stream);
+}
+
+
+static ngx_int_t
+ngx_http_v2_init_stream(ngx_http_request_t *r)
+{
+    ngx_connection_t      *fc;
+    ngx_http_cleanup_t    *cln;
+    ngx_http_v2_stream_t  *stream;
+
+    stream = r->stream;
+    fc = r->connection;
+
+    if (stream->initialized) {
+        return NGX_OK;
+    }
+
+    stream->initialized = 1;
 
     cln = ngx_http_cleanup_add(r, 0);
     if (cln == NULL) {
@@ -628,7 +819,7 @@ ngx_http_v2_header_filter(ngx_http_request_t *r)
     fc->need_last_buf = 1;
     fc->need_flush_buf = 1;
 
-    return ngx_http_v2_filter_send(fc, stream);
+    return NGX_OK;
 }
 
 
@@ -1569,5 +1760,8 @@ ngx_http_v2_filter_init(ngx_conf_t *cf)
     ngx_http_next_header_filter = ngx_http_top_header_filter;
     ngx_http_top_header_filter = ngx_http_v2_header_filter;
 
+    ngx_http_next_early_hints_filter = ngx_http_top_early_hints_filter;
+    ngx_http_top_early_hints_filter = ngx_http_v2_early_hints_filter;
+
     return NGX_OK;
 }
diff --git a/src/http/v3/ngx_http_v3_filter_module.c 
b/src/http/v3/ngx_http_v3_filter_module.c
index 4d2276dc0..64e6b8e8d 100644
--- a/src/http/v3/ngx_http_v3_filter_module.c
+++ b/src/http/v3/ngx_http_v3_filter_module.c
@@ -36,6 +36,7 @@ typedef struct {
 
 
 static ngx_int_t ngx_http_v3_header_filter(ngx_http_request_t *r);
+static ngx_int_t ngx_http_v3_early_hints_filter(ngx_http_request_t *r);
 static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r,
     ngx_chain_t *in);
 static ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r,
@@ -75,6 +76,7 @@ ngx_module_t  ngx_http_v3_filter_module = {
 
 
 static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+static ngx_http_output_header_filter_pt  ngx_http_next_early_hints_filter;
 static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
 
 
@@ -588,6 +590,154 @@ ngx_http_v3_header_filter(ngx_http_request_t *r)
 }
 
 
+static ngx_int_t
+ngx_http_v3_early_hints_filter(ngx_http_request_t *r)
+{
+    size_t                  len, n;
+    ngx_buf_t              *b;
+    ngx_uint_t              i;
+    ngx_chain_t            *out, *hl, *cl;
+    ngx_list_part_t        *part;
+    ngx_table_elt_t        *header;
+    ngx_http_v3_session_t  *h3c;
+
+    if (r->http_version != NGX_HTTP_VERSION_30) {
+        return ngx_http_next_early_hints_filter(r);
+    }
+
+    if (r != r->main) {
+        return NGX_OK;
+    }
+
+    len = 0;
+
+    part = &r->headers_out.headers.part;
+    header = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (header[i].hash == 0) {
+            continue;
+        }
+
+        len += ngx_http_v3_encode_field_l(NULL, &header[i].key,
+                                          &header[i].value);
+    }
+
+    if (len == 0) {
+        return NGX_OK;
+    }
+
+    len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0);
+
+    len += ngx_http_v3_encode_field_lri(NULL, 0,
+                                        NGX_HTTP_V3_HEADER_STATUS_200,
+                                        NULL, 3);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http3 header len:%uz", len);
+
+    b = ngx_create_temp_buf(r->pool, len);
+    if (b == NULL) {
+        return NGX_ERROR;
+    }
+
+    b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last,
+                                                                 0, 0, 0);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http3 output header: \":status: %03ui\"",
+                   (ngx_uint_t) NGX_HTTP_EARLY_HINTS);
+
+    b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
+                                             NGX_HTTP_V3_HEADER_STATUS_200,
+                                             NULL, 3);
+    b->last = ngx_sprintf(b->last, "%03ui", (ngx_uint_t) NGX_HTTP_EARLY_HINTS);
+
+    part = &r->headers_out.headers.part;
+    header = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (header[i].hash == 0) {
+            continue;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http3 output header: \"%V: %V\"",
+                       &header[i].key, &header[i].value);
+
+        b->last = (u_char *) ngx_http_v3_encode_field_l(b->last,
+                                                        &header[i].key,
+                                                        &header[i].value);
+    }
+
+    b->flush = 1;
+
+    cl = ngx_alloc_chain_link(r->pool);
+    if (cl == NULL) {
+        return NGX_ERROR;
+    }
+
+    cl->buf = b;
+    cl->next = NULL;
+
+    n = b->last - b->pos;
+
+    h3c = ngx_http_v3_get_session(r->connection);
+    h3c->payload_bytes += n;
+
+    len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_HEADERS)
+          + ngx_http_v3_encode_varlen_int(NULL, n);
+
+    b = ngx_create_temp_buf(r->pool, len);
+    if (b == NULL) {
+        return NGX_ERROR;
+    }
+
+    b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last,
+                                                    NGX_HTTP_V3_FRAME_HEADERS);
+    b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n);
+
+    hl = ngx_alloc_chain_link(r->pool);
+    if (hl == NULL) {
+        return NGX_ERROR;
+    }
+
+    hl->buf = b;
+    hl->next = cl;
+
+    out = hl;
+
+    for (cl = out; cl; cl = cl->next) {
+        h3c->total_bytes += cl->buf->last - cl->buf->pos;
+        r->header_size += cl->buf->last - cl->buf->pos;
+    }
+
+    return ngx_http_write_filter(r, out);
+}
+
+
 static ngx_int_t
 ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
 {
@@ -845,6 +995,9 @@ ngx_http_v3_filter_init(ngx_conf_t *cf)
     ngx_http_next_header_filter = ngx_http_top_header_filter;
     ngx_http_top_header_filter = ngx_http_v3_header_filter;
 
+    ngx_http_next_early_hints_filter = ngx_http_top_early_hints_filter;
+    ngx_http_top_early_hints_filter = ngx_http_v3_early_hints_filter;
+
     ngx_http_next_body_filter = ngx_http_top_body_filter;
     ngx_http_top_body_filter = ngx_http_v3_body_filter;
 
_______________________________________________
nginx-devel mailing list
nginx-devel@nginx.org
https://mailman.nginx.org/mailman/listinfo/nginx-devel

Reply via email to