[MEDIUM] http: add support for Proxy-Connection header

Despite what is explicitly stated in HTTP specifications,
browsers still use the undocumented Proxy-Connection header
instead of the Connection header when they connect through
a proxy. As such, proxies generally implement support for
this stupid header name, breaking the standards and making
it harder to support keep-alive between clients and proxies.

Thus, we add a new "option http-use-proxy-header" to tell
haproxy that if it sees requests which look like proxy
requests, it should use the Proxy-Connection header instead
of the Connection header.
This commit is contained in:
Willy Tarreau 2010-01-25 12:15:43 +01:00
parent 6939b5522d
commit 88d349d25d
5 changed files with 100 additions and 10 deletions

View File

@ -788,6 +788,8 @@ option forwardfor X X X X
option httpchk X - X X option httpchk X - X X
[no] option http-server- [no] option http-server-
close X X X X close X X X X
[no] option http-use-proxy-
header X X X -
[no] option httpclose X X X X [no] option httpclose X X X X
option httplog X X X X option httplog X X X X
[no] option http_proxy X X X X [no] option http_proxy X X X X
@ -2623,6 +2625,35 @@ no option http-server-close
See also : "option forceclose" and "option httpclose" See also : "option forceclose" and "option httpclose"
option http-use-proxy-header
[no] option http-use-proxy-header
Make use of non-standard Proxy-Connection header instead of Connection
May be used in sections : defaults | frontend | listen | backend
yes | yes | yes | no
Arguments : none
While RFC2616 explicitly states that HTTP/1.1 agents must use the
Connection header to indicate their wish of persistent or non-persistent
connections, both browsers and proxies ignore this header for proxied
connections and make use of the undocumented, non-standard Proxy-Connection
header instead. The issue begins when trying to put a load balancer between
browsers and such proxies, because there will be a difference between what
haproxy understands and what the client and the proxy agree on.
By setting this option in a frontend, haproxy can automatically switch to use
that non-standard header if it sees proxied requests. A proxied request is
defined here as one where the URI begins with neither a '/' nor a '*'. The
choice of header only affects requests passing through proxies making use of
one of the "httpclose", "forceclose" and "http-server-close" options. Note
that this option can only be specified in a frontend and will affect the
request along its whole life.
This option should normally never be used, except in front of a proxy.
See also : "option httpclose", "option forceclose" and "option
http-server-close".
option httpclose option httpclose
no option httpclose no option httpclose
Enable or disable passive HTTP connection closing Enable or disable passive HTTP connection closing

View File

@ -98,6 +98,7 @@
#define TX_HDR_CONN_PRS 0x08000000 /* "connection" header already parsed (req or res), results below */ #define TX_HDR_CONN_PRS 0x08000000 /* "connection" header already parsed (req or res), results below */
#define TX_HDR_CONN_CLO 0x10000000 /* "Connection: close" was present at least once */ #define TX_HDR_CONN_CLO 0x10000000 /* "Connection: close" was present at least once */
#define TX_HDR_CONN_KAL 0x20000000 /* "Connection: keep-alive" was present at least once */ #define TX_HDR_CONN_KAL 0x20000000 /* "Connection: keep-alive" was present at least once */
#define TX_USE_PX_CONN 0x40000000 /* Use "Proxy-Connection" instead of "Connection" */
/* The HTTP parser is more complex than it looks like, because we have to /* The HTTP parser is more complex than it looks like, because we have to

View File

@ -135,6 +135,7 @@
#define PR_O2_AS_M_ANY 0x00010000 /* mask covering all PR_O2_AS_M_* values */ #define PR_O2_AS_M_ANY 0x00010000 /* mask covering all PR_O2_AS_M_* values */
#define PR_O2_MYSQL_CHK 0x00020000 /* use MYSQL check for server health */ #define PR_O2_MYSQL_CHK 0x00020000 /* use MYSQL check for server health */
#define PR_O2_USE_PXHDR 0x00040000 /* use Proxy-Connection for proxy requests */
/* end of proxy->options2 */ /* end of proxy->options2 */
/* bits for sticking rules */ /* bits for sticking rules */

View File

@ -147,6 +147,7 @@ static const struct cfg_opt cfg_opts2[] =
{ "tcp-smart-accept", PR_O2_SMARTACC, PR_CAP_FE, 0 }, { "tcp-smart-accept", PR_O2_SMARTACC, PR_CAP_FE, 0 },
{ "tcp-smart-connect", PR_O2_SMARTCON, PR_CAP_BE, 0 }, { "tcp-smart-connect", PR_O2_SMARTCON, PR_CAP_BE, 0 },
{ "independant-streams", PR_O2_INDEPSTR, PR_CAP_FE|PR_CAP_BE, 0 }, { "independant-streams", PR_O2_INDEPSTR, PR_CAP_FE|PR_CAP_BE, 0 },
{ "http-use-proxy-header", PR_O2_USE_PXHDR, PR_CAP_FE, 0 },
{ NULL, 0, 0, 0 } { NULL, 0, 0, 0 }
}; };

