You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ic...@apache.org on 2022/04/07 10:41:47 UTC

svn commit: r1899648 - in /httpd/httpd/trunk: changes-entries/core_response_buckets.txt include/mod_core.h modules/http/http_core.c modules/http/http_filters.c modules/http/http_protocol.c server/protocol.c

Author: icing
Date: Thu Apr  7 10:41:46 2022
New Revision: 1899648

URL: http://svn.apache.org/viewvc?rev=1899648&view=rev
Log:
  *) core/mod_http: use RESPONSE meta buckets and a new HTTP/1.x specific
     filter to send responses through the output filter chain.
     Specifically: the HTTP_HEADER output filter and ap_send_interim_response()
     create a RESPONSE bucket and no longer are concerned with HTTP/1.x
     serialization.
     A new HTTP1_RESPONSE_OUT transcode filter writes the proper HTTP/1.x
     bytes when dealing with a RESPONSE bucket. That filter installs itself
     on the pre_read_request hook when the connection has protocol 'http/1.1'.


Added:
    httpd/httpd/trunk/changes-entries/core_response_buckets.txt
Modified:
    httpd/httpd/trunk/include/mod_core.h
    httpd/httpd/trunk/modules/http/http_core.c
    httpd/httpd/trunk/modules/http/http_filters.c
    httpd/httpd/trunk/modules/http/http_protocol.c
    httpd/httpd/trunk/server/protocol.c

Added: httpd/httpd/trunk/changes-entries/core_response_buckets.txt
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/changes-entries/core_response_buckets.txt?rev=1899648&view=auto
==============================================================================
--- httpd/httpd/trunk/changes-entries/core_response_buckets.txt (added)
+++ httpd/httpd/trunk/changes-entries/core_response_buckets.txt Thu Apr  7 10:41:46 2022
@@ -0,0 +1,9 @@
+  *) core/mod_http: use RESPONSE meta buckets and a new HTTP/1.x specific
+     filter to send responses through the output filter chain.
+     Specifically: the HTTP_HEADER output filter and ap_send_interim_response()
+     create a RESPONSE bucket and no longer are concerned with HTTP/1.x
+     serialization.
+     A new HTTP1_RESPONSE_OUT transcode filter writes the proper HTTP/1.x
+     bytes when dealing with a RESPONSE bucket. That filter installs itself
+     on the pre_read_request hook when the connection has protocol 'http/1.1'.
+     [Stefan Eissing]
\ No newline at end of file

Modified: httpd/httpd/trunk/include/mod_core.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/include/mod_core.h?rev=1899648&r1=1899647&r2=1899648&view=diff
==============================================================================
--- httpd/httpd/trunk/include/mod_core.h (original)
+++ httpd/httpd/trunk/include/mod_core.h Thu Apr  7 10:41:46 2022
@@ -31,6 +31,7 @@
 #include "apr_buckets.h"
 
 #include "httpd.h"
+#include "http_protocol.h"
 #include "util_filter.h"
 
 
@@ -57,6 +58,9 @@ apr_status_t ap_h1_body_in_filter(ap_fil
                                      ap_input_mode_t mode, apr_read_type_e block,
                                      apr_off_t readbytes);
 
+/* HTTP/1.1 response formatting filter. */
+apr_status_t ap_h1_response_out_filter(ap_filter_t *f, apr_bucket_brigade *b);
+
 /* HTTP/1.1 chunked transfer encoding filter. */
 apr_status_t ap_http_chunk_filter(ap_filter_t *f, apr_bucket_brigade *b);
 
@@ -100,6 +104,14 @@ AP_CORE_DECLARE(void) ap_init_rng(apr_po
 /* Update RNG state in parent after fork */
 AP_CORE_DECLARE(void) ap_random_parent_after_fork(void);
 
+/**
+ * Set the keepalive status for this request based on the response
+ * @param r The current request
+ * @param resp The response being send
+ * @return 1 if keepalive can be set, 0 otherwise
+ */
+AP_CORE_DECLARE(int) ap_h1_set_keepalive(request_rec *r, ap_bucket_response *resp);
+
 #ifdef __cplusplus
 }
 #endif

Modified: httpd/httpd/trunk/modules/http/http_core.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http/http_core.c?rev=1899648&r1=1899647&r2=1899648&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http/http_core.c (original)
+++ httpd/httpd/trunk/modules/http/http_core.c Thu Apr  7 10:41:46 2022
@@ -38,6 +38,7 @@
 AP_DECLARE_DATA ap_filter_rec_t *ap_http_input_filter_handle;
 AP_DECLARE_DATA ap_filter_rec_t *ap_h1_body_in_filter_handle;
 AP_DECLARE_DATA ap_filter_rec_t *ap_http_header_filter_handle;
+AP_DECLARE_DATA ap_filter_rec_t *ap_h1_response_out_filter_handle;
 AP_DECLARE_DATA ap_filter_rec_t *ap_chunk_filter_handle;
 AP_DECLARE_DATA ap_filter_rec_t *ap_http_outerror_filter_handle;
 AP_DECLARE_DATA ap_filter_rec_t *ap_byterange_filter_handle;
@@ -268,6 +269,15 @@ static int http_create_request(request_r
     return OK;
 }
 
+static void http_pre_read_request(request_rec *r, conn_rec *c)
+{
+    if (!r->main && !r->prev
+        && !strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))) {
+        ap_add_output_filter_handle(ap_h1_response_out_filter_handle,
+                                    NULL, r, r->connection);
+    }
+}
+
 static int http_send_options(request_rec *r)
 {
     if ((r->method_number == M_OPTIONS) && r->uri && (r->uri[0] == '*') &&
@@ -299,6 +309,7 @@ static void register_hooks(apr_pool_t *p
     ap_hook_http_scheme(http_scheme,NULL,NULL,APR_HOOK_REALLY_LAST);
     ap_hook_default_port(http_port,NULL,NULL,APR_HOOK_REALLY_LAST);
     ap_hook_create_request(http_create_request, NULL, NULL, APR_HOOK_REALLY_LAST);
+    ap_hook_pre_read_request(http_pre_read_request, NULL, NULL, APR_HOOK_REALLY_LAST);
     ap_http_input_filter_handle =
         ap_register_input_filter("HTTP_IN", ap_http_filter,
                                  NULL, AP_FTYPE_PROTOCOL);
@@ -308,6 +319,9 @@ static void register_hooks(apr_pool_t *p
     ap_http_header_filter_handle =
         ap_register_output_filter("HTTP_HEADER", ap_http_header_filter,
                                   NULL, AP_FTYPE_PROTOCOL);
+    ap_h1_response_out_filter_handle =
+        ap_register_output_filter("HTTP1_RESPONSE_OUT", ap_h1_response_out_filter,
+                                  NULL, AP_FTYPE_TRANSCODE);
     ap_chunk_filter_handle =
         ap_register_output_filter("CHUNK", ap_http_chunk_filter,
                                   NULL, AP_FTYPE_TRANSCODE);

Modified: httpd/httpd/trunk/modules/http/http_filters.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http/http_filters.c?rev=1899648&r1=1899647&r2=1899648&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http/http_filters.c (original)
+++ httpd/httpd/trunk/modules/http/http_filters.c Thu Apr  7 10:41:46 2022
@@ -57,6 +57,9 @@
 
 APLOG_USE_MODULE(http);
 
+static apr_bucket *create_trailers_bucket(request_rec *r, apr_bucket_alloc_t *bucket_alloc);
+static apr_bucket *create_response_bucket(request_rec *r, apr_bucket_alloc_t *bucket_alloc);
+
 typedef struct http_filter_ctx
 {
     apr_off_t remaining;
@@ -942,67 +945,6 @@ static void fixup_vary(request_rec *r)
     }
 }
 
-/* Send a request's HTTP response headers to the client.
- */
-static apr_status_t send_all_header_fields(header_struct *h,
-                                           const request_rec *r)
-{
-    const apr_array_header_t *elts;
-    const apr_table_entry_t *t_elt;
-    const apr_table_entry_t *t_end;
-    struct iovec *vec;
-    struct iovec *vec_next;
-
-    elts = apr_table_elts(r->headers_out);
-    if (elts->nelts == 0) {
-        return APR_SUCCESS;
-    }
-    t_elt = (const apr_table_entry_t *)(elts->elts);
-    t_end = t_elt + elts->nelts;
-    vec = (struct iovec *)apr_palloc(h->pool, 4 * elts->nelts *
-                                     sizeof(struct iovec));
-    vec_next = vec;
-
-    /* For each field, generate
-     *    name ": " value CRLF
-     */
-    do {
-        vec_next->iov_base = (void*)(t_elt->key);
-        vec_next->iov_len = strlen(t_elt->key);
-        vec_next++;
-        vec_next->iov_base = ": ";
-        vec_next->iov_len = sizeof(": ") - 1;
-        vec_next++;
-        vec_next->iov_base = (void*)(t_elt->val);
-        vec_next->iov_len = strlen(t_elt->val);
-        vec_next++;
-        vec_next->iov_base = CRLF;
-        vec_next->iov_len = sizeof(CRLF) - 1;
-        vec_next++;
-        t_elt++;
-    } while (t_elt < t_end);
-
-    if (APLOGrtrace4(r)) {
-        t_elt = (const apr_table_entry_t *)(elts->elts);
-        do {
-            ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r, "  %s: %s",
-                          t_elt->key, t_elt->val);
-            t_elt++;
-        } while (t_elt < t_end);
-    }
-
-#if APR_CHARSET_EBCDIC
-    {
-        apr_size_t len;
-        char *tmp = apr_pstrcatv(r->pool, vec, vec_next - vec, &len);
-        ap_xlate_proto_to_ascii(tmp, len);
-        return apr_brigade_write(h->bb, NULL, NULL, tmp, len);
-    }
-#else
-    return apr_brigade_writev(h->bb, NULL, NULL, vec, vec_next - vec);
-#endif
-}
-
 /* Confirm that the status line is well-formed and matches r->status.
  * If they don't match, a filter may have negated the status line set by a
  * handler.
@@ -1040,8 +982,7 @@ static apr_status_t validate_status_line
  *
  * also prepare r->status_line.
  */
-static void basic_http_header_check(request_rec *r,
-                                    const char **protocol)
+static void basic_http_header_check(request_rec *r)
 {
     apr_status_t rv;
 
@@ -1069,131 +1010,15 @@ static void basic_http_header_check(requ
         && apr_table_get(r->subprocess_env, "downgrade-1.0")) {
         r->proto_num = HTTP_VERSION(1,0);
     }
-
-    /* kludge around broken browsers when indicated by force-response-1.0
-     */
-    if (r->proto_num == HTTP_VERSION(1,0)
-        && apr_table_get(r->subprocess_env, "force-response-1.0")) {
-        *protocol = "HTTP/1.0";
-        r->connection->keepalive = AP_CONN_CLOSE;
-    }
-    else {
-        *protocol = AP_SERVER_PROTOCOL;
-    }
-
-}
-
-/* fill "bb" with a barebones/initial HTTP response header */
-static void basic_http_header(request_rec *r, apr_bucket_brigade *bb,
-                              const char *protocol)
-{
-    char *date = NULL;
-    const char *proxy_date = NULL;
-    const char *server = NULL;
-    const char *us = ap_get_server_banner();
-    header_struct h;
-    struct iovec vec[4];
-
-    if (r->assbackwards) {
-        /* there are no headers to send */
-        return;
-    }
-
-    /* Output the HTTP/1.x Status-Line and the Date and Server fields */
-
-    vec[0].iov_base = (void *)protocol;
-    vec[0].iov_len  = strlen(protocol);
-    vec[1].iov_base = (void *)" ";
-    vec[1].iov_len  = sizeof(" ") - 1;
-    vec[2].iov_base = (void *)(r->status_line);
-    vec[2].iov_len  = strlen(r->status_line);
-    vec[3].iov_base = (void *)CRLF;
-    vec[3].iov_len  = sizeof(CRLF) - 1;
-#if APR_CHARSET_EBCDIC
-    {
-        char *tmp;
-        apr_size_t len;
-        tmp = apr_pstrcatv(r->pool, vec, 4, &len);
-        ap_xlate_proto_to_ascii(tmp, len);
-        apr_brigade_write(bb, NULL, NULL, tmp, len);
-    }
-#else
-    apr_brigade_writev(bb, NULL, NULL, vec, 4);
-#endif
-
-    h.pool = r->pool;
-    h.bb = bb;
-
-    /*
-     * keep the set-by-proxy server and date headers, otherwise
-     * generate a new server header / date header
-     */
-    if (r->proxyreq != PROXYREQ_NONE) {
-        proxy_date = apr_table_get(r->headers_out, "Date");
-        if (!proxy_date) {
-            /*
-             * proxy_date needs to be const. So use date for the creation of
-             * our own Date header and pass it over to proxy_date later to
-             * avoid a compiler warning.
-             */
-            date = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
-            ap_recent_rfc822_date(date, r->request_time);
-        }
-        server = apr_table_get(r->headers_out, "Server");
-    }
-    else {
-        date = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
-        ap_recent_rfc822_date(date, r->request_time);
-    }
-
-    form_header_field(&h, "Date", proxy_date ? proxy_date : date );
-
-    if (!server && *us)
-        server = us;
-    if (server)
-        form_header_field(&h, "Server", server);
-
-    if (APLOGrtrace3(r)) {
-        ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
-                      "Response sent with status %d%s",
-                      r->status,
-                      APLOGrtrace4(r) ? ", headers:" : "");
-
-        /*
-         * Date and Server are less interesting, use TRACE5 for them while
-         * using TRACE4 for the other headers.
-         */
-        ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "  Date: %s",
-                      proxy_date ? proxy_date : date );
-        if (server)
-            ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "  Server: %s",
-                          server);
-    }
-
-
-    /* unset so we don't send them again */
-    apr_table_unset(r->headers_out, "Date");        /* Avoid bogosity */
-    if (server) {
-        apr_table_unset(r->headers_out, "Server");
-    }
 }
 
 AP_DECLARE(void) ap_basic_http_header(request_rec *r, apr_bucket_brigade *bb)
 {
-    const char *protocol = NULL;
-
-    basic_http_header_check(r, &protocol);
-    basic_http_header(r, bb, protocol);
-}
-
-static void terminate_header(apr_bucket_brigade *bb)
-{
-    char crlf[] = CRLF;
-    apr_size_t buflen;
+    apr_bucket *b;
 
-    buflen = strlen(crlf);
-    ap_xlate_proto_to_ascii(crlf, buflen);
-    apr_brigade_write(bb, NULL, NULL, crlf, buflen);
+    basic_http_header_check(r);
+    b = create_response_bucket(r, bb->bucket_alloc);
+    APR_BRIGADE_INSERT_TAIL(bb, b);
 }
 
 AP_DECLARE_NONSTD(int) ap_send_http_trace(request_rec *r)
