summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--changes-entries/core_response_buckets.txt9
-rw-r--r--include/mod_core.h12
-rw-r--r--modules/http/http_core.c14
-rw-r--r--modules/http/http_filters.c867
-rw-r--r--modules/http/http_protocol.c86
-rw-r--r--server/protocol.c58
6 files changed, 592 insertions, 454 deletions
diff --git a/changes-entries/core_response_buckets.txt b/changes-entries/core_response_buckets.txt
new file mode 100644
index 0000000000..9a7972e33b
--- /dev/null
+++ b/changes-entries/core_response_buckets.txt
@@ -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
diff --git a/include/mod_core.h b/include/mod_core.h
index 4897fee6f5..0c795ceeee 100644
--- a/include/mod_core.h
+++ b/include/mod_core.h
@@ -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
diff --git a/modules/http/http_core.c b/modules/http/http_core.c
index ce87ef2045..92ab08911b 100644
--- a/modules/http/http_core.c
+++ b/modules/http/http_core.c
@@ -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);
diff --git a/modules/http/http_filters.c b/modules/http/http_filters.c
index 2f4b6499e6..35fad461f6 100644
--- a/modules/http/http_filters.c
+++ b/modules/http/http_filters.c
@@ -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;
+}
diff --git a/modules/http/http_protocol.c b/modules/http/http_protocol.c
index 993462826a..9335670052 100644
--- a/modules/http/http_protocol.c
+++ b/modules/http/http_protocol.c
@@ -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,
diff --git a/server/protocol.c b/server/protocol.c
index ee2bfd4cb2..a11ae6cce2 100644
--- a/server/protocol.c
+++ b/server/protocol.c
@@ -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);
}
/*