changeset 8679:e1eb7f4ca9f1 quic

HTTP/3: refactored request parser. The change reduces diff to the default branch for src/http/ngx_http_request.c and src/http/ngx_http_parse.c.
author Roman Arutyunyan <arut@nginx.com>
date Fri, 22 Jan 2021 16:34:06 +0300
parents 3443ee341cc1
children 58acdba9b3b2
files src/http/modules/ngx_http_quic_module.c src/http/modules/ngx_http_quic_module.h src/http/ngx_http.h src/http/ngx_http_parse.c src/http/ngx_http_request.c src/http/ngx_http_request.h src/http/v3/ngx_http_v3.h src/http/v3/ngx_http_v3_request.c src/http/v3/ngx_http_v3_streams.c
diffstat 9 files changed, 468 insertions(+), 399 deletions(-) [+]
line wrap: on
line diff
--- a/src/http/modules/ngx_http_quic_module.c
+++ b/src/http/modules/ngx_http_quic_module.c
@@ -175,6 +175,43 @@ static ngx_http_variable_t  ngx_http_qui
 };
 
 
+ngx_int_t
+ngx_http_quic_init(ngx_connection_t *c)
+{
+    ngx_quic_conf_t           *qcf;
+    ngx_http_connection_t     *hc, *phc;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    hc = c->data;
+
+    hc->ssl = 1;
+
+    if (c->quic == NULL) {
+        c->log->connection = c->number;
+
+        qcf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_quic_module);
+
+        ngx_quic_run(c, qcf);
+
+        return NGX_DONE;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http init quic stream");
+
+    phc = c->quic->parent->data;
+
+    if (phc->ssl_servername) {
+        hc->ssl_servername = phc->ssl_servername;
+        hc->conf_ctx = phc->conf_ctx;
+
+        clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module);
+        ngx_set_connection_log(c, clcf->error_log);
+    }
+
+    return NGX_OK;
+}
+
+
 static ngx_int_t
 ngx_http_variable_quic(ngx_http_request_t *r,
     ngx_http_variable_value_t *v, uintptr_t data)
--- a/src/http/modules/ngx_http_quic_module.h
+++ b/src/http/modules/ngx_http_quic_module.h
@@ -18,7 +18,7 @@
 #define NGX_HTTP_QUIC_ALPN_DRAFT_FMT  "\x05hq-%02uD"
 
 
-extern ngx_module_t  ngx_http_quic_module;
+ngx_int_t ngx_http_quic_init(ngx_connection_t *c);
 
 
 #endif /* _NGX_HTTP_QUIC_H_INCLUDED_ */
--- a/src/http/ngx_http.h
+++ b/src/http/ngx_http.h
@@ -134,6 +134,11 @@ void ngx_http_handler(ngx_http_request_t
 void ngx_http_run_posted_requests(ngx_connection_t *c);
 ngx_int_t ngx_http_post_request(ngx_http_request_t *r,
     ngx_http_posted_request_t *pr);
+ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r,
+    ngx_str_t *host);
+ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool,
+    ngx_uint_t alloc);
+void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc);
 void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc);
 void ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc);
 
