]> git.kaiwu.me - nginx.git/commitdiff
Merged with the default branch.
authorSergey Kandaurov <pluknet@nginx.com>
Wed, 22 Jun 2022 14:34:58 +0000 (18:34 +0400)
committerSergey Kandaurov <pluknet@nginx.com>
Wed, 22 Jun 2022 14:34:58 +0000 (18:34 +0400)
1  2 
README
src/http/ngx_http.h
src/http/ngx_http_core_module.c
src/http/ngx_http_core_module.h
src/http/ngx_http_request.c
src/http/ngx_http_request.h
src/http/ngx_http_upstream.c
src/http/v3/ngx_http_v3_filter_module.c
src/stream/ngx_stream_proxy_module.c

diff --cc README
index f1c399b07a7e4c4389dca15ce3e59c6191552e6d,0000000000000000000000000000000000000000..7b7605728895aafbea9121daa080e31316cb83b7
mode 100644,000000..100644
--- 1/README
--- /dev/null
+++ b/README
@@@ -1,232 -1,0 +1,232 @@@
-     on nginx mainline 1.21.x.  We merge new nginx releases into
 +Experimental QUIC support for nginx
 +-----------------------------------
 +
 +1. Introduction
 +2. Installing
 +3. Configuration
 +4. Clients
 +5. Troubleshooting
 +6. Contributing
 +7. Links
 +
 +1. Introduction
 +
 +    This is an experimental QUIC [1] / HTTP/3 [2] support for nginx.
 +
 +    The code is developed in a separate "quic" branch available
 +    at https://hg.nginx.org/nginx-quic.  Currently it is based
