diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h index ff3b59ec9..6c84f35bc 100644 --- a/include/proto/proto_http.h +++ b/include/proto/proto_http.h @@ -52,7 +52,7 @@ void http_msg_analyzer(struct http_msg *msg, struct hdr_idx *idx); void http_txn_reset_req(struct http_txn *txn); void http_txn_reset_res(struct http_txn *txn); -/* Export HTX analyzers */ +/* Export HTX analyzers and helpers */ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit); int htx_process_req_common(struct stream *s, struct channel *req, int an_bit, struct proxy *px); int htx_process_request(struct stream *s, struct channel *req, int an_bit); @@ -63,6 +63,8 @@ int htx_wait_for_response(struct stream *s, struct channel *rep, int an_bit); int htx_process_res_common(struct stream *s, struct channel *rep, int an_bit, struct proxy *px); int htx_request_forward_body(struct stream *s, struct channel *req, int an_bit); int htx_response_forward_body(struct stream *s, struct channel *res, int an_bit); +void htx_adjust_conn_mode(struct stream *s, struct http_txn *txn, struct http_msg *msg); +int htx_apply_redirect_rule(struct redirect_rule *rule, struct stream *s, struct http_txn *txn); void debug_hdr(const char *dir, struct stream *s, const char *start, const char *end); int apply_filter_to_req_headers(struct stream *s, struct channel *req, struct hdr_exp *exp); diff --git a/src/mux_h1.c b/src/mux_h1.c index 9501c5f04..e2d22a4e1 100644 --- a/src/mux_h1.c +++ b/src/mux_h1.c @@ -12,6 +12,9 @@ #include #include +#include +#include + #include #include #include @@ -42,13 +45,21 @@ #define H1C_F_CS_SHUTW_NOW 0x00002000 /* connection must be shut down for writes ASAP */ #define H1C_F_CS_SHUTW 0x00004000 /* connection is already shut down */ +#define H1C_F_WAIT_NEXT_REQ 0x00010000 /* waiting for the next request to start, use keep-alive timeout */ /* * H1 Stream flags (32 bits) */ #define H1S_F_NONE 0x00000000 #define H1S_F_ERROR 0x00000001 /* An error occurred on the H1 stream */ -#define H1S_F_MSG_XFERED 0x00000002 /* current message was transferred to the data layer */ +#define H1S_F_REQ_ERROR 0x00000002 /* An error occurred during the request parsing/xfer */ +#define H1S_F_RES_ERROR 0x00000004 /* An error occurred during the response parsing/xfer */ +#define H1S_F_MSG_XFERED 0x00000008 /* current message was transferred to the data layer */ +#define H1S_F_WANT_KAL 0x00000010 +#define H1S_F_WANT_TUN 0x00000020 +#define H1S_F_WANT_CLO 0x00000040 +#define H1S_F_WANT_MSK 0x00000070 +#define H1S_F_NOT_FIRST 0x00000080 /* The H1 stream is not the first one */ /* H1 connection descriptor */ @@ -209,7 +220,7 @@ static int h1_avail_streams(struct connection *conn) /*****************************************************************/ /* functions below are dedicated to the mux setup and management */ /*****************************************************************/ -static struct h1s *h1s_create(struct h1c *h1c) +static struct h1s *h1s_create(struct h1c *h1c, struct conn_stream *cs) { struct h1s *h1s; @@ -236,29 +247,71 @@ static struct h1s *h1s_create(struct h1c *h1c) if (!conn_is_back(h1c->conn)) { if (h1c->px->options2 & PR_O2_REQBUG_OK) h1s->req.err_pos = -1; + + if (h1c->flags & H1C_F_WAIT_NEXT_REQ) + h1s->flags |= H1S_F_NOT_FIRST; + h1c->flags &= ~H1C_F_WAIT_NEXT_REQ; + h1c->http_exp = tick_add_ifset(now_ms, h1c->px->timeout.httpreq); } else { if (h1c->px->options2 & PR_O2_RSPBUG_OK) h1s->res.err_pos = -1; } + + /* If a conn_stream already exists, attach it to this H1S */ + if (cs) { + cs->ctx = h1s; + h1s->cs = cs; + } end: return h1s; } static void h1s_destroy(struct h1s *h1s) { - struct h1c *h1c = h1s->h1c; + if (h1s) { + struct h1c *h1c = h1s->h1c; - h1c->h1s = NULL; - h1c->flags &= ~H1C_F_RX_FULL; + h1c->h1s = NULL; + h1c->flags &= ~(H1C_F_RX_FULL|H1C_F_RX_ALLOC); - if (h1s->recv_wait != NULL) - h1s->recv_wait->wait_reason &= ~SUB_CAN_RECV; - if (h1s->send_wait != NULL) - h1s->send_wait->wait_reason &= ~SUB_CAN_SEND; + if (h1s->recv_wait != NULL) + h1s->recv_wait->wait_reason &= ~SUB_CAN_RECV; + if (h1s->send_wait != NULL) + h1s->send_wait->wait_reason &= ~SUB_CAN_SEND; - h1_release_buf(h1c, &h1s->rxbuf); - pool_free(pool_head_h1s, h1s); + if (!conn_is_back(h1c->conn)) { + h1c->flags |= H1C_F_WAIT_NEXT_REQ; + h1c->http_exp = tick_add_ifset(now_ms, h1c->px->timeout.httpka); + } + + h1_release_buf(h1c, &h1s->rxbuf); + cs_free(h1s->cs); + pool_free(pool_head_h1s, h1s); + } +} + +static struct conn_stream *h1s_new_cs(struct h1s *h1s) +{ + struct conn_stream *cs; + + cs = cs_new(h1s->h1c->conn); + if (!cs) + goto err; + h1s->cs = cs; + cs->ctx = h1s; + + if (h1s->flags & H1S_F_NOT_FIRST) + cs->flags |= CS_FL_NOT_FIRST; + + if (stream_create_from_cs(cs) < 0) + goto err; + return cs; + + err: + cs_free(cs); + h1s->cs = NULL; + return NULL; } /* @@ -268,7 +321,6 @@ static void h1s_destroy(struct h1s *h1s) */ static int h1_init(struct connection *conn, struct proxy *proxy) { - struct conn_stream *cs = conn->mux_ctx; struct h1c *h1c; struct task *t = NULL; @@ -302,21 +354,9 @@ static int h1_init(struct connection *conn, struct proxy *proxy) h1c->wait_event.task->context = h1c; h1c->wait_event.wait_reason = 0; - - /* For backend mux connection, the CS already exists. In such case, - * create h1s and attached the cs to it. - */ - if (cs) { - struct h1s *h1s = cs->ctx; - - if (!h1s) { - h1s = h1s_create(h1c); - if (!h1s) - goto fail; - cs->ctx = h1s; - h1s->cs = cs; - } - } + /* Always Create a new H1S */ + if (!h1s_create(h1c, conn->mux_ctx)) + goto fail; conn->mux_ctx = h1c; task_wakeup(t, TASK_WOKEN_INIT); @@ -367,9 +407,7 @@ static void h1_release(struct connection *conn) if (h1c->wait_event.task) tasklet_free(h1c->wait_event.task); - if (h1c->h1s) - h1s_destroy(h1c->h1s); - + h1s_destroy(h1c->h1s); if (h1c->wait_event.wait_reason != 0) conn->xprt->unsubscribe(conn, h1c->wait_event.wait_reason, &h1c->wait_event); @@ -404,14 +442,242 @@ static void h1_cpy_error_message(struct h1c *h1c, struct buffer *dst, int status b_putblk(dst, b_head(err), b_data(err)); } +/* Remove all "Connection:" headers from the buffer , using the array of + * parsed headers . It returns the number of bytes removed. This should + * happen just after the headers parsing, so the buffer should not wrap. At the + * ends, all entries of reamin valid. + */ +static int h1_remove_conn_hdrs(struct h1m *h1m, struct http_hdr *hdrs, struct buffer *buf) +{ + int src, dst, delta; + + delta = 0; + for (src = 0, dst = 0; hdrs[src].n.len; src++) { + + if (hdrs[src].n.ptr >= buf->area && hdrs[src].n.ptr < buf->area + buf->size) + hdrs[src].n.ptr += delta; + hdrs[src].v.ptr += delta; + + if (!isteqi(hdrs[src].n, ist("Connection"))) { + if (src != dst) + hdrs[dst] = hdrs[src]; + dst++; + continue; + } + delta += b_rep_blk(buf, hdrs[src].n.ptr, hdrs[src+1].n.ptr+delta, NULL, 0); + } + + /* Don't forget to copy EOH */ + hdrs[src].n.ptr += delta; + hdrs[dst] = hdrs[src]; + + h1m->flags &= ~(H1_MF_CONN_KAL|H1_MF_CONN_CLO); + return delta; +} + +/* Add a "Connection:" header into the buffer . If is 0, the header + * is set to "keep-alive", otherwise it is set to "close", It returns the number + * of bytes added. This should happen just after the headers parsing, so the + * buffer should not wrap. At the ends, all entries of reamin valid. + */ +static int h1_add_conn_hdrs(struct h1m *h1m, struct http_hdr *hdrs, struct buffer *buf, + int type) +{ + const char *conn_hdr; + size_t nlen, vlen; + int i, delta; + + if (type == 0) { /* keep-alive */ + conn_hdr = "Connection: keep-alive\r\n"; + nlen = 10; vlen = 10; + } + else { /* close */ + conn_hdr = "Connection: close\r\n"; + nlen = 10; vlen = 5; + } + + /* Find EOH*/ + for (i = 0; hdrs[i].n.len; i++); + + /* Insert the "Connection: " header */ + delta = b_rep_blk(buf, hdrs[i].n.ptr, hdrs[i].n.ptr, conn_hdr, nlen+vlen+4); + + /* Update the header list */ + http_set_hdr(&hdrs[i], ist2(hdrs[i].n.ptr, nlen), ist2(hdrs[i].n.ptr+nlen+2, vlen)); + http_set_hdr(&hdrs[i+1], ist2(hdrs[i].n.ptr+delta, 0), ist("")); + + return delta; +} + +/* Deduce the connection mode of the client connection, depending on the + * configuration and the H1 message flags. This function is called twice, the + * first time when the request is parsed and the second time when the response + * is parsed. + */ +static void h1_set_cli_conn_mode(struct h1s *h1s, struct h1m *h1m) +{ + struct proxy *fe = h1s->h1c->px; + int flag = H1S_F_WANT_KAL; /* For client connection: server-close == keepalive */ + + /* Tunnel mode can only by set on the frontend */ + if ((fe->options & PR_O_HTTP_MODE) == PR_O_HTTP_TUN) + flag = H1S_F_WANT_TUN; + else if ((fe->options & PR_O_HTTP_MODE) == PR_O_HTTP_CLO) + flag = H1S_F_WANT_CLO; + + /* flags order: CLO > SCL > TUN > KAL */ + if ((h1s->flags & H1S_F_WANT_MSK) < flag) + h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | flag; + + if (h1m->flags & H1_MF_RESP) { + /* Either we've established an explicit tunnel, or we're + * switching the protocol. In both cases, we're very unlikely to + * understand the next protocols. We have to switch to tunnel + * mode, so that we transfer the request and responses then let + * this protocol pass unmodified. When we later implement + * specific parsers for such protocols, we'll want to check the + * Upgrade header which contains information about that protocol + * for responses with status 101 (eg: see RFC2817 about TLS). + */ + if ((h1s->meth == HTTP_METH_CONNECT && h1s->status == 200) || + h1s->status == 101) + h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_TUN; + else if (!(h1m->flags & H1_MF_XFER_LEN)) /* no length known => close */ + h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_CLO; + } + else { + if (h1s->flags & H1S_F_WANT_KAL && + (!(h1m->flags & (H1_MF_VER_11|H1_MF_CONN_KAL)) || /* no KA in HTTP/1.0 */ + h1m->flags & H1_MF_CONN_CLO)) /* explicit close */ + h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_CLO; + } + + /* If KAL, check if the frontend is stopping. If yes, switch in CLO mode */ + if (h1s->flags & H1S_F_WANT_KAL && fe->state == PR_STSTOPPED) + h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_CLO; +} + +/* Deduce the connection mode of the client connection, depending on the + * configuration and the H1 message flags. This function is called twice, the + * first time when the request is parsed and the second time when the response + * is parsed. + */ +static void h1_set_srv_conn_mode(struct h1s *h1s, struct h1m *h1m) +{ + struct proxy *be = h1s->h1c->px; + struct proxy *fe = strm_fe(si_strm(h1s->cs->data)); + int flag = H1S_F_WANT_KAL; + + /* Tunnel mode can only by set on the frontend */ + if ((fe->options & PR_O_HTTP_MODE) == PR_O_HTTP_TUN) + flag = H1S_F_WANT_TUN; + + /* For the server connection: server-close == httpclose */ + if ((fe->options & PR_O_HTTP_MODE) == PR_O_HTTP_SCL || + (be->options & PR_O_HTTP_MODE) == PR_O_HTTP_SCL || + (fe->options & PR_O_HTTP_MODE) == PR_O_HTTP_CLO || + (be->options & PR_O_HTTP_MODE) == PR_O_HTTP_CLO) + flag = H1S_F_WANT_CLO; + + /* flags order: CLO > SCL > TUN > KAL */ + if ((h1s->flags & H1S_F_WANT_MSK) < flag) + h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | flag; + + if (h1m->flags & H1_MF_RESP) { + /* Either we've established an explicit tunnel, or we're + * switching the protocol. In both cases, we're very unlikely to + * understand the next protocols. We have to switch to tunnel + * mode, so that we transfer the request and responses then let + * this protocol pass unmodified. When we later implement + * specific parsers for such protocols, we'll want to check the + * Upgrade header which contains information about that protocol + * for responses with status 101 (eg: see RFC2817 about TLS). + */ + if ((h1s->meth == HTTP_METH_CONNECT && h1s->status == 200) || + h1s->status == 101) + h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_TUN; + else if (!(h1m->flags & H1_MF_XFER_LEN)) /* no length known => close */ + h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_CLO; + else if (h1s->flags & H1S_F_WANT_KAL && + (!(h1m->flags & (H1_MF_VER_11|H1_MF_CONN_KAL)) || /* no KA in HTTP/1.0 */ + h1m->flags & H1_MF_CONN_CLO)) /* explicit close */ + h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_CLO; + } + + /* If KAL, check if the backend is stopping. If yes, switch in CLO mode */ + if (h1s->flags & H1S_F_WANT_KAL && be->state == PR_STSTOPPED) + h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_CLO; + + /* TODO: For now on the server-side, we disable keep-alive */ + if (h1s->flags & H1S_F_WANT_KAL) + h1s->flags = (h1s->flags & ~H1S_F_WANT_MSK) | H1S_F_WANT_CLO; +} + +static int h1_update_req_conn_hdr(struct h1s *h1s, struct h1m *h1m, + struct http_hdr *hdrs, struct buffer *buf) +{ + struct proxy *px = h1s->h1c->px; + int ret = 0; + + /* Don't update "Connection:" header in TUNNEL mode or if "Upgrage" + * token is found + */ + if (h1s->flags & H1S_F_WANT_TUN || h1m->flags & H1_MF_CONN_UPG) + goto end; + + if (h1s->flags & H1S_F_WANT_KAL || px->options2 & PR_O2_FAKE_KA) { + if (h1m->flags & H1_MF_CONN_CLO) + ret += h1_remove_conn_hdrs(h1m, hdrs, buf); + if (!(h1m->flags & (H1_MF_VER_11|H1_MF_CONN_KAL))) + ret += h1_add_conn_hdrs(h1m, hdrs, buf, 0); + } + else { /* H1S_F_WANT_CLO && !PR_O2_FAKE_KA */ + if (h1m->flags & H1_MF_CONN_KAL) + ret += h1_remove_conn_hdrs(h1m, hdrs, buf); + if ((h1m->flags & (H1_MF_VER_11|H1_MF_CONN_CLO)) == H1_MF_VER_11) + ret += h1_add_conn_hdrs(h1m, hdrs, buf, 1); + } + + end: + return ret; +} + +static int h1_update_res_conn_hdr(struct h1s *h1s, struct h1m *h1m, + struct http_hdr *hdrs, struct buffer *buf) +{ + int ret = 0; + + /* Don't update "Connection:" header in TUNNEL mode or if "Upgrage" + * token is found + */ + if (h1s->flags & H1S_F_WANT_TUN || h1m->flags & H1_MF_CONN_UPG) + goto end; + + if (h1s->flags & H1S_F_WANT_KAL) { + if (h1m->flags & H1_MF_CONN_CLO) + ret += h1_remove_conn_hdrs(h1m, hdrs, buf); + if (!(h1m->flags & (H1_MF_VER_11|H1_MF_CONN_KAL))) + ret += h1_add_conn_hdrs(h1m, hdrs, buf, 0); + } + else { /* H1S_F_WANT_CLO */ + if (h1m->flags & H1_MF_CONN_KAL) + ret += h1_remove_conn_hdrs(h1m, hdrs, buf); + if ((h1m->flags & (H1_MF_VER_11|H1_MF_CONN_CLO)) == H1_MF_VER_11) + ret += h1_add_conn_hdrs(h1m, hdrs, buf, 1); + } + + end: + return ret; +} + /* * Parse HTTP/1 headers. It returns the number of bytes parsed if > 0, or 0 if - * it couldn't proceed. Parsing errors are reported by setting H1S_F_ERROR flag - * and filling h1s->err_pos and h1s->err_state fields. This functions is + * it couldn't proceed. Parsing errors are reported by setting H1S_F_*_ERROR + * flag and filling h1s->err_pos and h1s->err_state fields. This functions is * responsibile to update the parser state . */ static size_t h1_process_headers(struct h1s *h1s, struct h1m *h1m, - struct buffer *buf, size_t ofs, size_t max) + struct buffer *buf, size_t *ofs, size_t max) { struct http_hdr hdrs[MAX_HTTP_HDR]; union h1_sl sl; @@ -421,18 +687,14 @@ static size_t h1_process_headers(struct h1s *h1s, struct h1m *h1m, if (b_head(buf) + b_data(buf) > b_wrap(buf)) b_slow_realign(buf, trash.area, 0); - ret = h1_headers_to_hdr_list(b_peek(buf, ofs), b_peek(buf, ofs) + max, + ret = h1_headers_to_hdr_list(b_peek(buf, *ofs), b_peek(buf, *ofs) + max, hdrs, sizeof(hdrs)/sizeof(hdrs[0]), h1m, &sl); if (ret <= 0) { /* Incomplete or invalid message. If the buffer is full, it's an * error because headers are too large to be handled by the * parser. */ - if (ret < 0 || (!ret && b_full(buf))) { - h1s->flags |= H1S_F_ERROR; - h1m->err_state = h1m->state; - h1m->err_pos = h1m->next; - ret = 0; - } + if (ret < 0 || (!ret && b_full(buf))) + goto error; goto end; } @@ -441,13 +703,8 @@ static size_t h1_process_headers(struct h1s *h1s, struct h1m *h1m, */ /* Be sure to keep some space to do headers rewritting */ - if (ret > (b_size(buf) - global.tune.maxrewrite)) { - h1s->flags |= H1S_F_ERROR; - h1m->err_state = h1m->state; - h1m->err_pos = h1m->next; - ret = 0; - goto end; - } + if (ret > (b_size(buf) - global.tune.maxrewrite)) + goto error; /* Save the request's method or the response's status and check if the * body length is known */ @@ -479,18 +736,36 @@ static size_t h1_process_headers(struct h1s *h1s, struct h1m *h1m, h1m->state = H1_MSG_TUNNEL; } + *ofs += ret; + if (!conn_is_back(h1s->h1c->conn)) { + h1_set_cli_conn_mode(h1s, h1m); + if (h1m->flags & H1_MF_RESP) + *ofs += h1_update_res_conn_hdr(h1s, h1m, hdrs, buf); + } + else { + h1_set_srv_conn_mode(h1s, h1m); + if (!(h1m->flags & H1_MF_RESP)) + *ofs += h1_update_req_conn_hdr(h1s, h1m, hdrs, buf); + } end: return ret; + + error: + h1s->flags |= (!(h1m->flags & H1_MF_RESP) ? H1S_F_REQ_ERROR : H1S_F_RES_ERROR); + h1m->err_state = h1m->state; + h1m->err_pos = h1m->next; + ret = 0; + goto end; } /* - * Parse HTTP/1 body. It returns the number of bytes parsed if > 0, or 0 if - * it couldn't proceed. Parsing errors are reported by setting H1S_F_ERROR flag + * Parse HTTP/1 body. It returns the number of bytes parsed if > 0, or 0 if it + * couldn't proceed. Parsing errors are reported by setting H1S_F_*_ERROR flag * and filling h1s->err_pos and h1s->err_state fields. This functions is * responsibile to update the parser state . */ static size_t h1_process_data(struct h1s *h1s, struct h1m *h1m, - struct buffer *buf, size_t ofs, size_t max) + struct buffer *buf, size_t *ofs, size_t max) { size_t total = 0; int ret = 0; @@ -502,6 +777,7 @@ static size_t h1_process_data(struct h1s *h1s, struct h1m *h1m, if ((uint64_t)ret > h1m->curr_len) ret = h1m->curr_len; h1m->curr_len -= ret; + *ofs += ret; total += ret; if (!h1m->curr_len) h1m->state = H1_MSG_DONE; @@ -510,11 +786,11 @@ static size_t h1_process_data(struct h1s *h1s, struct h1m *h1m, new_chunk: /* te:chunked : parse chunks */ if (h1m->state == H1_MSG_CHUNK_CRLF) { - ret = h1_skip_chunk_crlf(buf, ofs, ofs + max); + ret = h1_skip_chunk_crlf(buf, *ofs, *ofs + max); if (ret <= 0) goto end; max -= ret; - ofs += ret; + *ofs += ret; total += ret; h1m->state = H1_MSG_CHUNK_SIZE; } @@ -522,13 +798,13 @@ static size_t h1_process_data(struct h1s *h1s, struct h1m *h1m, if (h1m->state == H1_MSG_CHUNK_SIZE) { unsigned int chksz; - ret = h1_parse_chunk_size(buf, ofs, ofs + max, &chksz); + ret = h1_parse_chunk_size(buf, *ofs, *ofs + max, &chksz); if (ret <= 0) goto end; h1m->curr_len = chksz; h1m->body_len += chksz; max -= ret; - ofs += ret; + *ofs += ret; total += ret; h1m->state = (!chksz ? H1_MSG_TRAILERS : H1_MSG_DATA); } @@ -541,7 +817,7 @@ static size_t h1_process_data(struct h1s *h1s, struct h1m *h1m, ret = h1m->curr_len; h1m->curr_len -= ret; max -= ret; - ofs += ret; + *ofs += ret; total += ret; if (h1m->curr_len) goto end; @@ -550,11 +826,11 @@ static size_t h1_process_data(struct h1s *h1s, struct h1m *h1m, } if (h1m->state == H1_MSG_TRAILERS) { - ret = h1_measure_trailers(buf, ofs, ofs + max); + ret = h1_measure_trailers(buf, *ofs, *ofs + max); if (ret <= 0) goto end; max -= ret; - ofs += ret; + *ofs += ret; total += ret; h1m->state = H1_MSG_DONE; } @@ -568,14 +844,15 @@ static size_t h1_process_data(struct h1s *h1s, struct h1m *h1m, } else { /* no content length, read till SHUTW */ + *ofs += max; total = max; } end: if (ret < 0) { - h1s->flags |= H1S_F_ERROR; + h1s->flags |= (!(h1m->flags & H1_MF_RESP) ? H1S_F_REQ_ERROR : H1S_F_RES_ERROR); h1m->err_state = h1m->state; - h1m->err_pos = ofs + max + ret; + h1m->err_pos = *ofs + max + ret; return 0; } @@ -590,27 +867,27 @@ static size_t h1_process_data(struct h1s *h1s, struct h1m *h1m, */ static void h1_sync_messages(struct h1c *h1c) { - if (!h1c->h1s) + struct h1s *h1s = h1c->h1s; + + if (!h1s) return; - if (h1c->h1s->res.state >= H1_MSG_DONE && - (h1c->h1s->status < 200 && (h1c->h1s->status == 100 || h1c->h1s->status >= 102)) && - ((conn_is_back(h1c->conn) && !b_data(&h1c->obuf)) || !b_data(&h1c->h1s->rxbuf))) { + if (h1s->res.state == H1_MSG_DONE && + (h1s->status < 200 && (h1s->status == 100 || h1s->status >= 102)) && + ((conn_is_back(h1c->conn) && !b_data(&h1c->obuf)) || !b_data(&h1s->rxbuf))) { /* For 100-Continue response or any other informational 1xx * response which is non-final, don't reset the request, the * transaction is not finished. We take care the response was * transferred before. */ - h1m_init_res(&h1c->h1s->res); + h1m_init_res(&h1s->res); } - else if (!b_data(&h1c->h1s->rxbuf) && !b_data(&h1c->obuf) && - h1c->h1s->req.state >= H1_MSG_DONE && h1c->h1s->res.state >= H1_MSG_DONE) { - h1m_init_req(&h1c->h1s->req); - h1m_init_res(&h1c->h1s->res); - - // TODO: For now, the Keep-alive timeout is handled by the stream. - //if (h1c->task && !conn_is_back(h1c->conn)) - // h1c->http_exp = tick_add_ifset(now_ms, h1c->px->timeout.httpka); + else if (!b_data(&h1s->rxbuf) && !b_data(&h1c->obuf) && + h1s->req.state == H1_MSG_DONE && h1s->res.state == H1_MSG_DONE) { + if (h1s->flags & H1S_F_WANT_TUN) { + h1s->req.state = H1_MSG_TUNNEL; + h1s->res.state = H1_MSG_TUNNEL; + } } } @@ -621,20 +898,27 @@ static void h1_sync_messages(struct h1c *h1c) */ static size_t h1_process_input(struct h1c *h1c, struct buffer *buf, size_t count) { - struct h1s *h1s = h1c->h1s; - struct conn_stream *cs = NULL; + struct h1s *h1s = NULL; struct h1m *h1m; size_t total = 0; size_t ret = 0; + size_t max; + int errflag; if (h1c->flags & H1C_F_CS_ERROR) goto end; - if (!h1s) { - h1s = h1s_create(h1c); - if (h1s == NULL) - goto err; - } + /* Create a new H1S without CS if not already done */ + if (!h1c->h1s && !h1s_create(h1c, NULL)) + goto err; + h1s = h1c->h1s; + +#if 0 + // FIXME: Use a proxy option to enable early creation of the CS + /* Create the CS if not already attached to the H1S */ + if (!h1s->cs && !h1s_new_cs(h1s)) + goto err; +#endif if (!h1_get_buf(h1c, &h1s->rxbuf)) { h1c->flags |= H1C_F_RX_ALLOC; @@ -643,32 +927,28 @@ static size_t h1_process_input(struct h1c *h1c, struct buffer *buf, size_t count if (count > b_room(&h1s->rxbuf)) count = b_room(&h1s->rxbuf); + max = count; - h1m = (!conn_is_back(h1c->conn) ? &h1s->req : &h1s->res); - while (h1m->state < H1_MSG_DONE && count) { + if (!conn_is_back(h1c->conn)) { + h1m = &h1s->req; + errflag = H1S_F_REQ_ERROR; + } + else { + h1m = &h1s->res; + errflag = H1S_F_RES_ERROR; + } + while (!(h1s->flags & errflag) && max) { if (h1m->state <= H1_MSG_LAST_LF) { - if (h1m->state == H1_MSG_RQBEFORE) { - if (h1c->task && !conn_is_back(h1c->conn)) - if (!h1s->cs) - h1c->http_exp = tick_add_ifset(now_ms, h1c->px->timeout.httpreq); - } - ret = h1_process_headers(h1s, h1m, buf, total, count); + ret = h1_process_headers(h1s, h1m, buf, &total, max); if (!ret) break; - /* Create the CS if not already attached to the H1S */ - if (!h1s->cs) { - cs = cs_new(h1c->conn); - if (!cs) - goto err; - h1s->cs = cs; - cs->ctx = h1s; - if (stream_create_from_cs(cs) < 0) - goto err; - } + /* Reset request timeout */ + h1s->h1c->http_exp = TICK_ETERNITY; - if (h1c->task && !conn_is_back(h1c->conn)) - h1c->http_exp = TICK_ETERNITY; + /* Create the CS if not already attached to the H1S */ + if (!h1s->cs && !h1s_new_cs(h1s)) + goto err; } else if (h1m->state <= H1_MSG_TRAILERS) { /* Do not parse the body if the header part is not yet @@ -676,23 +956,26 @@ static size_t h1_process_input(struct h1c *h1c, struct buffer *buf, size_t count */ if (!(h1s->flags & H1S_F_MSG_XFERED)) break; - ret = h1_process_data(h1s, h1m, buf, total, count); + ret = h1_process_data(h1s, h1m, buf, &total, max); if (!ret) break; } + else if (h1m->state == H1_MSG_DONE) + break; + else if (h1m->state == H1_MSG_TUNNEL) { + total += max; + max = 0; + break; + } else { - h1s->flags |= H1S_F_ERROR; + h1s->flags |= errflag; break; } - total += ret; - count -= ret; - - if ((h1s->flags & H1S_F_ERROR)) - break; + max -= ret; } - if (h1s->flags & H1S_F_ERROR) { + if (h1s->flags & errflag) { /* For now, if an error occurred during the message parsing when * a stream is already attached to the mux, we transfer * everything to let the stream handle the error itself. We @@ -707,25 +990,23 @@ static size_t h1_process_input(struct h1c *h1c, struct buffer *buf, size_t count h1_cpy_error_message(h1c, &h1c->obuf, 400); goto err; } - total += count; + total += max; + max = 0; } - ret = b_xfer(&h1s->rxbuf, buf, total); + b_xfer(&h1s->rxbuf, buf, total); if (b_data(&h1s->rxbuf)) { h1s->cs->flags |= CS_FL_RCV_MORE; if (b_full(&h1s->rxbuf)) h1c->flags |= H1C_F_RX_FULL; } - + ret = count - max; end: return ret; err: - if (cs) - cs_free(cs); - if (h1s) - h1s_destroy(h1s); + h1s_destroy(h1s); h1c->flags |= H1C_F_CS_ERROR; sess_log(h1c->conn->owner); ret = 0; @@ -741,8 +1022,10 @@ static size_t h1_process_output(struct h1c *h1c, struct buffer *buf, size_t coun { struct h1s *h1s = h1c->h1s; struct h1m *h1m; + size_t max; size_t total = 0; size_t ret = 0; + int errflag; if (!h1_get_buf(h1c, &h1c->obuf)) { h1c->flags |= H1C_F_OUT_ALLOC; @@ -751,42 +1034,54 @@ static size_t h1_process_output(struct h1c *h1c, struct buffer *buf, size_t coun if (count > b_room(&h1c->obuf)) count = b_room(&h1c->obuf); - h1m = (!conn_is_back(h1c->conn) ? &h1s->res : &h1s->req); - while (h1m->state < H1_MSG_DONE && count) { + max = count; + if (!conn_is_back(h1c->conn)) { + h1m = &h1s->res; + errflag = H1S_F_RES_ERROR; + } + else { + h1m = &h1s->req; + errflag = H1S_F_REQ_ERROR; + } + while (!(h1s->flags & errflag) && max) { if (h1m->state <= H1_MSG_LAST_LF) { - ret = h1_process_headers(h1s, h1m, buf, total, count); + ret = h1_process_headers(h1s, h1m, buf, &total, max); if (!ret) { /* incomplete or invalid response, this is abnormal coming from * haproxy and may only result in a bad errorfile or bad Lua code * so that won't be fixed, raise an error now. */ - h1s->flags |= H1S_F_ERROR; + h1s->flags |= errflag; break; } } else if (h1m->state <= H1_MSG_TRAILERS) { - ret = h1_process_data(h1s, h1m, buf, total, count); + ret = h1_process_data(h1s, h1m, buf, &total, max); if (!ret) break; } + else if (h1m->state == H1_MSG_DONE) + break; + else if (h1m->state == H1_MSG_TUNNEL) { + total += max; + max = 0; + break; + } else { - h1s->flags |= H1S_F_ERROR; + h1s->flags |= errflag; break; } - total += ret; - count -= ret; - - if ((h1s->flags & H1S_F_ERROR)) - break; + max -= ret; } // TODO: Handle H1S errors - ret = b_xfer(&h1c->obuf, buf, total); + b_xfer(&h1c->obuf, buf, total); if (b_full(&h1c->obuf)) h1c->flags |= H1C_F_OUT_FULL; - end: + ret = count - max; + end: return ret; } @@ -893,7 +1188,6 @@ static int h1_send(struct h1c *h1c) if (ret > 0) { h1c->flags &= ~H1C_F_OUT_FULL; b_del(&h1c->obuf, ret); - h1_sync_messages(h1c); sent = 1; } @@ -901,6 +1195,7 @@ static int h1_send(struct h1c *h1c) /* We're done, no more to send */ if (!b_data(&h1c->obuf)) { h1_release_buf(h1c, &h1c->obuf); + h1_sync_messages(h1c); if (h1c->flags & H1C_F_CS_SHUTW_NOW) h1_shutw_conn(conn); } @@ -976,13 +1271,20 @@ static int h1_process(struct h1c * h1c) } } - if (h1c->task && !conn_is_back(conn)) { - if (!h1c->h1s || !h1c->h1s->cs) - h1c->idle_exp = tick_add_ifset(now_ms, h1c->px->timeout.client); - else - h1c->idle_exp = TICK_ETERNITY; - h1c->task->expire = tick_first(h1c->http_exp, h1c->idle_exp); + /* If there is a stream attached to the mux, let it + * handle the timeout. + */ + if (h1c->h1s && h1c->h1s->cs) + h1c->idle_exp = TICK_ETERNITY; + else { + int tout = (!conn_is_back(conn) + ? h1c->px->timeout.client + : h1c->px->timeout.server); + h1c->idle_exp = tick_add_ifset(now_ms, tout); } + h1c->task->expire = tick_first(h1c->http_exp, h1c->idle_exp); + if (tick_isset(h1c->task->expire)) + task_queue(h1c->task); return 0; } @@ -1021,27 +1323,44 @@ static struct task *h1_timeout_task(struct task *t, void *context, unsigned shor goto end; if (!expired) { - /* For now, do not handle timeout for server-side mux */ - if (!conn_is_back(h1c->conn)) - t->expire = tick_first(t->expire, tick_first(h1c->idle_exp, h1c->http_exp)); + t->expire = tick_first(t->expire, tick_first(h1c->idle_exp, h1c->http_exp)); return t; } - if (!(h1c->px->options & PR_O_IGNORE_PRB) && h1_get_buf(h1c, &h1c->obuf)) { - // TODO: do not send error if ka timeout - h1_cpy_error_message(h1c, &h1c->obuf, 408); - h1c->flags |= H1C_F_CS_ERROR; - h1c->idle_exp = TICK_ETERNITY; - h1c->http_exp = TICK_ETERNITY; - t->expire = TICK_ETERNITY; + h1c->flags |= H1C_F_CS_ERROR; + h1c->idle_exp = TICK_ETERNITY; + h1c->http_exp = TICK_ETERNITY; + t->expire = TICK_ETERNITY; + + /* Don't try send error message on the server-side */ + if (conn_is_back(h1c->conn)) + goto release; + + /* Don't send error message if no input data is pending _AND_ if null + * requests is ignored or it's not the first request. + */ + if (!b_data(&h1c->ibuf) && (h1c->px->options & PR_O_IGNORE_PRB || + h1c->flags & H1C_F_WAIT_NEXT_REQ)) + goto release; + + /* Try to allocate output buffer to store the error message. If + * allocation fails, just go away. + */ + if (!h1_get_buf(h1c, &h1c->obuf)) + goto release; + + h1_cpy_error_message(h1c, &h1c->obuf, 408); + tasklet_wakeup(h1c->wait_event.task); + sess_log(h1c->conn->owner); + return t; + + release: + if (h1c->h1s) { tasklet_wakeup(h1c->wait_event.task); - sess_log(h1c->conn->owner); return t; } - h1c->task = NULL; - if (!h1c->h1s || !h1c->h1s->cs) - h1_release(h1c->conn); + h1_release(h1c->conn); end: task_delete(t); task_free(t); @@ -1068,7 +1387,7 @@ static struct conn_stream *h1_attach(struct connection *conn) if (!cs) goto end; - h1s = h1s_create(h1c); + h1s = h1s_create(h1c, cs); if (h1s == NULL) goto end; @@ -1133,6 +1452,9 @@ static void h1_shutr(struct conn_stream *cs, enum cs_shr_mode mode) if (!h1s) return; + if ((h1s->flags & H1S_F_WANT_KAL) && !(cs->flags & (CS_FL_REOS|CS_FL_EOS))) + return; + /* NOTE: Be sure to handle abort (cf. h2_shutr) */ if (cs->flags & CS_FL_SHR) return; @@ -1153,6 +1475,11 @@ static void h1_shutw(struct conn_stream *cs, enum cs_shw_mode mode) return; h1c = h1s->h1c; + if ((h1s->flags & H1S_F_WANT_KAL) && + !(cs->flags & (CS_FL_REOS|CS_FL_EOS)) && + h1s->req.state == H1_MSG_DONE && h1s->res.state == H1_MSG_DONE) + return; + h1c->flags |= H1C_F_CS_SHUTW_NOW; if ((cs->flags & CS_FL_SHW) || b_data(&h1c->obuf)) return; diff --git a/src/proto_http.c b/src/proto_http.c index face0bfea..bd4221abe 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -790,6 +790,9 @@ void http_adjust_conn_mode(struct stream *s, struct http_txn *txn, struct http_m struct proxy *fe = strm_fe(s); int tmp = TX_CON_WANT_KAL; + if (IS_HTX_STRM(s)) + return htx_adjust_conn_mode(s, txn, msg); + if ((fe->options & PR_O_HTTP_MODE) == PR_O_HTTP_TUN || (s->be->options & PR_O_HTTP_MODE) == PR_O_HTTP_TUN) tmp = TX_CON_WANT_TUN; @@ -2563,6 +2566,9 @@ int http_apply_redirect_rule(struct redirect_rule *rule, struct stream *s, struc struct buffer *chunk; int ret = 0; + if (IS_HTX_STRM(s)) + return htx_apply_redirect_rule(rule, s, txn); + chunk = alloc_trash_chunk(); if (!chunk) goto leave; @@ -7657,8 +7663,11 @@ void http_init_txn(struct stream *s) { struct http_txn *txn = s->txn; struct proxy *fe = strm_fe(s); + struct conn_stream *cs = objt_cs(s->si[0].end); - txn->flags = 0; + txn->flags = ((cs && cs->flags & CS_FL_NOT_FIRST) + ? (TX_NOT_FIRST|TX_WAIT_NEXT_RQ) + : 0); txn->status = -1; txn->cookie_first_date = 0; diff --git a/src/proto_htx.c b/src/proto_htx.c index dbe08905f..4eb22bc74 100644 --- a/src/proto_htx.c +++ b/src/proto_htx.c @@ -30,6 +30,10 @@ #include #include + +static void htx_end_request(struct stream *s); +static void htx_end_response(struct stream *s); + /* This stream analyser waits for a complete HTTP request. It returns 1 if the * processing can continue on next analysers, or zero if it either needs more * data or wants to immediately abort the request (eg: timeout, error, ...). It @@ -132,7 +136,6 @@ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit) } } - /* * Now we quickly check if we have found a full valid request. * If not so, we check the FD and buffer states before leaving. @@ -148,7 +151,6 @@ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit) * a timeout or connection reset is not counted as an error. However * a bad request is. */ - if (unlikely(msg->msg_state < HTTP_MSG_BODY)) { /* * First, let's catch bad requests. @@ -462,9 +464,6 @@ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit) (ci_head(req)[msg->sl.rq.v + 7] >= '1')))) msg->flags |= HTTP_MSGF_VER_11; - /* "connection" has not been parsed yet */ - txn->flags &= ~(TX_HDR_CONN_PRS | TX_HDR_CONN_CLO | TX_HDR_CONN_KAL | TX_HDR_CONN_UPG); - /* if the frontend has "option http-use-proxy-header", we'll check if * we have what looks like a proxied connection instead of a connection, * and in this case set the TX_USE_PX_CONN flag to use Proxy-connection. @@ -612,9 +611,8 @@ int htx_wait_for_request(struct stream *s, struct channel *req, int an_bit) * one is non-null, or one of them is non-null and we are there for the first * time. */ - if (!(txn->flags & TX_HDR_CONN_PRS) || - ((sess->fe->options & PR_O_HTTP_MODE) != (s->be->options & PR_O_HTTP_MODE))) - http_adjust_conn_mode(s, txn, msg); + if ((sess->fe->options & PR_O_HTTP_MODE) != (s->be->options & PR_O_HTTP_MODE)) + htx_adjust_conn_mode(s, txn, msg); /* we may have to wait for the request's body */ if ((s->be->options & PR_O_WREQ_BODY) && @@ -846,7 +844,7 @@ int htx_process_req_common(struct stream *s, struct channel *req, int an_bit, st if (!ret) continue; } - if (!http_apply_redirect_rule(rule, s, txn)) + if (!htx_apply_redirect_rule(rule, s, txn)) goto return_bad_req; goto done; } @@ -1205,29 +1203,6 @@ int htx_process_request(struct stream *s, struct channel *req, int an_bit) } } - /* 11: add "Connection: close" or "Connection: keep-alive" if needed and not yet set. - * If an "Upgrade" token is found, the header is left untouched in order not to have - * to deal with some servers bugs : some of them fail an Upgrade if anything but - * "Upgrade" is present in the Connection header. - */ - if (!(txn->flags & TX_HDR_CONN_UPG) && (txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN) { - unsigned int want_flags = 0; - - if (msg->flags & HTTP_MSGF_VER_11) { - if ((txn->flags & TX_CON_WANT_MSK) >= TX_CON_WANT_SCL && - !((sess->fe->options2|s->be->options2) & PR_O2_FAKE_KA)) - want_flags |= TX_CON_CLO_SET; - } else { - if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL || - ((sess->fe->options2|s->be->options2) & PR_O2_FAKE_KA)) - want_flags |= TX_CON_KAL_SET; - } - - if (want_flags != (txn->flags & (TX_CON_CLO_SET|TX_CON_KAL_SET))) - http_change_connection_header(txn, msg, want_flags); - } - - /* If we have no server assigned yet and we're balancing on url_param * with a POST request, we may be interested in checking the body for * that parameter. This will be done in another analyser. @@ -1527,7 +1502,8 @@ int htx_request_forward_body(struct stream *s, struct channel *req, int an_bit) */ msg->err_state = msg->msg_state; msg->msg_state = HTTP_MSG_ERROR; - http_resync_states(s); + htx_end_request(s); + htx_end_response(s); return 1; } @@ -1586,8 +1562,9 @@ int htx_request_forward_body(struct stream *s, struct channel *req, int an_bit) if ((txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN) channel_dont_close(req); - http_resync_states(s); + htx_end_request(s); if (!(req->analysers & an_bit)) { + htx_end_response(s); if (unlikely(msg->msg_state == HTTP_MSG_ERROR)) { if (req->flags & CF_SHUTW) { /* request errors are most likely due to the @@ -2054,9 +2031,6 @@ int htx_wait_for_response(struct stream *s, struct channel *rep, int an_bit) ((ci_head(rep)[5] == '1') && (ci_head(rep)[7] >= '1')))) msg->flags |= HTTP_MSGF_VER_11; - /* "connection" has not been parsed yet */ - txn->flags &= ~(TX_HDR_CONN_PRS|TX_HDR_CONN_CLO|TX_HDR_CONN_KAL|TX_HDR_CONN_UPG|TX_CON_CLO_SET|TX_CON_KAL_SET); - /* transfer length unknown*/ msg->flags &= ~HTTP_MSGF_XFER_LEN; @@ -2226,7 +2200,7 @@ int htx_wait_for_response(struct stream *s, struct channel *rep, int an_bit) (txn->status >= 100 && txn->status < 200) || txn->status == 204 || txn->status == 304) { msg->flags |= HTTP_MSGF_XFER_LEN; - goto skip_content_length; + goto end; } use_close_only = 0; @@ -2275,56 +2249,6 @@ int htx_wait_for_response(struct stream *s, struct channel *rep, int an_bit) msg->body_len = msg->chunk_len = cl; } - skip_content_length: - /* Now we have to check if we need to modify the Connection header. - * This is more difficult on the response than it is on the request, - * because we can have two different HTTP versions and we don't know - * how the client will interprete a response. For instance, let's say - * that the client sends a keep-alive request in HTTP/1.0 and gets an - * HTTP/1.1 response without any header. Maybe it will bound itself to - * HTTP/1.0 because it only knows about it, and will consider the lack - * of header as a close, or maybe it knows HTTP/1.1 and can consider - * the lack of header as a keep-alive. Thus we will use two flags - * indicating how a request MAY be understood by the client. In case - * of multiple possibilities, we'll fix the header to be explicit. If - * ambiguous cases such as both close and keepalive are seen, then we - * will fall back to explicit close. Note that we won't take risks with - * HTTP/1.0 clients which may not necessarily understand keep-alive. - * See doc/internals/connection-header.txt for the complete matrix. - */ - if ((txn->status >= 200) && !(txn->flags & TX_HDR_CONN_PRS) && - (txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN) { - int to_del = 0; - - /* on unknown transfer length, we must close */ - if (!(msg->flags & HTTP_MSGF_XFER_LEN)) - txn->flags = (txn->flags & ~TX_CON_WANT_MSK) | TX_CON_WANT_CLO; - - /* now adjust header transformations depending on current state */ - if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_CLO) { - to_del |= 2; /* remove "keep-alive" on any response */ - if (!(msg->flags & HTTP_MSGF_VER_11)) - to_del |= 1; /* remove "close" for HTTP/1.0 responses */ - } - else { /* SCL / KAL */ - to_del |= 1; /* remove "close" on any response */ - if (txn->req.flags & msg->flags & HTTP_MSGF_VER_11) - to_del |= 2; /* remove "keep-alive" on pure 1.1 responses */ - } - - /* Parse and remove some headers from the connection header */ - http_parse_connection_header(txn, msg, to_del); - - /* Some keep-alive responses are converted to Server-close if - * the server wants to close. - */ - if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL) { - if ((txn->flags & TX_HDR_CONN_CLO) || - (!(txn->flags & TX_HDR_CONN_KAL) && !(msg->flags & HTTP_MSGF_VER_11))) - txn->flags = (txn->flags & ~TX_CON_WANT_MSK) | TX_CON_WANT_SCL; - } - } - end: /* we want to have the response time before we start processing it */ s->logs.t_data = tv_ms_elapsed(&s->logs.tv_accept, &now); @@ -2384,7 +2308,7 @@ int htx_process_res_common(struct stream *s, struct channel *rep, int an_bit, st if (unlikely(objt_applet(s->target) == &http_stats_applet)) { rep->analysers &= ~an_bit; rep->analyse_exp = TICK_ETERNITY; - goto skip_filters; + goto end; } /* @@ -2502,7 +2426,7 @@ int htx_process_res_common(struct stream *s, struct channel *rep, int an_bit, st /* OK that's all we can do for 1xx responses */ if (unlikely(txn->status < 200 && txn->status != 101)) - goto skip_header_mangling; + goto end; /* * Now check for a server cookie. @@ -2626,40 +2550,7 @@ int htx_process_res_common(struct stream *s, struct channel *rep, int an_bit, st goto return_srv_prx_502; } - skip_filters: - /* - * Adjust "Connection: close" or "Connection: keep-alive" if needed. - * If an "Upgrade" token is found, the header is left untouched in order - * not to have to deal with some client bugs : some of them fail an upgrade - * if anything but "Upgrade" is present in the Connection header. We don't - * want to touch any 101 response either since it's switching to another - * protocol. - */ - if ((txn->status != 101) && !(txn->flags & TX_HDR_CONN_UPG) && - (txn->flags & TX_CON_WANT_MSK) != TX_CON_WANT_TUN) { - unsigned int want_flags = 0; - - if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL || - (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL) { - /* we want a keep-alive response here. Keep-alive header - * required if either side is not 1.1. - */ - if (!(txn->req.flags & msg->flags & HTTP_MSGF_VER_11)) - want_flags |= TX_CON_KAL_SET; - } - else { /* CLO */ - /* we want a close response here. Close header required if - * the server is 1.1, regardless of the client. - */ - if (msg->flags & HTTP_MSGF_VER_11) - want_flags |= TX_CON_CLO_SET; - } - - if (want_flags != (txn->flags & (TX_CON_CLO_SET|TX_CON_KAL_SET))) - http_change_connection_header(txn, msg, want_flags); - } - - skip_header_mangling: + end: /* Always enter in the body analyzer */ rep->analysers &= ~AN_RES_FLT_XFER_DATA; rep->analysers |= AN_RES_HTTP_XFER_BODY; @@ -2726,14 +2617,14 @@ int htx_response_forward_body(struct stream *s, struct channel *res, int an_bit) return 0; if ((res->flags & (CF_READ_ERROR|CF_READ_TIMEOUT|CF_WRITE_ERROR|CF_WRITE_TIMEOUT)) || - ((res->flags & CF_SHUTW) && (res->to_forward || co_data(res))) || - !s->req.analysers) { + ((res->flags & CF_SHUTW) && (res->to_forward || co_data(res)))) { /* Output closed while we were sending data. We must abort and * wake the other side up. */ msg->err_state = msg->msg_state; msg->msg_state = HTTP_MSG_ERROR; - http_resync_states(s); + htx_end_response(s); + htx_end_request(s); return 1; } @@ -2763,13 +2654,9 @@ int htx_response_forward_body(struct stream *s, struct channel *res, int an_bit) } /* other states, DONE...TUNNEL */ - /* for keep-alive we don't want to forward closes on DONE */ - if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL || - (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL) - channel_dont_close(res); - - http_resync_states(s); + htx_end_response(s); if (!(res->analysers & an_bit)) { + htx_end_request(s); if (unlikely(msg->msg_state == HTTP_MSG_ERROR)) { if (res->flags & CF_SHUTW) { /* response errors are most likely due to the @@ -2807,20 +2694,12 @@ int htx_response_forward_body(struct stream *s, struct channel *res, int an_bit) } } - /* we need to obey the req analyser, so if it leaves, we must too */ - if (!s->req.analysers) - goto return_bad_res; - /* When TE: chunked is used, we need to get there again to parse * remaining chunks even if the server has closed, so we don't want to - * set CF_DONTCLOSE. Similarly, if keep-alive is set on the client side - * or if there are filters registered on the stream, we don't want to - * forward a close + * set CF_DONTCLOSE. Similarly, if there are filters registered on the + * stream, we don't want to forward a close */ - if ((msg->flags & HTTP_MSGF_TE_CHNK) || - HAS_DATA_FILTERS(s, res) || - (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_KAL || - (txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_SCL) + if ((msg->flags & HTTP_MSGF_TE_CHNK) || HAS_DATA_FILTERS(s, res)) channel_dont_close(res); /* We know that more data are expected, but we couldn't send more that @@ -2878,6 +2757,534 @@ int htx_response_forward_body(struct stream *s, struct channel *res, int an_bit) return 0; } +void htx_adjust_conn_mode(struct stream *s, struct http_txn *txn, struct http_msg *msg) +{ + struct proxy *fe = strm_fe(s); + int tmp = TX_CON_WANT_CLO; + + if ((fe->options & PR_O_HTTP_MODE) == PR_O_HTTP_TUN) + tmp = TX_CON_WANT_TUN; + + if ((txn->flags & TX_CON_WANT_MSK) < tmp) + txn->flags = (txn->flags & ~TX_CON_WANT_MSK) | TX_CON_WANT_CLO; +} + +/* Perform an HTTP redirect based on the information in . The function + * returns non-zero on success, or zero in case of a, irrecoverable error such + * as too large a request to build a valid response. + */ +int htx_apply_redirect_rule(struct redirect_rule *rule, struct stream *s, struct http_txn *txn) +{ + struct http_msg *req = &txn->req; + struct http_msg *res = &txn->rsp; + const char *msg_fmt; + struct buffer *chunk; + int ret = 0; + + chunk = alloc_trash_chunk(); + if (!chunk) + goto leave; + + /* build redirect message */ + switch(rule->code) { + case 308: + msg_fmt = HTTP_308; + break; + case 307: + msg_fmt = HTTP_307; + break; + case 303: + msg_fmt = HTTP_303; + break; + case 301: + msg_fmt = HTTP_301; + break; + case 302: + default: + msg_fmt = HTTP_302; + break; + } + + if (unlikely(!chunk_strcpy(chunk, msg_fmt))) + goto leave; + + switch(rule->type) { + case REDIRECT_TYPE_SCHEME: { + const char *path; + const char *host; + struct hdr_ctx ctx; + int pathlen; + int hostlen; + + host = ""; + hostlen = 0; + ctx.idx = 0; + if (http_find_header2("Host", 4, ci_head(req->chn), &txn->hdr_idx, &ctx)) { + host = ctx.line + ctx.val; + hostlen = ctx.vlen; + } + + path = http_txn_get_path(txn); + /* build message using path */ + if (path) { + pathlen = req->sl.rq.u_l + (ci_head(req->chn) + req->sl.rq.u) - path; + if (rule->flags & REDIRECT_FLAG_DROP_QS) { + int qs = 0; + while (qs < pathlen) { + if (path[qs] == '?') { + pathlen = qs; + break; + } + qs++; + } + } + } else { + path = "/"; + pathlen = 1; + } + + if (rule->rdr_str) { /* this is an old "redirect" rule */ + /* check if we can add scheme + "://" + host + path */ + if (chunk->data + rule->rdr_len + 3 + hostlen + pathlen > chunk->size - 4) + goto leave; + + /* add scheme */ + memcpy(chunk->area + chunk->data, rule->rdr_str, + rule->rdr_len); + chunk->data += rule->rdr_len; + } + else { + /* add scheme with executing log format */ + chunk->data += build_logline(s, + chunk->area + chunk->data, + chunk->size - chunk->data, + &rule->rdr_fmt); + + /* check if we can add scheme + "://" + host + path */ + if (chunk->data + 3 + hostlen + pathlen > chunk->size - 4) + goto leave; + } + /* add "://" */ + memcpy(chunk->area + chunk->data, "://", 3); + chunk->data += 3; + + /* add host */ + memcpy(chunk->area + chunk->data, host, hostlen); + chunk->data += hostlen; + + /* add path */ + memcpy(chunk->area + chunk->data, path, pathlen); + chunk->data += pathlen; + + /* append a slash at the end of the location if needed and missing */ + if (chunk->data && chunk->area[chunk->data - 1] != '/' && + (rule->flags & REDIRECT_FLAG_APPEND_SLASH)) { + if (chunk->data > chunk->size - 5) + goto leave; + chunk->area[chunk->data] = '/'; + chunk->data++; + } + + break; + } + case REDIRECT_TYPE_PREFIX: { + const char *path; + int pathlen; + + path = http_txn_get_path(txn); + /* build message using path */ + if (path) { + pathlen = req->sl.rq.u_l + (ci_head(req->chn) + req->sl.rq.u) - path; + if (rule->flags & REDIRECT_FLAG_DROP_QS) { + int qs = 0; + while (qs < pathlen) { + if (path[qs] == '?') { + pathlen = qs; + break; + } + qs++; + } + } + } else { + path = "/"; + pathlen = 1; + } + + if (rule->rdr_str) { /* this is an old "redirect" rule */ + if (chunk->data + rule->rdr_len + pathlen > chunk->size - 4) + goto leave; + + /* add prefix. Note that if prefix == "/", we don't want to + * add anything, otherwise it makes it hard for the user to + * configure a self-redirection. + */ + if (rule->rdr_len != 1 || *rule->rdr_str != '/') { + memcpy(chunk->area + chunk->data, + rule->rdr_str, rule->rdr_len); + chunk->data += rule->rdr_len; + } + } + else { + /* add prefix with executing log format */ + chunk->data += build_logline(s, + chunk->area + chunk->data, + chunk->size - chunk->data, + &rule->rdr_fmt); + + /* Check length */ + if (chunk->data + pathlen > chunk->size - 4) + goto leave; + } + + /* add path */ + memcpy(chunk->area + chunk->data, path, pathlen); + chunk->data += pathlen; + + /* append a slash at the end of the location if needed and missing */ + if (chunk->data && chunk->area[chunk->data - 1] != '/' && + (rule->flags & REDIRECT_FLAG_APPEND_SLASH)) { + if (chunk->data > chunk->size - 5) + goto leave; + chunk->area[chunk->data] = '/'; + chunk->data++; + } + + break; + } + case REDIRECT_TYPE_LOCATION: + default: + if (rule->rdr_str) { /* this is an old "redirect" rule */ + if (chunk->data + rule->rdr_len > chunk->size - 4) + goto leave; + + /* add location */ + memcpy(chunk->area + chunk->data, rule->rdr_str, + rule->rdr_len); + chunk->data += rule->rdr_len; + } + else { + /* add location with executing log format */ + chunk->data += build_logline(s, + chunk->area + chunk->data, + chunk->size - chunk->data, + &rule->rdr_fmt); + + /* Check left length */ + if (chunk->data > chunk->size - 4) + goto leave; + } + break; + } + + if (rule->cookie_len) { + memcpy(chunk->area + chunk->data, "\r\nSet-Cookie: ", 14); + chunk->data += 14; + memcpy(chunk->area + chunk->data, rule->cookie_str, + rule->cookie_len); + chunk->data += rule->cookie_len; + } + + /* add end of headers and the keep-alive/close status. */ + txn->status = rule->code; + /* let's log the request time */ + s->logs.tv_request = now; + + if (((!(req->flags & HTTP_MSGF_TE_CHNK) && !req->body_len) || (req->msg_state == HTTP_MSG_DONE))) { + /* keep-alive possible */ + if (!(req->flags & HTTP_MSGF_VER_11)) { + if (unlikely(txn->flags & TX_USE_PX_CONN)) { + memcpy(chunk->area + chunk->data, + "\r\nProxy-Connection: keep-alive", 30); + chunk->data += 30; + } else { + memcpy(chunk->area + chunk->data, + "\r\nConnection: keep-alive", 24); + chunk->data += 24; + } + } + memcpy(chunk->area + chunk->data, "\r\n\r\n", 4); + chunk->data += 4; + FLT_STRM_CB(s, flt_http_reply(s, txn->status, chunk)); + co_inject(res->chn, chunk->area, chunk->data); + /* "eat" the request */ + b_del(&req->chn->buf, req->sov); + req->next -= req->sov; + req->sov = 0; + s->req.analysers = AN_REQ_HTTP_XFER_BODY | (s->req.analysers & AN_REQ_FLT_END); + s->res.analysers = AN_RES_HTTP_XFER_BODY | (s->res.analysers & AN_RES_FLT_END); + req->msg_state = HTTP_MSG_CLOSED; + res->msg_state = HTTP_MSG_DONE; + /* Trim any possible response */ + b_set_data(&res->chn->buf, co_data(res->chn)); + res->next = res->sov = 0; + /* let the server side turn to SI_ST_CLO */ + channel_shutw_now(req->chn); + } else { + /* keep-alive not possible */ + if (unlikely(txn->flags & TX_USE_PX_CONN)) { + memcpy(chunk->area + chunk->data, + "\r\nProxy-Connection: close\r\n\r\n", 29); + chunk->data += 29; + } else { + memcpy(chunk->area + chunk->data, + "\r\nConnection: close\r\n\r\n", 23); + chunk->data += 23; + } + http_reply_and_close(s, txn->status, chunk); + req->chn->analysers &= AN_REQ_FLT_END; + } + + if (!(s->flags & SF_ERR_MASK)) + s->flags |= SF_ERR_LOCAL; + if (!(s->flags & SF_FINST_MASK)) + s->flags |= SF_FINST_R; + + ret = 1; + leave: + free_trash_chunk(chunk); + return ret; +} + +/* This function terminates the request because it was completly analyzed or + * because an error was triggered during the body forwarding. + */ +static void htx_end_request(struct stream *s) +{ + struct channel *chn = &s->req; + struct http_txn *txn = s->txn; + + DPRINTF(stderr,"[%u] %s: stream=%p states=%s,%s req->analysers=0x%08x res->analysers=0x%08x\n", + now_ms, __FUNCTION__, s, + h1_msg_state_str(txn->req.msg_state), h1_msg_state_str(txn->rsp.msg_state), + s->req.analysers, s->res.analysers); + + if (unlikely(txn->req.msg_state == HTTP_MSG_ERROR)) { + channel_abort(chn); + channel_truncate(chn); + goto end; + } + + if (unlikely(txn->req.msg_state < HTTP_MSG_DONE)) + return; + + if (txn->req.msg_state == HTTP_MSG_DONE) { + if (txn->rsp.msg_state < HTTP_MSG_DONE) { + /* The server has not finished to respond, so we + * don't want to move in order not to upset it. + */ + return; + } + + /* No need to read anymore, the request was completely parsed. + * We can shut the read side unless we want to abort_on_close, + * or we have a POST request. The issue with POST requests is + * that some browsers still send a CRLF after the request, and + * this CRLF must be read so that it does not remain in the kernel + * buffers, otherwise a close could cause an RST on some systems + * (eg: Linux). + */ + if ((!(s->be->options & PR_O_ABRT_CLOSE) || (s->si[0].flags & SI_FL_CLEAN_ABRT)) && + txn->meth != HTTP_METH_POST) + channel_dont_read(chn); + + /* if the server closes the connection, we want to immediately react + * and close the socket to save packets and syscalls. + */ + s->si[1].flags |= SI_FL_NOHALF; + + /* In any case we've finished parsing the request so we must + * disable Nagle when sending data because 1) we're not going + * to shut this side, and 2) the server is waiting for us to + * send pending data. + */ + chn->flags |= CF_NEVER_WAIT; + + /* When we get here, it means that both the request and the + * response have finished receiving. Depending on the connection + * mode, we'll have to wait for the last bytes to leave in either + * direction, and sometimes for a close to be effective. + */ + if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_TUN) { + /* Tunnel mode will not have any analyser so it needs to + * poll for reads. + */ + channel_auto_read(chn); + txn->req.msg_state = HTTP_MSG_TUNNEL; + } + else { + /* we're not expecting any new data to come for this + * transaction, so we can close it. + * However, there is an exception if the response length + * is undefined. In this case, we need to wait the close + * from the server. The response will be switched in + * TUNNEL mode until the end. + */ + if (!(txn->rsp.flags & HTTP_MSGF_XFER_LEN) && + txn->rsp.msg_state != HTTP_MSG_CLOSED) + return; + + if (!(chn->flags & (CF_SHUTW|CF_SHUTW_NOW))) { + channel_shutr_now(chn); + channel_shutw_now(chn); + } + } + + goto check_channel_flags; + } + + if (txn->req.msg_state == HTTP_MSG_CLOSING) { + http_msg_closing: + /* nothing else to forward, just waiting for the output buffer + * to be empty and for the shutw_now to take effect. + */ + if (channel_is_empty(chn)) { + txn->req.msg_state = HTTP_MSG_CLOSED; + goto http_msg_closed; + } + else if (chn->flags & CF_SHUTW) { + txn->req.err_state = txn->req.msg_state; + txn->req.msg_state = HTTP_MSG_ERROR; + goto end; + } + return; + } + + if (txn->req.msg_state == HTTP_MSG_CLOSED) { + http_msg_closed: + + /* if we don't know whether the server will close, we need to hard close */ + if (txn->rsp.flags & HTTP_MSGF_XFER_LEN) + s->si[1].flags |= SI_FL_NOLINGER; /* we want to close ASAP */ + + /* see above in MSG_DONE why we only do this in these states */ + if ((!(s->be->options & PR_O_ABRT_CLOSE) || (s->si[0].flags & SI_FL_CLEAN_ABRT))) + channel_dont_read(chn); + goto end; + } + + check_channel_flags: + /* Here, we are in HTTP_MSG_DONE or HTTP_MSG_TUNNEL */ + if (chn->flags & (CF_SHUTW|CF_SHUTW_NOW)) { + /* if we've just closed an output, let's switch */ + txn->req.msg_state = HTTP_MSG_CLOSING; + goto http_msg_closing; + } + + end: + chn->analysers &= AN_REQ_FLT_END; + if (txn->req.msg_state == HTTP_MSG_TUNNEL && HAS_REQ_DATA_FILTERS(s)) + chn->analysers |= AN_REQ_FLT_XFER_DATA; + channel_auto_close(chn); + channel_auto_read(chn); +} + + +/* This function terminates the response because it was completly analyzed or + * because an error was triggered during the body forwarding. + */ +static void htx_end_response(struct stream *s) +{ + struct channel *chn = &s->res; + struct http_txn *txn = s->txn; + + DPRINTF(stderr,"[%u] %s: stream=%p states=%s,%s req->analysers=0x%08x res->analysers=0x%08x\n", + now_ms, __FUNCTION__, s, + h1_msg_state_str(txn->req.msg_state), h1_msg_state_str(txn->rsp.msg_state), + s->req.analysers, s->res.analysers); + + if (unlikely(txn->rsp.msg_state == HTTP_MSG_ERROR)) { + channel_abort(chn); + channel_truncate(chn); + goto end; + } + + if (unlikely(txn->rsp.msg_state < HTTP_MSG_DONE)) + return; + + if (txn->rsp.msg_state == HTTP_MSG_DONE) { + /* In theory, we don't need to read anymore, but we must + * still monitor the server connection for a possible close + * while the request is being uploaded, so we don't disable + * reading. + */ + /* channel_dont_read(chn); */ + + if (txn->req.msg_state < HTTP_MSG_DONE) { + /* The client seems to still be sending data, probably + * because we got an error response during an upload. + * We have the choice of either breaking the connection + * or letting it pass through. Let's do the later. + */ + return; + } + + /* When we get here, it means that both the request and the + * response have finished receiving. Depending on the connection + * mode, we'll have to wait for the last bytes to leave in either + * direction, and sometimes for a close to be effective. + */ + if ((txn->flags & TX_CON_WANT_MSK) == TX_CON_WANT_TUN) { + channel_auto_read(chn); + chn->flags |= CF_NEVER_WAIT; + txn->rsp.msg_state = HTTP_MSG_TUNNEL; + } + else { + /* we're not expecting any new data to come for this + * transaction, so we can close it. + */ + if (!(chn->flags & (CF_SHUTW|CF_SHUTW_NOW))) { + channel_shutr_now(chn); + channel_shutw_now(chn); + } + } + goto check_channel_flags; + } + + if (txn->rsp.msg_state == HTTP_MSG_CLOSING) { + http_msg_closing: + /* nothing else to forward, just waiting for the output buffer + * to be empty and for the shutw_now to take effect. + */ + if (channel_is_empty(chn)) { + txn->rsp.msg_state = HTTP_MSG_CLOSED; + goto http_msg_closed; + } + else if (chn->flags & CF_SHUTW) { + txn->rsp.err_state = txn->rsp.msg_state; + txn->rsp.msg_state = HTTP_MSG_ERROR; + HA_ATOMIC_ADD(&s->be->be_counters.cli_aborts, 1); + if (objt_server(s->target)) + HA_ATOMIC_ADD(&objt_server(s->target)->counters.cli_aborts, 1); + goto end; + } + return; + } + + if (txn->rsp.msg_state == HTTP_MSG_CLOSED) { + http_msg_closed: + /* drop any pending data */ + channel_truncate(chn); + channel_auto_close(chn); + channel_auto_read(chn); + goto end; + } + + check_channel_flags: + /* Here, we are in HTTP_MSG_DONE or HTTP_MSG_TUNNEL */ + if (chn->flags & (CF_SHUTW|CF_SHUTW_NOW)) { + /* if we've just closed an output, let's switch */ + txn->rsp.msg_state = HTTP_MSG_CLOSING; + goto http_msg_closing; + } + + end: + chn->analysers &= AN_RES_FLT_END; + if (txn->rsp.msg_state == HTTP_MSG_TUNNEL && HAS_RSP_DATA_FILTERS(s)) + chn->analysers |= AN_RES_FLT_XFER_DATA; + channel_auto_close(chn); + channel_auto_read(chn); +} + __attribute__((constructor)) static void __htx_protocol_init(void) { diff --git a/src/stream.c b/src/stream.c index ae8318d09..515dc0d8e 100644 --- a/src/stream.c +++ b/src/stream.c @@ -666,8 +666,11 @@ static int sess_update_st_con_tcp(struct stream *s) return 1; } + /* FIXME: Add CF_WROTE_DATA because data was already move in the mux in + * h1. Without it, the SI remains in SI_ST_CON state. + */ /* we need to wait a bit more if there was no activity either */ - if (!(req->flags & CF_WRITE_ACTIVITY)) + if (!(req->flags & (CF_WROTE_DATA|CF_WRITE_ACTIVITY))) return 1; /* OK, this means that a connection succeeded. The caller will be