[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.
diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h
index e349f19..fc1e01d 100644
--- a/include/proto/proto_http.h
+++ b/include/proto/proto_http.h
@@ -62,6 +62,7 @@
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 d592b71..441b8dc 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 e323b1d..a027504 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -366,7 +366,7 @@
#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 @@
//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 @@
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;
}
-
- /*
- * 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;
+ /* 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;
+ }
- /* 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;
}
- cur_proxy->denied_resp++;
- goto return_srv_prx_502;
+ } 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;
+ }
+ }
}
+ }
+ 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 */