Backport of:

From c0c8c96a8c6bb923589942d8b7754b81a0377cee Mon Sep 17 00:00:00 2001
From: Stefan Eissing <icing@apache.org>
Date: Wed, 24 Mar 2021 14:28:49 +0000
Subject: [PATCH]   *) mod_http2: sync with github standalone version 1.15.17  
    - Log requests and sent the configured error response in case of early
 detected        errors like too many or too long headers. [Ruediger Pluem]   
   - new option 'H2OutputBuffering on/off' which controls the buffering of
 stream output.        The default is on, which is the behaviour of older
 mod-h2 versions. When off, all        bytes are made available immediately to
 the main connection for sending them        out to the client. This fixes
 interop issues with certain flavours of gRPC, see        also
 <https://github.com/icing/mod_h2/issues/207>.

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1888011 13f79535-47bb-0310-9956-ffa450edef68
---
 docs/manual/mod/mod_http2.xml  |  22 ++++++
 modules/http2/h2_bucket_beam.c |   1 +
 modules/http2/h2_config.c      |  24 ++++++
 modules/http2/h2_config.h      |   1 +
 modules/http2/h2_h2.c          |   1 +
 modules/http2/h2_headers.c     |  15 ++++
 modules/http2/h2_headers.h     |   5 ++
 modules/http2/h2_mplx.c        |  17 ----
 modules/http2/h2_request.c     | 138 ++++++++++++++++++++++-----------
 modules/http2/h2_stream.h      |   3 +-
 modules/http2/h2_task.c        |  18 +++--
 modules/http2/h2_task.h        |   1 +
 modules/http2/h2_version.h     |   5 +-
 13 files changed, 180 insertions(+), 71 deletions(-)

#diff --git a/docs/manual/mod/mod_http2.xml b/docs/manual/mod/mod_http2.xml
#index 5cf8d0acccc..287e774d33c 100644
#--- a/docs/manual/mod/mod_http2.xml
#+++ b/docs/manual/mod/mod_http2.xml
#@@ -981,4 +981,26 @@ H2TLSCoolDownSecs 0
#             </p>
#         </usage>
#     </directivesynopsis>
#+    
#+    <directivesynopsis>
#+        <name>H2OutputBuffering</name>
#+        <description>Determine buffering behaviour of output</description>
#+        <syntax>H2OutputBuffering on/off</syntax>
#+        <default>H2OutputBuffering on</default>
#+        <contextlist>
#+            <context>server config</context>
#+            <context>virtual host</context>
#+        </contextlist>
#+        <compatibility>Available in version 2.4.48 and later.</compatibility>
#+        
#+        <usage>
#+            <p>
#+                The option 'H2OutputBuffering on/off' controls the buffering of stream output.
#+                The default is on, which is the behaviour of previous versions. When off, all
#+                bytes are made available immediately to the main connection for sending them
#+                out to the client. This fixes interop issues with certain flavours of gRPC.
#+            </p>
#+        </usage>
#+    </directivesynopsis>
#+    
# </modulesynopsis>
--- a/modules/http2/h2_bucket_beam.c
+++ b/modules/http2/h2_bucket_beam.c
@@ -1039,6 +1039,7 @@ transfer:
                 H2_BLIST_INSERT_TAIL(&beam->hold_list, bsender);
 
                 remain -= bsender->length;
+                beam->received_bytes += bsender->length;
                 ++transferred;
                 ++transferred_buckets;
                 continue;
