From f5483bf639994663dbfbb346b7457c4b3a56b5c0 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Thu, 14 Aug 2008 18:35:40 +0200 Subject: [PATCH] [MAJOR] get rid of the SV_STHEADERS state 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 | 1 + include/types/proto_http.h | 9 +- src/proto_http.c | 1830 ++++++++++++++++++------------------ 3 files changed, 937 insertions(+), 903 deletions(-) diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h index e349f19eb0..fc1e01d38c 100644 --- a/include/proto/proto_http.h +++ b/include/proto/proto_http.h @@ -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); diff --git a/include/types/proto_http.h b/include/types/proto_http.h index d592b71dd3..441b8dcbd6 100644 --- a/include/types/proto_http.h +++ b/include/types/proto_http.h @@ -40,11 +40,10 @@ /* 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 diff --git a/src/proto_http.c b/src/proto_http.c index e323b1dba3..a027504099 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -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:""); + 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:""); + 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:""); - 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:""); - 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 */ -- 2.47.3