--- a/src/http/ngx_http_parse.c
+++ b/src/http/ngx_http_parse.c
@@ -143,7 +143,6 @@ ngx_http_parse_request_line(ngx_http_req
 
         /* HTTP methods: GET, HEAD, POST */
         case sw_start:
-            r->parse_start = p;
             r->request_start = p;
 
             if (ch == CR || ch == LF) {
@@ -896,7 +895,6 @@ ngx_http_parse_header_line(ngx_http_requ
 
         /* first char */
         case sw_start:
-            r->parse_start = p;
             r->header_name_start = p;
             r->invalid_header = 0;
 
--- a/src/http/ngx_http_request.c
+++ b/src/http/ngx_http_request.c
@@ -31,10 +31,6 @@ static ngx_int_t ngx_http_process_connec
 static ngx_int_t ngx_http_process_user_agent(ngx_http_request_t *r,
     ngx_table_elt_t *h, ngx_uint_t offset);
 
-static ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool,
-    ngx_uint_t alloc);
-static ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r,
-    ngx_str_t *host);
 static ngx_int_t ngx_http_find_virtual_server(ngx_connection_t *c,
     ngx_http_virtual_names_t *virtual_names, ngx_str_t *host,
     ngx_http_request_t *r, ngx_http_core_srv_conf_t **cscfp);
@@ -52,7 +48,6 @@ static void ngx_http_keepalive_handler(n
 static void ngx_http_set_lingering_close(ngx_connection_t *c);
 static void ngx_http_lingering_close_handler(ngx_event_t *ev);
 static ngx_int_t ngx_http_post_action(ngx_http_request_t *r);
-static void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t error);
 static void ngx_http_log_request(ngx_http_request_t *r);
 
 static u_char *ngx_http_log_error_handler(ngx_http_request_t *r,
@@ -303,54 +298,11 @@ ngx_http_init_connection(ngx_connection_
     hc->conf_ctx = hc->addr_conf->default_server->ctx;
 
 #if (NGX_HTTP_QUIC)
-
     if (hc->addr_conf->quic) {
-        ngx_quic_conf_t           *qcf;
-        ngx_http_connection_t     *phc;
-        ngx_http_core_loc_conf_t  *clcf;
-
-        hc->ssl = 1;
-
-#if (NGX_HTTP_V3)
-
-        if (hc->addr_conf->http3) {
-            ngx_int_t  rc;
-
-            rc = ngx_http_v3_init_connection(c);
-
-            if (rc == NGX_ERROR) {
-                ngx_http_close_connection(c);
-                return;
-            }
-
-            if (rc == NGX_DONE) {
-                return;
-            }
-        }
-
-#endif
-
-        if (c->quic == NULL) {
-            c->log->connection = c->number;
-
-            qcf = ngx_http_get_module_srv_conf(hc->conf_ctx,
-                                               ngx_http_quic_module);
-            ngx_quic_run(c, qcf);
+        if (ngx_http_quic_init(c) == NGX_DONE) {
             return;
         }
-
-        phc = c->quic->parent->data;
-
-        if (phc->ssl_servername) {
-            hc->ssl_servername = phc->ssl_servername;
-            hc->conf_ctx = phc->conf_ctx;
-
-            clcf = ngx_http_get_module_loc_conf(hc->conf_ctx,
-                                                ngx_http_core_module);
-            ngx_set_connection_log(c, clcf->error_log);
-        }
     }
-
 #endif
 
     ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t));
@@ -380,6 +332,13 @@ ngx_http_init_connection(ngx_connection_
     }
 #endif
 
+#if (NGX_HTTP_V3)
+    if (hc->addr_conf->http3) {
+        ngx_http_v3_init(c);
+        return;
+    }
+#endif
+
 #if (NGX_HTTP_SSL)
     {
     ngx_http_ssl_srv_conf_t  *sscf;
@@ -669,12 +628,6 @@ ngx_http_alloc_request(ngx_connection_t 
     r->method = NGX_HTTP_UNKNOWN;
     r->http_version = NGX_HTTP_VERSION_10;
 
-#if (NGX_HTTP_V3)
-    if (hc->addr_conf->http3) {
-        r->http_version = NGX_HTTP_VERSION_30;
-    }
-#endif
-
     r->headers_in.content_length_n = -1;
     r->headers_in.keep_alive_n = -1;
     r->headers_out.content_length_n = -1;
@@ -1140,16 +1093,7 @@ ngx_http_process_request_line(ngx_event_
             }
         }
 
-        switch (r->http_version) {
-#if (NGX_HTTP_V3)
-        case NGX_HTTP_VERSION_30:
-            rc = ngx_http_v3_parse_request(r, r->header_in);
-            break;
-#endif
-
-        default: /* HTTP/1.x */
-            rc = ngx_http_parse_request_line(r, r->header_in);
-        }
+        rc = ngx_http_parse_request_line(r, r->header_in);
 
         if (rc == NGX_OK) {
 
@@ -1157,7 +1101,7 @@ ngx_http_process_request_line(ngx_event_
 
             r->request_line.len = r->request_end - r->request_start;
             r->request_line.data = r->request_start;
-            r->request_length = r->header_in->pos - r->parse_start;
+            r->request_length = r->header_in->pos - r->request_start;
 
             ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                            "http request line: \"%V\"", &r->request_line);
@@ -1234,15 +1178,6 @@ ngx_http_process_request_line(ngx_event_
             break;
         }
 
-        if (rc == NGX_BUSY) {
-            if (ngx_handle_read_event(rev, 0) != NGX_OK) {
-                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
-                return;
-            }
-
-            break;
-        }
-
         if (rc != NGX_AGAIN) {
 
             /* there was error while a request line parsing */
@@ -1272,8 +1207,8 @@ ngx_http_process_request_line(ngx_event_
             }
 
             if (rv == NGX_DECLINED) {
-                r->request_line.len = r->header_in->end - r->parse_start;
-                r->request_line.data = r->parse_start;
+                r->request_line.len = r->header_in->end - r->request_start;
+                r->request_line.data = r->request_start;
 
                 ngx_log_error(NGX_LOG_INFO, c->log, 0,
                               "client sent too long URI");
@@ -1437,7 +1372,7 @@ ngx_http_process_request_headers(ngx_eve
 
     cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
 
-    rc = NGX_OK;
+    rc = NGX_AGAIN;
 
     for ( ;; ) {
 
@@ -1453,7 +1388,7 @@ ngx_http_process_request_headers(ngx_eve
                 }
 
                 if (rv == NGX_DECLINED) {
-                    p = r->parse_start;
+                    p = r->header_name_start;
 
                     r->lingering_close = 1;
 
@@ -1473,7 +1408,7 @@ ngx_http_process_request_headers(ngx_eve
 
                     ngx_log_error(NGX_LOG_INFO, c->log, 0,
                                 "client sent too long header line: \"%*s...\"",
-                                len, r->parse_start);
+                                len, r->header_name_start);
 
                     ngx_http_finalize_request(r,
                                             NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
@@ -1491,32 +1426,21 @@ ngx_http_process_request_headers(ngx_eve
         /* the host header could change the server configuration context */
         cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
 
-        switch (r->http_version) {
-#if (NGX_HTTP_V3)
-        case NGX_HTTP_VERSION_30:
-            rc = ngx_http_v3_parse_header(r, r->header_in,
-                                          cscf->underscores_in_headers);
-            break;
-#endif
-
-        default: /* HTTP/1.x */
-            rc = ngx_http_parse_header_line(r, r->header_in,
-                                            cscf->underscores_in_headers);
-        }
+        rc = ngx_http_parse_header_line(r, r->header_in,
+                                        cscf->underscores_in_headers);
 
         if (rc == NGX_OK) {
 
-            r->request_length += r->header_in->pos - r->parse_start;
+            r->request_length += r->header_in->pos - r->header_name_start;
 
             if (r->invalid_header && cscf->ignore_invalid_headers) {
 
                 /* there was error while a header line parsing */
 
                 ngx_log_error(NGX_LOG_INFO, c->log, 0,
-                              "client sent invalid header line: \"%*s: %*s\"",
-                              r->header_name_end - r->header_name_start,
-                              r->header_name_start,
-                              r->header_end - r->header_start, r->header_start);
+                              "client sent invalid header line: \"%*s\"",
+                              r->header_end - r->header_name_start,
+                              r->header_name_start);
                 continue;
             }
 
@@ -1532,17 +1456,11 @@ ngx_http_process_request_headers(ngx_eve
 
             h->key.len = r->header_name_end - r->header_name_start;
             h->key.data = r->header_name_start;
-
-            if (h->key.data[h->key.len]) {
-                h->key.data[h->key.len] = '\0';
-            }
+            h->key.data[h->key.len] = '\0';
 
             h->value.len = r->header_end - r->header_start;
             h->value.data = r->header_start;
-
-            if (h->value.data[h->value.len]) {
-                h->value.data[h->value.len] = '\0';
-            }
+            h->value.data[h->value.len] = '\0';
 
             h->lowcase_key = ngx_pnalloc(r->pool, h->key.len);
             if (h->lowcase_key == NULL) {
@@ -1578,7 +1496,7 @@ ngx_http_process_request_headers(ngx_eve
             ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                            "http header done");
 
-            r->request_length += r->header_in->pos - r->parse_start;
+            r->request_length += r->header_in->pos - r->header_name_start;
 
             r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;
 
@@ -1693,7 +1611,7 @@ ngx_http_alloc_large_header_buffer(ngx_h
         return NGX_OK;
     }
 
-    old = r->parse_start;
+    old = request_line ? r->request_start : r->header_name_start;
 
     cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
 
@@ -1771,14 +1689,6 @@ ngx_http_alloc_large_header_buffer(ngx_h
     b->pos = new + (r->header_in->pos - old);
     b->last = new + (r->header_in->pos - old);
 
-    r->parse_start = new;
-
-    r->header_in = b;
-
-    if (r->http_version > NGX_HTTP_VERSION_11) {
-        return NGX_OK;
-    }
-
     if (request_line) {
         r->request_start = new;
 
@@ -1827,6 +1737,8 @@ ngx_http_alloc_large_header_buffer(ngx_h
         r->header_end = new + (r->header_end - old);
     }
 
+    r->header_in = b;
+
     return NGX_OK;
 }
 
@@ -2047,46 +1959,13 @@ ngx_http_process_request_header(ngx_http
         return NGX_ERROR;
     }
 
-    if (r->headers_in.host == NULL && r->http_version == NGX_HTTP_VERSION_11) {
+    if (r->headers_in.host == NULL && r->http_version > NGX_HTTP_VERSION_10) {
         ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                    "client sent HTTP/1.1 request without \"Host\" header");
         ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
         return NGX_ERROR;
     }
 
-    if (r->headers_in.host == NULL && r->http_version == NGX_HTTP_VERSION_20) {
-        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
-                      "client sent HTTP/2 request without "
-                      "\":authority\" or \"Host\" header");
-        ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
-        return NGX_ERROR;
-    }
-
-    if (r->http_version == NGX_HTTP_VERSION_30) {
-        if (r->headers_in.server.len == 0) {
-            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
-                          "client sent HTTP/3 request without "
-                          "\":authority\" or \"Host\" header");
-            ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
-            return NGX_ERROR;
-        }
-
-        if (r->headers_in.host) {
-            if (r->headers_in.host->value.len != r->headers_in.server.len
-                || ngx_memcmp(r->headers_in.host->value.data,
-                              r->headers_in.server.data,
-                              r->headers_in.server.len)
-                   != 0)
-            {
-                ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
-                              "client sent HTTP/3 request with different "
-                              "values of \":authority\" and \"Host\" headers");
-                ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
-                return NGX_ERROR;
-            }
-        }
-    }
-
     if (r->headers_in.content_length) {
         r->headers_in.content_length_n =
                             ngx_atoof(r->headers_in.content_length->value.data,
@@ -2125,12 +2004,6 @@ ngx_http_process_request_header(ngx_http
         }
     }
 
-#if (NGX_HTTP_V3)
-    if (r->http_version == NGX_HTTP_VERSION_30) {
-        r->headers_in.chunked = 1;
-    }
-#endif
-
     if (r->headers_in.connection_type == NGX_HTTP_CONNECTION_KEEP_ALIVE) {
         if (r->headers_in.keep_alive) {
             r->headers_in.keep_alive_n =
@@ -2235,7 +2108,7 @@ ngx_http_process_request(ngx_http_reques
 }
 
 
-static ngx_int_t
+ngx_int_t
 ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc)
 {
     u_char  *h, ch;
@@ -2326,7 +2199,7 @@ ngx_http_validate_host(ngx_str_t *host, 
 }
 
 
-static ngx_int_t
+ngx_int_t
 ngx_http_set_virtual_server(ngx_http_request_t *r, ngx_str_t *host)
 {
     ngx_int_t                  rc;
@@ -3744,7 +3617,7 @@ ngx_http_post_action(ngx_http_request_t 
 }
 
 
-static void
+void
 ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc)
 {
     ngx_connection_t  *c;
@@ -3965,15 +3838,15 @@ ngx_http_log_error_handler(ngx_http_requ
     len -= p - buf;
     buf = p;
 
-    if (r->request_line.data == NULL && r->parse_start) {
-        for (p = r->parse_start; p < r->header_in->last; p++) {
+    if (r->request_line.data == NULL && r->request_start) {
+        for (p = r->request_start; p < r->header_in->last; p++) {
             if (*p == CR || *p == LF) {
                 break;
             }
         }
 
-        r->request_line.len = p - r->parse_start;
-        r->request_line.data = r->parse_start;
+        r->request_line.len = p - r->request_start;
+        r->request_line.data = r->request_start;
     }
 
     if (r->request_line.len) {
--- a/src/http/ngx_http_request.h
+++ b/src/http/ngx_http_request.h
@@ -325,6 +325,7 @@ typedef struct {
 
     unsigned                          ssl:1;
     unsigned                          proxy_protocol:1;
+    unsigned                          http3:1;
 } ngx_http_connection_t;
 
 
@@ -581,7 +582,6 @@ struct ngx_http_request_s {
      * via ngx_http_ephemeral_t
      */
 
-    u_char                           *parse_start;
     u_char                           *uri_start;
     u_char                           *uri_end;
     u_char                           *uri_ext;
--- a/src/http/v3/ngx_http_v3.h
+++ b/src/http/v3/ngx_http_v3.h
@@ -127,17 +127,11 @@ typedef struct {
     uint64_t                      next_push_id;
     uint64_t                      max_push_id;
 
-    ngx_uint_t                    settings_sent;
-                                               /* unsigned  settings_sent:1; */
     ngx_connection_t             *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM];
 } ngx_http_v3_connection_t;
 
 
-ngx_int_t ngx_http_v3_init_connection(ngx_connection_t *c);
-
-ngx_int_t ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b);
-ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b,
-    ngx_uint_t allow_underscores);
+void ngx_http_v3_init(ngx_connection_t *c);
 ngx_int_t ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b,
     ngx_http_chunked_t *ctx);
 
@@ -157,6 +151,8 @@ uintptr_t ngx_http_v3_encode_header_pbi(
 uintptr_t ngx_http_v3_encode_header_lpbi(u_char *p, ngx_uint_t index,
     u_char *data, size_t len);
 
+ngx_int_t ngx_http_v3_init_session(ngx_connection_t *c);
+void ngx_http_v3_init_uni_stream(ngx_connection_t *c);
 ngx_connection_t *ngx_http_v3_create_push_stream(ngx_connection_t *c,
     uint64_t push_id);
 ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic,
--- a/src/http/v3/ngx_http_v3_request.c
+++ b/src/http/v3/ngx_http_v3_request.c
@@ -10,8 +10,13 @@
 #include <ngx_http.h>
 
 
+static void ngx_http_v3_process_request(ngx_event_t *rev);
+static ngx_int_t ngx_http_v3_process_header(ngx_http_request_t *r,
+    ngx_str_t *name, ngx_str_t *value);
 static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r,
     ngx_str_t *name, ngx_str_t *value);
+static ngx_int_t ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r);
+static ngx_int_t ngx_http_v3_process_request_header(ngx_http_request_t *r);
 
 
 static const struct {
@@ -37,230 +42,256 @@ static const struct {
 };
 
 
-ngx_int_t
-ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b)
+void
+ngx_http_v3_init(ngx_connection_t *c)
 {
-    size_t                        len;
-    u_char                       *p;
-    ngx_int_t                     rc, n;
-    ngx_str_t                    *name, *value;
+    size_t                     size;
+    ngx_buf_t                 *b;
+    ngx_event_t               *rev;
+    ngx_http_request_t        *r;
+    ngx_http_connection_t     *hc;
+    ngx_http_core_srv_conf_t  *cscf;
+
+    if (ngx_http_v3_init_session(c) != NGX_OK) {
+        ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
+                                        "internal error");
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) {
+        ngx_http_v3_init_uni_stream(c);
+        return;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init request stream");
+
+    hc = c->data;
+
+    cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);
+
+    size = cscf->client_header_buffer_size;
+
+    b = c->buffer;
+
+    if (b == NULL) {
+        b = ngx_create_temp_buf(c->pool, size);
+        if (b == NULL) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        c->buffer = b;
+
+    } else if (b->start == NULL) {
+
+        b->start = ngx_palloc(c->pool, size);
+        if (b->start == NULL) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        b->pos = b->start;
+        b->last = b->start;
+        b->end = b->last + size;
+    }
+
+    c->log->action = "reading client request";
+
+    r = ngx_http_create_request(c);
+    if (r == NULL) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    r->http_version = NGX_HTTP_VERSION_30;
+
+    c->data = r;
+
+    rev = c->read;
+    rev->handler = ngx_http_v3_process_request;
+
+    ngx_http_v3_process_request(rev);
+}
+
+
+static void
+ngx_http_v3_process_request(ngx_event_t *rev)
+{
+    ssize_t                       n;
+    ngx_buf_t                    *b;
+    ngx_int_t                     rc;
     ngx_connection_t             *c;
+    ngx_http_request_t           *r;
+    ngx_http_core_srv_conf_t     *cscf;
     ngx_http_v3_parse_headers_t  *st;
 
-    c = r->connection;
+    c = rev->data;
+    r = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http3 process request");
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+        c->timedout = 1;
+        ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
+        return;
+    }
+
     st = r->h3_parse;
 
     if (st == NULL) {
-        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header");
-
         st = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_headers_t));
         if (st == NULL) {
-            goto failed;
+            ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+            return;
         }
 
         r->h3_parse = st;
-        r->parse_start = b->pos;
-        r->state = 1;
     }
 
-    while (b->pos < b->last) {
+    b = r->header_in;
+
+    for ( ;; ) {
+
+        if (b->pos == b->last) {
+
+            if (!rev->ready) {
+                break;
+            }
+
+            n = c->recv(c, b->start, b->end - b->start);
+
+            if (n == NGX_AGAIN) {
+                if (!rev->timer_set) {
+                    cscf = ngx_http_get_module_srv_conf(r,
+                                                        ngx_http_core_module);
+                    ngx_add_timer(rev, cscf->client_header_timeout);
+                }
+
+                if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+                    ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+                }
+
+                break;
+            }
+
+            if (n == 0) {
+                ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                              "client prematurely closed connection");
+            }
+
+            if (n == 0 || n == NGX_ERROR) {
+                c->error = 1;
+                c->log->action = "reading client request";
+
+                ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+                break;
+            }
+
+            b->pos = b->start;
+            b->last = b->start + n;
+        }
+
         rc = ngx_http_v3_parse_headers(c, st, *b->pos);
 
         if (rc > 0) {
             ngx_http_v3_finalize_connection(c, rc,
                                             "could not parse request headers");
-            goto failed;
+            ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+            break;
         }
 
         if (rc == NGX_ERROR) {
-            goto failed;
+            ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
+                                            "internal error");
+            ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+            break;
         }
 
         if (rc == NGX_BUSY) {
-            return NGX_BUSY;
+            if (rev->error) {
+                ngx_http_close_request(r, NGX_HTTP_CLOSE);
+                break;
+            }
+
+            if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+            }
+
+            break;
         }
 
         b->pos++;
+        r->request_length++;
 
         if (rc == NGX_AGAIN) {
             continue;
         }
 
-        name = &st->header_rep.header.name;
-        value = &st->header_rep.header.value;
-
-        n = ngx_http_v3_process_pseudo_header(r, name, value);
-
-        if (n == NGX_ERROR) {
-            goto failed;
-        }
+        /* rc == NGX_OK || rc == NGX_DONE */
 
-        if (n == NGX_OK && rc == NGX_OK) {
-            continue;
-        }
-
-        len = r->method_name.len + 1
-            + (r->uri_end - r->uri_start) + 1
-            + sizeof("HTTP/3.0") - 1;
-
-        p = ngx_pnalloc(c->pool, len);
-        if (p == NULL) {
-            goto failed;
+        if (ngx_http_v3_process_header(r, &st->header_rep.header.name,
+                                       &st->header_rep.header.value)
+            != NGX_OK)
+        {
+            break;
         }
 
-        r->request_start = p;
+        if (rc == NGX_DONE) {
+            if (ngx_http_v3_process_request_header(r) != NGX_OK) {
+                break;
+            }
 
-        p = ngx_cpymem(p, r->method_name.data, r->method_name.len);
-        r->method_end = p - 1;
-        *p++ = ' ';
-        p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start);
-        *p++ = ' ';
-        r->http_protocol.data = p;
-        p = ngx_cpymem(p, "HTTP/3.0", sizeof("HTTP/3.0") - 1);
-
-        r->request_end = p;
-        r->state = 0;
-
-        return NGX_OK;
+            ngx_http_process_request(r);
+            break;
+        }
     }
 
-    return NGX_AGAIN;
+    ngx_http_run_posted_requests(c);
 
-failed:
-
-    return NGX_HTTP_PARSE_INVALID_REQUEST;
+    return;
 }
 
 
-ngx_int_t
-ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b,
-    ngx_uint_t allow_underscores)
+static ngx_int_t
+ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name,
+    ngx_str_t *value)
 {
-    u_char                        ch;
-    ngx_int_t                     rc;
-    ngx_str_t                    *name, *value;
-    ngx_uint_t                    hash, i, n;
-    ngx_connection_t             *c;
-    ngx_http_v3_parse_headers_t  *st;
-    enum {
-        sw_start = 0,
-        sw_done,
-        sw_next,
-        sw_header
-    };
-
-    c = r->connection;
-    st = r->h3_parse;
-
-    switch (r->state) {
-
-    case sw_start:
-        r->parse_start = b->pos;
+    ngx_table_elt_t            *h;
+    ngx_http_header_t          *hh;
+    ngx_http_core_main_conf_t  *cmcf;
 
-        if (st->state) {
-            r->state = sw_next;
-            goto done;
-        }
-
-        name = &st->header_rep.header.name;
-
-        if (name->len && name->data[0] != ':') {
-            r->state = sw_done;
-            goto done;
-        }
+    if (name->len &&  name->data[0] == ':') {
+        return ngx_http_v3_process_pseudo_header(r, name, value);
+    }
 
-        /* fall through */
-
-    case sw_done:
-        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                       "http3 parse header done");
-        return NGX_HTTP_PARSE_HEADER_DONE;
-
-    case sw_next:
-        r->parse_start = b->pos;
-        r->invalid_header = 0;
-        break;
-
-    case sw_header:
-        break;
+    if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) {
+        return NGX_ERROR;
     }
 
-    while (b->pos < b->last) {
-        rc = ngx_http_v3_parse_headers(c, st, *b->pos++);
-
-        if (rc > 0) {
-            ngx_http_v3_finalize_connection(c, rc,
-                                            "could not parse request headers");
-            return NGX_HTTP_PARSE_INVALID_HEADER;
-        }
-
-        if (rc == NGX_ERROR) {
-            return NGX_HTTP_PARSE_INVALID_HEADER;
-        }
-
-        if (rc == NGX_DONE) {
-            r->state = sw_done;
-            goto done;
-        }
-
-        if (rc == NGX_OK) {
-            r->state = sw_next;
-            goto done;
-        }
+    h = ngx_list_push(&r->headers_in.headers);
+    if (h == NULL) {
+        ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NGX_ERROR;
     }
 
-    r->state = sw_header;
-    return NGX_AGAIN;
-
-done:
-
-    name = &st->header_rep.header.name;
-    value = &st->header_rep.header.value;
-
-    r->header_name_start = name->data;
-    r->header_name_end = name->data + name->len;
-    r->header_start = value->data;
-    r->header_end = value->data + value->len;
-
-    hash = 0;
-    i = 0;
-
-    for (n = 0; n < name->len; n++) {
-        ch = name->data[n];
+    h->key = *name;
+    h->value = *value;
+    h->lowcase_key = h->key.data;
+    h->hash = ngx_hash_key(h->key.data, h->key.len);
 
-        if (ch >= 'A' && ch <= 'Z') {
-            /*
-             * A request or response containing uppercase
-             * header field names MUST be treated as malformed
-             */
-            return NGX_HTTP_PARSE_INVALID_HEADER;
-        }
-
-        if (ch == '\0') {
-            return NGX_HTTP_PARSE_INVALID_HEADER;
-        }
+    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
 
-        if (ch == '_' && !allow_underscores) {
-            r->invalid_header = 1;
-            continue;
-        }
+    hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
+                       h->lowcase_key, h->key.len);
 
-        if ((ch < 'a' || ch > 'z')
-            && (ch < '0' || ch > '9')
-            && ch != '-' && ch != '_')
-        {
-            r->invalid_header = 1;
-            continue;
-        }
-
-        hash = ngx_hash(hash, ch);
-        r->lowcase_header[i++] = ch;
-        i &= (NGX_HTTP_LC_HEADER_LEN - 1);
+    if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
+        return NGX_ERROR;
     }
 
-    r->header_hash = hash;
-    r->lowcase_index = i;
-
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http3 header: \"%V: %V\"", name, value);
     return NGX_OK;
 }
 
@@ -269,75 +300,210 @@ static ngx_int_t
 ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name,
     ngx_str_t *value)
 {
-    ngx_uint_t         i;
-    ngx_connection_t  *c;
+    ngx_uint_t  i;
 
-    if (name->len == 0 || name->data[0] != ':') {
-        return NGX_DONE;
+    if (r->request_line.len) {
+        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                      "client sent out of order pseudo-headers");
+        goto failed;
     }
 
-    c = r->connection;
+    if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) {
 
-    if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) {
         r->method_name = *value;
 
         for (i = 0; i < sizeof(ngx_http_v3_methods)
                         / sizeof(ngx_http_v3_methods[0]); i++)
         {
             if (value->len == ngx_http_v3_methods[i].name.len
-                && ngx_strncmp(value->data, ngx_http_v3_methods[i].name.data,
-                               value->len) == 0)
+                && ngx_strncmp(value->data,
+                               ngx_http_v3_methods[i].name.data, value->len)
+                   == 0)
             {
                 r->method = ngx_http_v3_methods[i].method;
                 break;
             }
         }
 
-        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                        "http3 method \"%V\" %ui", value, r->method);
         return NGX_OK;
     }
 
     if (name->len == 5 && ngx_strncmp(name->data, ":path", 5) == 0) {
+
         r->uri_start = value->data;
         r->uri_end = value->data + value->len;
 
         if (ngx_http_parse_uri(r) != NGX_OK) {
-            ngx_log_error(NGX_LOG_INFO, c->log, 0,
-                          "client sent invalid :path header: \"%V\"", value);
-            return NGX_ERROR;
+            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                          "client sent invalid \":path\" header: \"%V\"",
+                          value);
+            goto failed;
         }
 
-        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                        "http3 path \"%V\"", value);
-
         return NGX_OK;
     }
 
     if (name->len == 7 && ngx_strncmp(name->data, ":scheme", 7) == 0) {
-        r->schema_start = value->data;
-        r->schema_end = value->data + value->len;
+
+        r->schema = *value;
 
-        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                        "http3 schema \"%V\"", value);
-
         return NGX_OK;
     }
 
     if (name->len == 10 && ngx_strncmp(name->data, ":authority", 10) == 0) {
+
         r->host_start = value->data;
         r->host_end = value->data + value->len;
 
-        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                        "http3 authority \"%V\"", value);
+        return NGX_OK;
+    }
 
