]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MAJOR] get rid of the SV_STHEADERS state
authorWilly Tarreau <w@1wt.eu>
Thu, 14 Aug 2008 16:35:40 +0000 (18:35 +0200)
committerWilly Tarreau <w@1wt.eu>
Thu, 14 Aug 2008 16:35:40 +0000 (18:35 +0200)
The HTTP response code has been moved to a specific function
called "process_response" and the SV_STHEADERS state has been
removed and replaced with the flag AN_RTR_HTTP_HDR.

include/proto/proto_http.h
include/types/proto_http.h
src/proto_http.c

index e349f19eb051ea528108a30a25726339c9f28ac3..fc1e01d38cfc22f3fe559fc09c8dd9ad0d50a55e 100644 (file)
@@ -62,6 +62,7 @@ void process_session(struct task *t, int *next);
 int process_cli(struct session *t);
 int process_srv(struct session *t);
 int process_request(struct session *t);
+int process_response(struct session *t);
 
 void client_retnclose(struct session *s, const struct chunk *msg);
 void client_return(struct session *s, const struct chunk *msg);
index d592b71dd3fa9b51d98b7ab26b8334586c3f20d8..441b8dcbd6bb1548b027ef499bfaeffa34518bf1 100644 (file)
 /* different possible states for the server side */
 #define SV_STIDLE      0
 #define SV_STCONN      1
-#define SV_STHEADERS   2
-#define SV_STDATA      3
-#define SV_STSHUTR     4
-#define SV_STSHUTW     5
-#define SV_STCLOSE     6
+#define SV_STDATA      2
+#define SV_STSHUTR     3
+#define SV_STSHUTW     4
+#define SV_STCLOSE     5
 
 /*
  * Transaction flags moved from session
index e323b1dba3d49f41338654549d554287d3756f43..a027504099d7c87fcaf8aed5df4cdd48a8b3b95d 100644 (file)
@@ -366,7 +366,7 @@ const char http_is_ver_token[256] = {
 
 #ifdef DEBUG_FULL
 static char *cli_stnames[4] = { "DAT", "SHR", "SHW", "CLS" };
-static char *srv_stnames[7] = { "IDL", "CON", "HDR", "DAT", "SHR", "SHW", "CLS" };
+static char *srv_stnames[6] = { "IDL", "CON", "DAT", "SHR", "SHW", "CLS" };
 #endif
 
 static void http_sess_log(struct session *s);
@@ -669,6 +669,10 @@ void process_session(struct task *t, int *next)
                //fprintf(stderr,"before_srv:cli=%d, srv=%d, resync=%d\n", s->cli_state, s->srv_state, fsm_resync);
                fsm_resync |= process_srv(s);
 
+               //fprintf(stderr,"before_rep:cli=%d, srv=%d, resync=%d\n", s->cli_state, s->srv_state, fsm_resync);
+               if (s->analysis & AN_RTR_ANY)
+                       fsm_resync |= process_response(s);
+
                //fprintf(stderr,"endof_loop:cli=%d, srv=%d, resync=%d\n", s->cli_state, s->srv_state, fsm_resync);
        } while (fsm_resync);
 
@@ -2504,1041 +2508,1071 @@ int process_request(struct session *t)
        return fsm_resync;
 }
 
-/*
- * manages the client FSM and its socket. BTW, it also tries to handle the
- * cookie. It returns 1 if a state has changed (and a resync may be needed),
- * 0 else.
+/* This function performs all the processing enabled for the current response.
+ * It returns 1 if it changes its state and it believes that another function
+ * must be updated, otherwise zero. It might make sense to explode it into
+ * several other functions.
  */
