APR_BRIGADE_INSERT_TAIL(header_brigade, e);
}
-static void terminate_headers(apr_bucket_alloc_t *bucket_alloc,
- apr_bucket_brigade *header_brigade)
-{
- apr_bucket *e;
-
- /* add empty line at the end of the headers */
- e = apr_bucket_immortal_create(CRLF_ASCII, 2, bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(header_brigade, e);
-}
-
#define MAX_MEM_SPOOL 16384
rb_methods rb_method;
+ int force10;
+ const char *upgrade;
+
int expecting_100;
unsigned int do_100_continue:1,
prefetch_nonblocking:1;
return OK;
}
+static void terminate_headers(proxy_http_req_t *req)
+{
+ apr_bucket_alloc_t *bucket_alloc = req->bucket_alloc;
+ apr_bucket *e;
+ char *buf;
+
+ /*
+ * Handle Connection: header if we do HTTP/1.1 request:
+ * If we plan to close the backend connection sent Connection: close
+ * otherwise sent Connection: Keep-Alive.
+ */
+ if (!req->force10) {
+ if (req->upgrade) {
+ buf = apr_pstrdup(req->p, "Connection: Upgrade" CRLF);
+ ap_xlate_proto_to_ascii(buf, strlen(buf));
+ e = apr_bucket_pool_create(buf, strlen(buf), req->p, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(req->header_brigade, e);
+
+ /* Tell the backend that it can upgrade the connection. */
+ buf = apr_pstrcat(req->p, "Upgrade: ", req->upgrade, CRLF, NULL);
+ }
+ else if (ap_proxy_connection_reusable(req->backend)) {
+ buf = apr_pstrdup(req->p, "Connection: Keep-Alive" CRLF);
+ }
+ else {
+ buf = apr_pstrdup(req->p, "Connection: close" CRLF);
+ }
+ ap_xlate_proto_to_ascii(buf, strlen(buf));
+ e = apr_bucket_pool_create(buf, strlen(buf), req->p, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(req->header_brigade, e);
+ }
+
+ /* add empty line at the end of the headers */
+ e = apr_bucket_immortal_create(CRLF_ASCII, 2, bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(req->header_brigade, e);
+}
+
static int ap_proxy_http_prefetch(proxy_http_req_t *req,
apr_uri_t *uri, char *url)
{
apr_bucket_brigade *input_brigade = req->input_brigade;
apr_bucket_brigade *temp_brigade;
apr_bucket *e;
- char *buf;
apr_status_t status;
apr_off_t bytes_read = 0;
apr_off_t bytes;
- int force10, rv;
apr_read_type_e block;
+ int rv;
- if (apr_table_get(r->subprocess_env, "force-proxy-request-1.0")) {
- if (req->expecting_100) {
- return HTTP_EXPECTATION_FAILED;
- }
- force10 = 1;
- } else {
- force10 = 0;
+ if (req->force10 && req->expecting_100) {
+ return HTTP_EXPECTATION_FAILED;
}
rv = ap_proxy_create_hdrbrgd(p, header_brigade, r, p_conn,
req->rb_method = RB_STREAM_CL;
}
else if (req->old_te_val) {
- if (force10
+ if (req->force10
|| (apr_table_get(r->subprocess_env, "proxy-sendcl")
&& !apr_table_get(r->subprocess_env, "proxy-sendchunks")
&& !apr_table_get(r->subprocess_env, "proxy-sendchunked"))) {
}
req->rb_method = RB_STREAM_CL;
}
- else if (!force10
+ else if (!req->force10
&& (apr_table_get(r->subprocess_env, "proxy-sendchunks")
|| apr_table_get(r->subprocess_env, "proxy-sendchunked"))
&& !apr_table_get(r->subprocess_env, "proxy-sendcl")) {
/* Yes I hate gotos. This is the subrequest shortcut */
skip_body:
- /*
- * Handle Connection: header if we do HTTP/1.1 request:
- * If we plan to close the backend connection sent Connection: close
- * otherwise sent Connection: Keep-Alive.
- */
- if (!force10) {
- if (!ap_proxy_connection_reusable(p_conn)) {
- buf = apr_pstrdup(p, "Connection: close" CRLF);
- }
- else {
- buf = apr_pstrdup(p, "Connection: Keep-Alive" CRLF);
- }
- ap_xlate_proto_to_ascii(buf, strlen(buf));
- e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(header_brigade, e);
- }
- terminate_headers(bucket_alloc, header_brigade);
+ terminate_headers(req);
return OK;
}
return 1;
}
+static int send_continue_body(proxy_http_req_t *req)
+{
+ int status;
+
+ /* Send the request body (fully). */
+ switch(req->rb_method) {
+ case RB_SPOOL_CL:
+ case RB_STREAM_CL:
+ case RB_STREAM_CHUNKED:
+ status = stream_reqbody(req);
+ break;
+ default:
+ /* Shouldn't happen */
+ status = HTTP_INTERNAL_SERVER_ERROR;
+ break;
+ }
+ if (status != OK) {
+ conn_rec *c = req->r->connection;
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req->r,
+ APLOGNO(10154) "pass request body failed "
+ "to %pI (%s) from %s (%s) with status %i",
+ req->backend->addr,
+ req->backend->hostname ? req->backend->hostname : "",
+ c->client_ip, c->remote_host ? c->remote_host : "",
+ status);
+ req->backend->close = 1;
+ }
+ return status;
+}
+
static
int ap_proxy_http_process_response(proxy_http_req_t *req)
{
proxy_conn_rec *backend = req->backend;
conn_rec *origin = req->origin;
int do_100_continue = req->do_100_continue;
+ int status;
char *buffer;
char fixed_buffer[HUGE_STRING_LEN];
origin->local_addr->port));
do {
apr_status_t rc;
+ const char *upgrade = NULL;
int major = 0, minor = 0;
int toclose = 0;
*/
te = apr_table_get(r->headers_out, "Transfer-Encoding");
+ upgrade = apr_table_get(r->headers_out, "Upgrade");
+ if (proxy_status == HTTP_SWITCHING_PROTOCOLS) {
+ if (!upgrade || !req->upgrade || (strcasecmp(req->upgrade,
+ upgrade) != 0)) {
+ return ap_proxyerror(r, HTTP_BAD_GATEWAY,
+ apr_pstrcat(p, "Unexpected Upgrade: ",
+ upgrade ? upgrade : "n/a",
+ " (expecting ",
+ req->upgrade ? req->upgrade
+ : "n/a", ")",
+ NULL));
+ }
+ backend->close = 1;
+ }
+
/* strip connection listed hop-by-hop headers from response */
toclose = ap_proxy_clear_connection_fn(r, r->headers_out);
if (toclose) {
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
"HTTP: received interim %d response", r->status);
if (!policy
+ || upgrade
|| (!strcasecmp(policy, "RFC")
&& (proxy_status != HTTP_CONTINUE
|| (req->expecting_100 = 1)))) {
major, minor, proxy_status_line);
if (do_send_body) {
- int status;
-
- /* Send the request body (fully). */
- switch(req->rb_method) {
- case RB_SPOOL_CL:
- case RB_STREAM_CL:
- case RB_STREAM_CHUNKED:
- status = stream_reqbody(req);
- break;
- default:
- /* Shouldn't happen */
- status = HTTP_INTERNAL_SERVER_ERROR;
- break;
- }
+ status = send_continue_body(req);
if (status != OK) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
- APLOGNO(10154) "pass request body failed "
- "to %pI (%s) from %s (%s) with status %i",
- backend->addr,
- backend->hostname ? backend->hostname : "",
- c->client_ip,
- c->remote_host ? c->remote_host : "",
- status);
- backend->close = 1;
return status;
}
}
do_100_continue = 0;
}
+ if (proxy_status == HTTP_SWITCHING_PROTOCOLS) {
+ apr_status_t rv;
+ proxy_tunnel_rec *tunnel;
+ apr_interval_time_t client_timeout = -1,
+ backend_timeout = -1;
+
+ /* If we didn't send the full body yet, do it now */
+ if (do_100_continue) {
+ req->expecting_100 = 0;
+ status = send_continue_body(req);
+ if (status != OK) {
+ return status;
+ }
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10239)
+ "HTTP: tunneling protocol %s", upgrade);
+
+ rv = ap_proxy_tunnel_create(&tunnel, r, origin, "HTTP");
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10240)
+ "can't create tunnel for %s", upgrade);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* Set timeout to the lowest configured for client or backend */
+ apr_socket_timeout_get(backend->sock, &backend_timeout);
+ apr_socket_timeout_get(ap_get_conn_socket(c), &client_timeout);
+ if (backend_timeout >= 0 && backend_timeout < client_timeout) {
+ tunnel->timeout = backend_timeout;
+ }
+ else {
+ tunnel->timeout = client_timeout;
+ }
+
+ /* Bidirectional non-HTTP stream will confuse mod_reqtimeoout, we
+ * use a single idle timeout from now on.
+ */
+ ap_remove_input_filter_byhandle(c->input_filters, "reqtimeout");
+
+ /* Let proxy tunnel forward everything */
+ status = ap_proxy_tunnel_run(tunnel);
+ if (ap_is_HTTP_ERROR(status)) {
+ /* Tunnel always return HTTP_GATEWAY_TIME_OUT on timeout,
+ * but we can differentiate between client and backend here.
+ */
+ if (status == HTTP_GATEWAY_TIME_OUT
+ && tunnel->timeout == client_timeout) {
+ status = HTTP_REQUEST_TIME_OUT;
+ }
+ }
+ else {
+ /* Update r->status for custom log */
+ status = HTTP_SWITCHING_PROTOCOLS;
+ }
+ r->status = status;
+
+ /* We are done with both connections */
+ return DONE;
+ }
+
if (interim_response) {
/* Already forwarded above, read next response */
continue;
return proxy_status;
}
+ /* Forward back Upgrade header if it matches the configured one(s). */
+ if (upgrade && ap_proxy_worker_can_upgrade(p, worker, upgrade)) {
+ apr_table_setn(r->headers_out, "Connection", "Upgrade");
+ apr_table_setn(r->headers_out, "Upgrade", apr_pstrdup(p, upgrade));
+ }
+
r->sent_bodyct = 1;
/*
* Is it an HTTP/0.9 response or did we maybe preread the 1st line of
dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
+ if (apr_table_get(r->subprocess_env, "force-proxy-request-1.0")) {
+ req->force10 = 1;
+ }
+ else if (*worker->s->upgrade) {
+ /* Forward Upgrade header if it matches the configured one(s). */
+ const char *upgrade = apr_table_get(r->headers_in, "Upgrade");
+ if (upgrade && ap_proxy_worker_can_upgrade(p, worker, upgrade)) {
+ req->upgrade = upgrade;
+ }
+ }
+
/* We possibly reuse input data prefetched in previous call(s), e.g. for a
* balancer fallback scenario, and in this case the 100 continue settings
* should be consistent between balancer members. If not, we need to ignore
int status;
char server_portstr[32];
proxy_conn_rec *backend = NULL;
- const char *upgrade_method, *upgrade;
+ const char *upgrade;
char *scheme;
apr_pool_t *p = r->pool;
char *locurl = url;
return DECLINED;
}
- /* XXX: what's the point of "NONE"? We probably should _always_ check
- * that the client wants an Upgrade..
- */
- upgrade_method = *worker->s->upgrade ? worker->s->upgrade : "WebSocket";
- if (ap_cstr_casecmp(upgrade_method, "NONE") != 0) {
- upgrade = apr_table_get(r->headers_in, "Upgrade");
- if (!upgrade || (ap_cstr_casecmp(upgrade, upgrade_method) != 0 &&
- ap_cstr_casecmp(upgrade_method, "ANY") != 0)) {
- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02900)
- "require upgrade for URL %s "
- "(Upgrade header is %s, expecting %s)",
- url, upgrade ? upgrade : "missing", upgrade_method);
- apr_table_setn(r->err_headers_out, "Connection", "Upgrade");
- apr_table_setn(r->err_headers_out, "Upgrade", upgrade_method);
- return HTTP_UPGRADE_REQUIRED;
- }
- }
- else {
- upgrade = "WebSocket";
+ upgrade = apr_table_get(r->headers_in, "Upgrade");
+ if (!upgrade || (*worker->s->upgrade &&
+ !ap_proxy_worker_can_upgrade(p, worker, upgrade))
+ || (!*worker->s->upgrade &&
+ ap_cstr_casecmp(upgrade, "WebSocket") != 0)) {
+ const char *worker_upgrade = *worker->s->upgrade ? worker->s->upgrade
+ : "WebSocket";
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02900)
+ "require upgrade for URL %s "
+ "(Upgrade header is %s, expecting %s)",
+ url, upgrade ? upgrade : "missing", worker_upgrade);
+ apr_table_setn(r->err_headers_out, "Connection", "Upgrade");
+ apr_table_setn(r->err_headers_out, "Upgrade", worker_upgrade);
+ return HTTP_UPGRADE_REQUIRED;
}
uri = apr_palloc(p, sizeof(*uri));