Skip to content

Conversation

@hongzhidao
Copy link
Contributor

@hongzhidao hongzhidao commented Jul 7, 2025

Supported:

  • proxy http2
  • request buffering and proxying buffering
  • proxy cache

tests: based on grpc.t

  • http2 protocol tests passed
  • proxy buffering tests passed
  • request buffering tests passed with new implementation
  • long header/field tests passed
  • trailers handling passed
  • SETTINGS tests passed
  • authority tests passed
  • upstream keepalive tests passed
  • non-buffering with upstream keepalive passed
  • ssl tests passed
  • better coverage tests for this feature but only replicate all the tests from grpc.t
  • cache tests

@hongzhidao hongzhidao marked this pull request as draft July 7, 2025 03:10
@hongzhidao hongzhidao changed the title Http2 upstream HTTP/2 to upstream Jul 7, 2025
@Maryna-f5 Maryna-f5 added this to the nginx-1.29.2 milestone Jul 7, 2025
@hongzhidao hongzhidao force-pushed the http2-upstream branch 3 times, most recently from 5dedbca to cc47b74 Compare July 8, 2025 02:53
@hongzhidao
Copy link
Contributor Author

Hi @arut,
I left some comments on the first commit, please check them.

@hongzhidao hongzhidao force-pushed the http2-upstream branch 2 times, most recently from 66338f9 to 63a769c Compare July 8, 2025 07:14
@arut
Copy link
Contributor

arut commented Jul 8, 2025

We must avoid sending the Connection header for HTTP/2, as well as for HTTP/3. This is a strictly HTTP/1 header and it makes the request malformed. That's why I added a variable $proxy_internal_connection for this header which evaluates depending on a new context field connection_type and it can be 0. This field is set by the protocol handler. This should probably be a part of the first patch.

@hongzhidao hongzhidao force-pushed the http2-upstream branch 2 times, most recently from 8e79530 to a1c2cd9 Compare July 8, 2025 22:30
Copy link
Member

@ac000 ac000 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @hongzhidao just some minor things I noticed...

    HTTP/2: ngx_http_v2_proxy_module.
    The module allows to use HTTP/2 protocol for proxying.
    HTTP/2 proxying is enabled by specifying "proxy_http_version 2.0".

    ...

Blank line between the subject and body...

...
diff --git a/src/http/v2/ngx_http_v2_proxy_module.c b/src/http/v2/ngx_http_v2_proxy_module.c
new file mode 100644
index 0000000000..a69ffcc406
--- /dev/null
+++ b/src/http/v2/ngx_http_v2_proxy_module.c

...

+ngx_int_t
+ngx_http_v2_proxy_handler(ngx_http_request_t *r)
+{

...

+#if (NGX_HTTP_SSL)
+        u->ssl = plcf->ssl;
+
+        if (u->ssl) {
+            ngx_str_set(&u->schema, "https://");
+
+        } else {
+            ngx_str_set(&u->schema, "https://");
+        }
+#else
+        ngx_str_set(&u->schema, "https://");
+#endif

Maybe I'm not understanding what the checks for 'ssl' here are for but in
the non-ssl cases shouldn't the schema be 'http://' ? (Otherwise all the
checks seem redundant as we set 'https://' regardless...)

...