-int process_cli(struct session *t)
+int process_response(struct session *t)
 {
-       int s = t->srv_state;
-       int c = t->cli_state;
+       struct http_txn *txn = &t->txn;
        struct buffer *req = t->req;
        struct buffer *rep = t->rep;
 
-       DPRINTF(stderr,"[%u] process_cli: c=%s s=%s set(r,w)=%d,%d exp(r,w)=%u,%u req=%08x rep=%08x\n",
+       DPRINTF(stderr,"[%u] process_rep: c=%s s=%s set(r,w)=%d,%d exp(r,w)=%u,%u req=%08x rep=%08x analysis=%02x\n",
                now_ms,
                cli_stnames[t->cli_state], srv_stnames[t->srv_state],
-               EV_FD_ISSET(t->cli_fd, DIR_RD), EV_FD_ISSET(t->cli_fd, DIR_WR),
-               req->rex, rep->wex,
-               req->flags, rep->flags);
+               EV_FD_ISSET(t->srv_fd, DIR_RD), EV_FD_ISSET(t->srv_fd, DIR_WR),
+               req->rex, rep->wex, req->flags, rep->flags, t->analysis);
 
-       /* if no analysis remains, it's time to forward the connection */
-       if (!(t->analysis & AN_REQ_ANY) && !(req->flags & (BF_MAY_CONNECT|BF_MAY_FORWARD)))
-               req->flags |= BF_MAY_CONNECT | BF_MAY_FORWARD;
+       if (t->analysis & AN_RTR_HTTP_HDR) { /* receiving server headers */
+               /*
+                * Now parse the partial (or complete) lines.
+                * We will check the response syntax, and also join multi-line
+                * headers. An index of all the lines will be elaborated while
+                * parsing.
+                *
+                * For the parsing, we use a 28 states FSM.
+                *
+                * Here is the information we currently have :
+                *   rep->data + req->som  = beginning of response
+                *   rep->data + req->eoh  = end of processed headers / start of current one
+                *   rep->data + req->eol  = end of current header or line (LF or CRLF)
+                *   rep->lr = first non-visited byte
+                *   rep->r  = end of data
+                */
 
-       if (c == CL_STDATA || c == CL_STSHUTR || c == CL_STSHUTW) {
-               /* read or write error */
-               if (rep->flags & BF_WRITE_ERROR || req->flags & BF_READ_ERROR) {
-                       buffer_shutr_done(req);
-                       buffer_shutw_done(rep);
-                       fd_delete(t->cli_fd);
-                       t->cli_state = CL_STCLOSE;
-                       if (!(t->flags & SN_ERR_MASK))
-                               t->flags |= SN_ERR_CLICL;
-                       if (!(t->flags & SN_FINST_MASK)) {
-                               if (t->analysis & AN_REQ_ANY)
-                                       t->flags |= SN_FINST_R;
-                               else if (t->pend_pos)
-                                       t->flags |= SN_FINST_Q;
-                               else if (s == SV_STCONN)
-                                       t->flags |= SN_FINST_C;
-                               else
-                                       t->flags |= SN_FINST_D;
-                       }
-                       return 1;
-               }
-               /* last read, or end of server write */
-               else if (!(req->flags & BF_SHUTR_STATUS) &&   /* already done */
-                        req->flags & (BF_READ_NULL | BF_SHUTW_STATUS)) {
-                       buffer_shutr_done(req);
-                       if (!(rep->flags & BF_SHUTW_STATUS)) {
-                               EV_FD_CLR(t->cli_fd, DIR_RD);
-                               t->cli_state = CL_STSHUTR;
-                       } else {
-                               /* output was already closed */
-                               fd_delete(t->cli_fd);
-                               t->cli_state = CL_STCLOSE;
-                       }
-                       return 1;
-               }       
-               /* last server read and buffer empty */
-               else if (!(rep->flags & BF_SHUTW_STATUS) &&   /* already done */
-                        rep->l == 0 && rep->flags & BF_SHUTR_STATUS && !(t->flags & SN_SELF_GEN)) {
-                       buffer_shutw_done(rep);
-                       if (!(req->flags & BF_SHUTR_STATUS)) {
-                               EV_FD_CLR(t->cli_fd, DIR_WR);
-                               shutdown(t->cli_fd, SHUT_WR);
-                               /* We must ensure that the read part is still alive when switching to shutw */
-                               /* FIXME: is this still true ? */
-                               EV_FD_SET(t->cli_fd, DIR_RD);
-                               req->rex = tick_add_ifset(now_ms, t->fe->timeout.client);
-                               t->cli_state = CL_STSHUTW;
-                       } else {
-                               fd_delete(t->cli_fd);
-                               t->cli_state = CL_STCLOSE;
+               int cur_idx;
+               struct http_msg *msg = &txn->rsp;
+               struct proxy *cur_proxy;
+
+               if (likely(rep->lr < rep->r))
+                       http_msg_analyzer(rep, msg, &txn->hdr_idx);
+
+               /* 1: we might have to print this header in debug mode */
+               if (unlikely((global.mode & MODE_DEBUG) &&
+                            (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) &&
+                            (msg->msg_state == HTTP_MSG_BODY || msg->msg_state == HTTP_MSG_ERROR))) {
+                       char *eol, *sol;
+
+                       sol = rep->data + msg->som;
+                       eol = sol + msg->sl.rq.l;
+                       debug_hdr("srvrep", t, sol, eol);
+
+                       sol += hdr_idx_first_pos(&txn->hdr_idx);
+                       cur_idx = hdr_idx_first_idx(&txn->hdr_idx);
+
+                       while (cur_idx) {
+                               eol = sol + txn->hdr_idx.v[cur_idx].len;
+                               debug_hdr("srvhdr", t, sol, eol);
+                               sol = eol + txn->hdr_idx.v[cur_idx].cr + 1;
+                               cur_idx = txn->hdr_idx.v[cur_idx].next;
                        }
-                       return 1;
                }
-               /* read timeout */
-               else if (tick_is_expired(req->rex, now_ms)) {
-                       buffer_shutr_done(req);
-                       req->flags |= BF_READ_TIMEOUT;
-                       if (!(rep->flags & BF_SHUTW_STATUS)) {
-                               EV_FD_CLR(t->cli_fd, DIR_RD);
-                               t->cli_state = CL_STSHUTR;
-                       } else {
-                               /* output was already closed */
-                               fd_delete(t->cli_fd);
-                               t->cli_state = CL_STCLOSE;
-                       }
-                       if (!(t->flags & SN_ERR_MASK))
-                               t->flags |= SN_ERR_CLITO;
-                       if (!(t->flags & SN_FINST_MASK)) {
-                               if (t->analysis & AN_REQ_ANY)
-                                       t->flags |= SN_FINST_R;
-                               else if (t->pend_pos)
-                                       t->flags |= SN_FINST_Q;
-                               else if (s == SV_STCONN)
-                                       t->flags |= SN_FINST_C;
-                               else
-                                       t->flags |= SN_FINST_D;
-                       }
-                       return 1;
-               }       
-               /* write timeout */
-               else if (tick_is_expired(rep->wex, now_ms)) {
-                       buffer_shutw_done(rep);
-                       rep->flags |= BF_WRITE_TIMEOUT;
-                       if (!(req->flags & BF_SHUTR_STATUS)) {
-                               EV_FD_CLR(t->cli_fd, DIR_WR);
-                               shutdown(t->cli_fd, SHUT_WR);
-                               /* We must ensure that the read part is still alive when switching to shutw */
-                               /* FIXME: is this still true ? */
-                               EV_FD_SET(t->cli_fd, DIR_RD);
-                               req->rex = tick_add_ifset(now_ms, t->fe->timeout.client);
-                               t->cli_state = CL_STSHUTW;
-                       } else {
-                               fd_delete(t->cli_fd);
-                               t->cli_state = CL_STCLOSE;
-                       }
 
-                       if (!(t->flags & SN_ERR_MASK))
-                               t->flags |= SN_ERR_CLITO;
-                       if (!(t->flags & SN_FINST_MASK)) {
-                               if (t->analysis & AN_REQ_ANY)
-                                       t->flags |= SN_FINST_R;
-                               else if (t->pend_pos)
-                                       t->flags |= SN_FINST_Q;
-                               else if (s == SV_STCONN)
-                                       t->flags |= SN_FINST_C;
-                               else
-                                       t->flags |= SN_FINST_D;
-                       }
-                       return 1;
+
+               if ((rep->l < rep->rlim - rep->data) && !tick_isset(rep->rex)) {
+                       EV_FD_COND_S(t->srv_fd, DIR_RD);
+                       /* fd in DIR_RD was disabled, perhaps because of a previous buffer
+                        * full. We cannot loop here since stream_sock_read will disable it only if
+                        * rep->l == rlim-data
+                        */
+                       rep->rex = tick_add_ifset(now_ms, t->be->timeout.server);
                }
 
-               /* manage read timeout */
-               if (!(req->flags & BF_SHUTR_STATUS)) {
-                       if (req->l >= req->rlim - req->data) {
-                               /* no room to read more data */
-                               if (EV_FD_COND_C(t->cli_fd, DIR_RD)) {
-                                       /* stop reading until we get some space */
-                                       req->rex = TICK_ETERNITY;
+
+               /*
+                * Now we quickly check if we have found a full valid response.
+                * If not so, we check the FD and buffer states before leaving.
+                * A full response is indicated by the fact that we have seen
+                * the double LF/CRLF, so the state is HTTP_MSG_BODY. Invalid
+                * responses are checked first.
+                *
+                * Depending on whether the client is still there or not, we
+                * may send an error response back or not. Note that normally
+                * we should only check for HTTP status there, and check I/O
+                * errors somewhere else.
+                */
+
+               if (unlikely(msg->msg_state != HTTP_MSG_BODY)) {
+
+                       /* Invalid response, or read error or write error */
+                       if (unlikely((msg->msg_state == HTTP_MSG_ERROR) ||
+                                    (req->flags & BF_WRITE_ERROR) ||
+                                    (rep->flags & BF_READ_ERROR))) {
+                               buffer_shutr_done(rep);
+                               buffer_shutw_done(req);
+                               fd_delete(t->srv_fd);
+                               if (t->srv) {
+                                       t->srv->cur_sess--;
+                                       t->srv->failed_resp++;
+                                       sess_change_server(t, NULL);
                                }
-                       } else {
-                               EV_FD_COND_S(t->cli_fd, DIR_RD);
-                               req->rex = tick_add_ifset(now_ms, t->fe->timeout.client);
+                               t->be->failed_resp++;
+                               t->srv_state = SV_STCLOSE;
+                               t->analysis &= ~AN_RTR_ANY;
+                               rep->flags |= BF_MAY_FORWARD;
+                               txn->status = 502;
+                               client_return(t, error_message(t, HTTP_ERR_502));
+                               if (!(t->flags & SN_ERR_MASK))
+                                       t->flags |= SN_ERR_SRVCL;
+                               if (!(t->flags & SN_FINST_MASK))
+                                       t->flags |= SN_FINST_H;
+                               /* We used to have a free connection slot. Since we'll never use it,
+                                * we have to inform the server that it may be used by another session.
+                                */
+                               if (t->srv && may_dequeue_tasks(t->srv, t->be))
+                                       process_srv_queue(t->srv);
+
+                               return 1;
                        }
-               }
 
-               /* manage write timeout */
-               if (!(rep->flags & BF_SHUTW_STATUS)) {
-                       /* first, we may have to produce data (eg: stats).
-                        * right now, this is limited to the SHUTR state.
+                       /* end of client write or end of server read.
+                        * since we are in header mode, if there's no space left for headers, we
+                        * won't be able to free more later, so the session will never terminate.
                         */
-                       if (req->flags & BF_SHUTR_STATUS && t->flags & SN_SELF_GEN) {
-                               produce_content(t);
-                               if (rep->l == 0) {
-                                       buffer_shutw_done(rep);
-                                       fd_delete(t->cli_fd);
-                                       t->cli_state = CL_STCLOSE;
-                                       return 1;
-                               }
+                       else if (unlikely(rep->flags & (BF_READ_NULL | BF_SHUTW_STATUS) ||
+                                         rep->l >= rep->rlim - rep->data)) {
+                               EV_FD_CLR(t->srv_fd, DIR_RD);
+                               buffer_shutr_done(rep);
+                               t->srv_state = SV_STSHUTR;
+                               t->analysis &= ~AN_RTR_ANY;
+                               //fprintf(stderr,"%p:%s(%d), c=%d, s=%d\n", t, __FUNCTION__, __LINE__, t->cli_state, t->cli_state);
+                               return 1;
                        }
 
-                       /* we don't enable client write if the buffer is empty, nor if the server has to analyze it */
-                       if ((rep->l == 0) || !(rep->flags & BF_MAY_FORWARD)) {
-                               if (EV_FD_COND_C(t->cli_fd, DIR_WR)) {
-                                       /* stop writing */
-                                       rep->wex = TICK_ETERNITY;
-                               }
-                       } else {
-                               /* buffer not empty */
-                               if (!tick_isset(rep->wex)) {
-                                       EV_FD_COND_S(t->cli_fd, DIR_WR);
-                                       /* restart writing */
-                                       rep->wex = tick_add_ifset(now_ms, t->fe->timeout.client);
-                                       if (!(req->flags & BF_SHUTR_STATUS) && tick_isset(rep->wex) && tick_isset(req->rex)) {
-                                               /* FIXME: to prevent the client from expiring read timeouts during writes,
-                                                * we refresh it, except if it was already infinite. */
-                                               req->rex = rep->wex;
-                                       }
+                       /* read timeout : return a 504 to the client. */
+                       else if (unlikely(EV_FD_ISSET(t->srv_fd, DIR_RD) &&
+                                         tick_is_expired(rep->rex, now_ms))) {
+                               buffer_shutr_done(rep);
+                               buffer_shutw_done(req);
+                               fd_delete(t->srv_fd);
+                               if (t->srv) {
+                                       t->srv->cur_sess--;
+                                       t->srv->failed_resp++;
+                                       sess_change_server(t, NULL);
                                }
+                               t->be->failed_resp++;
+                               t->srv_state = SV_STCLOSE;
+                               t->analysis &= ~AN_RTR_ANY;
+                               rep->flags |= BF_MAY_FORWARD;
+                               txn->status = 504;
+                               client_return(t, error_message(t, HTTP_ERR_504));
+                               if (!(t->flags & SN_ERR_MASK))
+                                       t->flags |= SN_ERR_SRVTO;
+                               if (!(t->flags & SN_FINST_MASK))
+                                       t->flags |= SN_FINST_H;
+                               /* We used to have a free connection slot. Since we'll never use it,
+                                * we have to inform the server that it may be used by another session.
+                                */
+                               if (t->srv && may_dequeue_tasks(t->srv, t->be))
+                                       process_srv_queue(t->srv);
+                               return 1;
                        }
-               }
-               return 0; /* other cases change nothing */
-       }
-       else { /* CL_STCLOSE: nothing to do */
-               if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) {
-                       int len;
-                       len = sprintf(trash, "%08x:%s.clicls[%04x:%04x]\n", t->uniq_id, t->be->id, (unsigned short)t->cli_fd, (unsigned short)t->srv_fd);
-                       write(1, trash, len);
-               }
-               return 0;
-       }
-       return 0;
-}
 
+                       /* last client read and buffer empty */
+                       /* FIXME!!! here, we don't want to switch to SHUTW if the
+                        * client shuts read too early, because we may still have
+                        * some work to do on the headers.
+                        * The side-effect is that if the client completely closes its
+                        * connection during SV_STHEADER, the connection to the server
+                        * is kept until a response comes back or the timeout is reached.
+                        * This sometimes causes fast loops when the request buffer is
+                        * full, so we still perform the transition right now. It will
+                        * make sense later anyway.
+                        */
+                       else if (unlikely(req->flags & BF_SHUTR_STATUS && (req->l == 0))) {
 
-/*
- * manages the server FSM and its socket. It returns 1 if a state has changed
- * (and a resync may be needed), 0 else.
- */
-int process_srv(struct session *t)
-{
-       int s = t->srv_state;
-       struct http_txn *txn = &t->txn;
-       struct buffer *req = t->req;
-       struct buffer *rep = t->rep;
-       int conn_err;
+                               EV_FD_CLR(t->srv_fd, DIR_WR);
+                               buffer_shutw_done(req);
 
-       DPRINTF(stderr,"[%u] process_srv: c=%s s=%s set(r,w)=%d,%d exp(r,w)=%u,%u req=%08x rep=%08x\n",
-               now_ms,
-               cli_stnames[t->cli_state], srv_stnames[t->srv_state],
-               EV_FD_ISSET(t->srv_fd, DIR_RD), EV_FD_ISSET(t->srv_fd, DIR_WR),
-               rep->rex, req->wex,
-               req->flags, rep->flags);
+                               /* We must ensure that the read part is still
+                                * alive when switching to shutw */
+                               EV_FD_SET(t->srv_fd, DIR_RD);
+                               rep->rex = tick_add_ifset(now_ms, t->be->timeout.server);
 
-       if (s == SV_STIDLE) {
-               if ((rep->flags & BF_SHUTW_STATUS) ||
-                        ((req->flags & BF_SHUTR_STATUS) &&
-                         (req->l == 0 || t->be->options & PR_O_ABRT_CLOSE))) { /* give up */
-                       req->cex = TICK_ETERNITY;
-                       if (t->pend_pos)
-                               t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now);
-                       /* note that this must not return any error because it would be able to
-                        * overwrite the client_retnclose() output.
-                        */
-                       if (txn->flags & TX_CLTARPIT)
-                               srv_close_with_err(t, SN_ERR_CLICL, SN_FINST_T, 0, NULL);
-                       else
-                               srv_close_with_err(t, SN_ERR_CLICL, t->pend_pos ? SN_FINST_Q : SN_FINST_C, 0, NULL);
+                               shutdown(t->srv_fd, SHUT_WR);
+                               t->srv_state = SV_STSHUTW;
+                               t->analysis &= ~AN_RTR_ANY;
+                               return 1;
+                       }
 
-                       return 1;
-               }
-               else if (req->flags & BF_MAY_CONNECT) {
-                       /* the client allows the server to connect */
-                       if (txn->flags & TX_CLTARPIT) {
-                               /* This connection is being tarpitted. The CLIENT side has
-                                * already set the connect expiration date to the right
-                                * timeout. We just have to check that it has not expired.
-                                */
-                               if (!tick_is_expired(req->cex, now_ms))
-                                       return 0;
+                       /* write timeout */
+                       /* FIXME!!! here, we don't want to switch to SHUTW if the
+                        * client shuts read too early, because we may still have
+                        * some work to do on the headers.
+                        */
+                       else if (unlikely(EV_FD_ISSET(t->srv_fd, DIR_WR) &&
+                                         tick_is_expired(req->wex, now_ms))) {
+                               EV_FD_CLR(t->srv_fd, DIR_WR);
+                               buffer_shutw_done(req);
+                               shutdown(t->srv_fd, SHUT_WR);
+                               /* We must ensure that the read part is still alive
+                                * when switching to shutw */
+                               EV_FD_SET(t->srv_fd, DIR_RD);
+                               rep->rex = tick_add_ifset(now_ms, t->be->timeout.server);
 
-                               /* We will set the queue timer to the time spent, just for
-                                * logging purposes. We fake a 500 server error, so that the
-                                * attacker will not suspect his connection has been tarpitted.
-                                * It will not cause trouble to the logs because we can exclude
-                                * the tarpitted connections by filtering on the 'PT' status flags.
-                                */
-                               req->cex = TICK_ETERNITY;
-                               t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now);
-                               srv_close_with_err(t, SN_ERR_PRXCOND, SN_FINST_T,
-                                                  500, error_message(t, HTTP_ERR_500));
+                               t->srv_state = SV_STSHUTW;
+                               t->analysis &= ~AN_RTR_ANY;
+                               if (!(t->flags & SN_ERR_MASK))
+                                       t->flags |= SN_ERR_SRVTO;
+                               if (!(t->flags & SN_FINST_MASK))
+                                       t->flags |= SN_FINST_H;
                                return 1;
                        }
 
-                       /* Right now, we will need to create a connection to the server.
-                        * We might already have tried, and got a connection pending, in
-                        * which case we will not do anything till it's pending. It's up
-                        * to any other session to release it and wake us up again.
+                       /*
+                        * And now the non-error cases.
                         */
-                       if (t->pend_pos) {
-                               if (!tick_is_expired(req->cex, now_ms)) {
-                                       return 0;
-                               } else {
-                                       /* we've been waiting too long here */
-                                       req->cex = TICK_ETERNITY;
-                                       t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now);
-                                       srv_close_with_err(t, SN_ERR_SRVTO, SN_FINST_Q,
-                                                          503, error_message(t, HTTP_ERR_503));
-                                       if (t->srv)
-                                               t->srv->failed_conns++;
-                                       t->be->failed_conns++;
-                                       return 1;
+
+                       /* Data remaining in the request buffer.
+                        * This happens during the first pass here, and during
+                        * long posts.
+                        */
+                       else if (likely(req->l)) {
+                               if (!tick_isset(req->wex)) {
+                                       EV_FD_COND_S(t->srv_fd, DIR_WR);
+                                       /* restart writing */
+                                       req->wex = tick_add_ifset(now_ms, t->be->timeout.server);
+                                       if (tick_isset(req->wex)) {
+                                               /* FIXME: to prevent the server from expiring read timeouts during writes,
+                                                * we refresh it. */
+                                               rep->rex = req->wex;
+                                       }
                                }
                        }
 
-                       do {
-                               /* first, get a connection */
-                               if (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD)
-                                       t->flags |= SN_REDIRECTABLE;
+                       /* nothing left in the request buffer */
+                       else {
+                               if (EV_FD_COND_C(t->srv_fd, DIR_WR)) {
+                                       /* stop writing */
+                                       req->wex = TICK_ETERNITY;
+                               }
+                       }
 
-                               if (srv_redispatch_connect(t))
-                                       return t->srv_state != SV_STIDLE;
+                       /* return 0 if nothing changed */
+                       return !(t->analysis & AN_RTR_ANY);
+               }
 
-                               if ((t->flags & SN_REDIRECTABLE) && t->srv && t->srv->rdr_len) {
-                                       /* Server supporting redirection and it is possible.
-                                        * Invalid requests are reported as such. It concerns all
-                                        * the largest ones.
-                                        */
-                                       struct chunk rdr;
-                                       char *path;
-                                       int len;
 
-                                       /* 1: create the response header */
-                                       rdr.len = strlen(HTTP_302);
-                                       rdr.str = trash;
-                                       memcpy(rdr.str, HTTP_302, rdr.len);
+               /*****************************************************************
+                * More interesting part now : we know that we have a complete   *
+                * response which at least looks like HTTP. We have an indicator *
+                * of each header's length, so we can parse them quickly.        *
+                ****************************************************************/
 
-                                       /* 2: add the server's prefix */
-                                       if (rdr.len + t->srv->rdr_len > sizeof(trash))
-                                               goto cancel_redir;
+               /* ensure we keep this pointer to the beginning of the message */
+               msg->sol = rep->data + msg->som;
 
-                                       memcpy(rdr.str + rdr.len, t->srv->rdr_pfx, t->srv->rdr_len);
-                                       rdr.len += t->srv->rdr_len;
+               /*
+                * 1: get the status code and check for cacheability.
+                */
 
-                                       /* 3: add the request URI */
-                                       path = http_get_path(txn);
-                                       if (!path)
-                                               goto cancel_redir;
-                                       len = txn->req.sl.rq.u_l + (txn->req.sol+txn->req.sl.rq.u) - path;
-                                       if (rdr.len + len > sizeof(trash) - 4) /* 4 for CRLF-CRLF */
-                                               goto cancel_redir;
+               t->logs.logwait &= ~LW_RESP;
+               txn->status = strl2ui(rep->data + msg->sl.st.c, msg->sl.st.c_l);
 
-                                       memcpy(rdr.str + rdr.len, path, len);
-                                       rdr.len += len;
-                                       memcpy(rdr.str + rdr.len, "\r\n\r\n", 4);
-                                       rdr.len += 4;
+               switch (txn->status) {
+               case 200:
+               case 203:
+               case 206:
+               case 300:
+               case 301:
+               case 410:
+                       /* RFC2616 @13.4:
+                        *   "A response received with a status code of
+                        *    200, 203, 206, 300, 301 or 410 MAY be stored
+                        *    by a cache (...) unless a cache-control
+                        *    directive prohibits caching."
+                        *   
+                        * RFC2616 @9.5: POST method :
+                        *   "Responses to this method are not cacheable,
+                        *    unless the response includes appropriate
+                        *    Cache-Control or Expires header fields."
+                        */
+                       if (likely(txn->meth != HTTP_METH_POST) &&
+                           (t->be->options & (PR_O_CHK_CACHE|PR_O_COOK_NOC)))
+                               txn->flags |= TX_CACHEABLE | TX_CACHE_COOK;
+                       break;
+               default:
+                       break;
+               }
 
-                                       srv_close_with_err(t, SN_ERR_PRXCOND, SN_FINST_C, 302, &rdr);
-                                       /* FIXME: we should increase a counter of redirects per server and per backend. */
-                                       if (t->srv)
-                                               t->srv->cum_sess++;
-                                       return 1;
-                               cancel_redir:
-                                       txn->status = 400;
-                                       t->fe->failed_req++;
-                                       srv_close_with_err(t, SN_ERR_PRXCOND, SN_FINST_C,
-                                                          400, error_message(t, HTTP_ERR_400));
+               /*
+                * 2: we may need to capture headers
+                */
+               if (unlikely((t->logs.logwait & LW_RSPHDR) && t->fe->rsp_cap))
+                       capture_headers(rep->data + msg->som, &txn->hdr_idx,
+                                       txn->rsp.cap, t->fe->rsp_cap);
+
+               /*
+                * 3: we will have to evaluate the filters.
+                * As opposed to version 1.2, now they will be evaluated in the
+                * filters order and not in the header order. This means that
+                * each filter has to be validated among all headers.
+                *
+                * Filters are tried with ->be first, then with ->fe if it is
+                * different from ->be.
+                */
+
+               t->flags &= ~SN_CONN_CLOSED; /* prepare for inspection */
+
+               cur_proxy = t->be;
+               while (1) {
+                       struct proxy *rule_set = cur_proxy;
+
+                       /* try headers filters */
+                       if (rule_set->rsp_exp != NULL) {
+                               if (apply_filters_to_response(t, rep, rule_set->rsp_exp) < 0) {
+                               return_bad_resp:
+                                       if (t->srv) {
+                                               t->srv->cur_sess--;
+                                               t->srv->failed_resp++;
+                                               sess_change_server(t, NULL);
+                                       }
+                                       cur_proxy->failed_resp++;
+                               return_srv_prx_502:
+                                       buffer_shutr_done(rep);
+                                       buffer_shutw_done(req);
+                                       fd_delete(t->srv_fd);
+                                       t->srv_state = SV_STCLOSE;
+                                       t->analysis &= ~AN_RTR_ANY;
+                                       rep->flags |= BF_MAY_FORWARD;
+                                       txn->status = 502;
+                                       client_return(t, error_message(t, HTTP_ERR_502));
+                                       if (!(t->flags & SN_ERR_MASK))
+                                               t->flags |= SN_ERR_PRXCOND;
+                                       if (!(t->flags & SN_FINST_MASK))
+                                               t->flags |= SN_FINST_H;
+                                       /* We used to have a free connection slot. Since we'll never use it,
+                                        * we have to inform the server that it may be used by another session.
+                                        */
+                                       if (t->srv && may_dequeue_tasks(t->srv, cur_proxy))
+                                               process_srv_queue(t->srv);
                                        return 1;
                                }
+                       }
 
-                               /* try to (re-)connect to the server, and fail if we expire the
-                                * number of retries.
-                                */
-                               if (srv_retryable_connect(t)) {
-                                       t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now);
-                                       return t->srv_state != SV_STIDLE;
-                               }
-                       } while (1);
-               }
-       }
-       else if (s == SV_STCONN) { /* connection in progress */
-               if ((rep->flags & BF_SHUTW_STATUS) ||
-                   ((req->flags & BF_SHUTR_STATUS) &&
-                    ((req->l == 0 && !(req->flags & BF_WRITE_STATUS)) ||
-                     t->be->options & PR_O_ABRT_CLOSE))) { /* give up */
-                       req->cex = TICK_ETERNITY;
-                       if (!(t->flags & SN_CONN_TAR)) {
-                               /* if we are in turn-around, we have already closed the FD */
-                               fd_delete(t->srv_fd);
+                       /* has the response been denied ? */
+                       if (txn->flags & TX_SVDENY) {
                                if (t->srv) {
                                        t->srv->cur_sess--;
+                                       t->srv->failed_secu++;
                                        sess_change_server(t, NULL);
                                }
+                               cur_proxy->denied_resp++;
+                               goto return_srv_prx_502;
                        }
 
-                       /* note that this must not return any error because it would be able to
-                        * overwrite the client_retnclose() output.
-                        */
-                       srv_close_with_err(t, SN_ERR_CLICL, SN_FINST_C, 0, NULL);
-                       return 1;
-               }
-               if (!(req->flags & BF_WRITE_STATUS) && !tick_is_expired(req->cex, now_ms)) {
-                       return 0; /* nothing changed */
-               }
-               else if (!(req->flags & BF_WRITE_STATUS) || (req->flags & BF_WRITE_ERROR)) {
-                       /* timeout, asynchronous connect error or first write error */
-                       //fprintf(stderr,"2: c=%d, s=%d\n", c, s);
-
-                       if (t->flags & SN_CONN_TAR) {
-                               /* We are doing a turn-around waiting for a new connection attempt. */
-                               if (!tick_is_expired(req->cex, now_ms))
-                                       return 0;
-                               t->flags &= ~SN_CONN_TAR;
-                       }
-                       else {
-                               fd_delete(t->srv_fd);
-                               if (t->srv) {
-                                       t->srv->cur_sess--;
-                                       sess_change_server(t, NULL);
-                               }
+                       /* We might have to check for "Connection:" */
+                       if (((t->fe->options | t->be->options) & (PR_O_HTTP_CLOSE|PR_O_FORCE_CLO)) &&
+                           !(t->flags & SN_CONN_CLOSED)) {
+                               char *cur_ptr, *cur_end, *cur_next;
+                               int cur_idx, old_idx, delta, val;
+                               struct hdr_idx_elem *cur_hdr;
 
-                               if (!(req->flags & BF_WRITE_STATUS))
-                                       conn_err = SN_ERR_SRVTO; // it was a connect timeout.
-                               else
-                                       conn_err = SN_ERR_SRVCL; // it was an asynchronous connect error.
+                               cur_next = rep->data + txn->rsp.som + hdr_idx_first_pos(&txn->hdr_idx);
+                               old_idx = 0;
 
-                               /* ensure that we have enough retries left */
-                               if (srv_count_retry_down(t, conn_err))
-                                       return 1;
+                               while ((cur_idx = txn->hdr_idx.v[old_idx].next)) {
+                                       cur_hdr  = &txn->hdr_idx.v[cur_idx];
+                                       cur_ptr  = cur_next;
+                                       cur_end  = cur_ptr + cur_hdr->len;
+                                       cur_next = cur_end + cur_hdr->cr + 1;
 
-                               if (req->flags & BF_WRITE_ERROR) {
-                                       /* we encountered an immediate connection error, and we
-                                        * will have to retry connecting to the same server, most
-                                        * likely leading to the same result. To avoid this, we
-                                        * fake a connection timeout to retry after a turn-around
-                                        * time of 1 second. We will wait in the previous if block.
-                                        */
-                                       t->flags |= SN_CONN_TAR;
-                                       req->cex = tick_add(now_ms, MS_TO_TICKS(1000));
-                                       return 0;
+                                       val = http_header_match2(cur_ptr, cur_end, "Connection", 10);
+                                       if (val) {
+                                               /* 3 possibilities :
+                                                * - we have already set Connection: close,
+                                                *   so we remove this line.
+                                                * - we have not yet set Connection: close,
+                                                *   but this line indicates close. We leave
+                                                *   it untouched and set the flag.
+                                                * - we have not yet set Connection: close,
+                                                *   and this line indicates non-close. We
+                                                *   replace it.
+                                                */
+                                               if (t->flags & SN_CONN_CLOSED) {
+                                                       delta = buffer_replace2(rep, cur_ptr, cur_next, NULL, 0);
+                                                       txn->rsp.eoh += delta;
+                                                       cur_next += delta;
+                                                       txn->hdr_idx.v[old_idx].next = cur_hdr->next;
+                                                       txn->hdr_idx.used--;
+                                                       cur_hdr->len = 0;
+                                               } else {
+                                                       if (strncasecmp(cur_ptr + val, "close", 5) != 0) {
+                                                               delta = buffer_replace2(rep, cur_ptr + val, cur_end,
+                                                                                       "close", 5);
+                                                               cur_next += delta;
+                                                               cur_hdr->len += delta;
+                                                               txn->rsp.eoh += delta;
+                                                       }
+                                                       t->flags |= SN_CONN_CLOSED;
+                                               }
+                                       }
+                                       old_idx = cur_idx;
                                }
                        }
 
-                       if (t->srv && t->conn_retries == 0 && t->be->options & PR_O_REDISP) {
-                               /* We're on our last chance, and the REDISP option was specified.
-                                * We will ignore cookie and force to balance or use the dispatcher.
-                                */
-                               /* let's try to offer this slot to anybody */
-                               if (may_dequeue_tasks(t->srv, t->be))
-                                       process_srv_queue(t->srv);
-
-                               /* it's left to the dispatcher to choose a server */
-                               t->flags &= ~(SN_DIRECT | SN_ASSIGNED | SN_ADDR_SET);
-                               t->prev_srv = t->srv;
-
-                               /* first, get a connection */
-                               if (srv_redispatch_connect(t))
-                                       return t->srv_state != SV_STCONN;
-                       } else {
-                               if (t->srv)
-                                       t->srv->retries++;
-                               t->be->retries++;
+                       /* add response headers from the rule sets in the same order */
+                       for (cur_idx = 0; cur_idx < rule_set->nb_rspadd; cur_idx++) {
+                               if (unlikely(http_header_add_tail(rep, &txn->rsp, &txn->hdr_idx,
+                                                                 rule_set->rsp_add[cur_idx])) < 0)
+                                       goto return_bad_resp;
                        }
 
-                       do {
-                               /* Now we will try to either reconnect to the same server or
-                                * connect to another server. If the connection gets queued
-                                * because all servers are saturated, then we will go back to
-                                * the SV_STIDLE state.
-                                */
-                               if (srv_retryable_connect(t)) {
-                                       t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now);
-                                       return t->srv_state != SV_STCONN;
-                               }
-
-                               /* we need to redispatch the connection to another server */
-                               if (srv_redispatch_connect(t))
-                                       return t->srv_state != SV_STCONN;
-                       } while (1);
+                       /* check whether we're already working on the frontend */
+                       if (cur_proxy == t->fe)
+                               break;
+                       cur_proxy = t->fe;
                }
-               else { /* no error or write 0 */
-                       t->logs.t_connect = tv_ms_elapsed(&t->logs.tv_accept, &now);
 
-                       //fprintf(stderr,"3: c=%d, s=%d\n", c, s);
-                       if (req->l == 0) /* nothing to write */ {
-                               EV_FD_CLR(t->srv_fd, DIR_WR);
-                               req->wex = TICK_ETERNITY;
-                       } else  /* need the right to write */ {
-                               EV_FD_SET(t->srv_fd, DIR_WR);
-                               req->wex = tick_add_ifset(now_ms, t->be->timeout.server);
-                               if (tick_isset(req->wex)) {
-                                       /* FIXME: to prevent the server from expiring read timeouts during writes,
-                                        * we refresh it. */
-                                       rep->rex = req->wex;
-                               }
-                       }
+               /*
+                * 4: check for server cookie.
+                */
+               if (t->be->cookie_name || t->be->appsession_name || t->be->capture_name
+                   || (t->be->options & PR_O_CHK_CACHE))
+                       manage_server_side_cookies(t, rep);
 
-                       if (t->be->mode == PR_MODE_TCP) { /* let's allow immediate data connection in this case */
-                               EV_FD_SET(t->srv_fd, DIR_RD);
-                               rep->rex = tick_add_ifset(now_ms, t->be->timeout.server);
-                               t->srv_state = SV_STDATA;
-                               rep->flags |= BF_MAY_FORWARD;
-                               rep->rlim = rep->data + BUFSIZE; /* no rewrite needed */
 
-                               /* if the user wants to log as soon as possible, without counting
-                                  bytes from the server, then this is the right moment. */
-                               if (t->fe->to_log && !(t->logs.logwait & LW_BYTES)) {
-                                       t->logs.t_close = t->logs.t_connect; /* to get a valid end date */
-                                       tcp_sess_log(t);
-                               }
-#ifdef CONFIG_HAP_TCPSPLICE
-                               if ((t->fe->options & t->be->options) & PR_O_TCPSPLICE) {
-                                       /* TCP splicing supported by both FE and BE */
-                                       tcp_splice_splicefd(t->cli_fd, t->srv_fd, 0);
-                               }
-#endif
-                       }
-                       else {
-                               t->srv_state = SV_STHEADERS;
-                               rep->rlim = rep->data + BUFSIZE - MAXREWRITE; /* rewrite needed */
-                               t->txn.rsp.msg_state = HTTP_MSG_RPBEFORE;
-                               /* reset hdr_idx which was already initialized by the request.
-                                * right now, the http parser does it.
-                                * hdr_idx_init(&t->txn.hdr_idx);
-                                */
-                       }
-                       req->cex = TICK_ETERNITY;
-                       return 1;
-               }
-       }
-       else if (s == SV_STHEADERS) { /* receiving server headers */
                /*
-                * Now parse the partial (or complete) lines.
-                * We will check the response syntax, and also join multi-line
-                * headers. An index of all the lines will be elaborated while
-                * parsing.
-                *
-                * For the parsing, we use a 28 states FSM.
-                *
-                * Here is the information we currently have :
-                *   rep->data + req->som  = beginning of response
-                *   rep->data + req->eoh  = end of processed headers / start of current one
-                *   rep->data + req->eol  = end of current header or line (LF or CRLF)
-                *   rep->lr = first non-visited byte
-                *   rep->r  = end of data
+                * 5: check for cache-control or pragma headers if required.
                 */
+               if ((t->be->options & (PR_O_COOK_NOC | PR_O_CHK_CACHE)) != 0)
+                       check_response_for_cacheability(t, rep);
 
-               int cur_idx;
-               struct http_msg *msg = &txn->rsp;
-               struct proxy *cur_proxy;
+               /*
+                * 6: add server cookie in the response if needed
+                */
+               if ((t->srv) && !(t->flags & SN_DIRECT) && (t->be->options & PR_O_COOK_INS) &&
+                   (!(t->be->options & PR_O_COOK_POST) || (txn->meth == HTTP_METH_POST))) {
+                       int len;
 
-               if (likely(rep->lr < rep->r))
-                       http_msg_analyzer(rep, msg, &txn->hdr_idx);
+                       /* the server is known, it's not the one the client requested, we have to
+                        * insert a set-cookie here, except if we want to insert only on POST
+                        * requests and this one isn't. Note that servers which don't have cookies
+                        * (eg: some backup servers) will return a full cookie removal request.
+                        */
+                       len = sprintf(trash, "Set-Cookie: %s=%s; path=/",
+                                     t->be->cookie_name,
+                                     t->srv->cookie ? t->srv->cookie : "; Expires=Thu, 01-Jan-1970 00:00:01 GMT");
 
-               /* 1: we might have to print this header in debug mode */
-               if (unlikely((global.mode & MODE_DEBUG) &&
-                            (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) &&
-                            (msg->msg_state == HTTP_MSG_BODY || msg->msg_state == HTTP_MSG_ERROR))) {
-                       char *eol, *sol;
+                       if (t->be->cookie_domain)
+                               len += sprintf(trash+len, "; domain=%s", t->be->cookie_domain);
 
-                       sol = rep->data + msg->som;
-                       eol = sol + msg->sl.rq.l;
-                       debug_hdr("srvrep", t, sol, eol);
+                       if (unlikely(http_header_add_tail2(rep, &txn->rsp, &txn->hdr_idx,
+                                                          trash, len)) < 0)
+                               goto return_bad_resp;
+                       txn->flags |= TX_SCK_INSERTED;
 
-                       sol += hdr_idx_first_pos(&txn->hdr_idx);
-                       cur_idx = hdr_idx_first_idx(&txn->hdr_idx);
+                       /* Here, we will tell an eventual cache on the client side that we don't
+                        * want it to cache this reply because HTTP/1.0 caches also cache cookies !
+                        * Some caches understand the correct form: 'no-cache="set-cookie"', but
+                        * others don't (eg: apache <= 1.3.26). So we use 'private' instead.
+                        */
+                       if ((t->be->options & PR_O_COOK_NOC) && (txn->flags & TX_CACHEABLE)) {
 
-                       while (cur_idx) {
-                               eol = sol + txn->hdr_idx.v[cur_idx].len;
-                               debug_hdr("srvhdr", t, sol, eol);
-                               sol = eol + txn->hdr_idx.v[cur_idx].cr + 1;
-                               cur_idx = txn->hdr_idx.v[cur_idx].next;
+                               txn->flags &= ~TX_CACHEABLE & ~TX_CACHE_COOK;
+
+                               if (unlikely(http_header_add_tail2(rep, &txn->rsp, &txn->hdr_idx,
+                                                                  "Cache-control: private", 22)) < 0)
+                                       goto return_bad_resp;
                        }
                }
 
 
-               if ((rep->l < rep->rlim - rep->data) && !tick_isset(rep->rex)) {
-                       EV_FD_COND_S(t->srv_fd, DIR_RD);
-                       /* fd in DIR_RD was disabled, perhaps because of a previous buffer
-                        * full. We cannot loop here since stream_sock_read will disable it only if
-                        * rep->l == rlim-data
+               /*
+                * 7: check if result will be cacheable with a cookie.
+                * We'll block the response if security checks have caught
+                * nasty things such as a cacheable cookie.
+                */
+               if (((txn->flags & (TX_CACHEABLE | TX_CACHE_COOK | TX_SCK_ANY)) ==
+                    (TX_CACHEABLE | TX_CACHE_COOK | TX_SCK_ANY)) &&
+                   (t->be->options & PR_O_CHK_CACHE)) {
+
+                       /* we're in presence of a cacheable response containing
+                        * a set-cookie header. We'll block it as requested by
+                        * the 'checkcache' option, and send an alert.
                         */
-                       rep->rex = tick_add_ifset(now_ms, t->be->timeout.server);
-               }
+                       if (t->srv) {
+                               t->srv->cur_sess--;
+                               t->srv->failed_secu++;
+                               sess_change_server(t, NULL);
+                       }
+                       t->be->denied_resp++;
 
+                       Alert("Blocking cacheable cookie in response from instance %s, server %s.\n",
+                             t->be->id, t->srv?t->srv->id:"<dispatch>");
+                       send_log(t->be, LOG_ALERT,
+                                "Blocking cacheable cookie in response from instance %s, server %s.\n",
+                                t->be->id, t->srv?t->srv->id:"<dispatch>");
+                       goto return_srv_prx_502;
+               }
 
                /*
-                * Now we quickly check if we have found a full valid response.
-                * If not so, we check the FD and buffer states before leaving.
-                * A full response is indicated by the fact that we have seen
-                * the double LF/CRLF, so the state is HTTP_MSG_BODY. Invalid
-                * responses are checked first.
-                *
-                * Depending on whether the client is still there or not, we
-                * may send an error response back or not. Note that normally
-                * we should only check for HTTP status there, and check I/O
-                * errors somewhere else.
+                * 8: add "Connection: close" if needed and not yet set.
+                * Note that we do not need to add it in case of HTTP/1.0.
                 */
+               if (!(t->flags & SN_CONN_CLOSED) &&
+                   ((t->fe->options | t->be->options) & (PR_O_HTTP_CLOSE|PR_O_FORCE_CLO))) {
+                       if ((unlikely(msg->sl.st.v_l != 8) ||
+                            unlikely(req->data[msg->som + 7] != '0')) &&
+                           unlikely(http_header_add_tail2(rep, &txn->rsp, &txn->hdr_idx,
+                                                          "Connection: close", 17)) < 0)
+                               goto return_bad_resp;
+                       t->flags |= SN_CONN_CLOSED;
+               }
 
-               if (unlikely(msg->msg_state != HTTP_MSG_BODY)) {
 
-                       /* Invalid response, or read error or write error */
-                       if (unlikely((msg->msg_state == HTTP_MSG_ERROR) ||
-                                    (req->flags & BF_WRITE_ERROR) ||
-                                    (rep->flags & BF_READ_ERROR))) {
-                               buffer_shutr_done(rep);
-                               buffer_shutw_done(req);
-                               fd_delete(t->srv_fd);
-                               if (t->srv) {
-                                       t->srv->cur_sess--;
-                                       t->srv->failed_resp++;
-                                       sess_change_server(t, NULL);
-                               }
-                               t->be->failed_resp++;
-                               t->srv_state = SV_STCLOSE;
-                               rep->flags |= BF_MAY_FORWARD;
-                               txn->status = 502;
-                               client_return(t, error_message(t, HTTP_ERR_502));
-                               if (!(t->flags & SN_ERR_MASK))
-                                       t->flags |= SN_ERR_SRVCL;
-                               if (!(t->flags & SN_FINST_MASK))
-                                       t->flags |= SN_FINST_H;
-                               /* We used to have a free connection slot. Since we'll never use it,
-                                * we have to inform the server that it may be used by another session.
-                                */
-                               if (t->srv && may_dequeue_tasks(t->srv, t->be))
-                                       process_srv_queue(t->srv);
+               /*************************************************************
+                * OK, that's finished for the headers. We have done what we *
+                * could. Let's switch to the DATA state.                    *
+                ************************************************************/
 
-                               return 1;
-                       }
+               t->srv_state = SV_STDATA;
+               t->analysis &= ~AN_RTR_ANY;
+               rep->flags |= BF_MAY_FORWARD;
+               rep->rlim = rep->data + BUFSIZE; /* no more rewrite needed */
+               t->logs.t_data = tv_ms_elapsed(&t->logs.tv_accept, &now);
 
-                       /* end of client write or end of server read.
-                        * since we are in header mode, if there's no space left for headers, we
-                        * won't be able to free more later, so the session will never terminate.
-                        */
-                       else if (unlikely(rep->flags & (BF_READ_NULL | BF_SHUTW_STATUS) ||
-                                         rep->l >= rep->rlim - rep->data)) {
-                               EV_FD_CLR(t->srv_fd, DIR_RD);
-                               buffer_shutr_done(rep);
-                               t->srv_state = SV_STSHUTR;
-                               //fprintf(stderr,"%p:%s(%d), c=%d, s=%d\n", t, __FUNCTION__, __LINE__, t->cli_state, t->cli_state);
-                               return 1;
-                       }
+               /* client connection already closed or option 'forceclose' required :
+                * we close the server's outgoing connection right now.
+                */
+               if ((req->l == 0) &&
+                   (req->flags & BF_SHUTR_STATUS || t->be->options & PR_O_FORCE_CLO)) {
+                       EV_FD_CLR(t->srv_fd, DIR_WR);
+                       buffer_shutw_done(req);
 
-                       /* read timeout : return a 504 to the client. */
-                       else if (unlikely(EV_FD_ISSET(t->srv_fd, DIR_RD) &&
-                                         tick_is_expired(rep->rex, now_ms))) {
-                               buffer_shutr_done(rep);
-                               buffer_shutw_done(req);
-                               fd_delete(t->srv_fd);
-                               if (t->srv) {
-                                       t->srv->cur_sess--;
-                                       t->srv->failed_resp++;
-                                       sess_change_server(t, NULL);
-                               }
-                               t->be->failed_resp++;
-                               t->srv_state = SV_STCLOSE;
-                               rep->flags |= BF_MAY_FORWARD;
-                               txn->status = 504;
-                               client_return(t, error_message(t, HTTP_ERR_504));
-                               if (!(t->flags & SN_ERR_MASK))
-                                       t->flags |= SN_ERR_SRVTO;
-                               if (!(t->flags & SN_FINST_MASK))
-                                       t->flags |= SN_FINST_H;
-                               /* We used to have a free connection slot. Since we'll never use it,
-                                * we have to inform the server that it may be used by another session.
-                                */
-                               if (t->srv && may_dequeue_tasks(t->srv, t->be))
-                                       process_srv_queue(t->srv);
-                               return 1;
-                       }
+                       /* We must ensure that the read part is still alive when switching
+                        * to shutw */
+                       EV_FD_SET(t->srv_fd, DIR_RD);
+                       rep->rex = tick_add_ifset(now_ms, t->be->timeout.server);
 
-                       /* last client read and buffer empty */
-                       /* FIXME!!! here, we don't want to switch to SHUTW if the
-                        * client shuts read too early, because we may still have
-                        * some work to do on the headers.
-                        * The side-effect is that if the client completely closes its
-                        * connection during SV_STHEADER, the connection to the server
-                        * is kept until a response comes back or the timeout is reached.
-                        * This sometimes causes fast loops when the request buffer is
-                        * full, so we still perform the transition right now. It will
-                        * make sense later anyway.
-                        */
-                       else if (unlikely(req->flags & BF_SHUTR_STATUS && (req->l == 0))) {
+                       shutdown(t->srv_fd, SHUT_WR);
+                       t->srv_state = SV_STSHUTW;
+                       t->analysis &= ~AN_RTR_ANY;
+               }
 
-                               EV_FD_CLR(t->srv_fd, DIR_WR);
-                               buffer_shutw_done(req);
+#ifdef CONFIG_HAP_TCPSPLICE
+               if ((t->fe->options & t->be->options) & PR_O_TCPSPLICE) {
+                       /* TCP splicing supported by both FE and BE */
+                       tcp_splice_splicefd(t->cli_fd, t->srv_fd, 0);
+               }
+#endif
+               /* if the user wants to log as soon as possible, without counting
+                * bytes from the server, then this is the right moment. We have
+                * to temporarily assign bytes_out to log what we currently have.
+                */
+               if (t->fe->to_log && !(t->logs.logwait & LW_BYTES)) {
+                       t->logs.t_close = t->logs.t_data; /* to get a valid end date */
+                       t->logs.bytes_out = txn->rsp.eoh;
+                       if (t->fe->to_log & LW_REQ)
+                               http_sess_log(t);
+                       else
+                               tcp_sess_log(t);
+                       t->logs.bytes_out = 0;
+               }
 
-                               /* We must ensure that the read part is still
-                                * alive when switching to shutw */
-                               EV_FD_SET(t->srv_fd, DIR_RD);
-                               rep->rex = tick_add_ifset(now_ms, t->be->timeout.server);
-
-                               shutdown(t->srv_fd, SHUT_WR);
-                               t->srv_state = SV_STSHUTW;
-                               return 1;
-                       }
+               /* Note: we must not try to cheat by jumping directly to DATA,
+                * otherwise we would not let the client side wake up.
+                */
 
-                       /* write timeout */
-                       /* FIXME!!! here, we don't want to switch to SHUTW if the
-                        * client shuts read too early, because we may still have
-                        * some work to do on the headers.
-                        */
-                       else if (unlikely(EV_FD_ISSET(t->srv_fd, DIR_WR) &&
-                                         tick_is_expired(req->wex, now_ms))) {
-                               EV_FD_CLR(t->srv_fd, DIR_WR);
-                               buffer_shutw_done(req);
-                               shutdown(t->srv_fd, SHUT_WR);
-                               /* We must ensure that the read part is still alive
-                                * when switching to shutw */
-                               EV_FD_SET(t->srv_fd, DIR_RD);
-                               rep->rex = tick_add_ifset(now_ms, t->be->timeout.server);
+               return 1;
+       }
+       return 0;
+}
 
-                               t->srv_state = SV_STSHUTW;
-                               if (!(t->flags & SN_ERR_MASK))
-                                       t->flags |= SN_ERR_SRVTO;
-                               if (!(t->flags & SN_FINST_MASK))
-                                       t->flags |= SN_FINST_H;
-                               return 1;
-                       }
+/*
+ * manages the client FSM and its socket. BTW, it also tries to handle the
+ * cookie. It returns 1 if a state has changed (and a resync may be needed),
+ * 0 else.
+ */
+int process_cli(struct session *t)
+{
+       int s = t->srv_state;
+       int c = t->cli_state;
+       struct buffer *req = t->req;
+       struct buffer *rep = t->rep;
 
-                       /*
-                        * And now the non-error cases.
-                        */
+       DPRINTF(stderr,"[%u] process_cli: c=%s s=%s set(r,w)=%d,%d exp(r,w)=%u,%u req=%08x rep=%08x\n",
+               now_ms,
+               cli_stnames[t->cli_state], srv_stnames[t->srv_state],
+               EV_FD_ISSET(t->cli_fd, DIR_RD), EV_FD_ISSET(t->cli_fd, DIR_WR),
+               req->rex, rep->wex,
+               req->flags, rep->flags);
 
-                       /* Data remaining in the request buffer.
-                        * This happens during the first pass here, and during
-                        * long posts.
-                        */
-                       else if (likely(req->l)) {
-                               if (!tick_isset(req->wex)) {
-                                       EV_FD_COND_S(t->srv_fd, DIR_WR);
-                                       /* restart writing */
-                                       req->wex = tick_add_ifset(now_ms, t->be->timeout.server);
-                                       if (tick_isset(req->wex)) {
-                                               /* FIXME: to prevent the server from expiring read timeouts during writes,
-                                                * we refresh it. */
-                                               rep->rex = req->wex;
-                                       }
-                               }
-                       }
+       /* if no analysis remains, it's time to forward the connection */
+       if (!(t->analysis & AN_REQ_ANY) && !(req->flags & (BF_MAY_CONNECT|BF_MAY_FORWARD)))
+               req->flags |= BF_MAY_CONNECT | BF_MAY_FORWARD;
 
-                       /* nothing left in the request buffer */
-                       else {
-                               if (EV_FD_COND_C(t->srv_fd, DIR_WR)) {
-                                       /* stop writing */
-                                       req->wex = TICK_ETERNITY;
-                               }
+       if (c == CL_STDATA || c == CL_STSHUTR || c == CL_STSHUTW) {
+               /* read or write error */
+               if (rep->flags & BF_WRITE_ERROR || req->flags & BF_READ_ERROR) {
+                       buffer_shutr_done(req);
+                       buffer_shutw_done(rep);
+                       fd_delete(t->cli_fd);
+                       t->cli_state = CL_STCLOSE;
+                       if (!(t->flags & SN_ERR_MASK))
+                               t->flags |= SN_ERR_CLICL;
+                       if (!(t->flags & SN_FINST_MASK)) {
+                               if (t->analysis & AN_REQ_ANY)
+                                       t->flags |= SN_FINST_R;
+                               else if (t->pend_pos)
+                                       t->flags |= SN_FINST_Q;
+                               else if (s == SV_STCONN)
+                                       t->flags |= SN_FINST_C;
+                               else
+                                       t->flags |= SN_FINST_D;
                        }
-
-                       return t->srv_state != SV_STHEADERS;
+                       return 1;
                }
-
-
-               /*****************************************************************
-                * More interesting part now : we know that we have a complete   *
-                * response which at least looks like HTTP. We have an indicator *
-                * of each header's length, so we can parse them quickly.        *
-                ****************************************************************/
-
-               /* ensure we keep this pointer to the beginning of the message */
-               msg->sol = rep->data + msg->som;
-
-               /*
-                * 1: get the status code and check for cacheability.
-                */
-
-               t->logs.logwait &= ~LW_RESP;
-               txn->status = strl2ui(rep->data + msg->sl.st.c, msg->sl.st.c_l);
-
-               switch (txn->status) {
-               case 200:
-               case 203:
-               case 206:
-               case 300:
-               case 301:
-               case 410:
-                       /* RFC2616 @13.4:
-                        *   "A response received with a status code of
-                        *    200, 203, 206, 300, 301 or 410 MAY be stored
-                        *    by a cache (...) unless a cache-control
-                        *    directive prohibits caching."
-                        *   
-                        * RFC2616 @9.5: POST method :
-                        *   "Responses to this method are not cacheable,
-                        *    unless the response includes appropriate
-                        *    Cache-Control or Expires header fields."
-                        */
-                       if (likely(txn->meth != HTTP_METH_POST) &&
-                           (t->be->options & (PR_O_CHK_CACHE|PR_O_COOK_NOC)))
-                               txn->flags |= TX_CACHEABLE | TX_CACHE_COOK;
-                       break;
-               default:
-                       break;
+               /* last read, or end of server write */
+               else if (!(req->flags & BF_SHUTR_STATUS) &&   /* already done */
+                        req->flags & (BF_READ_NULL | BF_SHUTW_STATUS)) {
+                       buffer_shutr_done(req);
+                       if (!(rep->flags & BF_SHUTW_STATUS)) {
+                               EV_FD_CLR(t->cli_fd, DIR_RD);
+                               t->cli_state = CL_STSHUTR;
+                       } else {
+                               /* output was already closed */
+                               fd_delete(t->cli_fd);
+                               t->cli_state = CL_STCLOSE;
+                       }
+                       return 1;
+               }       
+               /* last server read and buffer empty */
+               else if (!(rep->flags & BF_SHUTW_STATUS) &&   /* already done */
+                        rep->l == 0 && rep->flags & BF_SHUTR_STATUS && !(t->flags & SN_SELF_GEN)) {
+                       buffer_shutw_done(rep);
+                       if (!(req->flags & BF_SHUTR_STATUS)) {
+                               EV_FD_CLR(t->cli_fd, DIR_WR);
+                               shutdown(t->cli_fd, SHUT_WR);
+                               /* We must ensure that the read part is still alive when switching to shutw */
+                               /* FIXME: is this still true ? */
+                               EV_FD_SET(t->cli_fd, DIR_RD);
+                               req->rex = tick_add_ifset(now_ms, t->fe->timeout.client);
+                               t->cli_state = CL_STSHUTW;
+                       } else {
+                               fd_delete(t->cli_fd);
+                               t->cli_state = CL_STCLOSE;
+                       }
+                       return 1;
                }
+               /* read timeout */
+               else if (tick_is_expired(req->rex, now_ms)) {
+                       buffer_shutr_done(req);
+                       req->flags |= BF_READ_TIMEOUT;
+                       if (!(rep->flags & BF_SHUTW_STATUS)) {
+                               EV_FD_CLR(t->cli_fd, DIR_RD);
+                               t->cli_state = CL_STSHUTR;
+                       } else {
+                               /* output was already closed */
+                               fd_delete(t->cli_fd);
+                               t->cli_state = CL_STCLOSE;
+                       }
+                       if (!(t->flags & SN_ERR_MASK))
+                               t->flags |= SN_ERR_CLITO;
+                       if (!(t->flags & SN_FINST_MASK)) {
+                               if (t->analysis & AN_REQ_ANY)
+                                       t->flags |= SN_FINST_R;
+                               else if (t->pend_pos)
+                                       t->flags |= SN_FINST_Q;
+                               else if (s == SV_STCONN)
+                                       t->flags |= SN_FINST_C;
+                               else
+                                       t->flags |= SN_FINST_D;
+                       }
+                       return 1;
+               }       
+               /* write timeout */
+               else if (tick_is_expired(rep->wex, now_ms)) {
+                       buffer_shutw_done(rep);
+                       rep->flags |= BF_WRITE_TIMEOUT;
+                       if (!(req->flags & BF_SHUTR_STATUS)) {
+                               EV_FD_CLR(t->cli_fd, DIR_WR);
+                               shutdown(t->cli_fd, SHUT_WR);
+                               /* We must ensure that the read part is still alive when switching to shutw */
+                               /* FIXME: is this still true ? */
+                               EV_FD_SET(t->cli_fd, DIR_RD);
+                               req->rex = tick_add_ifset(now_ms, t->fe->timeout.client);
+                               t->cli_state = CL_STSHUTW;
+                       } else {
+                               fd_delete(t->cli_fd);
+                               t->cli_state = CL_STCLOSE;
+                       }
 
-               /*
-                * 2: we may need to capture headers
-                */
-               if (unlikely((t->logs.logwait & LW_RSPHDR) && t->fe->rsp_cap))
-                       capture_headers(rep->data + msg->som, &txn->hdr_idx,
-                                       txn->rsp.cap, t->fe->rsp_cap);
-
-               /*
-                * 3: we will have to evaluate the filters.
-                * As opposed to version 1.2, now they will be evaluated in the
-                * filters order and not in the header order. This means that
-                * each filter has to be validated among all headers.
-                *
-                * Filters are tried with ->be first, then with ->fe if it is
-                * different from ->be.
-                */
-
-               t->flags &= ~SN_CONN_CLOSED; /* prepare for inspection */
-
-               cur_proxy = t->be;
-               while (1) {
-                       struct proxy *rule_set = cur_proxy;
-
-                       /* try headers filters */
-                       if (rule_set->rsp_exp != NULL) {
-                               if (apply_filters_to_response(t, rep, rule_set->rsp_exp) < 0) {
-                               return_bad_resp:
-                                       if (t->srv) {
-                                               t->srv->cur_sess--;
-                                               t->srv->failed_resp++;
-                                               sess_change_server(t, NULL);
-                                       }
-                                       cur_proxy->failed_resp++;
-                               return_srv_prx_502:
-                                       buffer_shutr_done(rep);
-                                       buffer_shutw_done(req);
-                                       fd_delete(t->srv_fd);
-                                       t->srv_state = SV_STCLOSE;
-                                       rep->flags |= BF_MAY_FORWARD;
-                                       txn->status = 502;
-                                       client_return(t, error_message(t, HTTP_ERR_502));
-                                       if (!(t->flags & SN_ERR_MASK))
-                                               t->flags |= SN_ERR_PRXCOND;
-                                       if (!(t->flags & SN_FINST_MASK))
-                                               t->flags |= SN_FINST_H;
-                                       /* We used to have a free connection slot. Since we'll never use it,
-                                        * we have to inform the server that it may be used by another session.
-                                        */
-                                       if (t->srv && may_dequeue_tasks(t->srv, cur_proxy))
-                                               process_srv_queue(t->srv);
+                       if (!(t->flags & SN_ERR_MASK))
+                               t->flags |= SN_ERR_CLITO;
+                       if (!(t->flags & SN_FINST_MASK)) {
+                               if (t->analysis & AN_REQ_ANY)
+                                       t->flags |= SN_FINST_R;
+                               else if (t->pend_pos)
+                                       t->flags |= SN_FINST_Q;
+                               else if (s == SV_STCONN)
+                                       t->flags |= SN_FINST_C;
+                               else
+                                       t->flags |= SN_FINST_D;
+                       }
+                       return 1;
+               }
+
+               /* manage read timeout */
+               if (!(req->flags & BF_SHUTR_STATUS)) {
+                       if (req->l >= req->rlim - req->data) {
+                               /* no room to read more data */
+                               if (EV_FD_COND_C(t->cli_fd, DIR_RD)) {
+                                       /* stop reading until we get some space */
+                                       req->rex = TICK_ETERNITY;
+                               }
+                       } else {
+                               EV_FD_COND_S(t->cli_fd, DIR_RD);
+                               req->rex = tick_add_ifset(now_ms, t->fe->timeout.client);
+                       }
+               }
+
+               /* manage write timeout */
+               if (!(rep->flags & BF_SHUTW_STATUS)) {
+                       /* first, we may have to produce data (eg: stats).
+                        * right now, this is limited to the SHUTR state.
+                        */
+                       if (req->flags & BF_SHUTR_STATUS && t->flags & SN_SELF_GEN) {
+                               produce_content(t);
+                               if (rep->l == 0) {
+                                       buffer_shutw_done(rep);
+                                       fd_delete(t->cli_fd);
+                                       t->cli_state = CL_STCLOSE;
                                        return 1;
                                }
                        }
 
-                       /* has the response been denied ? */
-                       if (txn->flags & TX_SVDENY) {
-                               if (t->srv) {
-                                       t->srv->cur_sess--;
-                                       t->srv->failed_secu++;
-                                       sess_change_server(t, NULL);
+                       /* we don't enable client write if the buffer is empty, nor if the server has to analyze it */
+                       if ((rep->l == 0) || !(rep->flags & BF_MAY_FORWARD)) {
+                               if (EV_FD_COND_C(t->cli_fd, DIR_WR)) {
+                                       /* stop writing */
+                                       rep->wex = TICK_ETERNITY;
+                               }
+                       } else {
+                               /* buffer not empty */
+                               if (!tick_isset(rep->wex)) {
+                                       EV_FD_COND_S(t->cli_fd, DIR_WR);
+                                       /* restart writing */
+                                       rep->wex = tick_add_ifset(now_ms, t->fe->timeout.client);
+                                       if (!(req->flags & BF_SHUTR_STATUS) && tick_isset(rep->wex) && tick_isset(req->rex)) {
+                                               /* FIXME: to prevent the client from expiring read timeouts during writes,
+                                                * we refresh it, except if it was already infinite. */
+                                               req->rex = rep->wex;
+                                       }
                                }
-                               cur_proxy->denied_resp++;
-                               goto return_srv_prx_502;
                        }
+               }
+               return 0; /* other cases change nothing */
+       }
+       else { /* CL_STCLOSE: nothing to do */
+               if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) {
+                       int len;
+                       len = sprintf(trash, "%08x:%s.clicls[%04x:%04x]\n", t->uniq_id, t->be->id, (unsigned short)t->cli_fd, (unsigned short)t->srv_fd);
+                       write(1, trash, len);
+               }
+               return 0;
+       }
+       return 0;
+}
 
-                       /* We might have to check for "Connection:" */
-                       if (((t->fe->options | t->be->options) & (PR_O_HTTP_CLOSE|PR_O_FORCE_CLO)) &&
-                           !(t->flags & SN_CONN_CLOSED)) {
-                               char *cur_ptr, *cur_end, *cur_next;
-                               int cur_idx, old_idx, delta, val;
-                               struct hdr_idx_elem *cur_hdr;
 
-                               cur_next = rep->data + txn->rsp.som + hdr_idx_first_pos(&txn->hdr_idx);
-                               old_idx = 0;
+/*
+ * manages the server FSM and its socket. It returns 1 if a state has changed
+ * (and a resync may be needed), 0 else.
+ */
+int process_srv(struct session *t)
+{
+       int s = t->srv_state;
+       struct http_txn *txn = &t->txn;
+       struct buffer *req = t->req;
+       struct buffer *rep = t->rep;
+       int conn_err;
 
-                               while ((cur_idx = txn->hdr_idx.v[old_idx].next)) {
-                                       cur_hdr  = &txn->hdr_idx.v[cur_idx];
-                                       cur_ptr  = cur_next;
-                                       cur_end  = cur_ptr + cur_hdr->len;
-                                       cur_next = cur_end + cur_hdr->cr + 1;
+       DPRINTF(stderr,"[%u] process_srv: c=%s s=%s set(r,w)=%d,%d exp(r,w)=%u,%u req=%08x rep=%08x\n",
+               now_ms,
+               cli_stnames[t->cli_state], srv_stnames[t->srv_state],
+               EV_FD_ISSET(t->srv_fd, DIR_RD), EV_FD_ISSET(t->srv_fd, DIR_WR),
+               rep->rex, req->wex,
+               req->flags, rep->flags);
 
-                                       val = http_header_match2(cur_ptr, cur_end, "Connection", 10);
-                                       if (val) {
-                                               /* 3 possibilities :
-                                                * - we have already set Connection: close,
-                                                *   so we remove this line.
-                                                * - we have not yet set Connection: close,
-                                                *   but this line indicates close. We leave
-                                                *   it untouched and set the flag.
-                                                * - we have not yet set Connection: close,
-                                                *   and this line indicates non-close. We
-                                                *   replace it.
-                                                */
-                                               if (t->flags & SN_CONN_CLOSED) {
-                                                       delta = buffer_replace2(rep, cur_ptr, cur_next, NULL, 0);
-                                                       txn->rsp.eoh += delta;
-                                                       cur_next += delta;
-                                                       txn->hdr_idx.v[old_idx].next = cur_hdr->next;
-                                                       txn->hdr_idx.used--;
-                                                       cur_hdr->len = 0;
-                                               } else {
-                                                       if (strncasecmp(cur_ptr + val, "close", 5) != 0) {
-                                                               delta = buffer_replace2(rep, cur_ptr + val, cur_end,
-                                                                                       "close", 5);
-                                                               cur_next += delta;
-                                                               cur_hdr->len += delta;
-                                                               txn->rsp.eoh += delta;
-                                                       }
-                                                       t->flags |= SN_CONN_CLOSED;
-                                               }
-                                       }
-                                       old_idx = cur_idx;
-                               }
+       if (s == SV_STIDLE) {
+               if ((rep->flags & BF_SHUTW_STATUS) ||
+                        ((req->flags & BF_SHUTR_STATUS) &&
+                         (req->l == 0 || t->be->options & PR_O_ABRT_CLOSE))) { /* give up */
+                       req->cex = TICK_ETERNITY;
+                       if (t->pend_pos)
+                               t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now);
+                       /* note that this must not return any error because it would be able to
+                        * overwrite the client_retnclose() output.
+                        */
+                       if (txn->flags & TX_CLTARPIT)
+                               srv_close_with_err(t, SN_ERR_CLICL, SN_FINST_T, 0, NULL);
+                       else
+                               srv_close_with_err(t, SN_ERR_CLICL, t->pend_pos ? SN_FINST_Q : SN_FINST_C, 0, NULL);
+
+                       return 1;
+               }
+               else if (req->flags & BF_MAY_CONNECT) {
+                       /* the client allows the server to connect */
+                       if (txn->flags & TX_CLTARPIT) {
+                               /* This connection is being tarpitted. The CLIENT side has
+                                * already set the connect expiration date to the right
+                                * timeout. We just have to check that it has not expired.
+                                */
+                               if (!tick_is_expired(req->cex, now_ms))
+                                       return 0;
+
+                               /* We will set the queue timer to the time spent, just for
+                                * logging purposes. We fake a 500 server error, so that the
+                                * attacker will not suspect his connection has been tarpitted.
+                                * It will not cause trouble to the logs because we can exclude
+                                * the tarpitted connections by filtering on the 'PT' status flags.
+                                */
+                               req->cex = TICK_ETERNITY;
+                               t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now);
+                               srv_close_with_err(t, SN_ERR_PRXCOND, SN_FINST_T,
+                                                  500, error_message(t, HTTP_ERR_500));
+                               return 1;
                        }
 
-                       /* add response headers from the rule sets in the same order */
-                       for (cur_idx = 0; cur_idx < rule_set->nb_rspadd; cur_idx++) {
-                               if (unlikely(http_header_add_tail(rep, &txn->rsp, &txn->hdr_idx,
-                                                                 rule_set->rsp_add[cur_idx])) < 0)
-                                       goto return_bad_resp;
+                       /* Right now, we will need to create a connection to the server.
+                        * We might already have tried, and got a connection pending, in
+                        * which case we will not do anything till it's pending. It's up
+                        * to any other session to release it and wake us up again.
+                        */
+                       if (t->pend_pos) {
+                               if (!tick_is_expired(req->cex, now_ms)) {
+                                       return 0;
+                               } else {
+                                       /* we've been waiting too long here */
+                                       req->cex = TICK_ETERNITY;
+                                       t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now);
+                                       srv_close_with_err(t, SN_ERR_SRVTO, SN_FINST_Q,
+                                                          503, error_message(t, HTTP_ERR_503));
+                                       if (t->srv)
+                                               t->srv->failed_conns++;
+                                       t->be->failed_conns++;
+                                       return 1;
+                               }
                        }
 
-                       /* check whether we're already working on the frontend */
-                       if (cur_proxy == t->fe)
-                               break;
-                       cur_proxy = t->fe;
-               }
+                       do {
+                               /* first, get a connection */
+                               if (txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD)
+                                       t->flags |= SN_REDIRECTABLE;
 
-               /*
-                * 4: check for server cookie.
-                */
-               if (t->be->cookie_name || t->be->appsession_name || t->be->capture_name
-                   || (t->be->options & PR_O_CHK_CACHE))
-                       manage_server_side_cookies(t, rep);
+                               if (srv_redispatch_connect(t))
+                                       return t->srv_state != SV_STIDLE;
 
+                               if ((t->flags & SN_REDIRECTABLE) && t->srv && t->srv->rdr_len) {
+                                       /* Server supporting redirection and it is possible.
+                                        * Invalid requests are reported as such. It concerns all
+                                        * the largest ones.
+                                        */
+                                       struct chunk rdr;
+                                       char *path;
+                                       int len;
 
-               /*
-                * 5: check for cache-control or pragma headers if required.
-                */
-               if ((t->be->options & (PR_O_COOK_NOC | PR_O_CHK_CACHE)) != 0)
-                       check_response_for_cacheability(t, rep);
+                                       /* 1: create the response header */
+                                       rdr.len = strlen(HTTP_302);
+                                       rdr.str = trash;
+                                       memcpy(rdr.str, HTTP_302, rdr.len);
 
-               /*
-                * 6: add server cookie in the response if needed
-                */
-               if ((t->srv) && !(t->flags & SN_DIRECT) && (t->be->options & PR_O_COOK_INS) &&
-                   (!(t->be->options & PR_O_COOK_POST) || (txn->meth == HTTP_METH_POST))) {
-                       int len;
+                                       /* 2: add the server's prefix */
+                                       if (rdr.len + t->srv->rdr_len > sizeof(trash))
+                                               goto cancel_redir;
 
-                       /* the server is known, it's not the one the client requested, we have to
-                        * insert a set-cookie here, except if we want to insert only on POST
-                        * requests and this one isn't. Note that servers which don't have cookies
-                        * (eg: some backup servers) will return a full cookie removal request.
-                        */
-                       len = sprintf(trash, "Set-Cookie: %s=%s; path=/",
-                                     t->be->cookie_name,
-                                     t->srv->cookie ? t->srv->cookie : "; Expires=Thu, 01-Jan-1970 00:00:01 GMT");
+                                       memcpy(rdr.str + rdr.len, t->srv->rdr_pfx, t->srv->rdr_len);
+                                       rdr.len += t->srv->rdr_len;
+
+                                       /* 3: add the request URI */
+                                       path = http_get_path(txn);
+                                       if (!path)
+                                               goto cancel_redir;
+                                       len = txn->req.sl.rq.u_l + (txn->req.sol+txn->req.sl.rq.u) - path;
+                                       if (rdr.len + len > sizeof(trash) - 4) /* 4 for CRLF-CRLF */
+                                               goto cancel_redir;
 
-                       if (t->be->cookie_domain)
-                               len += sprintf(trash+len, "; domain=%s", t->be->cookie_domain);
+                                       memcpy(rdr.str + rdr.len, path, len);
+                                       rdr.len += len;
+                                       memcpy(rdr.str + rdr.len, "\r\n\r\n", 4);
+                                       rdr.len += 4;
 
-                       if (unlikely(http_header_add_tail2(rep, &txn->rsp, &txn->hdr_idx,
-                                                          trash, len)) < 0)
-                               goto return_bad_resp;
-                       txn->flags |= TX_SCK_INSERTED;
+                                       srv_close_with_err(t, SN_ERR_PRXCOND, SN_FINST_C, 302, &rdr);
+                                       /* FIXME: we should increase a counter of redirects per server and per backend. */
+                                       if (t->srv)
+                                               t->srv->cum_sess++;
+                                       return 1;
+                               cancel_redir:
+                                       txn->status = 400;
+                                       t->fe->failed_req++;
+                                       srv_close_with_err(t, SN_ERR_PRXCOND, SN_FINST_C,
+                                                          400, error_message(t, HTTP_ERR_400));
+                                       return 1;
+                               }
 
-                       /* Here, we will tell an eventual cache on the client side that we don't
-                        * want it to cache this reply because HTTP/1.0 caches also cache cookies !
-                        * Some caches understand the correct form: 'no-cache="set-cookie"', but
-                        * others don't (eg: apache <= 1.3.26). So we use 'private' instead.
-                        */
-                       if ((t->be->options & PR_O_COOK_NOC) && (txn->flags & TX_CACHEABLE)) {
+                               /* try to (re-)connect to the server, and fail if we expire the
+                                * number of retries.
+                                */
+                               if (srv_retryable_connect(t)) {
+                                       t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now);
+                                       return t->srv_state != SV_STIDLE;
+                               }
+                       } while (1);
+               }
+       }
+       else if (s == SV_STCONN) { /* connection in progress */
+               if ((rep->flags & BF_SHUTW_STATUS) ||
+                   ((req->flags & BF_SHUTR_STATUS) &&
+                    ((req->l == 0 && !(req->flags & BF_WRITE_STATUS)) ||
+                     t->be->options & PR_O_ABRT_CLOSE))) { /* give up */
+                       req->cex = TICK_ETERNITY;
+                       if (!(t->flags & SN_CONN_TAR)) {
+                               /* if we are in turn-around, we have already closed the FD */
+                               fd_delete(t->srv_fd);
+                               if (t->srv) {
+                                       t->srv->cur_sess--;
+                                       sess_change_server(t, NULL);
+                               }
+                       }
 
-                               txn->flags &= ~TX_CACHEABLE & ~TX_CACHE_COOK;
+                       /* note that this must not return any error because it would be able to
+                        * overwrite the client_retnclose() output.
+                        */
+                       srv_close_with_err(t, SN_ERR_CLICL, SN_FINST_C, 0, NULL);
+                       return 1;
+               }
+               if (!(req->flags & BF_WRITE_STATUS) && !tick_is_expired(req->cex, now_ms)) {
+                       return 0; /* nothing changed */
+               }
+               else if (!(req->flags & BF_WRITE_STATUS) || (req->flags & BF_WRITE_ERROR)) {
+                       /* timeout, asynchronous connect error or first write error */
+                       //fprintf(stderr,"2: c=%d, s=%d\n", c, s);
 
-                               if (unlikely(http_header_add_tail2(rep, &txn->rsp, &txn->hdr_idx,
-                                                                  "Cache-control: private", 22)) < 0)
-                                       goto return_bad_resp;
+                       if (t->flags & SN_CONN_TAR) {
+                               /* We are doing a turn-around waiting for a new connection attempt. */
+                               if (!tick_is_expired(req->cex, now_ms))
+                                       return 0;
+                               t->flags &= ~SN_CONN_TAR;
                        }
-               }
+                       else {
+                               fd_delete(t->srv_fd);
+                               if (t->srv) {
+                                       t->srv->cur_sess--;
+                                       sess_change_server(t, NULL);
+                               }
 
+                               if (!(req->flags & BF_WRITE_STATUS))
+                                       conn_err = SN_ERR_SRVTO; // it was a connect timeout.
+                               else
+                                       conn_err = SN_ERR_SRVCL; // it was an asynchronous connect error.
 
-               /*
-                * 7: check if result will be cacheable with a cookie.
-                * We'll block the response if security checks have caught
-                * nasty things such as a cacheable cookie.
-                */
-               if (((txn->flags & (TX_CACHEABLE | TX_CACHE_COOK | TX_SCK_ANY)) ==
-                    (TX_CACHEABLE | TX_CACHE_COOK | TX_SCK_ANY)) &&
-                   (t->be->options & PR_O_CHK_CACHE)) {
+                               /* ensure that we have enough retries left */
+                               if (srv_count_retry_down(t, conn_err))
+                                       return 1;
 
-                       /* we're in presence of a cacheable response containing
-                        * a set-cookie header. We'll block it as requested by
-                        * the 'checkcache' option, and send an alert.
-                        */
-                       if (t->srv) {
-                               t->srv->cur_sess--;
-                               t->srv->failed_secu++;
-                               sess_change_server(t, NULL);
+                               if (req->flags & BF_WRITE_ERROR) {
+                                       /* we encountered an immediate connection error, and we
+                                        * will have to retry connecting to the same server, most
+                                        * likely leading to the same result. To avoid this, we
+                                        * fake a connection timeout to retry after a turn-around
+                                        * time of 1 second. We will wait in the previous if block.
+                                        */
+                                       t->flags |= SN_CONN_TAR;
+                                       req->cex = tick_add(now_ms, MS_TO_TICKS(1000));
+                                       return 0;
+                               }
                        }
-                       t->be->denied_resp++;
-
-                       Alert("Blocking cacheable cookie in response from instance %s, server %s.\n",
-                             t->be->id, t->srv?t->srv->id:"<dispatch>");
-                       send_log(t->be, LOG_ALERT,
-                                "Blocking cacheable cookie in response from instance %s, server %s.\n",
-                                t->be->id, t->srv?t->srv->id:"<dispatch>");
-                       goto return_srv_prx_502;
-               }
 
-               /*
-                * 8: add "Connection: close" if needed and not yet set.
-                * Note that we do not need to add it in case of HTTP/1.0.
-                */
-               if (!(t->flags & SN_CONN_CLOSED) &&
-                   ((t->fe->options | t->be->options) & (PR_O_HTTP_CLOSE|PR_O_FORCE_CLO))) {
-                       if ((unlikely(msg->sl.st.v_l != 8) ||
-                            unlikely(req->data[msg->som + 7] != '0')) &&
-                           unlikely(http_header_add_tail2(rep, &txn->rsp, &txn->hdr_idx,
-                                                          "Connection: close", 17)) < 0)
-                               goto return_bad_resp;
-                       t->flags |= SN_CONN_CLOSED;
-               }
+                       if (t->srv && t->conn_retries == 0 && t->be->options & PR_O_REDISP) {
+                               /* We're on our last chance, and the REDISP option was specified.
+                                * We will ignore cookie and force to balance or use the dispatcher.
+                                */
+                               /* let's try to offer this slot to anybody */
+                               if (may_dequeue_tasks(t->srv, t->be))
+                                       process_srv_queue(t->srv);
 
+                               /* it's left to the dispatcher to choose a server */
+                               t->flags &= ~(SN_DIRECT | SN_ASSIGNED | SN_ADDR_SET);
+                               t->prev_srv = t->srv;
 
-               /*************************************************************
-                * OK, that's finished for the headers. We have done what we *
-                * could. Let's switch to the DATA state.                    *
-                ************************************************************/
+                               /* first, get a connection */
+                               if (srv_redispatch_connect(t))
+                                       return t->srv_state != SV_STCONN;
+                       } else {
+                               if (t->srv)
+                                       t->srv->retries++;
+                               t->be->retries++;
+                       }
 
-               t->srv_state = SV_STDATA;
-               rep->flags |= BF_MAY_FORWARD;
-               rep->rlim = rep->data + BUFSIZE; /* no more rewrite needed */
-               t->logs.t_data = tv_ms_elapsed(&t->logs.tv_accept, &now);
+                       do {
+                               /* Now we will try to either reconnect to the same server or
+                                * connect to another server. If the connection gets queued
+                                * because all servers are saturated, then we will go back to
+                                * the SV_STIDLE state.
+                                */
+                               if (srv_retryable_connect(t)) {
+                                       t->logs.t_queue = tv_ms_elapsed(&t->logs.tv_accept, &now);
+                                       return t->srv_state != SV_STCONN;
+                               }
 
-               /* client connection already closed or option 'forceclose' required :
-                * we close the server's outgoing connection right now.
-                */
-               if ((req->l == 0) &&
-                   (req->flags & BF_SHUTR_STATUS || t->be->options & PR_O_FORCE_CLO)) {
-                       EV_FD_CLR(t->srv_fd, DIR_WR);
-                       buffer_shutw_done(req);
+                               /* we need to redispatch the connection to another server */
+                               if (srv_redispatch_connect(t))
+                                       return t->srv_state != SV_STCONN;
+                       } while (1);
+               }
+               else { /* no error or write 0 */
+                       t->logs.t_connect = tv_ms_elapsed(&t->logs.tv_accept, &now);
 
-                       /* We must ensure that the read part is still alive when switching
-                        * to shutw */
-                       EV_FD_SET(t->srv_fd, DIR_RD);
-                       rep->rex = tick_add_ifset(now_ms, t->be->timeout.server);
+                       //fprintf(stderr,"3: c=%d, s=%d\n", c, s);
+                       if (req->l == 0) /* nothing to write */ {
+                               EV_FD_CLR(t->srv_fd, DIR_WR);
+                               req->wex = TICK_ETERNITY;
+                       } else  /* need the right to write */ {
+                               EV_FD_SET(t->srv_fd, DIR_WR);
+                               req->wex = tick_add_ifset(now_ms, t->be->timeout.server);
+                               if (tick_isset(req->wex)) {
+                                       /* FIXME: to prevent the server from expiring read timeouts during writes,
+                                        * we refresh it. */
+                                       rep->rex = req->wex;
+                               }
+                       }
 
-                       shutdown(t->srv_fd, SHUT_WR);
-                       t->srv_state = SV_STSHUTW;
-               }
+                       if (t->be->mode == PR_MODE_TCP) { /* let's allow immediate data connection in this case */
+                               EV_FD_SET(t->srv_fd, DIR_RD);
+                               rep->rex = tick_add_ifset(now_ms, t->be->timeout.server);
+                               t->srv_state = SV_STDATA;
+                               rep->flags |= BF_MAY_FORWARD;
+                               rep->rlim = rep->data + BUFSIZE; /* no rewrite needed */
 
+                               /* if the user wants to log as soon as possible, without counting
+                                  bytes from the server, then this is the right moment. */
+                               if (t->fe->to_log && !(t->logs.logwait & LW_BYTES)) {
+                                       t->logs.t_close = t->logs.t_connect; /* to get a valid end date */
+                                       tcp_sess_log(t);
+                               }
 #ifdef CONFIG_HAP_TCPSPLICE
-               if ((t->fe->options & t->be->options) & PR_O_TCPSPLICE) {
-                       /* TCP splicing supported by both FE and BE */
-                       tcp_splice_splicefd(t->cli_fd, t->srv_fd, 0);
-               }
+                               if ((t->fe->options & t->be->options) & PR_O_TCPSPLICE) {
+                                       /* TCP splicing supported by both FE and BE */
+                                       tcp_splice_splicefd(t->cli_fd, t->srv_fd, 0);
+                               }
 #endif
-               /* if the user wants to log as soon as possible, without counting
-                * bytes from the server, then this is the right moment. We have
-                * to temporarily assign bytes_out to log what we currently have.
-                */
-               if (t->fe->to_log && !(t->logs.logwait & LW_BYTES)) {
-                       t->logs.t_close = t->logs.t_data; /* to get a valid end date */
-                       t->logs.bytes_out = txn->rsp.eoh;
-                       if (t->fe->to_log & LW_REQ)
-                               http_sess_log(t);
-                       else
-                               tcp_sess_log(t);
-                       t->logs.bytes_out = 0;
+                       }
+                       else {
+                               t->srv_state = SV_STDATA;
+                               t->analysis |= AN_RTR_HTTP_HDR;
+                               rep->rlim = rep->data + BUFSIZE - MAXREWRITE; /* rewrite needed */
+                               t->txn.rsp.msg_state = HTTP_MSG_RPBEFORE;
+                               /* reset hdr_idx which was already initialized by the request.
+                                * right now, the http parser does it.
+                                * hdr_idx_init(&t->txn.hdr_idx);
+                                */
+                       }
+                       req->cex = TICK_ETERNITY;
+                       return 1;
                }
-
-               /* Note: we must not try to cheat by jumping directly to DATA,
-                * otherwise we would not let the client side wake up.
-                */
-
-               return 1;
        }
        else if (s == SV_STDATA) {
                /* read or write error */