@@ -1312,17 +1137,10 @@ AP_DECLARE_NONSTD(int) ap_send_http_trac
     return DONE;
 }
 
-static apr_bucket *create_trailers_bucket(request_rec *r, apr_bucket_alloc_t *bucket_alloc)
-{
-    if (r->trailers_out && !apr_is_empty_table(r->trailers_out)) {
-        ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "sending trailers");
-        return ap_bucket_headers_create(r->trailers_out, r->pool, bucket_alloc);
-    }
-    return NULL;
-}
-
 typedef struct header_filter_ctx {
-    int headers_sent;
+    int final_status;
+    int final_header_only;
+    int dying;
 } header_filter_ctx;
 
 AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f,
@@ -1330,13 +1148,8 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_
 {
     request_rec *r = f->r;
     conn_rec *c = r->connection;
-    int header_only = (r->header_only || AP_STATUS_IS_HEADER_ONLY(r->status));
-    const char *protocol = NULL;
-    apr_bucket *e, *eos = NULL;
-    apr_bucket_brigade *b2;
-    header_struct h;
+    apr_bucket *e, *respb = NULL, *trigger = NULL, *eos = NULL;
     header_filter_ctx *ctx = f->ctx;
-    const char *ctype;
     ap_bucket_error *eb = NULL;
     apr_status_t rv = APR_SUCCESS;
     int recursive_error = 0;
@@ -1346,9 +1159,10 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_
     if (!ctx) {
         ctx = f->ctx = apr_pcalloc(r->pool, sizeof(header_filter_ctx));
     }
-    else if (ctx->headers_sent) {
-        /* Eat body if response must not have one. */
-        if (header_only) {
+
+    if (ctx->final_status) {
+        /* Sent the final status, eat body if response must not have one. */
+        if (ctx->final_header_only) {
             /* Still next filters may be waiting for EOS, so pass it (alone)
              * when encountered and be done with this filter.
              */
@@ -1364,62 +1178,124 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_
             return rv;
         }
     }
-
-    for (e = APR_BRIGADE_FIRST(b);
-         e != APR_BRIGADE_SENTINEL(b);
-         e = APR_BUCKET_NEXT(e))
-    {
-        if (AP_BUCKET_IS_ERROR(e) && !eb) {
-            eb = e->data;
-            continue;
-        }
-        if (APR_BUCKET_IS_EOS(e)) {
-            if (!eos) eos = e;
-            continue;
-        }
-        /*
-         * If we see an EOC bucket it is a signal that we should get out
-         * of the way doing nothing.
+    else {
+        /* Determine if it is time to insert the response bucket for
+         * the request. Request handlers just write content or an EOS
+         * and that needs to take the current state of request_rec to
+         * send on status and headers as a response bucket.
+         *
+         * But we also send interim responses (as response buckets)
+         * through this filter and that must not trigger generating
+         * an additional response bucket.
+         *
+         * Waiting on a DATA/ERROR/EOS bucket alone is not enough,
+         * unfortunately, as some handlers trigger response generation
+         * by just writing a FLUSH (see mod_lua's websocket for example).
          */
-        if (AP_BUCKET_IS_EOC(e)) {
-            ap_remove_output_filter(f);
-            return ap_pass_brigade(f->next, b);
+        for (e = APR_BRIGADE_FIRST(b);
+             e != APR_BRIGADE_SENTINEL(b) && !trigger;
+             e = APR_BUCKET_NEXT(e))
+        {
+            if (AP_BUCKET_IS_RESPONSE(e)) {
+                /* remember the last one if there are many. */
+                respb = e;
+            }
+            else if (APR_BUCKET_IS_FLUSH(e)) {
+                /* flush without response bucket triggers */
+                if (!respb) trigger = e;
+            }
+            else if (APR_BUCKET_IS_EOS(e)) {
+                trigger = e;
+            }
+            else if (AP_BUCKET_IS_ERROR(e)) {
+                /* Need to handle this below via ap_die() */
+                break;
+            }
+            else {
+                /* First content bucket, always triggering the response.*/
+                trigger = e;
+            }
         }
-    }
 
-    if (!ctx->headers_sent && !check_headers(r)) {
-        /* We may come back here from ap_die() below,
-         * so clear anything from this response.
-         */
-        apr_table_clear(r->headers_out);
-        apr_table_clear(r->err_headers_out);
-        apr_brigade_cleanup(b);
+        if (respb) {
+            ap_bucket_response *resp = respb->data;
+            if (resp->status >= 200 || resp->status == 101) {
+                /* Someone is passing the final response, remember it
+                 * so we no longer generate one. */
+                ctx->final_status = resp->status;
+                ctx->final_header_only = AP_STATUS_IS_HEADER_ONLY(resp->status);
+            }
+        }
 
-        /* Don't recall ap_die() if we come back here (from its own internal
-         * redirect or error response), otherwise we can end up in infinite
-         * recursion; better fall through with 500, minimal headers and an
-         * empty body (EOS only).
-         */
-        if (!check_headers_recursion(r)) {
-            ap_die(HTTP_INTERNAL_SERVER_ERROR, r);
-            return AP_FILTER_ERROR;
+        if (trigger && !ctx->final_status) {
+            ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+                          "ap_http_header_filter prep response status %d",
+                          r->status);
+            if (!check_headers(r)) {
+                /* We may come back here from ap_die() below,
+                 * so clear anything from this response.
+                 */
+                apr_table_clear(r->headers_out);
+                apr_table_clear(r->err_headers_out);
+                apr_brigade_cleanup(b);
+
+                /* Don't recall ap_die() if we come back here (from its own internal
+                 * redirect or error response), otherwise we can end up in infinite
+                 * recursion; better fall through with 500, minimal headers and an
+                 * empty body (EOS only).
+                 */
+                if (!check_headers_recursion(r)) {
+                    ap_die(HTTP_INTERNAL_SERVER_ERROR, r);
+                    return AP_FILTER_ERROR;
+                }
+                r->status = HTTP_INTERNAL_SERVER_ERROR;
+                e = ap_bucket_eoc_create(c->bucket_alloc);
+                APR_BRIGADE_INSERT_TAIL(b, e);
+                e = apr_bucket_eos_create(c->bucket_alloc);
+                APR_BRIGADE_INSERT_TAIL(b, e);
+                r->content_type = r->content_encoding = NULL;
+                r->content_languages = NULL;
+                ap_set_content_length(r, 0);
+                recursive_error = 1;
+            }
+            respb = create_response_bucket(r, b->bucket_alloc);
+            APR_BUCKET_INSERT_BEFORE(trigger, respb);
+            ctx->final_status = r->status;
+            ctx->final_header_only = (r->header_only || AP_STATUS_IS_HEADER_ONLY(r->status));
+            r->sent_bodyct = 1;         /* Whatever follows is real body stuff... */
         }
-        r->status = HTTP_INTERNAL_SERVER_ERROR;
-        e = ap_bucket_eoc_create(c->bucket_alloc);
-        APR_BRIGADE_INSERT_TAIL(b, e);
-        e = apr_bucket_eos_create(c->bucket_alloc);
-        APR_BRIGADE_INSERT_TAIL(b, e);
-        r->content_type = r->content_encoding = NULL;
-        r->content_languages = NULL;
-        ap_set_content_length(r, 0);
-        recursive_error = 1;
     }
-    else if (eb) {
-        int status;
-        status = eb->status;
-        apr_brigade_cleanup(b);
-        ap_die(status, r);
-        return AP_FILTER_ERROR;
+
+    /* Look for ERROR/EOC/EOS that require special handling. */
+    for (e = APR_BRIGADE_FIRST(b);
+         e != APR_BRIGADE_SENTINEL(b);
+         e = APR_BUCKET_NEXT(e))
+    {
+        if (APR_BUCKET_IS_METADATA(e)) {
+            if (APR_BUCKET_IS_EOS(e)) {
+                if (!eos) eos = e;
+            }
+            else if (AP_BUCKET_IS_EOC(e)) {
+                /* If we see an EOC bucket it is a signal that we should get out
+                 * of the way doing nothing.
+                 */
+                ap_remove_output_filter(f);
+                return ap_pass_brigade(f->next, b);
+            }
+            else if (AP_BUCKET_IS_ERROR(e)) {
+                int status;
+                eb = e->data;
+                status = eb->status;
+                apr_brigade_cleanup(b);
+                ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+                              "ap_http_header_filter error bucket, die with %d and error",
+                              status);
+                /* This will invoke us again */
+                ctx->dying = 1;
+                ap_die(status, r);
+                return AP_FILTER_ERROR;
+            }
+        }
     }
 
     if (r->assbackwards) {
@@ -1430,148 +1306,15 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_
     }
 
     if (eos) {
-        /* on having seen EOS and added possible trailers, we
-         * can remove this filter.
-         */
         e = create_trailers_bucket(r, b->bucket_alloc);
         if (e) {
             APR_BUCKET_INSERT_BEFORE(eos, e);
         }
         ap_remove_output_filter(f);
     }
-
-    if (ctx->headers_sent) {
-        /* we did already the stuff below, just pass on */
-        return ap_pass_brigade(f->next, b);
-    }
-
-    /*
-     * Now that we are ready to send a response, we need to combine the two
-     * header field tables into a single table.  If we don't do this, our
-     * later attempts to set or unset a given fieldname might be bypassed.
-     */
-    if (!apr_is_empty_table(r->err_headers_out)) {
-        r->headers_out = apr_table_overlay(r->pool, r->err_headers_out,
-                                           r->headers_out);
-    }
-
-    /*
-     * Remove the 'Vary' header field if the client can't handle it.
-     * Since this will have nasty effects on HTTP/1.1 caches, force
-     * the response into HTTP/1.0 mode.
-     *
-     * Note: the force-response-1.0 should come before the call to
-     *       basic_http_header_check()
-     */
-    if (apr_table_get(r->subprocess_env, "force-no-vary") != NULL) {
-        apr_table_unset(r->headers_out, "Vary");
-        r->proto_num = HTTP_VERSION(1,0);
-        apr_table_setn(r->subprocess_env, "force-response-1.0", "1");
-    }
-    else {
-        fixup_vary(r);
-    }
-
-    /*
-     * Now remove any ETag response header field if earlier processing
-     * says so (such as a 'FileETag None' directive).
-     */
-    if (apr_table_get(r->notes, "no-etag") != NULL) {
-        apr_table_unset(r->headers_out, "ETag");
-    }
-
-    /* determine the protocol and whether we should use keepalives. */
-    basic_http_header_check(r, &protocol);
-    ap_set_keepalive(r);
-
-    if (AP_STATUS_IS_HEADER_ONLY(r->status)) {
-        apr_table_unset(r->headers_out, "Transfer-Encoding");
-        apr_table_unset(r->headers_out, "Content-Length");
-        r->content_type = r->content_encoding = NULL;
-        r->content_languages = NULL;
-        r->clength = r->chunked = 0;
-    }
-    else if (r->chunked) {
-        apr_table_mergen(r->headers_out, "Transfer-Encoding", "chunked");
-        apr_table_unset(r->headers_out, "Content-Length");
-    }
-
-    ctype = ap_make_content_type(r, r->content_type);
-    if (ctype) {
-        apr_table_setn(r->headers_out, "Content-Type", ctype);
-    }
-
-    if (r->content_encoding) {
-        apr_table_setn(r->headers_out, "Content-Encoding",
-                       r->content_encoding);
-    }
-
-    if (!apr_is_empty_array(r->content_languages)) {
-        int i;
-        char *token;
-        char **languages = (char **)(r->content_languages->elts);
-        const char *field = apr_table_get(r->headers_out, "Content-Language");
-
-        while (field && (token = ap_get_list_item(r->pool, &field)) != NULL) {
-            for (i = 0; i < r->content_languages->nelts; ++i) {
-                if (!ap_cstr_casecmp(token, languages[i]))
-                    break;
-            }
-            if (i == r->content_languages->nelts) {
-                *((char **) apr_array_push(r->content_languages)) = token;
-            }
-        }
-
-        field = apr_array_pstrcat(r->pool, r->content_languages, ',');
-        apr_table_setn(r->headers_out, "Content-Language", field);
-    }
-
-    /*
-     * Control cachability for non-cacheable responses if not already set by
-     * some other part of the server configuration.
-     */
-    if (r->no_cache && !apr_table_get(r->headers_out, "Expires")) {
-        char *date = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
-        ap_recent_rfc822_date(date, r->request_time);
-        apr_table_addn(r->headers_out, "Expires", date);
-    }
-
-    b2 = apr_brigade_create(r->pool, c->bucket_alloc);
-    basic_http_header(r, b2, protocol);
-
-    h.pool = r->pool;
-    h.bb = b2;
-
-    send_all_header_fields(&h, r);
-
-    terminate_header(b2);
-
-    if (header_only) {
-        e = APR_BRIGADE_LAST(b);
-        if (e != APR_BRIGADE_SENTINEL(b) && APR_BUCKET_IS_EOS(e)) {
-            APR_BUCKET_REMOVE(e);
-            APR_BRIGADE_INSERT_TAIL(b2, e);
-            ap_remove_output_filter(f);
-        }
-        apr_brigade_cleanup(b);
-    }
-
-    rv = ap_pass_brigade(f->next, b2);
-    apr_brigade_cleanup(b2);
-    ctx->headers_sent = 1;
-
-    if (rv != APR_SUCCESS || header_only) {
-        goto out;
-    }
-
-    r->sent_bodyct = 1;         /* Whatever follows is real body stuff... */
-
-    if (r->chunked) {
-        /* We can't add this filter until we have already sent the headers.
-         * If we add it before this point, then the headers will be chunked
-         * as well, and that is just wrong.
-         */
-        ap_add_output_filter("CHUNK", NULL, r, r->connection);
+    else if (ctx->final_status == 101) {
+        /* switching protocol, whatever comes next is not HTTP/1.x */
+        ap_remove_output_filter(f);
     }
 
     rv = ap_pass_brigade(f->next, b);
@@ -1989,3 +1732,351 @@ apr_status_t ap_http_outerror_filter(ap_
 
     return ap_pass_brigade(f->next,  b);
 }
+
+
+/* fill "bb" with a barebones/initial HTTP/1.x response header */
+static void h1_append_response_head(request_rec *r,
+                                    ap_bucket_response *resp,
+                                    const char *protocol,
+                                    apr_bucket_brigade *bb)
+{
+    const char *date = NULL;
+    const char *server = NULL;
+    const char *status_line;
+    struct iovec vec[4];
+
+    if (r->assbackwards) {
+        /* there are no headers to send */
+        return;
+    }
+
+    /* Output the HTTP/1.x Status-Line and the Date and Server fields */
+    if (resp->reason) {
+        status_line =  apr_psprintf(r->pool, "%d %s", resp->status, resp->reason);
+    }
+    else {
+        status_line = ap_get_status_line_ex(r->pool, resp->status);
+    }
+
+    vec[0].iov_base = (void *)protocol;
+    vec[0].iov_len  = strlen(protocol);
+    vec[1].iov_base = (void *)" ";
+    vec[1].iov_len  = sizeof(" ") - 1;
+    vec[2].iov_base = (void *)(status_line);
+    vec[2].iov_len  = strlen(status_line);
+    vec[3].iov_base = (void *)CRLF;
+    vec[3].iov_len  = sizeof(CRLF) - 1;
+#if APR_CHARSET_EBCDIC
+    {
+        char *tmp;
+        apr_size_t len;
+        tmp = apr_pstrcatv(r->pool, vec, 4, &len);
+        ap_xlate_proto_to_ascii(tmp, len);
+        apr_brigade_write(bb, NULL, NULL, tmp, len);
+    }
+#else
+    apr_brigade_writev(bb, NULL, NULL, vec, 4);
+#endif
+
+    date = apr_table_get(resp->headers, "Date");
+    server = apr_table_get(resp->headers, "Server");
+    if (date) {
+        /* We always write that as first, just because we
+         * always did and some quirky clients might rely on that.
+         */
+        ap_h1_append_header(bb, r->pool, "Date", date);
+        apr_table_unset(resp->headers, "Date");
+    }
+    if (server) {
+        /* We always write that second, just because we
+         * always did and some quirky clients might rely on that.
+         */
+        ap_h1_append_header(bb, r->pool, "Server", server);
+        apr_table_unset(resp->headers, "Server");
+    }
+
+    if (APLOGrtrace3(r)) {
+        ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+                      "Response sent with status %d%s",
+                      r->status,
+                      APLOGrtrace4(r) ? ", headers:" : "");
+
+        /*
+         * Date and Server are less interesting, use TRACE5 for them while
+         * using TRACE4 for the other headers.
+         */
+        if (date)
+            ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "  Date: %s",
+                          date);
+        if (server)
+            ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "  Server: %s",
+                          server);
+    }
+}
+
+typedef struct h1_response_ctx {
+    int final_response_sent;    /* strict: a response status >= 200 was sent */
+    int discard_body;           /* the response is header only, discard body */
+    apr_bucket_brigade *tmpbb;
+} h1_response_ctx;
+
+AP_CORE_DECLARE_NONSTD(apr_status_t) ap_h1_response_out_filter(ap_filter_t *f,
+                                                               apr_bucket_brigade *b)
+{
+    request_rec *r = f->r;
+    conn_rec *c = r->connection;
+    apr_bucket *e, *next = NULL;
+    h1_response_ctx *ctx = f->ctx;
+    apr_status_t rv = APR_SUCCESS;
+    core_server_config *conf = ap_get_core_module_config(r->server->module_config);
+    int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE);
+
+    AP_DEBUG_ASSERT(!r->main);
+
+    if (!ctx) {
+        ctx = f->ctx = apr_pcalloc(r->pool, sizeof(*ctx));
+    }
+
+    for (e = APR_BRIGADE_FIRST(b);
+         e != APR_BRIGADE_SENTINEL(b);
+         e = next)
+    {
+        next = APR_BUCKET_NEXT(e);
+
+        if (APR_BUCKET_IS_METADATA(e)) {
+
+            if (APR_BUCKET_IS_EOS(e)) {
+                if (!ctx->final_response_sent) {
+                    /* should not happen. do we generate a 500 here? */
+                }
+                ap_remove_output_filter(f);
+                goto pass;
+            }
+            else if (AP_BUCKET_IS_RESPONSE(e)) {
+                ap_bucket_response *resp = e->data;
+
+                ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+                              "ap_http1_response_out_filter seeing response bucket status=%d",
+                              resp->status);
+                if (strict && resp->status < 100) {
+                    /* error, not a valid http status */
+                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10386)
+                                  "ap_http1_response_out_filter seeing headers "
+                                  "status=%d in strict mode",
+                                  resp->status);
+                    rv = AP_FILTER_ERROR;
+                    goto cleanup;
+                }
+                else if (ctx->final_response_sent) {
+                    /* already sent the final response for the request.
+                     */
+                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10387)
+                                  "ap_http1_response_out_filter seeing headers "
+                                  "status=%d after final response already sent",
+                                  resp->status);
+                    rv = AP_FILTER_ERROR;
+                    goto cleanup;
+                }
+                else {
+                    /* a response status to transcode, might be final or interim
+                     */
+                    const char *proto = AP_SERVER_PROTOCOL;
+
+                    ctx->final_response_sent = (resp->status >= 200)
+                        || (!strict && resp->status < 100);
+                    ctx->discard_body = ctx->final_response_sent &&
+                        (r->header_only || AP_STATUS_IS_HEADER_ONLY(resp->status));
+
+                    if (!ctx->tmpbb) {
+                        ctx->tmpbb = apr_brigade_create(r->pool, c->bucket_alloc);
+                    }
+                    if (next != APR_BRIGADE_SENTINEL(b)) {
+                        apr_brigade_split_ex(b, next, ctx->tmpbb);
+                    }
+
+                    if (ctx->final_response_sent) {
+                        ap_h1_set_keepalive(r, resp);
+
+                        if (AP_STATUS_IS_HEADER_ONLY(resp->status)) {
+                            apr_table_unset(resp->headers, "Transfer-Encoding");
+                        }
+                        else if (r->chunked) {
+                            apr_table_mergen(resp->headers, "Transfer-Encoding", "chunked");
+                            apr_table_unset(resp->headers, "Content-Length");
+                        }
+                    }
+
+                    /* kludge around broken browsers when indicated by force-response-1.0
+                     */
+                    if (r->proto_num == HTTP_VERSION(1,0)
+                        && apr_table_get(r->subprocess_env, "force-response-1.0")) {
+                        r->connection->keepalive = AP_CONN_CLOSE;
+                        proto = "HTTP/1.0";
+                    }
+                    h1_append_response_head(r, resp, proto, b);
+                    ap_h1_append_headers(b, r, resp->headers);
+                    ap_h1_terminate_header(b);
+                    apr_bucket_delete(e);
+
+                    if (ctx->final_response_sent && r->chunked) {
+                        /* We can't add this filter until we have already sent the headers.
+                         * If we add it before this point, then the headers will be chunked
+                         * as well, and that is just wrong.
+                         */
+                        rv = ap_pass_brigade(f->next, b);
+                        apr_brigade_cleanup(b);
+                        ap_log_rerror(APLOG_MARK, APLOG_TRACE2, rv, r,
+                                      "ap_http1_response_out_filter passed response"
+                                      ", add CHUNK filter");
+                        if (APR_SUCCESS != rv) {
+                            apr_brigade_cleanup(ctx->tmpbb);
+                            goto cleanup;
+                        }
+                        ap_add_output_filter("CHUNK", NULL, r, r->connection);
+                    }
+
+                    APR_BRIGADE_CONCAT(b, ctx->tmpbb);
+
+                    if (resp->status == 101) {
+                        /* switched to another protocol, get out of the way */
+                        AP_DEBUG_ASSERT(!r->chunked);
+                        ap_remove_output_filter(f);
+                        goto pass;
+                    }
+                }
+            }
+        }
+        else if (!ctx->final_response_sent && strict) {
+            /* data buckets before seeing the final response are in error.
+             */
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10390)
+                          "ap_http1_response_out_filter seeing data before headers, %ld bytes ",
+                          (long)e->length);
+            rv = AP_FILTER_ERROR;
+            goto cleanup;
+        }
+        else if (ctx->discard_body) {
+            apr_bucket_delete(e);
+        }
+    }
+
+pass:
+    rv = ap_pass_brigade(f->next, b);
+cleanup:
+    return rv;
+}
+
+static const char *get_status_reason(const char *status_line)
+{
+    if (status_line && strlen(status_line) > 4) {
+        return status_line + 4;
+    }
+    return NULL;
+}
+
+static apr_bucket *create_response_bucket(request_rec *r, apr_bucket_alloc_t *bucket_alloc)
+{
+    const char *ctype;
+
+    /*
+     * Now that we are ready to send a response, we need to combine the two
+     * header field tables into a single table.  If we don't do this, our
+     * later attempts to set or unset a given fieldname might be bypassed.
+     */
+    if (!apr_is_empty_table(r->err_headers_out)) {
+        r->headers_out = apr_table_overlay(r->pool, r->err_headers_out,
+                                           r->headers_out);
+    }
+
+    ap_set_std_response_headers(r);
+
+    /*
+     * Remove the 'Vary' header field if the client can't handle it.
+     * Since this will have nasty effects on HTTP/1.1 caches, force
+     * the response into HTTP/1.0 mode.
+     *
+     * Note: the force-response-1.0 should come before the call to
+     *       basic_http_header_check()
+     */
+    if (apr_table_get(r->subprocess_env, "force-no-vary") != NULL) {
+        apr_table_unset(r->headers_out, "Vary");
+        r->proto_num = HTTP_VERSION(1,0);
+        apr_table_setn(r->subprocess_env, "force-response-1.0", "1");
+    }
+    else {
+        fixup_vary(r);
+    }
+
+    /*
+     * Now remove any ETag response header field if earlier processing
+     * says so (such as a 'FileETag None' directive).
+     */
+    if (apr_table_get(r->notes, "no-etag") != NULL) {
+        apr_table_unset(r->headers_out, "ETag");
+    }
+
+    /* determine the protocol and whether we should use keepalives. */
+    basic_http_header_check(r);
+
+    if (AP_STATUS_IS_HEADER_ONLY(r->status)) {
+        apr_table_unset(r->headers_out, "Content-Length");
+        r->content_type = r->content_encoding = NULL;
+        r->content_languages = NULL;
+        r->clength = r->chunked = 0;
+    }
+
+    ctype = ap_make_content_type(r, r->content_type);
+    if (ctype) {
+        apr_table_setn(r->headers_out, "Content-Type", ctype);
+    }
+
+    if (r->content_encoding) {
+        apr_table_setn(r->headers_out, "Content-Encoding",
+                       r->content_encoding);
+    }
+
+    if (!apr_is_empty_array(r->content_languages)) {
+        int i;
+        char *token;
+        char **languages = (char **)(r->content_languages->elts);
+        const char *field = apr_table_get(r->headers_out, "Content-Language");
+
+        while (field && (token = ap_get_list_item(r->pool, &field)) != NULL) {
+            for (i = 0; i < r->content_languages->nelts; ++i) {
+                if (!ap_cstr_casecmp(token, languages[i]))
+                    break;
+            }
+            if (i == r->content_languages->nelts) {
+                *((char **) apr_array_push(r->content_languages)) = token;
+            }
+        }
+
+        field = apr_array_pstrcat(r->pool, r->content_languages, ',');
+        apr_table_setn(r->headers_out, "Content-Language", field);
+    }
+
+    /*
+     * Control cachability for non-cacheable responses if not already set by
+     * some other part of the server configuration.
+     */
+    if (r->no_cache && !apr_table_get(r->headers_out, "Expires")) {
+        char *date = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
+        ap_recent_rfc822_date(date, r->request_time);
+        apr_table_addn(r->headers_out, "Expires", date);
+    }
+
+    /* r->headers_out fully prepared. Create a headers bucket
+     * containing the response to send down the filter chain.
+     */
+    return ap_bucket_response_create(r->status, get_status_reason(r->status_line),
+                                     r->headers_out, r->notes, r->pool, bucket_alloc);
+}
+
+static apr_bucket *create_trailers_bucket(request_rec *r, apr_bucket_alloc_t *bucket_alloc)
+{
+    if (r->trailers_out && !apr_is_empty_table(r->trailers_out)) {
+        ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "sending trailers");
+        return ap_bucket_headers_create(r->trailers_out, r->pool, bucket_alloc);
+    }
+    return NULL;
+}

Modified: httpd/httpd/trunk/modules/http/http_protocol.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http/http_protocol.c?rev=1899648&r1=1899647&r2=1899648&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http/http_protocol.c (original)
+++ httpd/httpd/trunk/modules/http/http_protocol.c Thu Apr  7 10:41:46 2022
@@ -213,15 +213,21 @@ static int is_mpm_running(void)
     return 1;
 }
 
-
-AP_DECLARE(int) ap_set_keepalive(request_rec *r)
+int ap_h1_set_keepalive(request_rec *r, ap_bucket_response *resp)
 {
-    int ka_sent = 0;
-    int left = r->server->keep_alive_max - r->connection->keepalives;
-    int wimpy = ap_find_token(r->pool,
-                              apr_table_get(r->headers_out, "Connection"),
-                              "close");
-    const char *conn = apr_table_get(r->headers_in, "Connection");
+    int ka_sent, left = 0, wimpy;
+    const char *conn;
+
+    if (r->proto_num >= HTTP_VERSION(2,0)) {
+        goto update_keepalives;
+    }
+
+    ka_sent = 0;
+    left = r->server->keep_alive_max - r->connection->keepalives;
+    wimpy = ap_find_token(r->pool,
+                          apr_table_get(resp->headers, "Connection"),
+                          "close");
+    conn = apr_table_get(r->headers_in, "Connection");
 
     /* The following convoluted conditional determines whether or not
      * the current connection should remain persistent after this response
@@ -255,18 +261,17 @@ AP_DECLARE(int) ap_set_keepalive(request
     if ((r->connection->keepalive != AP_CONN_CLOSE)
         && !r->expecting_100
         && (r->header_only
-            || AP_STATUS_IS_HEADER_ONLY(r->status)
-            || apr_table_get(r->headers_out, "Content-Length")
+            || AP_STATUS_IS_HEADER_ONLY(resp->status)
+            || apr_table_get(resp->headers, "Content-Length")
             || ap_is_chunked(r->pool,
-                                  apr_table_get(r->headers_out,
-                                                "Transfer-Encoding"))
+                             apr_table_get(resp->headers, "Transfer-Encoding"))
             || ((r->proto_num >= HTTP_VERSION(1,1))
                 && (r->chunked = 1))) /* THIS CODE IS CORRECT, see above. */
         && r->server->keep_alive
         && (r->server->keep_alive_timeout > 0)
         && ((r->server->keep_alive_max == 0)
             || (left > 0))
-        && !ap_status_drops_connection(r->status)
+        && !ap_status_drops_connection(resp->status)
         && !wimpy
         && !ap_find_token(r->pool, conn, "close")
         && (!apr_table_get(r->subprocess_env, "nokeepalive")
@@ -281,17 +286,17 @@ AP_DECLARE(int) ap_set_keepalive(request
         /* If they sent a Keep-Alive token, send one back */
         if (ka_sent) {
             if (r->server->keep_alive_max) {
-                apr_table_setn(r->headers_out, "Keep-Alive",
+                apr_table_setn(resp->headers, "Keep-Alive",
                        apr_psprintf(r->pool, "timeout=%d, max=%d",
                             (int)apr_time_sec(r->server->keep_alive_timeout),
                             left));
             }
             else {
-                apr_table_setn(r->headers_out, "Keep-Alive",
+                apr_table_setn(resp->headers, "Keep-Alive",
                       apr_psprintf(r->pool, "timeout=%d",
                             (int)apr_time_sec(r->server->keep_alive_timeout)));
             }
-            apr_table_mergen(r->headers_out, "Connection", "Keep-Alive");
+            apr_table_mergen(resp->headers, "Connection", "Keep-Alive");
         }
 
         return 1;
@@ -306,9 +311,10 @@ AP_DECLARE(int) ap_set_keepalive(request
      * to a HTTP/1.1 client. Better safe than sorry.
      */
     if (!wimpy) {
-        apr_table_mergen(r->headers_out, "Connection", "close");
+        apr_table_mergen(resp->headers, "Connection", "close");
     }
 
+update_keepalives:
     /*
      * If we had previously been a keepalive connection and this
      * is the last one, then bump up the number of keepalives
@@ -324,6 +330,17 @@ AP_DECLARE(int) ap_set_keepalive(request
     return 0;
 }
 
+AP_DECLARE(int) ap_set_keepalive(request_rec *r)
+{
+    ap_bucket_response resp;
+
+    memset(&resp, 0, sizeof(resp));
+    resp.status = r->status;
+    resp.headers = r->headers_out;
+    resp.notes = r->notes;
+    return ap_h1_set_keepalive(r, &resp);
+}
+
 AP_DECLARE(ap_condition_e) ap_condition_if_match(request_rec *r,
         apr_table_t *headers)
 {
@@ -1485,20 +1502,35 @@ AP_DECLARE(void) ap_clear_method_list(ap
     l->method_list->nelts = 0;
 }
 
-AP_DECLARE(apr_status_t) ap_h1_append_header(apr_bucket_brigade *b,
-                                             apr_pool_t *p,
+AP_DECLARE(apr_status_t) ap_h1_append_header(apr_bucket_brigade *bb,
+                                             apr_pool_t *pool,
                                              const char *name, const char *value)
 {
-    char *buf;
+#if APR_CHARSET_EBCDIC
+    char *headfield;
     apr_size_t len;
 
-    if (!name || !*name || !value || !*value) {
-        return APR_SUCCESS;
-    }
-    buf = apr_pstrcat(p, name, ": ", value, CRLF, NULL);
-    len = strlen(buf);
-    ap_xlate_proto_to_ascii(buf, len);
-    return apr_brigade_write(b, NULL, NULL, buf, len);
+    headfield = apr_pstrcat(pool, name, ": ", value, CRLF, NULL);
+    len = strlen(headfield);
+
+    ap_xlate_proto_to_ascii(headfield, len);
+    return apr_brigade_write(bb, NULL, NULL, headfield, len);
+#else
+    struct iovec vec[4];
+    struct iovec *v = vec;
+    v->iov_base = (void *)name;
+    v->iov_len = strlen(name);
+    v++;
+    v->iov_base = ": ";
+    v->iov_len = sizeof(": ") - 1;
+    v++;
+    v->iov_base = (void *)value;
+    v->iov_len = strlen(value);
+    v++;
+    v->iov_base = CRLF;
+    v->iov_len = sizeof(CRLF) - 1;
+    return apr_brigade_writev(bb, NULL, NULL, vec, 4);
+#endif /* !APR_CHARSET_EBCDIC */
 }
 
 AP_DECLARE(apr_status_t) ap_h1_append_headers(apr_bucket_brigade *bb,

Modified: httpd/httpd/trunk/server/protocol.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/server/protocol.c?rev=1899648&r1=1899647&r2=1899648&view=diff
==============================================================================
--- httpd/httpd/trunk/server/protocol.c (original)
+++ httpd/httpd/trunk/server/protocol.c Thu Apr  7 10:41:46 2022
@@ -2386,26 +2386,6 @@ typedef struct hdr_ptr {
 } hdr_ptr;
 
  
-#if APR_CHARSET_EBCDIC
-static int send_header(void *data, const char *key, const char *val)
-{
-    char *header_line = NULL;
-    hdr_ptr *hdr = (hdr_ptr*)data;
-
-    header_line = apr_pstrcat(hdr->bb->p, key, ": ", val, CRLF, NULL);
-    ap_xlate_proto_to_ascii(header_line, strlen(header_line));
-    ap_fputs(hdr->f, hdr->bb, header_line);
-    return 1;
-}
-#else
-static int send_header(void *data, const char *key, const char *val)
-{
-     ap_fputstrs(((hdr_ptr*)data)->f, ((hdr_ptr*)data)->bb,
-                 key, ": ", val, CRLF, NULL);
-     return 1;
- }
-#endif
-
 AP_DECLARE(void) ap_set_std_response_headers(request_rec *r)
 {
     const char *server = NULL, *date;
@@ -2440,10 +2420,10 @@ AP_DECLARE(void) ap_set_std_response_hea
 
 AP_DECLARE(void) ap_send_interim_response(request_rec *r, int send_headers)
 {
-    hdr_ptr x;
-    char *response_line = NULL;
-    const char *status_line;
     request_rec *rr;
+    apr_bucket *b;
+    apr_bucket_brigade *bb;
+    const char *reason = NULL;
 
     if (r->proto_num < HTTP_VERSION(1,1)) {
         /* don't send interim response to HTTP/1.0 Client */
@@ -2473,26 +2453,26 @@ AP_DECLARE(void) ap_send_interim_respons
         }
     }
 
-    status_line = r->status_line;
-    if (status_line == NULL) {
-        status_line = ap_get_status_line_ex(r->pool, r->status);
-    }
-    response_line = apr_pstrcat(r->pool,
-                                AP_SERVER_PROTOCOL " ", status_line, CRLF,
-                                NULL);
-    ap_xlate_proto_to_ascii(response_line, strlen(response_line));
-
-    x.f = r->connection->output_filters;
-    x.bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
-
-    ap_fputs(x.f, x.bb, response_line);
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+                  "ap_send_interim_response: send %d", r->status);
+    bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+    if (send_headers) {
+        ap_set_std_response_headers(r);
+    }
+    if (r->status_line && strlen(r->status_line) > 4) {
+        reason = r->status_line + 4;
+    }
+    b = ap_bucket_response_create(r->status, reason,
+                                  send_headers? r->headers_out : NULL,
+                                  r->notes, r->pool, r->connection->bucket_alloc);
+    APR_BRIGADE_INSERT_TAIL(bb, b);
     if (send_headers) {
-        apr_table_do(send_header, &x, r->headers_out, NULL);
         apr_table_clear(r->headers_out);
     }
-    ap_fputs(x.f, x.bb, CRLF_ASCII);
-    ap_fflush(x.f, x.bb);
-    apr_brigade_destroy(x.bb);
+    b = apr_bucket_flush_create(r->connection->bucket_alloc);
+    APR_BRIGADE_INSERT_TAIL(bb, b);
+    ap_pass_brigade(r->proto_output_filters, bb);
+    apr_brigade_destroy(bb);
 }
 
 /*



Re: svn commit: r1899648 - in /httpd/httpd/trunk: changes-entries/core_response_buckets.txt include/mod_core.h modules/http/http_core.c modules/http/http_filters.c modules/http/http_protocol.c server/protocol.c

Posted by Ruediger Pluem <rp...@apache.org>.

On 4/14/22 10:18 AM, Stefan Eissing wrote:
> 
> 
>> Am 13.04.2022 um 17:16 schrieb Ruediger Pluem <rp...@apache.org>:
>>
>>
>>
>> On 4/7/22 12:41 PM, icing@apache.org wrote:
>>> Author: icing
>>> Date: Thu Apr  7 10:41:46 2022
>>> New Revision: 1899648
>>>
>>> URL: http://svn.apache.org/viewvc?rev=1899648&view=rev
>>> Log:
>>>  *) core/mod_http: use RESPONSE meta buckets and a new HTTP/1.x specific
>>>     filter to send responses through the output filter chain.
>>>     Specifically: the HTTP_HEADER output filter and ap_send_interim_response()
>>>     create a RESPONSE bucket and no longer are concerned with HTTP/1.x
>>>     serialization.
>>>     A new HTTP1_RESPONSE_OUT transcode filter writes the proper HTTP/1.x
>>>     bytes when dealing with a RESPONSE bucket. That filter installs itself
>>>     on the pre_read_request hook when the connection has protocol 'http/1.1'.
>>>
>>>
>>> Added:
>>>    httpd/httpd/trunk/changes-entries/core_response_buckets.txt
>>> Modified:
>>>    httpd/httpd/trunk/include/mod_core.h
>>>    httpd/httpd/trunk/modules/http/http_core.c
>>>    httpd/httpd/trunk/modules/http/http_filters.c
>>>    httpd/httpd/trunk/modules/http/http_protocol.c
>>>    httpd/httpd/trunk/server/protocol.c
>>>
>>
>>> Modified: httpd/httpd/trunk/modules/http/http_filters.c
>>> URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http/http_filters.c?rev=1899648&r1=1899647&r2=1899648&view=diff
>>> ==============================================================================
>>> --- httpd/httpd/trunk/modules/http/http_filters.c (original)
>>> +++ httpd/httpd/trunk/modules/http/http_filters.c Thu Apr  7 10:41:46 2022
>>
>>> @@ -1364,62 +1178,124 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_
>>>             return rv;
>>>         }
>>>     }
>>> -
>>> -    for (e = APR_BRIGADE_FIRST(b);
>>> -         e != APR_BRIGADE_SENTINEL(b);
>>> -         e = APR_BUCKET_NEXT(e))
>>> -    {
>>> -        if (AP_BUCKET_IS_ERROR(e) && !eb) {
>>> -            eb = e->data;
>>> -            continue;
>>> -        }
>>> -        if (APR_BUCKET_IS_EOS(e)) {
>>> -            if (!eos) eos = e;
>>> -            continue;
>>> -        }
>>> -        /*
>>> -         * If we see an EOC bucket it is a signal that we should get out
>>> -         * of the way doing nothing.
>>> +    else {
>>> +        /* Determine if it is time to insert the response bucket for
>>> +         * the request. Request handlers just write content or an EOS
>>> +         * and that needs to take the current state of request_rec to
>>> +         * send on status and headers as a response bucket.
>>> +         *
>>> +         * But we also send interim responses (as response buckets)
>>> +         * through this filter and that must not trigger generating
>>> +         * an additional response bucket.
>>> +         *
>>> +         * Waiting on a DATA/ERROR/EOS bucket alone is not enough,
>>> +         * unfortunately, as some handlers trigger response generation
>>> +         * by just writing a FLUSH (see mod_lua's websocket for example).
>>>          */
>>> -        if (AP_BUCKET_IS_EOC(e)) {
>>> -            ap_remove_output_filter(f);
>>> -            return ap_pass_brigade(f->next, b);
>>> +        for (e = APR_BRIGADE_FIRST(b);
>>> +             e != APR_BRIGADE_SENTINEL(b) && !trigger;
>>> +             e = APR_BUCKET_NEXT(e))
>>> +        {
>>> +            if (AP_BUCKET_IS_RESPONSE(e)) {
>>> +                /* remember the last one if there are many. */
>>> +                respb = e;
>>> +            }
>>> +            else if (APR_BUCKET_IS_FLUSH(e)) {
>>> +                /* flush without response bucket triggers */
>>> +                if (!respb) trigger = e;
>>> +            }
>>> +            else if (APR_BUCKET_IS_EOS(e)) {
>>> +                trigger = e;
>>> +            }
>>> +            else if (AP_BUCKET_IS_ERROR(e)) {
>>> +                /* Need to handle this below via ap_die() */
>>> +                break;
>>> +            }
>>> +            else {
>>> +                /* First content bucket, always triggering the response.*/
>>> +                trigger = e;
>>> +            }
>>
>> Why don't we remove ourselves any longer if we hit an EOC bucket and pass stuff on immediately?
>> If we want to handle this HTTP protocol agnostic here we need to ensure that the EOC bucket
>> triggers the immediate removal of the ap_h1_response_out_filter once the ap_h1_response_out_filter
>> sees it without putting any headers on the wire.
>> I am not sure what the appropriate behaviour would be in the HTTP > 1.1 case.
> 
> Valid point. The previous filter implementation removed itself immediately on
> encountering an EOC, regardless of where it appeared in the brigade.
> 
> The current implementation inserts, if not done previously, a RESPONSE bucket.
> Afterwards it checks on EOC, remove itself and passes on the brigade.
> 
> The behaviour differs, if an EOC is sent *without* a final RESPONSE having been
> created before that. The question is what the intended behaviour of passing an
> EOC is. Reading the comment in mod_proxy_http.c:1096, it seems that the intention really
> is to NOT generate a response.

Correct. The idea is that when the backend just closed the connection after we send the request
we do the same with our client: Just closing the connection without any response provided that we
had a keepalive connection to the frontend. This comes across to our client as if it hit a race
between sending its request to us and a keepalive closure from our side.
I am aware that this is HTTP/1.1 specific. In the light of the separation you do in these patches
I am fine if the ap_h1_response_out_filter acts accordingly and just does not process a RESPONSE bucket in
case there is an EOC bucket as well. This would allow other protocol versions to adopt to their needs.

Regards

Rüdiger


Re: svn commit: r1899648 - in /httpd/httpd/trunk: changes-entries/core_response_buckets.txt include/mod_core.h modules/http/http_core.c modules/http/http_filters.c modules/http/http_protocol.c server/protocol.c

Posted by Stefan Eissing <st...@eissing.org>.

> Am 13.04.2022 um 17:16 schrieb Ruediger Pluem <rp...@apache.org>:
> 
> 
> 
> On 4/7/22 12:41 PM, icing@apache.org wrote:
>> Author: icing
>> Date: Thu Apr  7 10:41:46 2022
>> New Revision: 1899648
>> 
>> URL: http://svn.apache.org/viewvc?rev=1899648&view=rev
>> Log:
>>  *) core/mod_http: use RESPONSE meta buckets and a new HTTP/1.x specific
>>     filter to send responses through the output filter chain.
>>     Specifically: the HTTP_HEADER output filter and ap_send_interim_response()
>>     create a RESPONSE bucket and no longer are concerned with HTTP/1.x
>>     serialization.
>>     A new HTTP1_RESPONSE_OUT transcode filter writes the proper HTTP/1.x
>>     bytes when dealing with a RESPONSE bucket. That filter installs itself
>>     on the pre_read_request hook when the connection has protocol 'http/1.1'.
>> 
>> 
>> Added:
>>    httpd/httpd/trunk/changes-entries/core_response_buckets.txt
>> Modified:
>>    httpd/httpd/trunk/include/mod_core.h
>>    httpd/httpd/trunk/modules/http/http_core.c
>>    httpd/httpd/trunk/modules/http/http_filters.c
>>    httpd/httpd/trunk/modules/http/http_protocol.c
>>    httpd/httpd/trunk/server/protocol.c
>> 
> 
>> Modified: httpd/httpd/trunk/modules/http/http_filters.c
>> URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http/http_filters.c?rev=1899648&r1=1899647&r2=1899648&view=diff
>> ==============================================================================
>> --- httpd/httpd/trunk/modules/http/http_filters.c (original)
>> +++ httpd/httpd/trunk/modules/http/http_filters.c Thu Apr  7 10:41:46 2022
> 
>> @@ -1364,62 +1178,124 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_
>>             return rv;
>>         }
>>     }
>> -
>> -    for (e = APR_BRIGADE_FIRST(b);
>> -         e != APR_BRIGADE_SENTINEL(b);
>> -         e = APR_BUCKET_NEXT(e))
>> -    {
>> -        if (AP_BUCKET_IS_ERROR(e) && !eb) {
>> -            eb = e->data;
>> -            continue;
>> -        }
>> -        if (APR_BUCKET_IS_EOS(e)) {
>> -            if (!eos) eos = e;
>> -            continue;
>> -        }
>> -        /*
>> -         * If we see an EOC bucket it is a signal that we should get out
>> -         * of the way doing nothing.
>> +    else {
>> +        /* Determine if it is time to insert the response bucket for
>> +         * the request. Request handlers just write content or an EOS
>> +         * and that needs to take the current state of request_rec to
>> +         * send on status and headers as a response bucket.
>> +         *
>> +         * But we also send interim responses (as response buckets)
>> +         * through this filter and that must not trigger generating
>> +         * an additional response bucket.
>> +         *
>> +         * Waiting on a DATA/ERROR/EOS bucket alone is not enough,
>> +         * unfortunately, as some handlers trigger response generation
>> +         * by just writing a FLUSH (see mod_lua's websocket for example).
>>          */
>> -        if (AP_BUCKET_IS_EOC(e)) {
>> -            ap_remove_output_filter(f);
>> -            return ap_pass_brigade(f->next, b);
>> +        for (e = APR_BRIGADE_FIRST(b);
>> +             e != APR_BRIGADE_SENTINEL(b) && !trigger;
>> +             e = APR_BUCKET_NEXT(e))
>> +        {
>> +            if (AP_BUCKET_IS_RESPONSE(e)) {
>> +                /* remember the last one if there are many. */
>> +                respb = e;
>> +            }
>> +            else if (APR_BUCKET_IS_FLUSH(e)) {
>> +                /* flush without response bucket triggers */
>> +                if (!respb) trigger = e;
>> +            }
>> +            else if (APR_BUCKET_IS_EOS(e)) {
>> +                trigger = e;
>> +            }
>> +            else if (AP_BUCKET_IS_ERROR(e)) {
>> +                /* Need to handle this below via ap_die() */
>> +                break;
>> +            }
>> +            else {
>> +                /* First content bucket, always triggering the response.*/
>> +                trigger = e;
>> +            }
> 
> Why don't we remove ourselves any longer if we hit an EOC bucket and pass stuff on immediately?
> If we want to handle this HTTP protocol agnostic here we need to ensure that the EOC bucket
> triggers the immediate removal of the ap_h1_response_out_filter once the ap_h1_response_out_filter
> sees it without putting any headers on the wire.
> I am not sure what the appropriate behaviour would be in the HTTP > 1.1 case.

Valid point. The previous filter implementation removed itself immediately on
encountering an EOC, regardless of where it appeared in the brigade.

The current implementation inserts, if not done previously, a RESPONSE bucket.
Afterwards it checks on EOC, remove itself and passes on the brigade.

The behaviour differs, if an EOC is sent *without* a final RESPONSE having been
created before that. The question is what the intended behaviour of passing an
EOC is. Reading the comment in mod_proxy_http.c:1096, it seems that the intention really
is to NOT generate a response.

Seems I should change the new implementation to also do that. For H2, not seeing 
a RESPONSE would be a stream error and reset the stream. Which seems fine.

>>         }
>> -    }
>> 
>> -    if (!ctx->headers_sent && !check_headers(r)) {
>> -        /* We may come back here from ap_die() below,
>> -         * so clear anything from this response.
>> -         */
>> -        apr_table_clear(r->headers_out);
>> -        apr_table_clear(r->err_headers_out);
>> -        apr_brigade_cleanup(b);
>> +        if (respb) {
>> +            ap_bucket_response *resp = respb->data;
>> +            if (resp->status >= 200 || resp->status == 101) {
> 
> Why using the literal 101 instead of HTTP_SWITCHING_PROTOCOLS?
> 
>> +                /* Someone is passing the final response, remember it
>> +                 * so we no longer generate one. */
>> +                ctx->final_status = resp->status;
>> +                ctx->final_header_only = AP_STATUS_IS_HEADER_ONLY(resp->status);
>> +            }
>> +        }
>> 
> 
>> @@ -1430,148 +1306,15 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_
>>     }
>> 
>>     if (eos) {
>> -        /* on having seen EOS and added possible trailers, we
>> -         * can remove this filter.
>> -         */
>>         e = create_trailers_bucket(r, b->bucket_alloc);
>>         if (e) {
>>             APR_BUCKET_INSERT_BEFORE(eos, e);
>>         }
>>         ap_remove_output_filter(f);
>>     }
>> -
>> -    if (ctx->headers_sent) {
>> -        /* we did already the stuff below, just pass on */
>> -        return ap_pass_brigade(f->next, b);
>> -    }
>> -
>> -    /*
>> -     * Now that we are ready to send a response, we need to combine the two
>> -     * header field tables into a single table.  If we don't do this, our
>> -     * later attempts to set or unset a given fieldname might be bypassed.
>> -     */
>> -    if (!apr_is_empty_table(r->err_headers_out)) {
>> -        r->headers_out = apr_table_overlay(r->pool, r->err_headers_out,
>> -                                           r->headers_out);
>> -    }
>> -
>> -    /*
>> -     * Remove the 'Vary' header field if the client can't handle it.
>> -     * Since this will have nasty effects on HTTP/1.1 caches, force
>> -     * the response into HTTP/1.0 mode.
>> -     *
>> -     * Note: the force-response-1.0 should come before the call to
>> -     *       basic_http_header_check()
>> -     */
>> -    if (apr_table_get(r->subprocess_env, "force-no-vary") != NULL) {
>> -        apr_table_unset(r->headers_out, "Vary");
>> -        r->proto_num = HTTP_VERSION(1,0);
>> -        apr_table_setn(r->subprocess_env, "force-response-1.0", "1");
>> -    }
>> -    else {
>> -        fixup_vary(r);
>> -    }
>> -
>> -    /*
>> -     * Now remove any ETag response header field if earlier processing
>> -     * says so (such as a 'FileETag None' directive).
>> -     */
>> -    if (apr_table_get(r->notes, "no-etag") != NULL) {
>> -        apr_table_unset(r->headers_out, "ETag");
>> -    }
>> -
>> -    /* determine the protocol and whether we should use keepalives. */
>> -    basic_http_header_check(r, &protocol);
>> -    ap_set_keepalive(r);
>> -
>> -    if (AP_STATUS_IS_HEADER_ONLY(r->status)) {
>> -        apr_table_unset(r->headers_out, "Transfer-Encoding");
>> -        apr_table_unset(r->headers_out, "Content-Length");
>> -        r->content_type = r->content_encoding = NULL;
>> -        r->content_languages = NULL;
>> -        r->clength = r->chunked = 0;
>> -    }
>> -    else if (r->chunked) {
>> -        apr_table_mergen(r->headers_out, "Transfer-Encoding", "chunked");
>> -        apr_table_unset(r->headers_out, "Content-Length");
>> -    }
>> -
>> -    ctype = ap_make_content_type(r, r->content_type);
>> -    if (ctype) {
>> -        apr_table_setn(r->headers_out, "Content-Type", ctype);
>> -    }
>> -
>> -    if (r->content_encoding) {
>> -        apr_table_setn(r->headers_out, "Content-Encoding",
>> -                       r->content_encoding);
>> -    }
>> -
>> -    if (!apr_is_empty_array(r->content_languages)) {
>> -        int i;
>> -        char *token;
>> -        char **languages = (char **)(r->content_languages->elts);
>> -        const char *field = apr_table_get(r->headers_out, "Content-Language");
>> -
>> -        while (field && (token = ap_get_list_item(r->pool, &field)) != NULL) {
>> -            for (i = 0; i < r->content_languages->nelts; ++i) {
>> -                if (!ap_cstr_casecmp(token, languages[i]))
>> -                    break;
>> -            }
>> -            if (i == r->content_languages->nelts) {
>> -                *((char **) apr_array_push(r->content_languages)) = token;
>> -            }
>> -        }
>> -
>> -        field = apr_array_pstrcat(r->pool, r->content_languages, ',');
>> -        apr_table_setn(r->headers_out, "Content-Language", field);
>> -    }
>> -
>> -    /*
>> -     * Control cachability for non-cacheable responses if not already set by
>> -     * some other part of the server configuration.
>> -     */
>> -    if (r->no_cache && !apr_table_get(r->headers_out, "Expires")) {
>> -        char *date = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
>> -        ap_recent_rfc822_date(date, r->request_time);
>> -        apr_table_addn(r->headers_out, "Expires", date);
>> -    }
>> -
>> -    b2 = apr_brigade_create(r->pool, c->bucket_alloc);
>> -    basic_http_header(r, b2, protocol);
>> -
>> -    h.pool = r->pool;
>> -    h.bb = b2;
>> -
>> -    send_all_header_fields(&h, r);
>> -
>> -    terminate_header(b2);
>> -
>> -    if (header_only) {
>> -        e = APR_BRIGADE_LAST(b);
>> -        if (e != APR_BRIGADE_SENTINEL(b) && APR_BUCKET_IS_EOS(e)) {
>> -            APR_BUCKET_REMOVE(e);
>> -            APR_BRIGADE_INSERT_TAIL(b2, e);
>> -            ap_remove_output_filter(f);
>> -        }
>> -        apr_brigade_cleanup(b);
>> -    }
>> -
>> -    rv = ap_pass_brigade(f->next, b2);
>> -    apr_brigade_cleanup(b2);
>> -    ctx->headers_sent = 1;
>> -
>> -    if (rv != APR_SUCCESS || header_only) {
>> -        goto out;
>> -    }
>> -
>> -    r->sent_bodyct = 1;         /* Whatever follows is real body stuff... */
>> -
>> -    if (r->chunked) {
>> -        /* We can't add this filter until we have already sent the headers.
>> -         * If we add it before this point, then the headers will be chunked
>> -         * as well, and that is just wrong.
>> -         */
>> -        ap_add_output_filter("CHUNK", NULL, r, r->connection);
>> +    else if (ctx->final_status == 101) {
> 
> Why using the literal 101 instead of HTTP_SWITCHING_PROTOCOLS?

The HTTP status numbers are burned into my cortex, but I agree that
using the constant gives better readability. Will change.
> 
>> +        /* switching protocol, whatever comes next is not HTTP/1.x */
>> +        ap_remove_output_filter(f);
>>     }
>> 
>>     rv = ap_pass_brigade(f->next, b);
>> @@ -1989,3 +1732,351 @@ apr_status_t ap_http_outerror_filter(ap_
>> 
>>     return ap_pass_brigade(f->next,  b);
>> }
>> +
>> +
>> +/* fill "bb" with a barebones/initial HTTP/1.x response header */
>> +static void h1_append_response_head(request_rec *r,
>> +                                    ap_bucket_response *resp,
>> +                                    const char *protocol,
>> +                                    apr_bucket_brigade *bb)
>> +{
>> +    const char *date = NULL;
>> +    const char *server = NULL;
>> +    const char *status_line;
>> +    struct iovec vec[4];
>> +
>> +    if (r->assbackwards) {
>> +        /* there are no headers to send */
>> +        return;
>> +    }
>> +
>> +    /* Output the HTTP/1.x Status-Line and the Date and Server fields */
>> +    if (resp->reason) {
>> +        status_line =  apr_psprintf(r->pool, "%d %s", resp->status, resp->reason);
>> +    }
>> +    else {
>> +        status_line = ap_get_status_line_ex(r->pool, resp->status);
>> +    }
>> +
>> +    vec[0].iov_base = (void *)protocol;
>> +    vec[0].iov_len  = strlen(protocol);
>> +    vec[1].iov_base = (void *)" ";
>> +    vec[1].iov_len  = sizeof(" ") - 1;
>> +    vec[2].iov_base = (void *)(status_line);
>> +    vec[2].iov_len  = strlen(status_line);
>> +    vec[3].iov_base = (void *)CRLF;
>> +    vec[3].iov_len  = sizeof(CRLF) - 1;
>> +#if APR_CHARSET_EBCDIC
>> +    {
>> +        char *tmp;
>> +        apr_size_t len;
>> +        tmp = apr_pstrcatv(r->pool, vec, 4, &len);
>> +        ap_xlate_proto_to_ascii(tmp, len);
>> +        apr_brigade_write(bb, NULL, NULL, tmp, len);
>> +    }
>> +#else
>> +    apr_brigade_writev(bb, NULL, NULL, vec, 4);
>> +#endif
>> +
>> +    date = apr_table_get(resp->headers, "Date");
>> +    server = apr_table_get(resp->headers, "Server");
>> +    if (date) {
>> +        /* We always write that as first, just because we
>> +         * always did and some quirky clients might rely on that.
>> +         */
>> +        ap_h1_append_header(bb, r->pool, "Date", date);
>> +        apr_table_unset(resp->headers, "Date");
>> +    }
>> +    if (server) {
>> +        /* We always write that second, just because we
>> +         * always did and some quirky clients might rely on that.
>> +         */
>> +        ap_h1_append_header(bb, r->pool, "Server", server);
>> +        apr_table_unset(resp->headers, "Server");
>> +    }
>> +
>> +    if (APLOGrtrace3(r)) {
>> +        ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
>> +                      "Response sent with status %d%s",
>> +                      r->status,
>> +                      APLOGrtrace4(r) ? ", headers:" : "");
>> +
>> +        /*
>> +         * Date and Server are less interesting, use TRACE5 for them while
>> +         * using TRACE4 for the other headers.
>> +         */
>> +        if (date)
>> +            ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "  Date: %s",
>> +                          date);
>> +        if (server)
>> +            ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "  Server: %s",
>> +                          server);
>> +    }
>> +}
>> +
>> +typedef struct h1_response_ctx {
>> +    int final_response_sent;    /* strict: a response status >= 200 was sent */
>> +    int discard_body;           /* the response is header only, discard body */
>> +    apr_bucket_brigade *tmpbb;
>> +} h1_response_ctx;
>> +
>> +AP_CORE_DECLARE_NONSTD(apr_status_t) ap_h1_response_out_filter(ap_filter_t *f,
>> +                                                               apr_bucket_brigade *b)
>> +{
>> +    request_rec *r = f->r;
>> +    conn_rec *c = r->connection;
>> +    apr_bucket *e, *next = NULL;
>> +    h1_response_ctx *ctx = f->ctx;
>> +    apr_status_t rv = APR_SUCCESS;
>> +    core_server_config *conf = ap_get_core_module_config(r->server->module_config);
>> +    int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE);
>> +
>> +    AP_DEBUG_ASSERT(!r->main);
>> +
>> +    if (!ctx) {
>> +        ctx = f->ctx = apr_pcalloc(r->pool, sizeof(*ctx));
>> +    }
>> +
>> +    for (e = APR_BRIGADE_FIRST(b);
>> +         e != APR_BRIGADE_SENTINEL(b);
>> +         e = next)
>> +    {
>> +        next = APR_BUCKET_NEXT(e);
>> +
>> +        if (APR_BUCKET_IS_METADATA(e)) {
>> +
>> +            if (APR_BUCKET_IS_EOS(e)) {
>> +                if (!ctx->final_response_sent) {
>> +                    /* should not happen. do we generate a 500 here? */
>> +                }
>> +                ap_remove_output_filter(f);
>> +                goto pass;
>> +            }
>> +            else if (AP_BUCKET_IS_RESPONSE(e)) {
>> +                ap_bucket_response *resp = e->data;
>> +
>> +                ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
>> +                              "ap_http1_response_out_filter seeing response bucket status=%d",
>> +                              resp->status);
>> +                if (strict && resp->status < 100) {
>> +                    /* error, not a valid http status */
>> +                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10386)
>> +                                  "ap_http1_response_out_filter seeing headers "
>> +                                  "status=%d in strict mode",
>> +                                  resp->status);
>> +                    rv = AP_FILTER_ERROR;
>> +                    goto cleanup;
>> +                }
>> +                else if (ctx->final_response_sent) {
>> +                    /* already sent the final response for the request.
>> +                     */
>> +                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10387)
>> +                                  "ap_http1_response_out_filter seeing headers "
>> +                                  "status=%d after final response already sent",
>> +                                  resp->status);
>> +                    rv = AP_FILTER_ERROR;
>> +                    goto cleanup;
>> +                }
>> +                else {
>> +                    /* a response status to transcode, might be final or interim
>> +                     */
>> +                    const char *proto = AP_SERVER_PROTOCOL;
>> +
>> +                    ctx->final_response_sent = (resp->status >= 200)
>> +                        || (!strict && resp->status < 100);
>> +                    ctx->discard_body = ctx->final_response_sent &&
>> +                        (r->header_only || AP_STATUS_IS_HEADER_ONLY(resp->status));
>> +
>> +                    if (!ctx->tmpbb) {
>> +                        ctx->tmpbb = apr_brigade_create(r->pool, c->bucket_alloc);
>> +                    }
>> +                    if (next != APR_BRIGADE_SENTINEL(b)) {
>> +                        apr_brigade_split_ex(b, next, ctx->tmpbb);
>> +                    }
>> +
>> +                    if (ctx->final_response_sent) {
>> +                        ap_h1_set_keepalive(r, resp);
>> +
>> +                        if (AP_STATUS_IS_HEADER_ONLY(resp->status)) {
>> +                            apr_table_unset(resp->headers, "Transfer-Encoding");
>> +                        }
>> +                        else if (r->chunked) {
>> +                            apr_table_mergen(resp->headers, "Transfer-Encoding", "chunked");
>> +                            apr_table_unset(resp->headers, "Content-Length");
>> +                        }
>> +                    }
>> +
>> +                    /* kludge around broken browsers when indicated by force-response-1.0
>> +                     */
>> +                    if (r->proto_num == HTTP_VERSION(1,0)
>> +                        && apr_table_get(r->subprocess_env, "force-response-1.0")) {
>> +                        r->connection->keepalive = AP_CONN_CLOSE;
>> +                        proto = "HTTP/1.0";
>> +                    }
>> +                    h1_append_response_head(r, resp, proto, b);
>> +                    ap_h1_append_headers(b, r, resp->headers);
>> +                    ap_h1_terminate_header(b);
>> +                    apr_bucket_delete(e);
>> +
>> +                    if (ctx->final_response_sent && r->chunked) {
>> +                        /* We can't add this filter until we have already sent the headers.
>> +                         * If we add it before this point, then the headers will be chunked
>> +                         * as well, and that is just wrong.
>> +                         */
>> +                        rv = ap_pass_brigade(f->next, b);
>> +                        apr_brigade_cleanup(b);
>> +                        ap_log_rerror(APLOG_MARK, APLOG_TRACE2, rv, r,
>> +                                      "ap_http1_response_out_filter passed response"
>> +                                      ", add CHUNK filter");
>> +                        if (APR_SUCCESS != rv) {
>> +                            apr_brigade_cleanup(ctx->tmpbb);
>> +                            goto cleanup;
>> +                        }
>> +                        ap_add_output_filter("CHUNK", NULL, r, r->connection);
>> +                    }
>> +
>> +                    APR_BRIGADE_CONCAT(b, ctx->tmpbb);
>> +
>> +                    if (resp->status == 101) {
> 
> Why using the literal 101 instead of HTTP_SWITCHING_PROTOCOLS?
> 
>> +                        /* switched to another protocol, get out of the way */
>> +                        AP_DEBUG_ASSERT(!r->chunked);
>> +                        ap_remove_output_filter(f);
>> +                        goto pass;
>> +                    }
>> +                }
>> +            }
>> +        }
>> +        else if (!ctx->final_response_sent && strict) {
>> +            /* data buckets before seeing the final response are in error.
>> +             */
>> +            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10390)
>> +                          "ap_http1_response_out_filter seeing data before headers, %ld bytes ",
>> +                          (long)e->length);
>> +            rv = AP_FILTER_ERROR;
>> +            goto cleanup;
>> +        }
>> +        else if (ctx->discard_body) {
>> +            apr_bucket_delete(e);
>> +        }
>> +    }
>> +
>> +pass:
>> +    rv = ap_pass_brigade(f->next, b);
>> +cleanup:
>> +    return rv;
>> +}
>> +
> 
> Regards
> 
> Rüdiger
> 


Re: svn commit: r1899648 - in /httpd/httpd/trunk: changes-entries/core_response_buckets.txt include/mod_core.h modules/http/http_core.c modules/http/http_filters.c modules/http/http_protocol.c server/protocol.c

Posted by Ruediger Pluem <rp...@apache.org>.

On 4/7/22 12:41 PM, icing@apache.org wrote:
> Author: icing
> Date: Thu Apr  7 10:41:46 2022
> New Revision: 1899648
> 
> URL: http://svn.apache.org/viewvc?rev=1899648&view=rev
> Log:
>   *) core/mod_http: use RESPONSE meta buckets and a new HTTP/1.x specific
>      filter to send responses through the output filter chain.
>      Specifically: the HTTP_HEADER output filter and ap_send_interim_response()
>      create a RESPONSE bucket and no longer are concerned with HTTP/1.x
>      serialization.
>      A new HTTP1_RESPONSE_OUT transcode filter writes the proper HTTP/1.x
>      bytes when dealing with a RESPONSE bucket. That filter installs itself
>      on the pre_read_request hook when the connection has protocol 'http/1.1'.
> 
> 
> Added:
>     httpd/httpd/trunk/changes-entries/core_response_buckets.txt
> Modified:
>     httpd/httpd/trunk/include/mod_core.h
>     httpd/httpd/trunk/modules/http/http_core.c
>     httpd/httpd/trunk/modules/http/http_filters.c
>     httpd/httpd/trunk/modules/http/http_protocol.c
>     httpd/httpd/trunk/server/protocol.c
> 

> Modified: httpd/httpd/trunk/modules/http/http_filters.c
> URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http/http_filters.c?rev=1899648&r1=1899647&r2=1899648&view=diff
> ==============================================================================
> --- httpd/httpd/trunk/modules/http/http_filters.c (original)
> +++ httpd/httpd/trunk/modules/http/http_filters.c Thu Apr  7 10:41:46 2022

> @@ -1364,62 +1178,124 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_
>              return rv;
>          }
>      }
> -
> -    for (e = APR_BRIGADE_FIRST(b);
> -         e != APR_BRIGADE_SENTINEL(b);
> -         e = APR_BUCKET_NEXT(e))
> -    {
> -        if (AP_BUCKET_IS_ERROR(e) && !eb) {
> -            eb = e->data;
> -            continue;
> -        }
> -        if (APR_BUCKET_IS_EOS(e)) {
> -            if (!eos) eos = e;
> -            continue;
> -        }
> -        /*
> -         * If we see an EOC bucket it is a signal that we should get out
> -         * of the way doing nothing.
> +    else {
> +        /* Determine if it is time to insert the response bucket for
> +         * the request. Request handlers just write content or an EOS
> +         * and that needs to take the current state of request_rec to
> +         * send on status and headers as a response bucket.
> +         *
> +         * But we also send interim responses (as response buckets)
> +         * through this filter and that must not trigger generating
> +         * an additional response bucket.
> +         *
> +         * Waiting on a DATA/ERROR/EOS bucket alone is not enough,
> +         * unfortunately, as some handlers trigger response generation
> +         * by just writing a FLUSH (see mod_lua's websocket for example).
>           */
> -        if (AP_BUCKET_IS_EOC(e)) {
> -            ap_remove_output_filter(f);
> -            return ap_pass_brigade(f->next, b);
> +        for (e = APR_BRIGADE_FIRST(b);
> +             e != APR_BRIGADE_SENTINEL(b) && !trigger;
> +             e = APR_BUCKET_NEXT(e))
> +        {
> +            if (AP_BUCKET_IS_RESPONSE(e)) {
> +                /* remember the last one if there are many. */
> +                respb = e;
> +            }
> +            else if (APR_BUCKET_IS_FLUSH(e)) {
> +                /* flush without response bucket triggers */
> +                if (!respb) trigger = e;
> +            }
> +            else if (APR_BUCKET_IS_EOS(e)) {
> +                trigger = e;
> +            }
> +            else if (AP_BUCKET_IS_ERROR(e)) {
> +                /* Need to handle this below via ap_die() */
> +                break;
> +            }
> +            else {
> +                /* First content bucket, always triggering the response.*/
> +                trigger = e;
> +            }

Why don't we remove ourselves any longer if we hit an EOC bucket and pass stuff on immediately?
If we want to handle this HTTP protocol agnostic here we need to ensure that the EOC bucket
triggers the immediate removal of the ap_h1_response_out_filter once the ap_h1_response_out_filter
sees it without putting any headers on the wire.
I am not sure what the appropriate behaviour would be in the HTTP > 1.1 case.

>          }
> -    }
>  
> -    if (!ctx->headers_sent && !check_headers(r)) {
> -        /* We may come back here from ap_die() below,
> -         * so clear anything from this response.
> -         */
> -        apr_table_clear(r->headers_out);
> -        apr_table_clear(r->err_headers_out);
> -        apr_brigade_cleanup(b);
> +        if (respb) {
> +            ap_bucket_response *resp = respb->data;
> +            if (resp->status >= 200 || resp->status == 101) {

Why using the literal 101 instead of HTTP_SWITCHING_PROTOCOLS?

> +                /* Someone is passing the final response, remember it
> +                 * so we no longer generate one. */
> +                ctx->final_status = resp->status;
> +                ctx->final_header_only = AP_STATUS_IS_HEADER_ONLY(resp->status);
> +            }
> +        }
>  

> @@ -1430,148 +1306,15 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_
>      }
>  
>      if (eos) {
> -        /* on having seen EOS and added possible trailers, we
> -         * can remove this filter.
> -         */
>          e = create_trailers_bucket(r, b->bucket_alloc);
>          if (e) {
>              APR_BUCKET_INSERT_BEFORE(eos, e);
>          }
>          ap_remove_output_filter(f);
>      }
> -
> -    if (ctx->headers_sent) {
> -        /* we did already the stuff below, just pass on */
> -        return ap_pass_brigade(f->next, b);
> -    }
> -
> -    /*
> -     * Now that we are ready to send a response, we need to combine the two
> -     * header field tables into a single table.  If we don't do this, our
> -     * later attempts to set or unset a given fieldname might be bypassed.
> -     */
> -    if (!apr_is_empty_table(r->err_headers_out)) {
> -        r->headers_out = apr_table_overlay(r->pool, r->err_headers_out,
> -                                           r->headers_out);
> -    }
> -
> -    /*
> -     * Remove the 'Vary' header field if the client can't handle it.
> -     * Since this will have nasty effects on HTTP/1.1 caches, force
> -     * the response into HTTP/1.0 mode.
> -     *
> -     * Note: the force-response-1.0 should come before the call to
> -     *       basic_http_header_check()
> -     */
> -    if (apr_table_get(r->subprocess_env, "force-no-vary") != NULL) {
> -        apr_table_unset(r->headers_out, "Vary");
> -        r->proto_num = HTTP_VERSION(1,0);
> -        apr_table_setn(r->subprocess_env, "force-response-1.0", "1");
> -    }
> -    else {
> -        fixup_vary(r);
> -    }
> -
> -    /*
> -     * Now remove any ETag response header field if earlier processing
> -     * says so (such as a 'FileETag None' directive).
> -     */
> -    if (apr_table_get(r->notes, "no-etag") != NULL) {
> -        apr_table_unset(r->headers_out, "ETag");
> -    }
> -
> -    /* determine the protocol and whether we should use keepalives. */
> -    basic_http_header_check(r, &protocol);
> -    ap_set_keepalive(r);
> -
> -    if (AP_STATUS_IS_HEADER_ONLY(r->status)) {
> -        apr_table_unset(r->headers_out, "Transfer-Encoding");
> -        apr_table_unset(r->headers_out, "Content-Length");
> -        r->content_type = r->content_encoding = NULL;
> -        r->content_languages = NULL;
> -        r->clength = r->chunked = 0;
> -    }
> -    else if (r->chunked) {
> -        apr_table_mergen(r->headers_out, "Transfer-Encoding", "chunked");
> -        apr_table_unset(r->headers_out, "Content-Length");
> -    }
> -
> -    ctype = ap_make_content_type(r, r->content_type);
> -    if (ctype) {
> -        apr_table_setn(r->headers_out, "Content-Type", ctype);
> -    }
> -
> -    if (r->content_encoding) {
> -        apr_table_setn(r->headers_out, "Content-Encoding",
> -                       r->content_encoding);
> -    }
> -
> -    if (!apr_is_empty_array(r->content_languages)) {
> -        int i;
> -        char *token;
> -        char **languages = (char **)(r->content_languages->elts);
> -        const char *field = apr_table_get(r->headers_out, "Content-Language");
> -
> -        while (field && (token = ap_get_list_item(r->pool, &field)) != NULL) {
> -            for (i = 0; i < r->content_languages->nelts; ++i) {
> -                if (!ap_cstr_casecmp(token, languages[i]))
> -                    break;
> -            }
> -            if (i == r->content_languages->nelts) {
> -                *((char **) apr_array_push(r->content_languages)) = token;
> -            }
> -        }
> -
> -        field = apr_array_pstrcat(r->pool, r->content_languages, ',');
> -        apr_table_setn(r->headers_out, "Content-Language", field);
> -    }
> -
> -    /*
> -     * Control cachability for non-cacheable responses if not already set by
> -     * some other part of the server configuration.
> -     */
> -    if (r->no_cache && !apr_table_get(r->headers_out, "Expires")) {
> -        char *date = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
> -        ap_recent_rfc822_date(date, r->request_time);
> -        apr_table_addn(r->headers_out, "Expires", date);
> -    }
> -
> -    b2 = apr_brigade_create(r->pool, c->bucket_alloc);
> -    basic_http_header(r, b2, protocol);
> -
> -    h.pool = r->pool;
> -    h.bb = b2;
> -
> -    send_all_header_fields(&h, r);
> -
> -    terminate_header(b2);
> -
> -    if (header_only) {
> -        e = APR_BRIGADE_LAST(b);
> -        if (e != APR_BRIGADE_SENTINEL(b) && APR_BUCKET_IS_EOS(e)) {
> -            APR_BUCKET_REMOVE(e);
> -            APR_BRIGADE_INSERT_TAIL(b2, e);
> -            ap_remove_output_filter(f);
> -        }
> -        apr_brigade_cleanup(b);
> -    }
> -
> -    rv = ap_pass_brigade(f->next, b2);
> -    apr_brigade_cleanup(b2);
> -    ctx->headers_sent = 1;
> -
> -    if (rv != APR_SUCCESS || header_only) {
> -        goto out;
> -    }
> -
> -    r->sent_bodyct = 1;         /* Whatever follows is real body stuff... */
> -
> -    if (r->chunked) {
> -        /* We can't add this filter until we have already sent the headers.
> -         * If we add it before this point, then the headers will be chunked
> -         * as well, and that is just wrong.
> -         */
> -        ap_add_output_filter("CHUNK", NULL, r, r->connection);
> +    else if (ctx->final_status == 101) {

Why using the literal 101 instead of HTTP_SWITCHING_PROTOCOLS?

> +        /* switching protocol, whatever comes next is not HTTP/1.x */
> +        ap_remove_output_filter(f);
>      }
>  
>      rv = ap_pass_brigade(f->next, b);
> @@ -1989,3 +1732,351 @@ apr_status_t ap_http_outerror_filter(ap_
>  
>      return ap_pass_brigade(f->next,  b);
>  }
> +
> +
> +/* fill "bb" with a barebones/initial HTTP/1.x response header */
> +static void h1_append_response_head(request_rec *r,
> +                                    ap_bucket_response *resp,
> +                                    const char *protocol,
> +                                    apr_bucket_brigade *bb)
> +{
> +    const char *date = NULL;
> +    const char *server = NULL;
> +    const char *status_line;
> +    struct iovec vec[4];
> +
> +    if (r->assbackwards) {
> +        /* there are no headers to send */
> +        return;
> +    }
> +
> +    /* Output the HTTP/1.x Status-Line and the Date and Server fields */
> +    if (resp->reason) {
> +        status_line =  apr_psprintf(r->pool, "%d %s", resp->status, resp->reason);
> +    }
> +    else {
> +        status_line = ap_get_status_line_ex(r->pool, resp->status);
> +    }
> +
> +    vec[0].iov_base = (void *)protocol;
> +    vec[0].iov_len  = strlen(protocol);
> +    vec[1].iov_base = (void *)" ";
> +    vec[1].iov_len  = sizeof(" ") - 1;
> +    vec[2].iov_base = (void *)(status_line);
> +    vec[2].iov_len  = strlen(status_line);
> +    vec[3].iov_base = (void *)CRLF;
> +    vec[3].iov_len  = sizeof(CRLF) - 1;
> +#if APR_CHARSET_EBCDIC
> +    {
> +        char *tmp;
> +        apr_size_t len;
> +        tmp = apr_pstrcatv(r->pool, vec, 4, &len);
> +        ap_xlate_proto_to_ascii(tmp, len);
> +        apr_brigade_write(bb, NULL, NULL, tmp, len);
> +    }
> +#else
> +    apr_brigade_writev(bb, NULL, NULL, vec, 4);
> +#endif
> +
> +    date = apr_table_get(resp->headers, "Date");
> +    server = apr_table_get(resp->headers, "Server");
> +    if (date) {
> +        /* We always write that as first, just because we
> +         * always did and some quirky clients might rely on that.
> +         */
> +        ap_h1_append_header(bb, r->pool, "Date", date);
> +        apr_table_unset(resp->headers, "Date");
> +    }
> +    if (server) {
> +        /* We always write that second, just because we
> +         * always did and some quirky clients might rely on that.
> +         */
> +        ap_h1_append_header(bb, r->pool, "Server", server);
> +        apr_table_unset(resp->headers, "Server");
> +    }
> +
> +    if (APLOGrtrace3(r)) {
> +        ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
> +                      "Response sent with status %d%s",
> +                      r->status,
> +                      APLOGrtrace4(r) ? ", headers:" : "");
> +
> +        /*
> +         * Date and Server are less interesting, use TRACE5 for them while
> +         * using TRACE4 for the other headers.
> +         */
> +        if (date)
> +            ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "  Date: %s",
> +                          date);
> +        if (server)
> +            ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "  Server: %s",
> +                          server);
> +    }
> +}
> +
> +typedef struct h1_response_ctx {
> +    int final_response_sent;    /* strict: a response status >= 200 was sent */
> +    int discard_body;           /* the response is header only, discard body */
> +    apr_bucket_brigade *tmpbb;
> +} h1_response_ctx;
> +
> +AP_CORE_DECLARE_NONSTD(apr_status_t) ap_h1_response_out_filter(ap_filter_t *f,
> +                                                               apr_bucket_brigade *b)
> +{
> +    request_rec *r = f->r;
> +    conn_rec *c = r->connection;
> +    apr_bucket *e, *next = NULL;
> +    h1_response_ctx *ctx = f->ctx;
> +    apr_status_t rv = APR_SUCCESS;
> +    core_server_config *conf = ap_get_core_module_config(r->server->module_config);
> +    int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE);
> +
> +    AP_DEBUG_ASSERT(!r->main);
> +
> +    if (!ctx) {
> +        ctx = f->ctx = apr_pcalloc(r->pool, sizeof(*ctx));
> +    }
> +
> +    for (e = APR_BRIGADE_FIRST(b);
> +         e != APR_BRIGADE_SENTINEL(b);
> +         e = next)
> +    {
> +        next = APR_BUCKET_NEXT(e);
> +
> +        if (APR_BUCKET_IS_METADATA(e)) {
> +
> +            if (APR_BUCKET_IS_EOS(e)) {
> +                if (!ctx->final_response_sent) {
> +                    /* should not happen. do we generate a 500 here? */
> +                }
> +                ap_remove_output_filter(f);
> +                goto pass;
> +            }
> +            else if (AP_BUCKET_IS_RESPONSE(e)) {
> +                ap_bucket_response *resp = e->data;
> +
> +                ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
> +                              "ap_http1_response_out_filter seeing response bucket status=%d",
> +                              resp->status);
> +                if (strict && resp->status < 100) {
> +                    /* error, not a valid http status */
> +                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10386)
> +                                  "ap_http1_response_out_filter seeing headers "
> +                                  "status=%d in strict mode",
> +                                  resp->status);
> +                    rv = AP_FILTER_ERROR;
> +                    goto cleanup;
> +                }
> +                else if (ctx->final_response_sent) {
> +                    /* already sent the final response for the request.
> +                     */
> +                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10387)
> +                                  "ap_http1_response_out_filter seeing headers "
> +                                  "status=%d after final response already sent",
> +                                  resp->status);
> +                    rv = AP_FILTER_ERROR;
> +                    goto cleanup;
> +                }
> +                else {
> +                    /* a response status to transcode, might be final or interim
> +                     */
> +                    const char *proto = AP_SERVER_PROTOCOL;
> +
> +                    ctx->final_response_sent = (resp->status >= 200)
> +                        || (!strict && resp->status < 100);
> +                    ctx->discard_body = ctx->final_response_sent &&
> +                        (r->header_only || AP_STATUS_IS_HEADER_ONLY(resp->status));
> +
> +                    if (!ctx->tmpbb) {
> +                        ctx->tmpbb = apr_brigade_create(r->pool, c->bucket_alloc);
> +                    }
> +                    if (next != APR_BRIGADE_SENTINEL(b)) {
> +                        apr_brigade_split_ex(b, next, ctx->tmpbb);
> +                    }
> +
> +                    if (ctx->final_response_sent) {
> +                        ap_h1_set_keepalive(r, resp);
> +
> +                        if (AP_STATUS_IS_HEADER_ONLY(resp->status)) {
> +                            apr_table_unset(resp->headers, "Transfer-Encoding");
> +                        }
> +                        else if (r->chunked) {
> +                            apr_table_mergen(resp->headers, "Transfer-Encoding", "chunked");
> +                            apr_table_unset(resp->headers, "Content-Length");
> +                        }
> +                    }
> +
> +                    /* kludge around broken browsers when indicated by force-response-1.0
> +                     */
> +                    if (r->proto_num == HTTP_VERSION(1,0)
> +                        && apr_table_get(r->subprocess_env, "force-response-1.0")) {
> +                        r->connection->keepalive = AP_CONN_CLOSE;
> +                        proto = "HTTP/1.0";
> +                    }
> +                    h1_append_response_head(r, resp, proto, b);
> +                    ap_h1_append_headers(b, r, resp->headers);
> +                    ap_h1_terminate_header(b);
> +                    apr_bucket_delete(e);
> +
> +                    if (ctx->final_response_sent && r->chunked) {
> +                        /* We can't add this filter until we have already sent the headers.
> +                         * If we add it before this point, then the headers will be chunked
> +                         * as well, and that is just wrong.
> +                         */
> +                        rv = ap_pass_brigade(f->next, b);
> +                        apr_brigade_cleanup(b);
> +                        ap_log_rerror(APLOG_MARK, APLOG_TRACE2, rv, r,
> +                                      "ap_http1_response_out_filter passed response"
> +                                      ", add CHUNK filter");
> +                        if (APR_SUCCESS != rv) {
> +                            apr_brigade_cleanup(ctx->tmpbb);
> +                            goto cleanup;
> +                        }
> +                        ap_add_output_filter("CHUNK", NULL, r, r->connection);
> +                    }
> +
> +                    APR_BRIGADE_CONCAT(b, ctx->tmpbb);
> +
> +                    if (resp->status == 101) {

Why using the literal 101 instead of HTTP_SWITCHING_PROTOCOLS?

> +                        /* switched to another protocol, get out of the way */
> +                        AP_DEBUG_ASSERT(!r->chunked);
> +                        ap_remove_output_filter(f);
> +                        goto pass;
> +                    }
> +                }
> +            }
> +        }
> +        else if (!ctx->final_response_sent && strict) {
> +            /* data buckets before seeing the final response are in error.
> +             */
> +            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10390)
> +                          "ap_http1_response_out_filter seeing data before headers, %ld bytes ",
> +                          (long)e->length);
> +            rv = AP_FILTER_ERROR;
> +            goto cleanup;
> +        }
> +        else if (ctx->discard_body) {
> +            apr_bucket_delete(e);
> +        }
> +    }
> +
> +pass:
> +    rv = ap_pass_brigade(f->next, b);
> +cleanup:
> +    return rv;
> +}
> +

Regards

Rüdiger