+static ngx_int_t
+ngx_http_v2_proxy_body_output_filter(void *data, ngx_chain_t *in)
+{

...

+        /*
+         * We have already got the response and were sending some additional

s/were/we're/

+         * control frames.  Even if there is still something unsent, stop
+         * here anyway.
+         */

...

ngx_http_v2_proxy_parse_frame(ngx_http_request_t *r,
+    ngx_http_v2_proxy_ctx_t *ctx, ngx_buf_t *b)
+{

...

+        /* suppress warning */
+        case ngx_http_v2_proxy_st_payload:
+        case ngx_http_v2_proxy_st_padding:
+            break;

You could just use 'default:' here. Of course you won't get warned if you add
more enum values and don't handle them, but maybe you aren't planning on
adding any more or it's not a concern...

+        }

...

+static ngx_int_t
+ngx_http_v2_proxy_parse_header(ngx_http_request_t *r,
+    ngx_http_v2_proxy_ctx_t *ctx, ngx_buf_t *b)
+{

...

+    if (state < sw_fragment) {
+
+        if (b->last - b->pos < (ssize_t) ctx->rest) {

Or even (ptrdiff_t) to be more explicit about _why_ we're casting...

+            last = b->last;

...

+    if (state == sw_padding) {
+
+        if (b->last - b->pos < (ssize_t) ctx->rest) {

... and here ... and likely a few other places...

+            ctx->rest -= b->last - b->pos;

...

@hongzhidao
Copy link
Contributor Author

Hi @ac000,
Thanks for the catching.
I borrowed most of the code from ngx_http_grpc_module to make it work.

  1. About the scheme setting.
+#if (NGX_HTTP_SSL)
+        u->ssl = plcf->ssl;
+
+        if (u->ssl) {
+            ngx_str_set(&u->schema, "https://");
+
+        } else {
+            ngx_str_set(&u->schema, "https://");
+        }
+#else
+        ngx_str_set(&u->schema, "https://");
+#endif

I feel it should be touched again, I'm looking into @arut implementation about the proxy http3.

  1. I'd like to discuss the others based on ngx_http_grpc_module. And we can keep them consistent.
+static ngx_int_t
+ngx_http_v2_proxy_body_output_filter(void *data, ngx_chain_t *in)
+{

...

+        /*
+         * We have already got the response and were sending some additional

s/were/we're/

+         * control frames.  Even if there is still something unsent, stop
+         * here anyway.
+         */

@arut
Copy link
Contributor

arut commented Jul 9, 2025

I feel it should be touched again, I'm looking into @arut implementation about the proxy http3.

ngx_http_proxy_module already has code for this, namely the ngx_http_proxy_eval() function. You just need to call it, see the v3 proxy. Both http and https schemas should be supported in HTTP/2. HTTP/3 only supports https.

@hongzhidao hongzhidao force-pushed the http2-upstream branch 2 times, most recently from c32eeeb to 731645c Compare July 15, 2025 11:18
@hongzhidao
Copy link
Contributor Author

ngx_http_proxy_module already has code for this, namely the ngx_http_proxy_eval() function. You just need to call it, see the v3 proxy. Both http and https schemas should be supported in HTTP/2. HTTP/3 only supports https.

Hi @arut, take a look plz, did you mean something like this?

diff --git a/src/http/v2/ngx_http_v2_proxy_module.c b/src/http/v2/ngx_http_v2_proxy_module.c
index 6a4a54a..cc3b4c2 100644
--- a/src/http/v2/ngx_http_v2_proxy_module.c
+++ b/src/http/v2/ngx_http_v2_proxy_module.c
@@ -232,18 +232,9 @@ ngx_http_v2_proxy_handler(ngx_http_request_t *r)

     if (plcf->proxy_lengths == NULL) {
         ctx->host = plcf->host;
-
+        u->schema = plcf->vars.schema;
 #if (NGX_HTTP_SSL)
         u->ssl = plcf->ssl;
-
-        if (u->ssl) {
-            ngx_str_set(&u->schema, "https://");
-
-        } else {
-            ngx_str_set(&u->schema, "https://");
-        }
-#else
-        ngx_str_set(&u->schema, "https://");
 #endif

     } else {
@@ -252,6 +243,10 @@ ngx_http_v2_proxy_handler(ngx_http_r
equest_t *r)
         }
     }

+    if (u->ssl) {
+        ngx_str_set(&u->ssl_alpn_protocol, NGX_HTTP_V2_A
LPN_PROTO);
+    }
+
     u->output.tag = (ngx_buf_tag_t) &ngx_http_v2_proxy_m
odule;

     u->conf = &plcf->upstream;
@@ -266,8 +261,6 @@ ngx_http_v2_proxy_handler(ngx_http_re
quest_t *r)
     u->input_filter = ngx_http_v2_proxy_filter;
     u->input_filter_ctx = ctx;

-    ngx_str_set(&u->ssl_alpn_protocol, NGX_HTTP_V2_ALPN_
PROTO);
-
     r->request_body_no_buffering = 1;

     rc = ngx_http_read_client_request_body(r, ngx_http_u
pstream_init);

@hongzhidao
Copy link
Contributor Author

We must avoid sending the Connection header for HTTP/2, as well as for HTTP/3. This is a strictly HTTP/1 header and it makes the request malformed. That's why I added a variable $proxy_internal_connection for this header which evaluates depending on a new context field connection_type and it can be 0. This field is set by the protocol handler. This should probably be a part of the first patch.

Good to learn, I added it into the proxy refactoring patch.

@hongzhidao hongzhidao force-pushed the http2-upstream branch 3 times, most recently from bb8bd55 to d45a64d Compare November 26, 2025 02:50
@arut
Copy link
Contributor

arut commented Nov 26, 2025

@hongzhidao HTTP/2 is not legacy, the flag should be 1 for HTTP/1.x.

@hongzhidao hongzhidao force-pushed the http2-upstream branch 3 times, most recently from a3f046f to 365730b Compare November 30, 2025 16:40
This commit is prepared for HTTP/2 and HTTP/3 support.

The ALPN protocol is now set per-connection in
ngx_http_upstream_ssl_init_connection(), allowing proper protocol negotiation
for each individual upstream connection regardless of SSL context sharing.
@hongzhidao hongzhidao force-pushed the http2-upstream branch 2 times, most recently from 74f9632 to 92ec8a0 Compare December 5, 2025 02:45
@hongzhidao hongzhidao force-pushed the http2-upstream branch 2 times, most recently from 036a5c2 to e658bca Compare December 5, 2025 02:59
The module allows to use HTTP/2 protocol for proxying.
HTTP/2 proxying is enabled by specifying "proxy_http_version 2".

Example:

    server {
        listen 8000;

        location / {
            proxy_http_version 2;
            proxy_pass https://127.0.0.1:8443;
        }
    }

    server {
        listen 8443 ssl;
        http2 on;

        ssl_certificate certs/example.com.crt;
        ssl_certificate_key certs/example.com.key;

        location / {
            return 200 foo;
        }
    }
Copy link
Contributor

@pluknet pluknet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good for me.

@arut arut merged commit 61690b5 into nginx:master Dec 8, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants