* (this MAJOR bump). Overall there is no MAJOR bumb
* for 20210506.0 + 20210924.0, MINOR bump only for
* adding ap_proxy_tunnel_conn_bytes_{in,out}().
+ * 20210924.1 (2.5.1-dev) Add ap_proxy_fill_error_brigade()
*/
#define MODULE_MAGIC_COOKIE 0x41503235UL /* "AP25" */
#ifndef MODULE_MAGIC_NUMBER_MAJOR
#define MODULE_MAGIC_NUMBER_MAJOR 20210924
#endif
-#define MODULE_MAGIC_NUMBER_MINOR 0 /* 0...n */
+#define MODULE_MAGIC_NUMBER_MINOR 1 /* 0...n */
/**
* Determine if the server's current MODULE_MAGIC_NUMBER is at least a
PROXY_DECLARE(int) ap_proxy_connection_reusable(proxy_conn_rec *conn);
/**
- * Signal the upstream chain that the connection to the backend broke in the
- * middle of the response. This is done by sending an error bucket with
- * status HTTP_BAD_GATEWAY and an EOS bucket up the filter chain.
+ * Fill a brigade that can be sent downstream to signal that the connection to
+ * the backend broke in the middle of the response. The brigade will contain
+ * an error bucket with the given status and eventually an EOC bucket if asked
+ * to or heuristically if the response header was sent already.
* @param r current request record of client request
- * @param brigade The brigade that is sent through the output filter chain
- * @deprecated To be removed in v2.6.
+ * @param status the error status
+ * @param bb the brigade to fill
+ * @param eoc 0 do not add an EOC bucket, > 0 always add one, < 0 add one
+ * only if the response header was sent already
+ */
+PROXY_DECLARE(void) ap_proxy_fill_error_brigade(request_rec *r, int status,
+ apr_bucket_brigade *bb,
+ int eoc);
+
+/**
+ * Fill a brigade that can be sent downstream to signal that the connection to
+ * the backend broke in the middle of the response. The brigade will contain
+ * an error bucket with status HTTP_BAD_GATEWAY and an EOS bucket.
+ * @param r current request record of client request
+ * @param brigade the brigade to fill
+ * @deprecated To be removed in v2.6 (see ap_proxy_fill_error_brigade).
*/
PROXY_DECLARE(void) ap_proxy_backend_broke(request_rec *r,
apr_bucket_brigade *brigade);
* upwards in the chain.
*/
if (data_sent) {
- ap_proxy_backend_broke(r, output_brigade);
+ ap_proxy_fill_error_brigade(r, HTTP_BAD_GATEWAY, output_brigade, -1);
} else if (!send_body && (is_idempotent(r) == METHOD_IDEMPOTENT)) {
/*
* This is only non fatal when we have not send (parts) of a possible
/*
* Ensure that we sent an EOS bucket thru the filter chain, if we already
- * have sent some data. Maybe ap_proxy_backend_broke was called and added
- * one to the brigade already (no longer making it empty). So we should
- * not do this in this case.
+ * have sent some data.
*/
- if (data_sent && !r->eos_sent && !r->connection->aborted
- && APR_BRIGADE_EMPTY(output_brigade)) {
+ if (data_sent && !r->eos_sent && !r->connection->aborted) {
e = apr_bucket_eos_create(r->connection->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(output_brigade, e);
}
" Number of keepalives %i", backend->hostname,
backend->port, c->keepalives);
- e = ap_bucket_error_create(HTTP_BAD_GATEWAY, NULL,
- r->pool, c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, e);
- e = ap_bucket_eoc_create(c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, e);
+ ap_proxy_fill_error_brigade(r, HTTP_BAD_GATEWAY, bb, 1);
ap_pass_brigade(r->output_filters, bb);
/* Mark the backend connection for closing */
backend->close = 1;
mode = APR_BLOCK_READ;
continue;
}
- else if (rv == APR_EOF) {
+ if (rv == APR_EOF) {
backend->close = 1;
break;
}
- else if (rv != APR_SUCCESS) {
+ if (rv != APR_SUCCESS || APR_BRIGADE_EMPTY(bb)) {
+ int error_status = HTTP_BAD_GATEWAY;
if (rv == APR_ENOSPC) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02475)
"Response chunk/line was too large to parse");
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02476)
"Response Transfer-Encoding was not recognised");
}
- else {
+ else if (rv != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01110)
"Network error reading response");
}
+ else {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10293)
+ "Unexpected empty data reading response");
+ error_status = HTTP_INTERNAL_SERVER_ERROR;
+ }
/* In this case, we are in real trouble because
* our backend bailed on us. Given we're half way
* disconnect the client too.
*/
apr_brigade_cleanup(bb);
- e = ap_bucket_error_create(HTTP_BAD_GATEWAY, NULL,
- r->pool, c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, e);
- e = ap_bucket_eoc_create(c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, e);
+ ap_proxy_fill_error_brigade(r, error_status, bb, 1);
ap_pass_brigade(r->output_filters, bb);
backend_broke = 1;
"readbytes: %#x", readbytes);
}
#endif
- /* sanity check */
- if (APR_BRIGADE_EMPTY(bb)) {
- break;
- }
/* Switch the allocator lifetime of the buckets */
- ap_proxy_buckets_lifetime_transform(r, bb, pass_bb);
+ rv = ap_proxy_buckets_lifetime_transform(r, bb, pass_bb);
+ if (rv != APR_SUCCESS) {
+ /* Same, half way through a response, our only option is
+ * to notice the output filters and then disconnect the
+ * client and backend.
+ */
+ if (!APR_BRIGADE_EMPTY(pass_bb)) {
+ /* Pass what we have still */
+ ap_pass_brigade(r->output_filters, pass_bb);
+ apr_brigade_cleanup(pass_bb);
+ }
+ ap_proxy_fill_error_brigade(r, HTTP_INTERNAL_SERVER_ERROR,
+ pass_bb, 1);
+ ap_pass_brigade(r->output_filters, pass_bb);
+ apr_brigade_cleanup(pass_bb);
+
+ backend_broke = 1;
+ backend->close = 1;
+ break;
+ }
/* found the last brigade? */
if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(pass_bb))) {
int status = r->status;
r->status = HTTP_OK;
r->status_line = NULL;
-
- apr_brigade_cleanup(bb);
- apr_brigade_cleanup(pass_bb);
-
return status;
}
while (!finish) {
+ apr_brigade_cleanup(bb);
rv = ap_get_brigade(rp->input_filters, bb,
AP_MODE_READBYTES, mode, conf->io_buffer_size);
if (APR_STATUS_IS_EAGAIN(rv)
if (ap_pass_brigade(r->output_filters, bb) || c->aborted) {
break;
}
- apr_brigade_cleanup(bb);
mode = APR_BLOCK_READ;
continue;
}
- else if (rv == APR_EOF) {
+ if (rv == APR_EOF) {
break;
}
- else if (rv != APR_SUCCESS) {
- ap_proxy_backend_broke(r, bb);
+ if (rv != APR_SUCCESS || APR_BRIGADE_EMPTY(bb)) {
+ int status = HTTP_BAD_GATEWAY;
+ if (rv == APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10294)
+ "Unexpected empty data reading uwscgi response");
+ status = HTTP_INTERNAL_SERVER_ERROR;
+ }
+ apr_brigade_cleanup(bb);
+ ap_proxy_fill_error_brigade(r, status, bb, -1);
ap_pass_brigade(r->output_filters, bb);
backend_broke = 1;
break;
}
+ /* found the last brigade? */
+ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb)))
+ finish = 1;
+
mode = APR_NONBLOCK_READ;
apr_brigade_length(bb, 0, &readbytes);
backend->worker->s->read += readbytes;
- if (APR_BRIGADE_EMPTY(bb)) {
- apr_brigade_cleanup(bb);
+ rv = ap_proxy_buckets_lifetime_transform(r, bb, pass_bb);
+ if (rv != APR_SUCCESS) {
+ /* Half way through a response, our only option is
+ * to notice the output filters and then disconnect the
+ * client and backend.
+ */
+ if (!APR_BRIGADE_EMPTY(pass_bb)) {
+ /* Pass what we have still */
+ ap_pass_brigade(r->output_filters, pass_bb);
+ apr_brigade_cleanup(pass_bb);
+ }
+ ap_proxy_fill_error_brigade(r, HTTP_INTERNAL_SERVER_ERROR,
+ pass_bb, -1);
+ ap_pass_brigade(r->output_filters, pass_bb);
+ apr_brigade_cleanup(pass_bb);
+
+ backend_broke = 1;
break;
}
-
- ap_proxy_buckets_lifetime_transform(r, bb, pass_bb);
-
- /* found the last brigade? */
- if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb)))
- finish = 1;
-
- /* do not pass chunk if it is zero_sized */
- apr_brigade_length(pass_bb, 0, &readbytes);
-
- if ((readbytes > 0
- && ap_pass_brigade(r->output_filters, pass_bb) != APR_SUCCESS)
+ if (ap_pass_brigade(r->output_filters, pass_bb) != APR_SUCCESS
|| c->aborted) {
finish = 1;
}
-
- apr_brigade_cleanup(bb);
apr_brigade_cleanup(pass_bb);
}
- e = apr_bucket_eos_create(c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, e);
- ap_pass_brigade(r->output_filters, bb);
-
apr_brigade_cleanup(bb);
if (c->aborted || backend_broke) {
code);
}
+PROXY_DECLARE(void) ap_proxy_fill_error_brigade(request_rec *r, int status,
+ apr_bucket_brigade *bb,
+ int eoc)
+{
+ apr_bucket *e, *eos;
+ conn_rec *c = r->connection;
+
+ /*
+ * Add an error and (eventually) EOC buckets to signal the http filter
+ * that it should get out of our way, BUT ensure that they are inserted
+ * BEFORE an EOS bucket in bb as some resource filters like mod_deflate
+ * pass everything up to the EOS down the chain immediately and sent the
+ * remainder of the brigade later (or even never). But in this case the
+ * ap_http_header_filter does not get out of our way soon enough.
+ */
+
+ eos = APR_BRIGADE_LAST(bb);
+ while (eos != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(eos)) {
+ eos = APR_BUCKET_PREV(eos);
+ }
+
+ e = ap_bucket_error_create(status, NULL, c->pool, c->bucket_alloc);
+ if (eos == APR_BRIGADE_SENTINEL(bb)) {
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ eos = APR_BRIGADE_SENTINEL(bb);
+ }
+ else {
+ APR_BUCKET_INSERT_BEFORE(eos, e);
+ }
+
+ /* If asked to (eoc > 0) or if heuristically (eoc < 0) the header was
+ * sent already we need to terminate the connection.
+ */
+ if (eoc > 0 || (eoc < 0 && r->sent_bodyct)) {
+ e = ap_bucket_eoc_create(c->bucket_alloc);
+ if (eos == APR_BRIGADE_SENTINEL(bb)) {
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ }
+ else {
+ APR_BUCKET_INSERT_BEFORE(eos, e);
+ }
+ }
+}
+
/* deprecated - to be removed in v2.6 */
PROXY_DECLARE(void) ap_proxy_backend_broke(request_rec *r,
apr_bucket_brigade *brigade)
{
- apr_bucket *e;
conn_rec *c = r->connection;
r->no_cache = 1;
*/
if (r->main)
r->main->no_cache = 1;
- e = ap_bucket_error_create(HTTP_BAD_GATEWAY, NULL, c->pool,
- c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(brigade, e);
- e = apr_bucket_eos_create(c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(brigade, e);
+
+ APR_BRIGADE_INSERT_TAIL(brigade, apr_bucket_eos_create(c->bucket_alloc));
+ ap_proxy_fill_error_brigade(r, HTTP_BAD_GATEWAY, brigade, 0);
}
/*