+    ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                  "client sent unknown pseudo-header \"%V\"", name);
+
+failed:
+
+    ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r)
+{
+    size_t      len;
+    u_char     *p;
+    ngx_int_t   rc;
+    ngx_str_t   host;
+
+    if (r->request_line.len) {
         return NGX_OK;
     }
 
-    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                   "http3 unknown pseudo header \"%V\" \"%V\"", name, value);
+    len = r->method_name.len + 1
+          + (r->uri_end - r->uri_start) + 1
+          + sizeof("HTTP/3.0") - 1;
+
+    p = ngx_pnalloc(r->pool, len);
+    if (p == NULL) {
+        ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NGX_ERROR;
+    }
+
+    r->request_line.data = p;
+
+    p = ngx_cpymem(p, r->method_name.data, r->method_name.len);
+    *p++ = ' ';
+    p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start);
+    *p++ = ' ';
+    p = ngx_cpymem(p, "HTTP/3.0", sizeof("HTTP/3.0") - 1);
+
+    r->request_line.len = p - r->request_line.data;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http3 request line: \"%V\"", &r->request_line);
+
+    ngx_str_set(&r->http_protocol, "HTTP/3.0");
+
+    if (ngx_http_process_request_uri(r) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (r->host_end) {
+
+        host.len = r->host_end - r->host_start;
+        host.data = r->host_start;
+
+        rc = ngx_http_validate_host(&host, r->pool, 0);
+
+        if (rc == NGX_DECLINED) {
+            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                          "client sent invalid host in request line");
+            goto failed;
+        }
+
+        if (rc == NGX_ERROR) {
+            ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return NGX_ERROR;
+        }
+
+        if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+
+        r->headers_in.server = host;
+    }
+
+    if (ngx_list_init(&r->headers_in.headers, r->pool, 20,
+                      sizeof(ngx_table_elt_t))
+        != NGX_OK)
+    {
+        ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NGX_ERROR;
+    }
 
     return NGX_OK;
