]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
*) core/mod_http: use RESPONSE meta buckets and a new HTTP/1.x specific
authorStefan Eissing <icing@apache.org>
Thu, 7 Apr 2022 10:41:46 +0000 (10:41 +0000)
committerStefan Eissing <icing@apache.org>
Thu, 7 Apr 2022 10:41:46 +0000 (10:41 +0000)
     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'.

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1899648 13f79535-47bb-0310-9956-ffa450edef68

changes-entries/core_response_buckets.txt [new file with mode: 0644]
include/mod_core.h
modules/http/http_core.c
modules/http/http_filters.c
modules/http/http_protocol.c
server/protocol.c

diff --git a/changes-entries/core_response_buckets.txt b/changes-entries/core_response_buckets.txt
new file mode 100644 (file)
index 0000000..9a7972e
--- /dev/null
@@ -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
index 4897fee6f5ce09507dcd3f0adbbd78fa1982a16a..0c795ceeeef0b92dbc0028e24d130cc9120b0792 100644 (file)
@@ -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_filter_t *f, apr_bucket_brigade *b,
                                      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_pool_t *p);
 /* 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
index ce87ef2045f235cffd933c7e849b2b8890f1d35f..92ab08911bf24e182998d7f51e41707306952d9b 100644 (file)
@@ -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_rec *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);
index 2f4b6499e6b9faec1cb9c8736d72086a992637a9..35fad461f681440f73d1e3d0965768c1966cc002 100644 (file)
@@ -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(request_rec *r)
  *
  * 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(request_rec *r,
         && 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_trace(request_rec *r)
     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_http_header_filter(ap_filter_t *f,
 {
     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_http_header_filter(ap_filter_t *f,
     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_http_header_filter(ap_filter_t *f,
             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_http_header_filter(ap_filter_t *f,
     }
 
     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_filter_t *f,
 
     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;
+}
index 993462826adf23cfdea55edeafcd524bc92caa73..933567005207eab5da84a431e346b005119c1ce0 100644 (file)
@@ -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_rec *r)
     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_rec *r)
         /* 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_rec *r)
      * 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_rec *r)
     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_method_list_t *l)
     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,
index ee2bfd4cb272dc0e50883bcdff6bd669a3caf89f..a11ae6cce25abee7bb019ccfc325964cf107b218 100644 (file)
@@ -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_headers(request_rec *r)
 
 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_response(request_rec *r, int send_headers)
         }
     }
 
-    status_line = r->status_line;
-    if (status_line == NULL) {
-        status_line = ap_get_status_line_ex(r->pool, r->status);
+    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);
     }
-    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);
+    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);
 }
 
 /*