From: William Lallemand Date: Thu, 28 Oct 2021 13:34:26 +0000 (+0200) Subject: MINOR: httpclient: request streaming with a callback X-Git-Tag: v2.5-dev12~24 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0da616ee18221ca7050f302df18cc12b272acd4b;p=thirdparty%2Fhaproxy.git MINOR: httpclient: request streaming with a callback This patch add a way to handle HTTP requests streaming using a callback. The end of the data must be specified by using the "end" parameter in httpclient_req_xfer(). --- diff --git a/include/haproxy/http_client-t.h b/include/haproxy/http_client-t.h index fa0f8fbdd4..6e820c42b1 100644 --- a/include/haproxy/http_client-t.h +++ b/include/haproxy/http_client-t.h @@ -7,16 +7,19 @@ struct httpclient { struct { struct ist url; /* URL of the request */ enum http_meth_t meth; /* method of the request */ - struct buffer buf; /* output buffer */ + struct buffer buf; /* output buffer, HTX */ } req; struct { struct ist vsn; uint16_t status; struct ist reason; struct http_hdr *hdrs; /* headers */ - struct buffer buf; /* input buffer */ + struct buffer buf; /* input buffer, raw HTTP */ } res; struct { + /* callbacks used to send the request, */ + void (*req_payload)(struct httpclient *hc); /* send a payload */ + /* callbacks used to receive the response, if not set, the IO * handler will consume the data without doing anything */ void (*res_stline)(struct httpclient *hc); /* start line received */ @@ -41,6 +44,7 @@ struct httpclient { /* States of the HTTP Client Appctx */ enum { HTTPCLIENT_S_REQ = 0, + HTTPCLIENT_S_REQ_BODY, HTTPCLIENT_S_RES_STLINE, HTTPCLIENT_S_RES_HDR, HTTPCLIENT_S_RES_BODY, diff --git a/include/haproxy/http_client.h b/include/haproxy/http_client.h index 13673e801b..1d99aa7d17 100644 --- a/include/haproxy/http_client.h +++ b/include/haproxy/http_client.h @@ -10,7 +10,7 @@ struct httpclient *httpclient_new(void *caller, enum http_meth_t meth, struct is struct appctx *httpclient_start(struct httpclient *hc); int httpclient_res_xfer(struct httpclient *hc, struct buffer *dst); int httpclient_req_gen(struct httpclient *hc, const struct ist url, enum http_meth_t meth, const struct http_hdr *hdrs, const struct ist payload); - +int httpclient_req_xfer(struct httpclient *hc, struct ist src, int end); /* Return the amount of data available in the httpclient response buffer */ static inline int httpclient_data(struct httpclient *hc) diff --git a/src/http_client.c b/src/http_client.c index 7756e82117..a07cf9e26f 100644 --- a/src/http_client.c +++ b/src/http_client.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -303,7 +304,10 @@ int httpclient_req_gen(struct httpclient *hc, const struct ist url, enum http_me goto error; } - htx->flags |= HTX_FL_EOM; + /* If req.payload was set, does not set the end of stream which *MUST* + * be set in the callback */ + if (!hc->ops.req_payload) + htx->flags |= HTX_FL_EOM; htx_to_buf(htx, &hc->req.buf); @@ -330,6 +334,44 @@ int httpclient_res_xfer(struct httpclient *hc, struct buffer *dst) return ret; } +/* + * Transfer raw HTTP payload from src, and insert it into HTX format in the + * httpclient. + * + * Must be used to transfer the request body. + * Then wakeup the httpclient so it can transfer it. + * + * tries to add the ending data flag if it succeed to copy all data. + * + * Return the number of bytes copied from src. + */ +int httpclient_req_xfer(struct httpclient *hc, struct ist src, int end) +{ + int ret = 0; + struct htx *htx; + + htx = htx_from_buf(&hc->req.buf); + if (!htx) + goto error; + + if (hc->appctx) + appctx_wakeup(hc->appctx); + + ret += htx_add_data(htx, src); + + + /* if we copied all the data and the end flag is set */ + if ((istlen(src) == ret) && end) { + htx->flags |= HTX_FL_EOM; + } + htx_to_buf(htx, &hc->req.buf); + +error: + + return ret; +} + + /* * Start the HTTP client * Create the appctx, session, stream and wakeup the applet @@ -532,9 +574,34 @@ static void httpclient_applet_io_handler(struct appctx *appctx) * just push this entirely */ b_xfer(&req->buf, &hc->req.buf, b_data(&hc->req.buf)); channel_add_input(req, b_data(&req->buf)); - appctx->st0 = HTTPCLIENT_S_RES_STLINE; + appctx->st0 = HTTPCLIENT_S_REQ_BODY; goto more; /* we need to leave the IO handler once we wrote the request */ break; + case HTTPCLIENT_S_REQ_BODY: + /* call the payload callback */ + { + if (hc->ops.req_payload) { + int ret; + + ret = b_xfer(&req->buf, &hc->req.buf, b_data(&hc->req.buf)); + if (ret) + channel_add_input(req, b_data(&req->buf)); + + /* call the request callback */ + hc->ops.req_payload(hc); + } + + htx = htxbuf(&req->buf); + if (!htx) + goto more; + + /* if the request contains the HTX_FL_EOM, we finished the request part. */ + if (htx->flags & HTX_FL_EOM) + appctx->st0 = HTTPCLIENT_S_RES_STLINE; + + goto more; /* we need to leave the IO handler once we wrote the request */ + } + break; case HTTPCLIENT_S_RES_STLINE: /* copy the start line in the hc structure,then remove the htx block */