From 5f983a9a3b5119950ca6b102977b9d69817d64e4 Mon Sep 17 00:00:00 2001 From: Christopher Faulet Date: Mon, 12 Oct 2020 15:18:50 +0200 Subject: MEDIUM: http-ana: Deal with L7 retries in HTTP analysers The code dealing with the copy of requests in the L7-buffer and the retransmits during L7 retries has been moved in the HTTP analysers. The copy is now performed in the REQ_HTTP_XFER_BODY analyser and the L7 retries is performed in the RES_WAIT_HTTP analyser. This way, si_cs_recv() and si_cs_send() don't care of it anymore. It is much more natural to deal with L7 retry in HTTP analysers. --- src/http_ana.c | 70 +++++++++++++++++++++++++++++++++++++++----------- src/stream.c | 4 --- src/stream_interface.c | 51 ------------------------------------ 3 files changed, 55 insertions(+), 70 deletions(-) diff --git a/src/http_ana.c b/src/http_ana.c index 7a9af9be1..8683f3a76 100644 --- a/src/http_ana.c +++ b/src/http_ana.c @@ -1100,6 +1100,29 @@ int http_request_forward_body(struct stream *s, struct channel *req, int an_bit) else { msg->msg_state = HTTP_MSG_DONE; req->to_forward = 0; + + if ((s->be->retry_type &~ PR_RE_CONN_FAILED) && !(s->si[1].flags & SI_FL_D_L7_RETRY)) { + struct stream_interface *si = &s->si[1]; + + /* If we want to be able to do L7 retries, copy the + * request, so that we are able to resend them if + * needed. + * + * Try to allocate a buffer if we had none. If it + * fails, the next test will just disable the l7 + * retries. + */ + DBG_TRACE_STATE("enable L7 retry, save the request", STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn); + si->flags |= SI_FL_L7_RETRY; + if (b_is_null(&si->l7_buffer)) + b_alloc(&si->l7_buffer); + if (b_is_null(&si->l7_buffer)) + si->flags &= ~SI_FL_L7_RETRY; + else { + memcpy(b_orig(&si->l7_buffer), b_orig(&req->buf), b_size(&req->buf)); + b_add(&si->l7_buffer, co_data(req)); + } + } } done: @@ -1118,6 +1141,7 @@ int http_request_forward_body(struct stream *s, struct channel *req, int an_bit) } goto return_bad_req; } + DBG_TRACE_LEAVE(STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn); return 1; } @@ -1245,19 +1269,20 @@ int http_request_forward_body(struct stream *s, struct channel *req, int an_bit) /* Returns 0 if we can attempt to retry, -1 otherwise */ static __inline int do_l7_retry(struct stream *s, struct stream_interface *si) { - struct channel *req, *res; - int co_data; + struct channel *req = &s->req; + struct channel *res = &s->res; si->conn_retries--; if (si->conn_retries < 0) - return -1; + goto no_retry; + + if (b_is_null(&req->buf) && !channel_alloc_buffer(req, &s->buffer_wait)) + goto no_retry; if (objt_server(s->target)) _HA_ATOMIC_ADD(&__objt_server(s->target)->counters.retries, 1); _HA_ATOMIC_ADD(&s->be->be_counters.retries, 1); - req = &s->req; - res = &s->res; /* Remove any write error from the request, and read error from the response */ req->flags &= ~(CF_WRITE_ERROR | CF_WRITE_TIMEOUT | CF_SHUTW | CF_SHUTW_NOW); res->flags &= ~(CF_READ_ERROR | CF_READ_TIMEOUT | CF_SHUTR | CF_EOI | CF_READ_NULL | CF_SHUTR_NOW); @@ -1272,17 +1297,20 @@ static __inline int do_l7_retry(struct stream *s, struct stream_interface *si) res->total = 0; s->flags &= ~(SF_ERR_SRVTO | SF_ERR_SRVCL); si_release_endpoint(&s->si[1]); - b_free(&req->buf); - /* Swap the L7 buffer with the channel buffer */ - /* We know we stored the co_data as b_data, so get it there */ - co_data = b_data(&si->l7_buffer); - b_set_data(&si->l7_buffer, b_size(&si->l7_buffer)); - b_xfer(&req->buf, &si->l7_buffer, b_data(&si->l7_buffer)); - - co_set_data(req, co_data); + + b_reset(&req->buf); + memcpy(b_orig(&req->buf), b_orig(&si->l7_buffer), b_size(&si->l7_buffer)); + b_set_data(&req->buf, b_size(&req->buf)); + co_set_data(req, b_data(&si->l7_buffer)); + + DBG_TRACE_DEVEL("perfrom a L7 retry", STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, s->txn); b_reset(&res->buf); co_set_data(res, 0); return 0; + + no_retry: + b_free(&si->l7_buffer); + return -1; } /* This stream analyser waits for a complete HTTP response. It returns 1 if the @@ -1349,8 +1377,11 @@ int http_wait_for_response(struct stream *s, struct channel *rep, int an_bit) * the SI_FL_L7_RETRY flag, so it's ok not * to check s->be->retry_type. */ - if (co_data(rep) || do_l7_retry(s, si_b) == 0) + if (co_data(rep) || do_l7_retry(s, si_b) == 0) { + DBG_TRACE_DEVEL("leaving on L7 retry", + STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn); return 0; + } } if (txn->flags & TX_NOT_FIRST) @@ -1511,10 +1542,19 @@ int http_wait_for_response(struct stream *s, struct channel *rep, int an_bit) * response which at least looks like HTTP. We have an indicator * of each header's length, so we can parse them quickly. */ - msg->msg_state = HTTP_MSG_BODY; BUG_ON(htx_get_first_type(htx) != HTX_BLK_RES_SL); sl = http_get_stline(htx); + if ((si_b->flags & SI_FL_L7_RETRY) && + l7_status_match(s->be, sl->info.res.status) && + do_l7_retry(s, si_b) == 0) { + DBG_TRACE_DEVEL("leaving on L7 retry", STRM_EV_STRM_ANA|STRM_EV_HTTP_ANA, s, txn); + return 0; + } + b_free(&s->si[1].l7_buffer); + + msg->msg_state = HTTP_MSG_BODY; + /* 0: we might have to print this header in debug mode */ if (unlikely((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)))) { diff --git a/src/stream.c b/src/stream.c index 690fe3e5a..72fee73bb 100644 --- a/src/stream.c +++ b/src/stream.c @@ -2026,10 +2026,6 @@ struct task *process_stream(struct task *t, void *context, unsigned short state) */ si_b->state = SI_ST_REQ; /* new connection requested */ si_b->conn_retries = s->be->conn_retries; - if ((s->be->retry_type &~ PR_RE_CONN_FAILED) && - (s->be->mode == PR_MODE_HTTP) && - !(si_b->flags & SI_FL_D_L7_RETRY)) - si_b->flags |= SI_FL_L7_RETRY; } } else { diff --git a/src/stream_interface.c b/src/stream_interface.c index 45bcea318..5f0dade9a 100644 --- a/src/stream_interface.c +++ b/src/stream_interface.c @@ -711,35 +711,6 @@ int si_cs_send(struct conn_stream *cs) if (oc->flags & CF_STREAMER) send_flag |= CO_SFL_STREAMER; - if ((si->flags & SI_FL_L7_RETRY) && !b_data(&si->l7_buffer)) { - struct stream *s = si_strm(si); - /* If we want to be able to do L7 retries, copy - * the data we're about to send, so that we are able - * to resend them if needed - */ - /* Try to allocate a buffer if we had none. - * If it fails, the next test will just - * disable the l7 retries by setting - * l7_conn_retries to 0. - */ - if (!s->txn || (s->txn->req.msg_state != HTTP_MSG_DONE)) - si->flags &= ~SI_FL_L7_RETRY; - else { - if (b_is_null(&si->l7_buffer)) - b_alloc(&si->l7_buffer); - if (b_is_null(&si->l7_buffer)) - si->flags &= ~SI_FL_L7_RETRY; - else { - memcpy(b_orig(&si->l7_buffer), - b_orig(&oc->buf), - b_size(&oc->buf)); - si->l7_buffer.head = co_data(oc); - b_add(&si->l7_buffer, co_data(oc)); - } - - } - } - ret = cs->conn->mux->snd_buf(cs, &oc->buf, co_data(oc), send_flag); if (ret > 0) { did_send = 1; @@ -1357,28 +1328,6 @@ int si_cs_recv(struct conn_stream *cs) break; } - /* L7 retries enabled and maximum connection retries not reached */ - if ((si->flags & SI_FL_L7_RETRY) && si->conn_retries) { - struct htx *htx; - struct htx_sl *sl; - - htx = htxbuf(&ic->buf); - if (htx) { - sl = http_get_stline(htx); - if (sl && l7_status_match(si_strm(si)->be, - sl->info.res.status)) { - /* If we got a status for which we would - * like to retry the request, empty - * the buffer and pretend there's an - * error on the channel. - */ - ic->flags |= CF_READ_ERROR; - htx_reset(htx); - return 1; - } - } - si->flags &= ~SI_FL_L7_RETRY; - } cur_read += ret; /* if we're allowed to directly forward data, we must update ->o */ -- cgit v1.2.1