View File

@ -761,8 +761,14 @@ void perform_http_redirect(struct session *s, struct stream_interface *si)
memcpy(rdr.str + rdr.len, path, len); memcpy(rdr.str + rdr.len, path, len);
rdr.len += len; rdr.len += len;
memcpy(rdr.str + rdr.len, "\r\nConnection: close\r\n\r\n", 23);
rdr.len += 23; if (unlikely(txn->flags & TX_USE_PX_CONN)) {
memcpy(rdr.str + rdr.len, "\r\nProxy-Connection: close\r\n\r\n", 29);
rdr.len += 29;
} else {
memcpy(rdr.str + rdr.len, "\r\nConnection: close\r\n\r\n", 23);
rdr.len += 23;
}
/* prepare to return without error. */ /* prepare to return without error. */
si->shutr(si); si->shutr(si);
@ -1849,13 +1855,20 @@ static int http_upgrade_v09_to_v10(struct buffer *req, struct http_msg *msg, str
void http_parse_connection_header(struct http_txn *txn, struct http_msg *msg, struct buffer *buf, int to_del) void http_parse_connection_header(struct http_txn *txn, struct http_msg *msg, struct buffer *buf, int to_del)
{ {
struct hdr_ctx ctx; struct hdr_ctx ctx;
const char *hdr_val = "Connection";
int hdr_len = 10;
if (txn->flags & TX_HDR_CONN_PRS) if (txn->flags & TX_HDR_CONN_PRS)
return; return;
if (unlikely(txn->flags & TX_USE_PX_CONN)) {
hdr_val = "Proxy-Connection";
hdr_len = 16;
}
ctx.idx = 0; ctx.idx = 0;
txn->flags &= ~(TX_CON_KAL_SET|TX_CON_CLO_SET); txn->flags &= ~(TX_CON_KAL_SET|TX_CON_CLO_SET);
while (http_find_header2("Connection", 10, msg->sol, &txn->hdr_idx, &ctx)) { while (http_find_header2(hdr_val, hdr_len, msg->sol, &txn->hdr_idx, &ctx)) {
if (ctx.vlen >= 10 && word_match(ctx.line + ctx.val, ctx.vlen, "keep-alive", 10)) { if (ctx.vlen >= 10 && word_match(ctx.line + ctx.val, ctx.vlen, "keep-alive", 10)) {
txn->flags |= TX_HDR_CONN_KAL; txn->flags |= TX_HDR_CONN_KAL;
if ((to_del & 2) && buf) if ((to_del & 2) && buf)
@ -1884,11 +1897,19 @@ void http_parse_connection_header(struct http_txn *txn, struct http_msg *msg, st
void http_change_connection_header(struct http_txn *txn, struct http_msg *msg, struct buffer *buf, int wanted) void http_change_connection_header(struct http_txn *txn, struct http_msg *msg, struct buffer *buf, int wanted)
{ {
struct hdr_ctx ctx; struct hdr_ctx ctx;
const char *hdr_val = "Connection";
int hdr_len = 10;
ctx.idx = 0; ctx.idx = 0;
if (unlikely(txn->flags & TX_USE_PX_CONN)) {
hdr_val = "Proxy-Connection";
hdr_len = 16;
}
txn->flags &= ~(TX_CON_CLO_SET | TX_CON_KAL_SET); txn->flags &= ~(TX_CON_CLO_SET | TX_CON_KAL_SET);
while (http_find_header2("Connection", 10, msg->sol, &txn->hdr_idx, &ctx)) { while (http_find_header2(hdr_val, hdr_len, msg->sol, &txn->hdr_idx, &ctx)) {
if (ctx.vlen >= 10 && word_match(ctx.line + ctx.val, ctx.vlen, "keep-alive", 10)) { if (ctx.vlen >= 10 && word_match(ctx.line + ctx.val, ctx.vlen, "keep-alive", 10)) {
if (wanted & TX_CON_KAL_SET) if (wanted & TX_CON_KAL_SET)
txn->flags |= TX_CON_KAL_SET; txn->flags |= TX_CON_KAL_SET;
@ -1908,12 +1929,24 @@ void http_change_connection_header(struct http_txn *txn, struct http_msg *msg, s
if ((wanted & TX_CON_CLO_SET) && !(txn->flags & TX_CON_CLO_SET)) { if ((wanted & TX_CON_CLO_SET) && !(txn->flags & TX_CON_CLO_SET)) {
txn->flags |= TX_CON_CLO_SET; txn->flags |= TX_CON_CLO_SET;
http_header_add_tail2(buf, msg, &txn->hdr_idx, "Connection: close", 17); hdr_val = "Connection: close";
hdr_len = 17;
if (unlikely(txn->flags & TX_USE_PX_CONN)) {
hdr_val = "Proxy-Connection: close";
hdr_len = 23;
}
http_header_add_tail2(buf, msg, &txn->hdr_idx, hdr_val, hdr_len);
} }
if ((wanted & TX_CON_KAL_SET) && !(txn->flags & TX_CON_KAL_SET)) { if ((wanted & TX_CON_KAL_SET) && !(txn->flags & TX_CON_KAL_SET)) {
txn->flags |= TX_CON_KAL_SET; txn->flags |= TX_CON_KAL_SET;
http_header_add_tail2(buf, msg, &txn->hdr_idx, "Connection: keep-alive", 22); hdr_val = "Connection: keep-alive";
hdr_len = 22;
if (unlikely(txn->flags & TX_USE_PX_CONN)) {
hdr_val = "Proxy-Connection: keep-alive";
hdr_len = 28;
}
http_header_add_tail2(buf, msg, &txn->hdr_idx, hdr_val, hdr_len);
} }
return; return;
} }
@ -2541,6 +2574,19 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit)
/* "connection" has not been parsed yet */ /* "connection" has not been parsed yet */
txn->flags &= ~(TX_HDR_CONN_PRS | TX_HDR_CONN_CLO | TX_HDR_CONN_KAL); txn->flags &= ~(TX_HDR_CONN_PRS | TX_HDR_CONN_CLO | TX_HDR_CONN_KAL);
/* if the frontend has "option http-use-proxy-header", we'll check if
* we have what looks like a proxied connection instead of a connection,
* and in this case set the TX_USE_PX_CONN flag to use Proxy-connection.
* Note that this is *not* RFC-compliant, however browsers and proxies
* happen to do that despite being non-standard :-(
* We consider that a request not beginning with either '/' or '*' is
* a proxied connection, which covers both "scheme://location" and
* CONNECT ip:port.
*/
if ((s->fe->options2 & PR_O2_USE_PXHDR) &&
msg->sol[msg->sl.rq.u] != '/' && msg->sol[msg->sl.rq.u] != '*')
txn->flags |= TX_USE_PX_CONN;
/* transfer length unknown*/ /* transfer length unknown*/
txn->flags &= ~TX_REQ_XFER_LEN; txn->flags &= ~TX_REQ_XFER_LEN;
@ -2940,8 +2986,13 @@ int http_process_req_common(struct session *s, struct buffer *req, int an_bit, s
(txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL)) { (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL)) {
/* keep-alive possible */ /* keep-alive possible */
if (!(txn->flags & TX_REQ_VER_11)) { if (!(txn->flags & TX_REQ_VER_11)) {
memcpy(rdr.str + rdr.len, "\r\nConnection: keep-alive", 24); if (unlikely(txn->flags & TX_USE_PX_CONN)) {
rdr.len += 24; memcpy(rdr.str + rdr.len, "\r\nProxy-Connection: keep-alive", 30);
rdr.len += 30;
} else {
memcpy(rdr.str + rdr.len, "\r\nConnection: keep-alive", 24);
rdr.len += 24;
}
} }
memcpy(rdr.str + rdr.len, "\r\n\r\n", 4); memcpy(rdr.str + rdr.len, "\r\n\r\n", 4);
rdr.len += 4; rdr.len += 4;
@ -2956,8 +3007,13 @@ int http_process_req_common(struct session *s, struct buffer *req, int an_bit, s
break; break;
} else { } else {
/* keep-alive not possible */ /* keep-alive not possible */
memcpy(rdr.str + rdr.len, "\r\nConnection: close\r\n\r\n", 23); if (unlikely(txn->flags & TX_USE_PX_CONN)) {
rdr.len += 23; memcpy(rdr.str + rdr.len, "\r\nProxy-Connection: close\r\n\r\n", 29);
rdr.len += 29;
} else {
memcpy(rdr.str + rdr.len, "\r\nConnection: close\r\n\r\n", 23);
rdr.len += 23;
}
stream_int_retnclose(req->prod, &rdr); stream_int_retnclose(req->prod, &rdr);
goto return_prx_cond; goto return_prx_cond;
} }