diff --git a/doc/configuration.txt b/doc/configuration.txt index 54005155f..98310214a 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -4446,7 +4446,13 @@ no option prefer-last-server this option is used, haproxy will try to reuse the same connection that is attached to the server instead of rebalancing to another server, causing a close of the connection. This can make sense for static file servers. It does - not make much sense to use this in combination with hashing algorithms. + not make much sense to use this in combination with hashing algorithms. Note, + haproxy already automatically tries to stick to a server which sends a 401 or + to a proxy which sends a 407 (authentication required). This is mandatory for + use with the broken NTLM authentication challenge, and significantly helps in + troubleshooting some faulty applications. Option prefer-last-server might be + desirable in these environments as well, to avoid redistributing the traffic + after every other response. If this option has been enabled in a "defaults" section, it can be disabled in a specific instance by prepending the "no" keyword before it. diff --git a/include/types/proto_http.h b/include/types/proto_http.h index 9d65d6f3a..957ac0234 100644 --- a/include/types/proto_http.h +++ b/include/types/proto_http.h @@ -83,7 +83,7 @@ #define TX_CON_CLO_SET 0x00400000 /* "connection: close" is now set */ #define TX_CON_KAL_SET 0x00800000 /* "connection: keep-alive" is now set */ -/* Unused: 0x1000000 */ +#define TX_PREFER_LAST 0x01000000 /* try to stay on same server if possible (eg: after 401) */ #define TX_HDR_CONN_UPG 0x02000000 /* The "Upgrade" token was found in the "Connection" header */ #define TX_WAIT_NEXT_RQ 0x04000000 /* waiting for the second request to start, use keep-alive timeout */ diff --git a/src/backend.c b/src/backend.c index a80b6b441..8fcce7dd2 100644 --- a/src/backend.c +++ b/src/backend.c @@ -534,7 +534,8 @@ int assign_server(struct session *s) s->target = NULL; conn = objt_conn(s->req->cons->end); - if (conn && (s->be->options & PR_O_PREF_LAST) && + if (conn && + ((s->be->options & PR_O_PREF_LAST) || (s->txn.flags & TX_PREFER_LAST)) && objt_server(conn->target) && __objt_server(conn->target)->proxy == s->be && srv_is_usable(__objt_server(conn->target)->state, __objt_server(conn->target)->eweight)) { /* This session was relying on a server in a previous request diff --git a/src/proto_http.c b/src/proto_http.c index 2c6e27273..624a51fa3 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -4281,6 +4281,8 @@ int http_send_name_header(struct http_txn *txn, struct proxy* be, const char* sr */ void http_end_txn_clean_session(struct session *s) { + int prev_status = s->txn.status; + /* FIXME: We need a more portable way of releasing a backend's and a * server's connections. We need a safer way to reinitialize buffer * flags. We also need a more accurate method for computing per-request @@ -4395,6 +4397,18 @@ void http_end_txn_clean_session(struct session *s) s->txn.meth = 0; http_reset_txn(s); s->txn.flags |= TX_NOT_FIRST | TX_WAIT_NEXT_RQ; + + if (prev_status == 401 || prev_status == 407) { + /* In HTTP keep-alive mode, if we receive a 401, we still have + * a chance of being able to send the visitor again to the same + * server over the same connection. This is required by some + * broken protocols such as NTLM, and anyway whenever there is + * an opportunity for sending the challenge to the proper place, + * it's better to do it (at least it helps with debugging). + */ + s->txn.flags |= TX_PREFER_LAST; + } + if (s->fe->options2 & PR_O2_INDEPSTR) s->req->cons->flags |= SI_FL_INDEP_STR; diff --git a/src/session.c b/src/session.c index fa5d6452c..f06d06bbf 100644 --- a/src/session.c +++ b/src/session.c @@ -536,6 +536,7 @@ int session_complete(struct session *s) txn->rsp.cap = NULL; txn->hdr_idx.v = NULL; txn->hdr_idx.size = txn->hdr_idx.used = 0; + txn->flags = 0; txn->req.flags = 0; txn->rsp.flags = 0; /* the HTTP messages need to know what buffer they're associated with */