+
+failed:
+
+    ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_http_v3_process_request_header(ngx_http_request_t *r)
+{
+    if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (r->headers_in.server.len == 0) {
+        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                      "client sent neither \":authority\" nor \"Host\" header");
+        goto failed;
+    }
+
+    if (r->headers_in.host) {
+        if (r->headers_in.host->value.len != r->headers_in.server.len
+            || ngx_memcmp(r->headers_in.host->value.data,
+                          r->headers_in.server.data,
+                          r->headers_in.server.len)
+               != 0)
+        {
+            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                          "client sent \":authority\" and \"Host\" headers "
+                          "with different values");
+            goto failed;
+        }
+    }
+
+    if (r->headers_in.content_length) {
+        r->headers_in.content_length_n =
+                            ngx_atoof(r->headers_in.content_length->value.data,
+                                      r->headers_in.content_length->value.len);
+
+        if (r->headers_in.content_length_n == NGX_ERROR) {
+            ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                          "client sent invalid \"Content-Length\" header");
+            goto failed;
+        }
+    }
+
+    return NGX_OK;
+
+failed:
+
+    ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
+    return NGX_ERROR;
 }
 
 
--- a/src/http/v3/ngx_http_v3_streams.c
+++ b/src/http/v3/ngx_http_v3_streams.c
@@ -40,44 +40,49 @@ static ngx_int_t ngx_http_v3_send_settin
 
 
 ngx_int_t