--- a/modules/http2/h2_config.c
+++ b/modules/http2/h2_config.c
@@ -78,6 +78,7 @@ typedef struct h2_config {
     int early_hints;              /* support status code 103 */
     int padding_bits;
     int padding_always;
+    int output_buffered;
 } h2_config;
 
 typedef struct h2_dir_config {
@@ -115,6 +116,7 @@ static h2_config defconf = {
     0,                      /* early hints, http status 103 */
     0,                      /* padding bits */
     1,                      /* padding always */
+    1,                      /* strean output buffered */
 };
 
 static h2_dir_config defdconf = {
@@ -159,6 +161,7 @@ void *h2_config_create_svr(apr_pool_t *p
     conf->early_hints          = DEF_VAL;
     conf->padding_bits         = DEF_VAL;
     conf->padding_always       = DEF_VAL;
+    conf->output_buffered      = DEF_VAL;
     return conf;
 }
 
@@ -193,6 +196,7 @@ static void *h2_config_merge(apr_pool_t
     }
     n->push_diary_size      = H2_CONFIG_GET(add, base, push_diary_size);
     n->copy_files           = H2_CONFIG_GET(add, base, copy_files);
+    n->output_buffered      = H2_CONFIG_GET(add, base, output_buffered);
     if (add->push_list && base->push_list) {
         n->push_list        = apr_array_append(pool, base->push_list, add->push_list);
     }
@@ -287,6 +291,8 @@ static apr_int64_t h2_srv_config_geti64(
             return H2_CONFIG_GET(conf, &defconf, padding_bits);
         case H2_CONF_PADDING_ALWAYS:
             return H2_CONFIG_GET(conf, &defconf, padding_always);
+        case H2_CONF_OUTPUT_BUFFER:
+            return H2_CONFIG_GET(conf, &defconf, output_buffered);
         default:
             return DEF_VAL;
     }
@@ -352,6 +358,9 @@ static void h2_srv_config_seti(h2_config
         case H2_CONF_PADDING_ALWAYS:
             H2_CONFIG_SET(conf, padding_always, val);
             break;
+        case H2_CONF_OUTPUT_BUFFER:
+            H2_CONFIG_SET(conf, output_buffered, val);
+            break;
         default:
             break;
     }
@@ -905,6 +914,19 @@ static const char *h2_conf_set_padding(c
     return NULL;
 }
 
+static const char *h2_conf_set_output_buffer(cmd_parms *cmd,
+                                      void *dirconf, const char *value)
+{
+    if (!strcasecmp(value, "On")) {
+        CONFIG_CMD_SET(cmd, dirconf, H2_CONF_OUTPUT_BUFFER, 1);
+        return NULL;
+    }
+    else if (!strcasecmp(value, "Off")) {
+        CONFIG_CMD_SET(cmd, dirconf, H2_CONF_OUTPUT_BUFFER, 0);
+        return NULL;
+    }
+    return "value must be On or Off";
+}
 
 void h2_get_num_workers(server_rec *s, int *minw, int *maxw)
 {
@@ -976,6 +998,8 @@ const command_rec h2_cmds[] = {
                   RSRC_CONF, "on to enable interim status 103 responses"),
     AP_INIT_TAKE1("H2Padding", h2_conf_set_padding, NULL,
                   RSRC_CONF, "set payload padding"),
+    AP_INIT_TAKE1("H2OutputBuffering", h2_conf_set_output_buffer, NULL,
+                  RSRC_CONF, "set stream output buffer on/off"),
     AP_END_CMD
 };
 
--- a/modules/http2/h2_config.h
+++ b/modules/http2/h2_config.h
@@ -44,6 +44,7 @@ typedef enum {
     H2_CONF_EARLY_HINTS,
     H2_CONF_PADDING_BITS,
     H2_CONF_PADDING_ALWAYS,
+    H2_CONF_OUTPUT_BUFFER,
 } h2_config_var_t;
 
 struct apr_hash_t;
--- a/modules/http2/h2_h2.c
+++ b/modules/http2/h2_h2.c
@@ -751,6 +751,7 @@ static int h2_h2_late_fixups(request_rec
         if (task) {
             /* check if we copy vs. setaside files in this location */
             task->output.copy_files = h2_config_rgeti(r, H2_CONF_COPY_FILES);
+            task->output.buffered = h2_config_rgeti(r, H2_CONF_OUTPUT_BUFFER);
             if (task->output.copy_files) {
                 ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c,
                               "h2_secondary_out(%s): copy_files on", task->id);
--- a/modules/http2/h2_headers.c
+++ b/modules/http2/h2_headers.c
@@ -64,6 +64,7 @@ apr_bucket * h2_bucket_headers_make(apr_
 
     b = apr_bucket_shared_make(b, br, 0, 0);
     b->type = &h2_bucket_type_headers;
+    b->length = h2_headers_length(r);
     
     return b;
 } 
@@ -125,6 +126,20 @@ h2_headers *h2_headers_create(int status
     return headers;
 }
 
+static int add_header_lengths(void *ctx, const char *name, const char *value) 
+{
+    apr_size_t *plen = ctx;
+    *plen += strlen(name) + strlen(value); 
+    return 1;
+}
+
+apr_size_t h2_headers_length(h2_headers *headers)
+{
+    apr_size_t len = 0;
+    apr_table_do(add_header_lengths, &len, headers->headers, NULL);
+    return len;
+}
+
 h2_headers *h2_headers_rcreate(request_rec *r, int status,
                                  apr_table_t *header, apr_pool_t *pool)
 {
--- a/modules/http2/h2_headers.h
+++ b/modules/http2/h2_headers.h
@@ -82,4 +82,9 @@ h2_headers *h2_headers_die(apr_status_t
 
 int h2_headers_are_response(h2_headers *headers);
 
+/**
+ * Give the number of bytes of all contained header strings.
+ */
+apr_size_t h2_headers_length(h2_headers *headers);
+
 #endif /* defined(__mod_h2__h2_headers__) */
--- a/modules/http2/h2_mplx.c
+++ b/modules/http2/h2_mplx.c
@@ -91,10 +91,6 @@ apr_status_t h2_mplx_m_child_init(apr_po
 
 static void mst_check_data_for(h2_mplx *m, h2_stream *stream, int mplx_is_locked);
 
-static void mst_stream_output_consumed(void *ctx, h2_bucket_beam *beam, apr_off_t length)
-{
-}
-
 static void mst_stream_input_ev(void *ctx, h2_bucket_beam *beam)
 {
     h2_stream *stream = ctx;
@@ -299,18 +295,6 @@ static int m_stream_destroy_iter(void *c
         stream->task = NULL;
         secondary = task->c;
         if (secondary) {
-            /* On non-serialized requests, the IO logging has not accounted for any
-             * meta data send over the network: response headers and h2 frame headers. we
-             * counted this on the stream and need to add this now.
-             * This is supposed to happen before the EOR bucket triggers the
-             * logging of the transaction. *fingers crossed* */
-            if (task->request && !task->request->serialize && h2_task_logio_add_bytes_out) {
-                apr_off_t unaccounted = stream->out_frame_octets - stream->out_data_octets;
-                if (unaccounted > 0) {
-                    h2_task_logio_add_bytes_out(secondary, unaccounted);
-                }
-            }
-        
             if (m->s->keep_alive_max == 0 || secondary->keepalives < m->s->keep_alive_max) {
                 reuse_secondary = ((m->spare_secondary->nelts < (m->limit_active * 3 / 2))
                                    && !task->rst_error);
@@ -540,7 +524,6 @@ static apr_status_t t_out_open(h2_mplx *
                       "h2_mplx(%s): out open", stream->task->id);
     }
     
-    h2_beam_on_consumed(stream->output, NULL, mst_stream_output_consumed, stream);
     h2_beam_on_produced(stream->output, mst_output_produced, stream);
     if (stream->task->output.copy_files) {
         h2_beam_on_file_beam(stream->output, h2_beam_no_files, NULL);
--- a/modules/http2/h2_request.c
+++ b/modules/http2/h2_request.c
@@ -267,9 +267,7 @@ static request_rec *my_ap_create_request
 
 request_rec *h2_request_create_rec(const h2_request *req, conn_rec *c)
 {
-    int access_status = HTTP_OK;    
-    const char *rpath;
-    const char *s;
+    int access_status;
 
 #if AP_MODULE_MAGIC_AT_LEAST(20150222, 13)
     request_rec *r = ap_create_request(c);
@@ -277,59 +275,88 @@ request_rec *h2_request_create_rec(const
     request_rec *r = my_ap_create_request(c);
 #endif
 
-    r->headers_in = apr_table_clone(r->pool, req->headers);
-
+#if AP_MODULE_MAGIC_AT_LEAST(20200331, 3)
     ap_run_pre_read_request(r, c);
-    
+
     /* Time to populate r with the data we have. */
     r->request_time = req->request_time;
-    r->method = apr_pstrdup(r->pool, req->method);
-    /* Provide quick information about the request method as soon as known */
-    r->method_number = ap_method_number_of(r->method);
-    if (r->method_number == M_GET && r->method[0] == 'H') {
-        r->header_only = 1;
-    }
-    r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0", 
+    r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
                                   req->method, req->path ? req->path : "");
     r->headers_in = apr_table_clone(r->pool, req->headers);
 
-    rpath = (req->path ? req->path : "");
-    ap_parse_uri(r, rpath);
-    r->protocol = (char*)"HTTP/2.0";
-    r->proto_num = HTTP_VERSION(2, 0);
-
-    r->the_request = apr_psprintf(r->pool, "%s %s %s", 
-                                  r->method, rpath, r->protocol);
-    
-    /* update what we think the virtual host is based on the headers we've
-     * now read. may update status.
-     * Leave r->hostname empty, vhost will parse if form our Host: header,
-     * otherwise we get complains about port numbers.
+    /* Start with r->hostname = NULL, ap_check_request_header() will get it
+     * form Host: header, otherwise we get complains about port numbers.
      */
     r->hostname = NULL;
-    ap_update_vhost_from_headers(r);
-    r->protocol = "HTTP/2.0";
-    r->proto_num = HTTP_VERSION(2, 0);
 
-    /* we may have switched to another server */
-    r->per_dir_config = r->server->lookup_defaults;
-    
-    s = apr_table_get(r->headers_in, "Expect");
-    if (s && s[0]) {
-        if (ap_cstr_casecmp(s, "100-continue") == 0) {
-            r->expecting_100 = 1;
+    /* Validate HTTP/1 request and select vhost. */
+    if (!ap_parse_request_line(r) || !ap_check_request_header(r)) {
+        /* we may have switched to another server still */
+        r->per_dir_config = r->server->lookup_defaults;
+        if (req->http_status != H2_HTTP_STATUS_UNSET) {
+            access_status = req->http_status;
+            /* Be safe and close the connection */
+            c->keepalive = AP_CONN_CLOSE;
         }
         else {
-            r->status = HTTP_EXPECTATION_FAILED;
-            ap_send_error_response(r, 0);
+            access_status = r->status;
+        }
+        r->status = HTTP_OK;
+        goto die;
+    }
+#else
+    {
+        const char *s;
+
+        r->headers_in = apr_table_clone(r->pool, req->headers);
+        ap_run_pre_read_request(r, c);
+
+        /* Time to populate r with the data we have. */
+        r->request_time = req->request_time;
+        r->method = apr_pstrdup(r->pool, req->method);
+        /* Provide quick information about the request method as soon as known */
+        r->method_number = ap_method_number_of(r->method);
+        if (r->method_number == M_GET && r->method[0] == 'H') {
+            r->header_only = 1;
         }
+        ap_parse_uri(r, req->path ? req->path : "");
+        r->protocol = (char*)"HTTP/2.0";
+        r->proto_num = HTTP_VERSION(2, 0);
+        r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
+                                      r->method, req->path ? req->path : "");
+
+        /* Start with r->hostname = NULL, ap_check_request_header() will get it
+         * form Host: header, otherwise we get complains about port numbers.
+         */
+        r->hostname = NULL;
+        ap_update_vhost_from_headers(r);
+
+         /* we may have switched to another server */
+         r->per_dir_config = r->server->lookup_defaults;
+
+         s = apr_table_get(r->headers_in, "Expect");
+         if (s && s[0]) {
+            if (ap_cstr_casecmp(s, "100-continue") == 0) {
+                r->expecting_100 = 1;
+            }
+            else {
+                r->status = HTTP_EXPECTATION_FAILED;
+                access_status = r->status;
+                goto die;
+            }
+         }
     }
+#endif
+
+    /* we may have switched to another server */
+    r->per_dir_config = r->server->lookup_defaults;
 
     if (req->http_status != H2_HTTP_STATUS_UNSET) {
         access_status = req->http_status;
         r->status = HTTP_OK;
         /* Be safe and close the connection */
         c->keepalive = AP_CONN_CLOSE;
+        goto die;
     }
 
     /*
@@ -341,28 +368,47 @@ request_rec *h2_request_create_rec(const
     ap_add_input_filter_handle(ap_http_input_filter_handle,
                                NULL, r, r->connection);
     
-    if (access_status != HTTP_OK
-        || (access_status = ap_run_post_read_request(r))) {
+    if ((access_status = ap_run_post_read_request(r))) {
         /* Request check post hooks failed. An example of this would be a
          * request for a vhost where h2 is disabled --> 421.
          */
         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03367)
                       "h2_request: access_status=%d, request_create failed",
                       access_status);
-        ap_die(access_status, r);
-        ap_update_child_status(c->sbh, SERVER_BUSY_LOG, r);
-        ap_run_log_transaction(r);
-        r = NULL;
-        goto traceout;
+        goto die;
     }
 
     AP_READ_REQUEST_SUCCESS((uintptr_t)r, (char *)r->method, 
                             (char *)r->uri, (char *)r->server->defn_name, 
                             r->status);
     return r;
-traceout:
+
+die:
+    ap_die(access_status, r);
+
+    /* ap_die() sent the response through the output filters, we must now
+     * end the request with an EOR bucket for stream/pipeline accounting.
+     */
+    {
+        apr_bucket_brigade *eor_bb;
+#if AP_MODULE_MAGIC_AT_LEAST(20180905, 1)
+        eor_bb = ap_acquire_brigade(c);
+        APR_BRIGADE_INSERT_TAIL(eor_bb,
+                                ap_bucket_eor_create(c->bucket_alloc, r));
+        ap_pass_brigade(c->output_filters, eor_bb);
+        ap_release_brigade(c, eor_bb);
+#else
+        eor_bb = apr_brigade_create(c->pool, c->bucket_alloc);
+        APR_BRIGADE_INSERT_TAIL(eor_bb,
+                                ap_bucket_eor_create(c->bucket_alloc, r));
+        ap_pass_brigade(c->output_filters, eor_bb);
+        apr_brigade_destroy(eor_bb);
+#endif
+    }
+
+    r = NULL;
     AP_READ_REQUEST_FAILURE((uintptr_t)r);
-    return r;
+    return NULL;
 }
 
 
--- a/modules/http2/h2_stream.h
+++ b/modules/http2/h2_stream.h
@@ -92,7 +92,8 @@ struct h2_stream {
     unsigned int input_eof : 1; /* no more request data coming */
     unsigned int out_checked : 1; /* output eof was double checked */
     unsigned int push_policy;   /* which push policy to use for this request */
-    
+    unsigned int input_buffering : 1; /* buffer request bodies for efficiency */
+
     struct h2_task *task;       /* assigned task to fullfill request */
     
     const h2_priority *pref_priority; /* preferred priority for this stream */
--- a/modules/http2/h2_task.c
+++ b/modules/http2/h2_task.c
@@ -89,6 +89,14 @@ static apr_status_t open_output(h2_task
     return h2_mplx_t_out_open(task->mplx, task->stream_id, task->output.beam);
 }
 
+static void output_consumed(void *ctx, h2_bucket_beam *beam, apr_off_t length)
+{
+    h2_task *task = ctx;
+    if (task && h2_task_logio_add_bytes_out) {
+        h2_task_logio_add_bytes_out(task->c, length);
+    }
+}
+
 static apr_status_t send_out(h2_task *task, apr_bucket_brigade* bb, int block)
 {
     apr_off_t written, left;
@@ -108,9 +116,6 @@ static apr_status_t send_out(h2_task *ta
         status = APR_SUCCESS;
     }
     if (status == APR_SUCCESS) {
-        if (h2_task_logio_add_bytes_out) {
-            h2_task_logio_add_bytes_out(task->c, written);
-        }
         ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, task->c, 
                       "h2_task(%s): send_out done", task->id);
     }
@@ -183,7 +188,9 @@ send:
         }
     }
     
-    if (APR_SUCCESS == rv && !task->output.opened && flush) {
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, task->c,
+                  "h2_secondary_out(%s): buffered=%d", task->id, task->output.buffered);
+    if (APR_SUCCESS == rv && !task->output.opened && (flush || !task->output.buffered)) {
         /* got a flush or could not write all, time to tell someone to read */
         rv = open_output(task);
     }
@@ -596,7 +603,8 @@ apr_status_t h2_task_do(h2_task *task, a
     
     h2_beam_buffer_size_set(task->output.beam, task->output.max_buffer);
     h2_beam_send_from(task->output.beam, task->pool);
-    
+    h2_beam_on_consumed(task->output.beam, NULL, output_consumed, task);
+
     h2_ctx_create_for(c, task);
     apr_table_setn(c->notes, H2_TASK_ID_NOTE, task->id);
 
--- a/modules/http2/h2_task.h
+++ b/modules/http2/h2_task.h
@@ -71,6 +71,7 @@ struct h2_task {
         unsigned int opened : 1;
         unsigned int sent_response : 1;
         unsigned int copy_files : 1;
+        unsigned int buffered : 1;
         struct h2_response_parser *rparser;
         apr_bucket_brigade *bb;
         apr_size_t max_buffer;