++    on nginx mainline 1.23.x.  We merge new nginx releases into
 +    this branch regularly.
 +
 +    The project code base is under the same BSD license as nginx.
 +
 +    The code is currently at a beta level of quality and should not
 +    be used in production.
 +
 +    We are working on improving HTTP/3 support with the goal of
 +    integrating it to the main NGINX codebase.  Expect frequent
 +    updates of this code and don't rely on it for whatever purpose.
 +
 +    We'll be grateful for any feedback and code submissions however
 +    we don't bear any responsibilities for any issues with this code.
 +
 +    You can always contact us via nginx-devel mailing list [3].
 +
 +    What works now:
 +
 +    We support IETF QUIC version 1.  Internet drafts are no longer supported.
 +
 +    nginx should be able to respond to HTTP/3 requests over QUIC and
 +    it should be possible to upload and download big files without errors.
 +
 +    + The handshake completes successfully
 +    + One endpoint can update keys and its peer responds correctly
 +    + 0-RTT data is being received and acted on
 +    + Connection is established using TLS Resume Ticket
 +    + A handshake that includes a Retry packet completes successfully
 +    + Stream data is being exchanged and ACK'ed
 +    + An H3 transaction succeeded
 +    + One or both endpoints insert entries into dynamic table and
 +      subsequently reference them from header blocks
 +    + Version Negotiation packet is sent to client with unknown version
 +    + Lost packets are detected and retransmitted properly
 +    + Clients may migrate to new address
 +
 +2. Installing
 +
 +    You will need a BoringSSL [4] library that provides QUIC support
 +
 +    $ hg clone -b quic https://hg.nginx.org/nginx-quic
 +    $ cd nginx-quic
 +    $ ./auto/configure --with-debug --with-http_v3_module         \
 +                       --with-cc-opt="-I../boringssl/include"     \
 +                       --with-ld-opt="-L../boringssl/build/ssl    \
 +                                      -L../boringssl/build/crypto"
 +    $ make
 +
 +    Alternatively, nginx can be configured with QuicTLS [5]
 +
 +    $ ./auto/configure --with-debug --with-http_v3_module         \
 +                       --with-cc-opt="-I../quictls/build/include" \
 +                       --with-ld-opt="-L../quictls/build/lib"
 +
 +    When configuring nginx, you can enable QUIC and HTTP/3 using the
 +    following new configuration options:
 +
 +        --with-http_v3_module     - enable QUIC and HTTP/3
 +        --with-stream_quic_module - enable QUIC in Stream
 +
 +3. Configuration
 +
 +    The HTTP "listen" directive got a new option "http3" which enables
 +    HTTP/3 over QUIC on the specified port.
 +
 +    The Stream "listen" directive got a new option "quic" which enables
 +    QUIC as client transport protocol instead of TCP or plain UDP.
 +
 +    Along with "http3" or "quic", you also have to specify "reuseport"
 +    option [6] to make it work properly with multiple workers.
 +
 +    To enable address validation:
 +
 +        quic_retry on;
 +
 +    To enable 0-RTT:
 +
 +        ssl_early_data on;
 +
 +    Make sure that TLS 1.3 is configured which is required for QUIC:
 +
 +        ssl_protocols TLSv1.3;
 +
 +    To enable GSO (Generic Segmentation Offloading):
 +
 +        quic_gso on;
 +
 +    To limit maximum UDP payload size on receive path:
 +
 +        quic_mtu <size>;
 +
 +    To set host key for various tokens:
 +
 +        quic_host_key <filename>;
 +
 +
 +    By default, GSO Linux-specific optimization [8] is disabled.
 +    Enable if your network interface is configured to support GSO.
 +
 +    A number of directives were added that configure HTTP/3:
 +
 +        http3_stream_buffer_size
 +        http3_max_concurrent_pushes
 +        http3_max_concurrent_streams
 +        http3_push
 +        http3_push_preload
 +        http3_hq (requires NGX_HTTP_V3_HQ macro)
 +
 +    In http, an additional variable is available: $http3.
 +    The value of $http3 is "h3" for HTTP/3 connections,
 +    "hq" for hq connections, or an empty string otherwise.
 +
 +    In stream, an additional variable is available: $quic.
 +    The value of $quic is "quic" if QUIC connection is used,
 +    or an empty string otherwise.
 +
 +Example configuration:
 +
 +    http {
 +        log_format quic '$remote_addr - $remote_user [$time_local] '
 +                        '"$request" $status $body_bytes_sent '
 +                        '"$http_referer" "$http_user_agent" "$http3"';
 +
 +        access_log logs/access.log quic;
 +
 +        server {
 +            # for better compatibility it's recommended
 +            # to use the same port for quic and https
 +            listen 8443 http3 reuseport;
 +            listen 8443 ssl;
 +
 +            ssl_certificate     certs/example.com.crt;
 +            ssl_certificate_key certs/example.com.key;
 +            ssl_protocols       TLSv1.3;
 +
 +            location / {
 +                # required for browsers to direct them into quic port
 +                add_header Alt-Svc 'h3=":8443"; ma=86400';
 +            }
 +        }
 +    }
 +
 +4. Clients
 +
 +    * Browsers
 +
 +        Known to work: Firefox 90+ and Chrome 92+ (QUIC version 1)
 +
 +        Beware of strange issues: sometimes browser may decide to ignore QUIC
 +        Cache clearing/restart might help.  Always check access.log and
 +        error.log to make sure you are using HTTP/3 and not TCP https.
 +
 +    * Console clients
 +
 +        Known to work: ngtcp2, firefox's neqo and chromium's console clients:
 +
 +        $ examples/client 127.0.0.1 8443 https://example.com:8443/index.html
 +
 +        $ ./neqo-client https://127.0.0.1:8443/
 +
 +        $ chromium-build/out/my_build/quic_client http://example.com:8443
 +
 +
 +   If you've got it right, in the access log you should see something like:
 +
 +   127.0.0.1 - - [24/Apr/2020:11:27:29 +0300] "GET / HTTP/3" 200 805 "-"
 +                                         "nghttp3/ngtcp2 client" "quic"
 +
 +
 +5. Troubleshooting
 +
 +    Here are some tips that may help you to identify problems:
 +
 +    + Ensure you are building with proper SSL library that supports QUIC
 +
 +    + Ensure you are using the proper SSL library in runtime
 +      (`nginx -V` will show you what you are using)
 +
 +    + Ensure your client is actually sending requests over QUIC
 +      (see "Clients" section about browsers and cache)
 +
 +      We recommend to start with simple console client like ngtcp2
 +      to ensure you've got server configured properly before trying
 +      with real browsers that may be very picky with certificates,
 +      for example.
 +
 +    + Build nginx with debug support [7] and check your debug log.
 +      It should contain all details about connection and why it
 +      failed. All related messages contain "quic " prefix and can
 +      be easily filtered out.
 +
 +    + If you want to investigate deeper, you may want to enable
 +      additional debugging in src/event/quic/ngx_event_quic_connection.h:
 +
 +        #define NGX_QUIC_DEBUG_PACKETS
 +        #define NGX_QUIC_DEBUG_FRAMES
 +        #define NGX_QUIC_DEBUG_ALLOC
 +        #define NGX_QUIC_DEBUG_CRYPTO
 +
 +6. Contributing
 +
 +    If you are willing to contribute, please refer to
 +    http://nginx.org/en/docs/contributing_changes.html
 +
 +7. Links
 +
 +    [1] https://datatracker.ietf.org/doc/html/rfc9000
 +    [2] https://datatracker.ietf.org/doc/html/rfc9114
 +    [3] https://mailman.nginx.org/mailman3/lists/nginx-devel.nginx.org/
 +    [4] https://boringssl.googlesource.com/boringssl/
 +    [5] https://github.com/quictls/openssl
 +    [6] https://nginx.org/en/docs/http/ngx_http_core_module.html#listen
 +    [7] https://nginx.org/en/docs/debugging_log.html
 +    [8] http://vger.kernel.org/lpc_net2018_talks/willemdebruijn-lpc2018-udpgso-paper-DRAFT-1.pdf
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
index f835f8e5c56d910749089c77970a25dcdda30af0,0000000000000000000000000000000000000000..41b1704bf190ab30173aa25a4a04ebc959084263
mode 100644,000000..100644
--- /dev/null
@@@ -1,1538 -1,0 +1,1536 @@@
-     u_char                     *start, *end, *last;
-     ngx_str_t                   path;
-     ngx_int_t                   rc;
-     ngx_uint_t                  i, push;
-     ngx_table_elt_t           **h;
-     ngx_http_v3_loc_conf_t     *h3lcf;
-     ngx_http_complex_value_t   *pushes;
 +
 +/*
 + * Copyright (C) Roman Arutyunyan
 + * Copyright (C) Nginx, Inc.
 + */
 +
 +
 +#include <ngx_config.h>
 +#include <ngx_core.h>
 +#include <ngx_http.h>
 +
 +
 +/* static table indices */
 +#define NGX_HTTP_V3_HEADER_AUTHORITY                 0
 +#define NGX_HTTP_V3_HEADER_PATH_ROOT                 1
 +#define NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO       4
 +#define NGX_HTTP_V3_HEADER_DATE                      6
 +#define NGX_HTTP_V3_HEADER_LAST_MODIFIED             10
 +#define NGX_HTTP_V3_HEADER_LOCATION                  12
 +#define NGX_HTTP_V3_HEADER_METHOD_GET                17
 +#define NGX_HTTP_V3_HEADER_SCHEME_HTTP               22
 +#define NGX_HTTP_V3_HEADER_SCHEME_HTTPS              23
 +#define NGX_HTTP_V3_HEADER_STATUS_200                25
 +#define NGX_HTTP_V3_HEADER_ACCEPT_ENCODING           31
 +#define NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN   53
 +#define NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING      59
 +#define NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE           72
 +#define NGX_HTTP_V3_HEADER_SERVER                    92
 +#define NGX_HTTP_V3_HEADER_USER_AGENT                95
 +
 +
 +typedef struct {
 +    ngx_chain_t         *free;
 +    ngx_chain_t         *busy;
 +} ngx_http_v3_filter_ctx_t;
 +
 +
 +static ngx_int_t ngx_http_v3_header_filter(ngx_http_request_t *r);
 +static ngx_int_t ngx_http_v3_push_resources(ngx_http_request_t *r,
 +    ngx_chain_t ***out);
 +static ngx_int_t ngx_http_v3_push_resource(ngx_http_request_t *r,
 +    ngx_str_t *path, ngx_chain_t ***out);
 +static ngx_int_t ngx_http_v3_create_push_request(
 +    ngx_http_request_t *pr, ngx_str_t *path, uint64_t push_id);
 +static ngx_int_t ngx_http_v3_set_push_header(ngx_http_request_t *r,
 +    const char *name, ngx_str_t *value);
 +static void ngx_http_v3_push_request_handler(ngx_event_t *ev);
 +static ngx_chain_t *ngx_http_v3_create_push_promise(ngx_http_request_t *r,
 +    ngx_str_t *path, uint64_t push_id);
 +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,
 +    ngx_http_v3_filter_ctx_t *ctx);
 +static ngx_int_t ngx_http_v3_filter_init(ngx_conf_t *cf);
 +
 +
 +static ngx_http_module_t  ngx_http_v3_filter_module_ctx = {
 +    NULL,                                  /* preconfiguration */
 +    ngx_http_v3_filter_init,               /* postconfiguration */
 +
 +    NULL,                                  /* create main configuration */
 +    NULL,                                  /* init main configuration */
 +
 +    NULL,                                  /* create server configuration */
 +    NULL,                                  /* merge server configuration */
 +
 +    NULL,                                  /* create location configuration */
 +    NULL                                   /* merge location configuration */
 +};
 +
 +
 +ngx_module_t  ngx_http_v3_filter_module = {
 +    NGX_MODULE_V1,
 +    &ngx_http_v3_filter_module_ctx,        /* module context */
 +    NULL,                                  /* module directives */
 +    NGX_HTTP_MODULE,                       /* module type */
 +    NULL,                                  /* init master */
 +    NULL,                                  /* init module */
 +    NULL,                                  /* init process */
 +    NULL,                                  /* init thread */
 +    NULL,                                  /* exit thread */
 +    NULL,                                  /* exit process */
 +    NULL,                                  /* exit master */
 +    NGX_MODULE_V1_PADDING
 +};
 +
 +
 +static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
 +static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
 +
 +
 +static ngx_int_t
 +ngx_http_v3_header_filter(ngx_http_request_t *r)
 +{
 +    u_char                    *p;
 +    size_t                     len, n;
 +    ngx_buf_t                 *b;
 +    ngx_str_t                  host, location;
 +    ngx_uint_t                 i, port;
 +    ngx_chain_t               *out, *hl, *cl, **ll;
 +    ngx_list_part_t           *part;
 +    ngx_table_elt_t           *header;
 +    ngx_connection_t          *c;
 +    ngx_http_v3_session_t     *h3c;
 +    ngx_http_v3_filter_ctx_t  *ctx;
 +    ngx_http_core_loc_conf_t  *clcf;
 +    ngx_http_core_srv_conf_t  *cscf;
 +    u_char                     addr[NGX_SOCKADDR_STRLEN];
 +
 +    if (r->http_version != NGX_HTTP_VERSION_30) {
 +        return ngx_http_next_header_filter(r);
 +    }
 +
 +    if (r->header_sent) {
 +        return NGX_OK;
 +    }
 +
 +    r->header_sent = 1;
 +
 +    if (r != r->main) {
 +        return NGX_OK;
 +    }
 +
 +    h3c = ngx_http_v3_get_session(r->connection);
 +
 +    if (r->method == NGX_HTTP_HEAD) {
 +        r->header_only = 1;
 +    }
 +
 +    if (r->headers_out.last_modified_time != -1) {
 +        if (r->headers_out.status != NGX_HTTP_OK
 +            && r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT
 +            && r->headers_out.status != NGX_HTTP_NOT_MODIFIED)
 +        {
 +            r->headers_out.last_modified_time = -1;
 +            r->headers_out.last_modified = NULL;
 +        }
 +    }
 +
 +    if (r->headers_out.status == NGX_HTTP_NO_CONTENT) {
 +        r->header_only = 1;
 +        ngx_str_null(&r->headers_out.content_type);
 +        r->headers_out.last_modified_time = -1;
 +        r->headers_out.last_modified = NULL;
 +        r->headers_out.content_length = NULL;
 +        r->headers_out.content_length_n = -1;
 +    }
 +
 +    if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) {
 +        r->header_only = 1;
 +    }
 +
 +    c = r->connection;
 +
 +    out = NULL;
 +    ll = &out;
 +
 +    if ((c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0
 +        && r->method != NGX_HTTP_HEAD)
 +    {
 +        if (ngx_http_v3_push_resources(r, &ll) != NGX_OK) {
 +            return NGX_ERROR;
 +        }
 +    }
 +
 +    len = ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0);
 +
 +    if (r->headers_out.status == NGX_HTTP_OK) {
 +        len += ngx_http_v3_encode_field_ri(NULL, 0,
 +                                           NGX_HTTP_V3_HEADER_STATUS_200);
 +
 +    } else {
 +        len += ngx_http_v3_encode_field_lri(NULL, 0,
 +                                            NGX_HTTP_V3_HEADER_STATUS_200,
 +                                            NULL, 3);
 +    }
 +
 +    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
 +
 +    if (r->headers_out.server == NULL) {
 +        if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
 +            n = sizeof(NGINX_VER) - 1;
 +
 +        } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
 +            n = sizeof(NGINX_VER_BUILD) - 1;
 +
 +        } else {
 +            n = sizeof("nginx") - 1;
 +        }
 +
 +        len += ngx_http_v3_encode_field_lri(NULL, 0,
 +                                            NGX_HTTP_V3_HEADER_SERVER,
 +                                            NULL, n);
 +    }
 +
 +    if (r->headers_out.date == NULL) {
 +        len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_DATE,
 +                                            NULL, ngx_cached_http_time.len);
 +    }
 +
 +    if (r->headers_out.content_type.len) {
 +        n = r->headers_out.content_type.len;
 +
 +        if (r->headers_out.content_type_len == r->headers_out.content_type.len
 +            && r->headers_out.charset.len)
 +        {
 +            n += sizeof("; charset=") - 1 + r->headers_out.charset.len;
 +        }
 +
 +        len += ngx_http_v3_encode_field_lri(NULL, 0,
 +                                    NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN,
 +                                    NULL, n);
 +    }
 +
 +    if (r->headers_out.content_length == NULL) {
 +        if (r->headers_out.content_length_n > 0) {
 +            len += ngx_http_v3_encode_field_lri(NULL, 0,
 +                                        NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO,
 +                                        NULL, NGX_OFF_T_LEN);
 +
 +        } else if (r->headers_out.content_length_n == 0) {
 +            len += ngx_http_v3_encode_field_ri(NULL, 0,
 +                                       NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO);
 +        }
 +    }
 +
 +    if (r->headers_out.last_modified == NULL
 +        && r->headers_out.last_modified_time != -1)
 +    {
 +        len += ngx_http_v3_encode_field_lri(NULL, 0,
 +                                  NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL,
 +                                  sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1);
 +    }
 +
 +    if (r->headers_out.location && r->headers_out.location->value.len) {
 +
 +        if (r->headers_out.location->value.data[0] == '/'
 +            && clcf->absolute_redirect)
 +        {
 +            if (clcf->server_name_in_redirect) {
 +                cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
 +                host = cscf->server_name;
 +
 +            } else if (r->headers_in.server.len) {
 +                host = r->headers_in.server;
 +
 +            } else {
 +                host.len = NGX_SOCKADDR_STRLEN;
 +                host.data = addr;
 +
 +                if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) {
 +                    return NGX_ERROR;
 +                }
 +            }
 +
 +            port = ngx_inet_get_port(c->local_sockaddr);
 +
 +            location.len = sizeof("https://") - 1 + host.len
 +                           + r->headers_out.location->value.len;
 +
 +            if (clcf->port_in_redirect) {
 +                port = (port == 443) ? 0 : port;
 +
 +            } else {
 +                port = 0;
 +            }
 +
 +            if (port) {
 +                location.len += sizeof(":65535") - 1;
 +            }
 +
 +            location.data = ngx_pnalloc(r->pool, location.len);
 +            if (location.data == NULL) {
 +                return NGX_ERROR;
 +            }
 +
 +            p = ngx_cpymem(location.data, "https://", sizeof("https://") - 1);
 +            p = ngx_cpymem(p, host.data, host.len);
 +
 +            if (port) {
 +                p = ngx_sprintf(p, ":%ui", port);
 +            }
 +
 +            p = ngx_cpymem(p, r->headers_out.location->value.data,
 +                              r->headers_out.location->value.len);
 +
 +            /* update r->headers_out.location->value for possible logging */
 +
 +            r->headers_out.location->value.len = p - location.data;
 +            r->headers_out.location->value.data = location.data;
 +            ngx_str_set(&r->headers_out.location->key, "Location");
 +        }
 +
 +        r->headers_out.location->hash = 0;
 +
 +        len += ngx_http_v3_encode_field_lri(NULL, 0,
 +                                           NGX_HTTP_V3_HEADER_LOCATION, NULL,
 +                                           r->headers_out.location->value.len);
 +    }
 +
 +#if (NGX_HTTP_GZIP)
 +    if (r->gzip_vary) {
 +        if (clcf->gzip_vary) {
 +            len += ngx_http_v3_encode_field_ri(NULL, 0,
 +                                      NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING);
 +
 +        } else {
 +            r->gzip_vary = 0;
 +        }
 +    }
 +#endif
 +
 +    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);
 +    }
 +
 +    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->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, c->log, 0,
 +                   "http3 output header: \":status: %03ui\"",
 +                   r->headers_out.status);
 +
 +    if (r->headers_out.status == NGX_HTTP_OK) {
 +        b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
 +                                                NGX_HTTP_V3_HEADER_STATUS_200);
 +
 +    } else {
 +        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", r->headers_out.status);
 +    }
 +
 +    if (r->headers_out.server == NULL) {
 +        if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
 +            p = (u_char *) NGINX_VER;
 +            n = sizeof(NGINX_VER) - 1;
 +
 +        } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
 +            p = (u_char *) NGINX_VER_BUILD;
 +            n = sizeof(NGINX_VER_BUILD) - 1;
 +
 +        } else {
 +            p = (u_char *) "nginx";
 +            n = sizeof("nginx") - 1;
 +        }
 +
 +        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                       "http3 output header: \"server: %*s\"", n, p);
 +
 +        b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
 +                                                     NGX_HTTP_V3_HEADER_SERVER,
 +                                                     p, n);
 +    }
 +
 +    if (r->headers_out.date == NULL) {
 +        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                       "http3 output header: \"date: %V\"",
 +                       &ngx_cached_http_time);
 +
 +        b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
 +                                                     NGX_HTTP_V3_HEADER_DATE,
 +                                                     ngx_cached_http_time.data,
 +                                                     ngx_cached_http_time.len);
 +    }
 +
 +    if (r->headers_out.content_type.len) {
 +        if (r->headers_out.content_type_len == r->headers_out.content_type.len
 +            && r->headers_out.charset.len)
 +        {
 +            n = r->headers_out.content_type.len + sizeof("; charset=") - 1
 +                + r->headers_out.charset.len;
 +
 +            p = ngx_pnalloc(r->pool, n);
 +            if (p == NULL) {
 +                return NGX_ERROR;
 +            }
 +
 +            p = ngx_cpymem(p, r->headers_out.content_type.data,
 +                           r->headers_out.content_type.len);
 +
 +            p = ngx_cpymem(p, "; charset=", sizeof("; charset=") - 1);
 +
 +            p = ngx_cpymem(p, r->headers_out.charset.data,
 +                           r->headers_out.charset.len);
 +
 +            /* updated r->headers_out.content_type is also needed for logging */
 +
 +            r->headers_out.content_type.len = n;
 +            r->headers_out.content_type.data = p - n;
 +        }
 +
 +        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                       "http3 output header: \"content-type: %V\"",
 +                       &r->headers_out.content_type);
 +
 +        b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
 +                                    NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN,
 +                                    r->headers_out.content_type.data,
 +                                    r->headers_out.content_type.len);
 +    }
 +
 +    if (r->headers_out.content_length == NULL
 +        && r->headers_out.content_length_n >= 0)
 +    {
 +        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                       "http3 output header: \"content-length: %O\"",
 +                       r->headers_out.content_length_n);
 +
 +        if (r->headers_out.content_length_n > 0) {
 +            p = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n);
 +            n = p - b->last;
 +
 +            b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
 +                                        NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO,
 +                                        NULL, n);
 +
 +            b->last = ngx_sprintf(b->last, "%O",
 +                                  r->headers_out.content_length_n);
 +
 +        } else {
 +            b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
 +                                       NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO);
 +        }
 +    }
 +
 +    if (r->headers_out.last_modified == NULL
 +        && r->headers_out.last_modified_time != -1)
 +    {
 +        n = sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1;
 +
 +        p = ngx_pnalloc(r->pool, n);
 +        if (p == NULL) {
 +            return NGX_ERROR;
 +        }
 +
 +        ngx_http_time(p, r->headers_out.last_modified_time);
 +
 +        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                       "http3 output header: \"last-modified: %*s\"", n, p);
 +
 +        b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
 +                                              NGX_HTTP_V3_HEADER_LAST_MODIFIED,
 +                                              p, n);
 +    }
 +
 +    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\"",
 +                       &r->headers_out.location->value);
 +
 +        b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
 +                                           NGX_HTTP_V3_HEADER_LOCATION,
 +                                           r->headers_out.location->value.data,
 +                                           r->headers_out.location->value.len);
 +    }
 +
 +#if (NGX_HTTP_GZIP)
 +    if (r->gzip_vary) {
 +        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                       "http3 output header: \"vary: Accept-Encoding\"");
 +
 +        b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
 +                                      NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING);
 +    }
 +#endif
 +
 +    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, c->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);
 +    }
 +
 +    if (r->header_only) {
 +        b->last_buf = 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->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;
 +
 +    *ll = hl;
 +    ll = &cl->next;
 +
 +    if (r->headers_out.content_length_n >= 0
 +        && !r->header_only && !r->expect_trailers)
 +    {
 +        len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_DATA)
 +              + ngx_http_v3_encode_varlen_int(NULL,
 +                                              r->headers_out.content_length_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_DATA);
 +        b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last,
 +                                              r->headers_out.content_length_n);
 +
 +        h3c->payload_bytes += r->headers_out.content_length_n;
 +        h3c->total_bytes += r->headers_out.content_length_n;
 +
 +        cl = ngx_alloc_chain_link(r->pool);
 +        if (cl == NULL) {
 +            return NGX_ERROR;
 +        }
 +
 +        cl->buf = b;
 +        cl->next = NULL;
 +
 +        *ll = cl;
 +
 +    } else {
 +        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_filter_ctx_t));
 +        if (ctx == NULL) {
 +            return NGX_ERROR;
 +        }
 +
 +        ngx_http_set_ctx(r, ctx, ngx_http_v3_filter_module);
 +    }
 +
 +    for (cl = out; cl; cl = cl->next) {
 +        h3c->total_bytes += cl->buf->last - cl->buf->pos;
 +    }
 +
 +    return ngx_http_write_filter(r, out);
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_push_resources(ngx_http_request_t *r, ngx_chain_t ***out)
 +{
-     h = r->headers_out.link.elts;
-     for (i = 0; i < r->headers_out.link.nelts; i++) {
++    u_char                    *start, *end, *last;
++    ngx_str_t                  path;
++    ngx_int_t                  rc;
++    ngx_uint_t                 i, push;
++    ngx_table_elt_t           *h;
++    ngx_http_v3_loc_conf_t    *h3lcf;
++    ngx_http_complex_value_t  *pushes;
 +
 +    h3lcf = ngx_http_get_module_loc_conf(r, ngx_http_v3_module);
 +
 +    if (h3lcf->pushes) {
 +        pushes = h3lcf->pushes->elts;
 +
 +        for (i = 0; i < h3lcf->pushes->nelts; i++) {
 +
 +            if (ngx_http_complex_value(r, &pushes[i], &path) != NGX_OK) {
 +                return NGX_ERROR;
 +            }
 +
 +            if (path.len == 0) {
 +                continue;
 +            }
 +
 +            if (path.len == 3 && ngx_strncmp(path.data, "off", 3) == 0) {
 +                continue;
 +            }
 +
 +            rc = ngx_http_v3_push_resource(r, &path, out);
 +
 +            if (rc == NGX_ERROR) {
 +                return NGX_ERROR;
 +            }
 +
 +            if (rc == NGX_ABORT) {
 +                return NGX_OK;
 +            }
 +
 +            /* NGX_OK, NGX_DECLINED */
 +        }
 +    }
 +
 +    if (!h3lcf->push_preload) {
 +        return NGX_OK;
 +    }
 +
-                        "http3 parse link: \"%V\"", &h[i]->value);
++    for (h = r->headers_out.link; h; h = h->next) {
 +
 +        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
-         start = h[i]->value.data;
-         end = h[i]->value.data + h[i]->value.len;
++                       "http3 parse link: \"%V\"", &h->value);
 +
++        start = h->value.data;
++        end = h->value.data + h->value.len;
 +
 +    next_link:
 +
 +        while (start < end && *start == ' ') { start++; }
 +
 +        if (start == end || *start++ != '<') {
 +            continue;
 +        }
 +
 +        while (start < end && *start == ' ') { start++; }
 +
 +        for (last = start; last < end && *last != '>'; last++) {
 +            /* void */
 +        }
 +
 +        if (last == start || last == end) {
 +            continue;
 +        }
 +
 +        path.len = last - start;
 +        path.data = start;
 +
 +        start = last + 1;
 +
 +        while (start < end && *start == ' ') { start++; }
 +
 +        if (start == end) {
 +            continue;
 +        }
 +
 +        if (*start == ',') {
 +            start++;
 +            goto next_link;
 +        }
 +
 +        if (*start++ != ';') {
 +            continue;
 +        }
 +
 +        last = ngx_strlchr(start, end, ',');
 +
 +        if (last == NULL) {
 +            last = end;
 +        }
 +
 +        push = 0;
 +
 +        for ( ;; ) {
 +
 +            while (start < last && *start == ' ') { start++; }
 +
 +            if (last - start >= 6
 +                && ngx_strncasecmp(start, (u_char *) "nopush", 6) == 0)
 +            {
 +                start += 6;
 +
 +                if (start == last || *start == ' ' || *start == ';') {
 +                    push = 0;
 +                    break;
 +                }
 +
 +                goto next_param;
 +            }
 +
 +            if (last - start >= 11
 +                && ngx_strncasecmp(start, (u_char *) "rel=preload", 11) == 0)
 +            {
 +                start += 11;
 +
 +                if (start == last || *start == ' ' || *start == ';') {
 +                    push = 1;
 +                }
 +
 +                goto next_param;
 +            }
 +
 +            if (last - start >= 4
 +                && ngx_strncasecmp(start, (u_char *) "rel=", 4) == 0)
 +            {
 +                start += 4;
 +
 +                while (start < last && *start == ' ') { start++; }
 +
 +                if (start == last || *start++ != '"') {
 +                    goto next_param;
 +                }
 +
 +                for ( ;; ) {
 +
 +                    while (start < last && *start == ' ') { start++; }
 +
 +                    if (last - start >= 7
 +                        && ngx_strncasecmp(start, (u_char *) "preload", 7) == 0)
 +                    {
 +                        start += 7;
 +
 +                        if (start < last && (*start == ' ' || *start == '"')) {
 +                            push = 1;
 +                            break;
 +                        }
 +                    }
 +
 +                    while (start < last && *start != ' ' && *start != '"') {
 +                        start++;
 +                    }
 +
 +                    if (start == last) {
 +                        break;
 +                    }
 +
 +                    if (*start == '"') {
 +                        break;
 +                    }
 +
 +                    start++;
 +                }
 +            }
 +
 +        next_param:
 +
 +            start = ngx_strlchr(start, last, ';');
 +
 +            if (start == NULL) {
 +                break;
 +            }
 +
 +            start++;
 +        }
 +
 +        if (push) {
 +            while (path.len && path.data[path.len - 1] == ' ') {
 +                path.len--;
 +            }
 +        }
 +
 +        if (push && path.len
 +            && !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/'))
 +        {
 +            rc = ngx_http_v3_push_resource(r, &path, out);
 +
 +            if (rc == NGX_ERROR) {
 +                return NGX_ERROR;
 +            }
 +
 +            if (rc == NGX_ABORT) {
 +                return NGX_OK;
 +            }
 +
 +            /* NGX_OK, NGX_DECLINED */
 +        }
 +
 +        if (last < end) {
 +            start = last + 1;
 +            goto next_link;
 +        }
 +    }
 +
 +    return NGX_OK;
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path,
 +    ngx_chain_t ***ll)
 +{
 +    uint64_t                 push_id;
 +    ngx_int_t                rc;
 +    ngx_chain_t             *cl;
 +    ngx_connection_t        *c;
 +    ngx_http_v3_session_t   *h3c;
 +    ngx_http_v3_srv_conf_t  *h3scf;
 +
 +    c = r->connection;
 +    h3c = ngx_http_v3_get_session(c);
 +    h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module);
 +
 +    ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                   "http3 push \"%V\" pushing:%ui/%ui id:%uL/%L",
 +                   path, h3c->npushing, h3scf->max_concurrent_pushes,
 +                   h3c->next_push_id, h3c->max_push_id);
 +
 +    if (!ngx_path_separator(path->data[0])) {
 +        ngx_log_error(NGX_LOG_WARN, c->log, 0,
 +                      "non-absolute path \"%V\" not pushed", path);
 +        return NGX_DECLINED;
 +    }
 +
 +    if (h3c->max_push_id == (uint64_t) -1
 +        || h3c->next_push_id > h3c->max_push_id)
 +    {
 +        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                       "http3 abort pushes due to max_push_id");
 +        return NGX_ABORT;
 +    }
 +
 +    if (h3c->goaway_push_id != (uint64_t) -1) {
 +        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                       "http3 abort pushes due to goaway");
 +        return NGX_ABORT;
 +    }
 +
 +    if (h3c->npushing >= h3scf->max_concurrent_pushes) {
 +        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
 +                       "http3 abort pushes due to max_concurrent_pushes");
 +        return NGX_ABORT;
 +    }
 +
 +    if (r->headers_in.server.len == 0) {
 +        return NGX_ABORT;
 +    }
 +
 +    push_id = h3c->next_push_id++;
 +
 +    rc = ngx_http_v3_create_push_request(r, path, push_id);
 +    if (rc != NGX_OK) {
 +        return rc;
 +    }
 +
 +    cl = ngx_http_v3_create_push_promise(r, path, push_id);
 +    if (cl == NULL) {
 +        return NGX_ERROR;
 +    }
 +
 +    for (**ll = cl; **ll; *ll = &(**ll)->next);
 +
 +    return NGX_OK;
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path,
 +    uint64_t push_id)
 +{
 +    ngx_connection_t          *c, *pc;
 +    ngx_http_request_t        *r;
 +    ngx_http_log_ctx_t        *ctx;
 +    ngx_http_connection_t     *hc, *phc;
 +    ngx_http_core_srv_conf_t  *cscf;
 +
 +    pc = pr->connection;
 +
 +    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
 +                   "http3 create push request id:%uL", push_id);
 +
 +    c = ngx_http_v3_create_push_stream(pc, push_id);
 +    if (c == NULL) {
 +        return NGX_ABORT;
 +    }
 +
 +#if (NGX_STAT_STUB)
 +    (void) ngx_atomic_fetch_add(ngx_stat_active, 1);
 +#endif
 +
 +    hc = ngx_palloc(c->pool, sizeof(ngx_http_connection_t));
 +    if (hc == NULL) {
 +        ngx_http_close_connection(c);
 +        return NGX_ERROR;
 +    }
 +
 +    phc = ngx_http_quic_get_connection(pc);
 +    ngx_memcpy(hc, phc, sizeof(ngx_http_connection_t));
 +    c->data = hc;
 +
 +    ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t));
 +    if (ctx == NULL) {
 +        ngx_http_close_connection(c);
 +        return NGX_ERROR;
 +    }
 +
 +    ctx->connection = c;
 +    ctx->request = NULL;
 +    ctx->current_request = NULL;
 +
 +    c->log->handler = pc->log->handler;
 +    c->log->data = ctx;
 +    c->log->action = "processing pushed request headers";
 +
 +    c->log_error = NGX_ERROR_INFO;
 +
 +    r = ngx_http_create_request(c);
 +    if (r == NULL) {
 +        ngx_http_close_connection(c);
 +        return NGX_ERROR;
 +    }
 +
 +    c->data = r;
 +
 +    ngx_str_set(&r->http_protocol, "HTTP/3.0");
 +
 +    r->http_version = NGX_HTTP_VERSION_30;
 +    r->method_name = ngx_http_core_get_method;
 +    r->method = NGX_HTTP_GET;
 +
 +    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
 +
 +    r->header_in = ngx_create_temp_buf(r->pool,
 +                                       cscf->client_header_buffer_size);
 +    if (r->header_in == NULL) {
 +        ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
 +        return NGX_ERROR;
 +    }
 +
 +    if (ngx_list_init(&r->headers_in.headers, r->pool, 4,
 +                      sizeof(ngx_table_elt_t))
 +        != NGX_OK)
 +    {
 +        ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
 +        return NGX_ERROR;
 +    }
 +
 +    r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE;
 +
 +    r->schema.data = ngx_pstrdup(r->pool, &pr->schema);
 +    if (r->schema.data == NULL) {
 +        ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
 +        return NGX_ERROR;
 +    }
 +
 +    r->schema.len = pr->schema.len;
 +
 +    r->uri_start = ngx_pstrdup(r->pool, path);
 +    if (r->uri_start == NULL) {
 +        ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
 +        return NGX_ERROR;
 +    }
 +
 +    r->uri_end = r->uri_start + path->len;
 +
 +    if (ngx_http_parse_uri(r) != NGX_OK) {
 +        ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
 +        return NGX_ERROR;
 +    }
 +
 +    if (ngx_http_process_request_uri(r) != NGX_OK) {
 +        return NGX_ERROR;
 +    }
 +
 +    if (ngx_http_v3_set_push_header(r, "host", &pr->headers_in.server)
 +        != NGX_OK)
 +    {
 +        return NGX_ERROR;
 +    }
 +
 +    if (pr->headers_in.accept_encoding) {
 +        if (ngx_http_v3_set_push_header(r, "accept-encoding",
 +                                        &pr->headers_in.accept_encoding->value)
 +            != NGX_OK)
 +        {
 +            return NGX_ERROR;
 +        }
 +    }
 +
 +    if (pr->headers_in.accept_language) {
 +        if (ngx_http_v3_set_push_header(r, "accept-language",
 +                                        &pr->headers_in.accept_language->value)
 +            != NGX_OK)
 +        {
 +            return NGX_ERROR;
 +        }
 +    }
 +
 +    if (pr->headers_in.user_agent) {
 +        if (ngx_http_v3_set_push_header(r, "user-agent",
 +                                        &pr->headers_in.user_agent->value)
 +            != NGX_OK)
 +        {
 +            return NGX_ERROR;
 +        }
 +    }
 +
 +    c->read->handler = ngx_http_v3_push_request_handler;
 +    c->read->handler = ngx_http_v3_push_request_handler;
 +
 +    ngx_post_event(c->read, &ngx_posted_events);
 +
 +    return NGX_OK;
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_set_push_header(ngx_http_request_t *r, const char *name,
 +    ngx_str_t *value)
 +{
 +    u_char                     *p;
 +    ngx_table_elt_t            *h;
 +    ngx_http_header_t          *hh;
 +    ngx_http_core_main_conf_t  *cmcf;
 +
 +    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
 +                   "http3 push header \"%s\": \"%V\"", name, value);
 +
 +    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
 +
 +    p = ngx_pnalloc(r->pool, value->len + 1);
 +    if (p == NULL) {
 +        ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
 +        return NGX_ERROR;
 +    }
 +
 +    ngx_memcpy(p, value->data, value->len);
 +    p[value->len] = '\0';
 +
 +    h = ngx_list_push(&r->headers_in.headers);
 +    if (h == NULL) {
 +        ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
 +        return NGX_ERROR;
 +    }
 +
 +    h->key.data = (u_char *) name;
 +    h->key.len = ngx_strlen(name);
 +    h->hash = ngx_hash_key(h->key.data, h->key.len);
 +    h->lowcase_key = (u_char *) name;
 +    h->value.data = p;
 +    h->value.len = value->len;
 +
 +    hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
 +                       h->lowcase_key, h->key.len);
 +
 +    if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
 +        return NGX_ERROR;
 +    }
 +
 +    return NGX_OK;
 +}
 +
 +
 +static void
 +ngx_http_v3_push_request_handler(ngx_event_t *ev)
 +{
 +    ngx_connection_t    *c;
 +    ngx_http_request_t  *r;
 +
 +    c = ev->data;
 +    r = c->data;
 +
 +    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 push request handler");
 +
 +    ngx_http_process_request(r);
 +}
 +
 +
 +static ngx_chain_t *
 +ngx_http_v3_create_push_promise(ngx_http_request_t *r, ngx_str_t *path,
 +    uint64_t push_id)
 +{
 +    size_t                  n, len;
 +    ngx_buf_t              *b;
 +    ngx_chain_t            *hl, *cl;
 +    ngx_http_v3_session_t  *h3c;
 +
 +    h3c = ngx_http_v3_get_session(r->connection);
 +
 +    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
 +                   "http3 create push promise id:%uL", push_id);
 +
 +    len = ngx_http_v3_encode_varlen_int(NULL, push_id);
 +
 +    len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0);
 +
 +    len += ngx_http_v3_encode_field_ri(NULL, 0,
 +                                       NGX_HTTP_V3_HEADER_METHOD_GET);
 +
 +    len += ngx_http_v3_encode_field_lri(NULL, 0,
 +                                        NGX_HTTP_V3_HEADER_AUTHORITY,
 +                                        NULL, r->headers_in.server.len);
 +
 +    if (path->len == 1 && path->data[0] == '/') {
 +        len += ngx_http_v3_encode_field_ri(NULL, 0,
 +                                           NGX_HTTP_V3_HEADER_PATH_ROOT);
 +
 +    } else {
 +        len += ngx_http_v3_encode_field_lri(NULL, 0,
 +                                            NGX_HTTP_V3_HEADER_PATH_ROOT,
 +                                            NULL, path->len);
 +    }
 +
 +    if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) {
 +        len += ngx_http_v3_encode_field_ri(NULL, 0,
 +                                           NGX_HTTP_V3_HEADER_SCHEME_HTTPS);
 +
 +    } else if (r->schema.len == 4
 +               && ngx_strncmp(r->schema.data, "http", 4) == 0)
 +    {
 +        len += ngx_http_v3_encode_field_ri(NULL, 0,
 +                                           NGX_HTTP_V3_HEADER_SCHEME_HTTP);
 +
 +    } else {
 +        len += ngx_http_v3_encode_field_lri(NULL, 0,
 +                                            NGX_HTTP_V3_HEADER_SCHEME_HTTP,
 +                                            NULL, r->schema.len);
 +    }
 +
 +    if (r->headers_in.accept_encoding) {
 +        len += ngx_http_v3_encode_field_lri(NULL, 0,
 +                                     NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, NULL,
 +                                     r->headers_in.accept_encoding->value.len);
 +    }
 +
 +    if (r->headers_in.accept_language) {
 +        len += ngx_http_v3_encode_field_lri(NULL, 0,
 +                                     NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, NULL,
 +                                     r->headers_in.accept_language->value.len);
 +    }
 +
 +    if (r->headers_in.user_agent) {
 +        len += ngx_http_v3_encode_field_lri(NULL, 0,
 +                                          NGX_HTTP_V3_HEADER_USER_AGENT, NULL,
 +                                          r->headers_in.user_agent->value.len);
 +    }
 +
 +    b = ngx_create_temp_buf(r->pool, len);
 +    if (b == NULL) {
 +        return NULL;
 +    }
 +
 +    b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, push_id);
 +
 +    b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last,
 +                                                                 0, 0, 0);
 +
 +    b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
 +                                                NGX_HTTP_V3_HEADER_METHOD_GET);
 +
 +    b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
 +                                                  NGX_HTTP_V3_HEADER_AUTHORITY,
 +                                                  r->headers_in.server.data,
 +                                                  r->headers_in.server.len);
 +
 +    if (path->len == 1 && path->data[0] == '/') {
 +        b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
 +                                                 NGX_HTTP_V3_HEADER_PATH_ROOT);
 +
 +    } else {
 +        b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
 +                                                  NGX_HTTP_V3_HEADER_PATH_ROOT,
 +                                                  path->data, path->len);
 +    }
 +
 +    if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) {
 +        b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
 +                                              NGX_HTTP_V3_HEADER_SCHEME_HTTPS);
 +
 +    } else if (r->schema.len == 4
 +               && ngx_strncmp(r->schema.data, "http", 4) == 0)
 +    {
 +        b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0,
 +                                               NGX_HTTP_V3_HEADER_SCHEME_HTTP);
 +
 +    } else {
 +        b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
 +                                                NGX_HTTP_V3_HEADER_SCHEME_HTTP,
 +                                                r->schema.data, r->schema.len);
 +    }
 +
 +    if (r->headers_in.accept_encoding) {
 +        b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
 +                                     NGX_HTTP_V3_HEADER_ACCEPT_ENCODING,
 +                                     r->headers_in.accept_encoding->value.data,
 +                                     r->headers_in.accept_encoding->value.len);
 +    }
 +
 +    if (r->headers_in.accept_language) {
 +        b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
 +                                     NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE,
 +                                     r->headers_in.accept_language->value.data,
 +                                     r->headers_in.accept_language->value.len);
 +    }
 +
 +    if (r->headers_in.user_agent) {
 +        b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0,
 +                                          NGX_HTTP_V3_HEADER_USER_AGENT,
 +                                          r->headers_in.user_agent->value.data,
 +                                          r->headers_in.user_agent->value.len);
 +    }
 +
 +    cl = ngx_alloc_chain_link(r->pool);
 +    if (cl == NULL) {
 +        return NULL;
 +    }
 +
 +    cl->buf = b;
 +    cl->next = NULL;
 +
 +    n = b->last - b->pos;
 +
 +    h3c->payload_bytes += n;
 +
 +    len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_PUSH_PROMISE)
 +          + ngx_http_v3_encode_varlen_int(NULL, n);
 +
 +    b = ngx_create_temp_buf(r->pool, len);
 +    if (b == NULL) {
 +        return NULL;
 +    }
 +
 +    b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last,
 +                                               NGX_HTTP_V3_FRAME_PUSH_PROMISE);
 +    b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n);
 +
 +    hl = ngx_alloc_chain_link(r->pool);
 +    if (hl == NULL) {
 +        return NULL;
 +    }
 +
 +    hl->buf = b;
 +    hl->next = cl;
 +
 +    return hl;
 +}
 +
 +
 +static ngx_int_t
 +ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
 +{
 +    u_char                    *chunk;
 +    off_t                      size;
 +    ngx_int_t                  rc;
 +    ngx_buf_t                 *b;
 +    ngx_chain_t               *out, *cl, *tl, **ll;
 +    ngx_http_v3_session_t     *h3c;
 +    ngx_http_v3_filter_ctx_t  *ctx;
 +
 +    if (in == NULL) {
 +        return ngx_http_next_body_filter(r, in);
 +    }
 +
 +    ctx = ngx_http_get_module_ctx(r, ngx_http_v3_filter_module);
 +    if (ctx == NULL) {
 +        return ngx_http_next_body_filter(r, in);
 +    }
 +
 +    h3c = ngx_http_v3_get_session(r->connection);
 +
 +    out = NULL;
 +    ll = &out;
 +
 +    size = 0;
 +    cl = in;
 +
 +    for ( ;; ) {
 +        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
 +                       "http3 chunk: %O", ngx_buf_size(cl->buf));
 +
 +        size += ngx_buf_size(cl->buf);
 +
 +        if (cl->buf->flush
 +            || cl->buf->sync
 +            || ngx_buf_in_memory(cl->buf)
 +            || cl->buf->in_file)
 +        {
 +            tl = ngx_alloc_chain_link(r->pool);
 +            if (tl == NULL) {
 +                return NGX_ERROR;
 +            }
 +
 +            tl->buf = cl->buf;
 +            *ll = tl;
 +            ll = &tl->next;
 +        }
 +
 +        if (cl->next == NULL) {
 +            break;
 +        }
 +
 +        cl = cl->next;
 +    }
 +
 +    if (size) {
 +        tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
 +        if (tl == NULL) {
 +            return NGX_ERROR;
 +        }
 +
 +        b = tl->buf;
 +        chunk = b->start;
 +
 +        if (chunk == NULL) {
 +            chunk = ngx_palloc(r->pool, NGX_HTTP_V3_VARLEN_INT_LEN * 2);
 +            if (chunk == NULL) {
 +                return NGX_ERROR;
 +            }
 +
 +            b->start = chunk;
 +            b->end = chunk + NGX_HTTP_V3_VARLEN_INT_LEN * 2;
 +        }
 +
 +        b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module;
 +        b->memory = 0;
 +        b->temporary = 1;
 +        b->pos = chunk;
 +
 +        b->last = (u_char *) ngx_http_v3_encode_varlen_int(chunk,
 +                                                       NGX_HTTP_V3_FRAME_DATA);
 +        b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, size);
 +
 +        tl->next = out;
 +        out = tl;
 +
 +        h3c->payload_bytes += size;
 +    }
 +
 +    if (cl->buf->last_buf) {
 +        tl = ngx_http_v3_create_trailers(r, ctx);
 +        if (tl == NULL) {
 +            return NGX_ERROR;
 +        }
 +
 +        cl->buf->last_buf = 0;
 +
 +        *ll = tl;
 +
 +    } else {
 +        *ll = NULL;
 +    }
 +
 +    for (cl = out; cl; cl = cl->next) {
 +        h3c->total_bytes += cl->buf->last - cl->buf->pos;
 +    }
 +
 +    rc = ngx_http_next_body_filter(r, out);
 +
 +    ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out,
 +                            (ngx_buf_tag_t) &ngx_http_v3_filter_module);
 +
 +    return rc;
 +}
 +
 +
 +static ngx_chain_t *
 +ngx_http_v3_create_trailers(ngx_http_request_t *r,
 +    ngx_http_v3_filter_ctx_t *ctx)
 +{
 +    size_t                  len, n;
 +    u_char                 *p;
 +    ngx_buf_t              *b;
 +    ngx_uint_t              i;
 +    ngx_chain_t            *cl, *hl;
 +    ngx_list_part_t        *part;
 +    ngx_table_elt_t        *header;
 +    ngx_http_v3_session_t  *h3c;
 +
 +    h3c = ngx_http_v3_get_session(r->connection);
 +
 +    len = 0;
 +
 +    part = &r->headers_out.trailers.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);
 +    }
 +
 +    cl = ngx_chain_get_free_buf(r->pool, &ctx->free);
 +    if (cl == NULL) {
 +        return NULL;
 +    }
 +
 +    b = cl->buf;
 +
 +    b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module;
 +    b->memory = 0;
 +    b->last_buf = 1;
 +
 +    if (len == 0) {
 +        b->temporary = 0;
 +        b->pos = b->last = NULL;
 +        return cl;
 +    }
 +
 +    b->temporary = 1;
 +
 +    len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0);
 +
 +    b->pos = ngx_palloc(r->pool, len);
 +    if (b->pos == NULL) {
 +        return NULL;
 +    }
 +
 +    b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->pos,
 +                                                                 0, 0, 0);
 +
 +    part = &r->headers_out.trailers.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 trailer: \"%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);
 +    }
 +
 +    n = b->last - b->pos;
 +
 +    h3c->payload_bytes += n;
 +
 +    hl = ngx_chain_get_free_buf(r->pool, &ctx->free);
 +    if (hl == NULL) {
 +        return NULL;
 +    }
 +
 +    b = hl->buf;
 +    p = b->start;
 +
 +    if (p == NULL) {
 +        p = ngx_palloc(r->pool, NGX_HTTP_V3_VARLEN_INT_LEN * 2);
 +        if (p == NULL) {
 +            return NULL;
 +        }
 +
 +        b->start = p;
 +        b->end = p + NGX_HTTP_V3_VARLEN_INT_LEN * 2;
 +    }
 +
 +    b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module;
 +    b->memory = 0;
 +    b->temporary = 1;
 +    b->pos = p;
 +
 +    b->last = (u_char *) ngx_http_v3_encode_varlen_int(p,
 +                                                    NGX_HTTP_V3_FRAME_HEADERS);
 +    b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n);
 +
 +    hl->next = cl;
 +
 +    return hl;
 +}
 +
 +
 +static ngx_int_t
 +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_body_filter = ngx_http_top_body_filter;
 +    ngx_http_top_body_filter = ngx_http_v3_body_filter;
 +
 +    return NGX_OK;
 +}
Simple merge