-ngx_http_v3_init_connection(ngx_connection_t *c)
+ngx_http_v3_init_session(ngx_connection_t *c)
 {
-    ngx_http_connection_t     *hc;
-    ngx_http_v3_uni_stream_t  *us;
+    ngx_connection_t          *pc;
+    ngx_http_connection_t     *phc;
     ngx_http_v3_connection_t  *h3c;
 
-    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init connection");
-
-    hc = c->data;
+    pc = c->quic->parent;
+    phc = pc->data;
 
-    if (c->quic == NULL) {
-        h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_connection_t));
-        if (h3c == NULL) {
-            return NGX_ERROR;
-        }
-
-        h3c->hc = *hc;
-
-        ngx_queue_init(&h3c->blocked);
-        ngx_queue_init(&h3c->pushing);
-
-        c->data = h3c;
+    if (phc->http3) {
         return NGX_OK;
     }
 
-    if (ngx_http_v3_send_settings(c) == NGX_ERROR) {
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init session");
+
+    h3c = ngx_pcalloc(pc->pool, sizeof(ngx_http_v3_connection_t));
+    if (h3c == NULL) {
         return NGX_ERROR;
     }
 
-    if ((c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) {
-        return NGX_OK;
-    }
+    h3c->hc = *phc;
+    h3c->hc.http3 = 1;
+
+    ngx_queue_init(&h3c->blocked);
+    ngx_queue_init(&h3c->pushing);
+
+    pc->data = h3c;
+
+    return ngx_http_v3_send_settings(c);
+}
+
+
+void
+ngx_http_v3_init_uni_stream(ngx_connection_t *c)
+{
+    ngx_http_v3_uni_stream_t  *us;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init uni stream");
 
     us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t));
     if (us == NULL) {
-        ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
-                                        NULL);
-        return NGX_ERROR;
+        ngx_http_close_connection(c);
+        return;
     }
 
     us->index = -1;
@@ -88,8 +93,6 @@ ngx_http_v3_init_connection(ngx_connecti
     c->write->handler = ngx_http_v3_dummy_write_handler;
 
     ngx_http_v3_read_uni_stream_type(c->read);
-
-    return NGX_DONE;
 }
 
 
@@ -478,10 +481,6 @@ ngx_http_v3_send_settings(ngx_connection
 
     h3c = c->quic->parent->data;
 
-    if (h3c->settings_sent) {
-        return NGX_OK;
-    }
-
     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings");
 
     cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL);
@@ -512,17 +511,12 @@ ngx_http_v3_send_settings(ngx_connection
         goto failed;
     }
 
-    h3c->settings_sent = 1;
-
     return NGX_OK;
 
 failed:
 
     ngx_http_v3_close_uni_stream(cc);
 
-    ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR,
-                                    "could not send settings");
-
     return NGX_ERROR;
 }