diff --git a/Makefile b/Makefile index ba3fd26f3..b874fa32f 100644 --- a/Makefile +++ b/Makefile @@ -992,7 +992,7 @@ OBJS += src/mux_h2.o src/mux_h1.o src/mux_fcgi.o src/log.o \ src/ebsttree.o src/freq_ctr.o src/systemd.o src/init.o \ src/http_acl.o src/dict.o src/dgram.o src/pipe.o \ src/hpack-huff.o src/hpack-enc.o src/ebtree.o src/hash.o \ - src/version.o + src/httpclient_cli.o src/version.o ifneq ($(TRACE),) OBJS += src/calltrace.o diff --git a/include/haproxy/http_client-t.h b/include/haproxy/http_client-t.h index 350a301a4..1b273827d 100644 --- a/include/haproxy/http_client-t.h +++ b/include/haproxy/http_client-t.h @@ -63,12 +63,4 @@ enum { #define HTTPCLIENT_USERAGENT "HAProxy" -/* What kind of data we need to read */ -/* flags meant for the httpclient CLI API */ -#define HC_F_RES_STLINE 0x01 -#define HC_F_RES_HDR 0x02 -#define HC_F_RES_BODY 0x04 -#define HC_F_RES_END 0x08 - - #endif /* ! _HAPROXY_HTTCLIENT__T_H */ diff --git a/src/http_client.c b/src/http_client.c index 1fe14a0e5..fd09c697c 100644 --- a/src/http_client.c +++ b/src/http_client.c @@ -58,211 +58,6 @@ static int resolvers_disabled = 0; static int httpclient_retries = CONN_RETRIES; static int httpclient_timeout_connect = MS_TO_TICKS(5000); -/* --- This part of the file implement an HTTP client over the CLI --- - * The functions will be starting by "hc_cli" for "httpclient cli" - */ - -/* the CLI context for the httpclient command */ -struct hcli_svc_ctx { - struct httpclient *hc; /* the httpclient instance */ - uint flags; /* flags from HC_CLI_F_* above */ -}; - -/* These are the callback used by the HTTP Client when it needs to notify new - * data, we only sets a flag in the IO handler via the svcctx. - */ -void hc_cli_res_stline_cb(struct httpclient *hc) -{ - struct appctx *appctx = hc->caller; - struct hcli_svc_ctx *ctx; - - if (!appctx) - return; - - ctx = appctx->svcctx; - ctx->flags |= HC_F_RES_STLINE; - appctx_wakeup(appctx); -} - -void hc_cli_res_headers_cb(struct httpclient *hc) -{ - struct appctx *appctx = hc->caller; - struct hcli_svc_ctx *ctx; - - if (!appctx) - return; - - ctx = appctx->svcctx; - ctx->flags |= HC_F_RES_HDR; - appctx_wakeup(appctx); -} - -void hc_cli_res_body_cb(struct httpclient *hc) -{ - struct appctx *appctx = hc->caller; - struct hcli_svc_ctx *ctx; - - if (!appctx) - return; - - ctx = appctx->svcctx; - ctx->flags |= HC_F_RES_BODY; - appctx_wakeup(appctx); -} - -void hc_cli_res_end_cb(struct httpclient *hc) -{ - struct appctx *appctx = hc->caller; - struct hcli_svc_ctx *ctx; - - if (!appctx) - return; - - ctx = appctx->svcctx; - ctx->flags |= HC_F_RES_END; - appctx_wakeup(appctx); -} - -/* - * Parse an httpclient keyword on the cli: - * httpclient - */ -static int hc_cli_parse(char **args, char *payload, struct appctx *appctx, void *private) -{ - struct hcli_svc_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx)); - struct httpclient *hc; - char *err = NULL; - enum http_meth_t meth; - char *meth_str; - struct ist uri; - struct ist body = IST_NULL; - - if (!cli_has_level(appctx, ACCESS_LVL_ADMIN)) - return 1; - - if (!*args[1] || !*args[2]) { - memprintf(&err, ": not enough parameters"); - goto err; - } - - meth_str = args[1]; - uri = ist(args[2]); - - if (payload) - body = ist(payload); - - meth = find_http_meth(meth_str, strlen(meth_str)); - - hc = httpclient_new(appctx, meth, uri); - if (!hc) { - goto err; - } - - /* update the httpclient callbacks */ - hc->ops.res_stline = hc_cli_res_stline_cb; - hc->ops.res_headers = hc_cli_res_headers_cb; - hc->ops.res_payload = hc_cli_res_body_cb; - hc->ops.res_end = hc_cli_res_end_cb; - - ctx->hc = hc; /* store the httpclient ptr in the applet */ - ctx->flags = 0; - - if (httpclient_req_gen(hc, hc->req.url, hc->req.meth, NULL, body) != ERR_NONE) - goto err; - - - if (!httpclient_start(hc)) - goto err; - - return 0; - -err: - memprintf(&err, "Can't start the HTTP client%s.\n", err ? err : ""); - return cli_err(appctx, err); -} - -/* This function dumps the content of the httpclient receive buffer - * on the CLI output - * - * Return 1 when the processing is finished - * return 0 if it needs to be called again - */ -static int hc_cli_io_handler(struct appctx *appctx) -{ - struct hcli_svc_ctx *ctx = appctx->svcctx; - struct httpclient *hc = ctx->hc; - struct http_hdr *hdrs, *hdr; - - if (ctx->flags & HC_F_RES_STLINE) { - chunk_printf(&trash, "%.*s %d %.*s\n", (unsigned int)istlen(hc->res.vsn), istptr(hc->res.vsn), - hc->res.status, (unsigned int)istlen(hc->res.reason), istptr(hc->res.reason)); - if (applet_putchk(appctx, &trash) == -1) - goto more; - ctx->flags &= ~HC_F_RES_STLINE; - } - - if (ctx->flags & HC_F_RES_HDR) { - chunk_reset(&trash); - hdrs = hc->res.hdrs; - for (hdr = hdrs; isttest(hdr->v); hdr++) { - if (!h1_format_htx_hdr(hdr->n, hdr->v, &trash)) - goto too_many_hdrs; - } - if (!chunk_memcat(&trash, "\r\n", 2)) - goto too_many_hdrs; - if (applet_putchk(appctx, &trash) == -1) - goto more; - ctx->flags &= ~HC_F_RES_HDR; - } - - if (ctx->flags & HC_F_RES_BODY) { - httpclient_res_xfer(hc, &appctx->outbuf); - - /* remove the flag if the buffer was emptied */ - if (httpclient_data(hc)) - goto more; - ctx->flags &= ~HC_F_RES_BODY; - } - - /* we must close only if F_END is the last flag */ - if (ctx->flags == HC_F_RES_END) { - ctx->flags &= ~HC_F_RES_END; - goto end; - } - -more: - if (!ctx->flags) - applet_have_no_more_data(appctx); - return 0; -end: - return 1; - -too_many_hdrs: - return cli_err(appctx, "Too many headers.\n"); -} - -static void hc_cli_release(struct appctx *appctx) -{ - struct hcli_svc_ctx *ctx = appctx->svcctx; - struct httpclient *hc = ctx->hc; - - /* Everything possible was printed on the CLI, we can destroy the client */ - httpclient_stop_and_destroy(hc); - - return; -} - -/* register cli keywords */ -static struct cli_kw_list cli_kws = {{ },{ - { { "httpclient", NULL }, "httpclient : launch an HTTP request", hc_cli_parse, hc_cli_io_handler, hc_cli_release, NULL, ACCESS_EXPERT}, - { { NULL }, NULL, NULL, NULL } -}}; - -INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws); - - -/* --- This part of the file implements the actual HTTP client API --- */ - /* * Generate a simple request and fill the httpclient request buffer with it. * The request contains a request line generated from the absolute and diff --git a/src/httpclient_cli.c b/src/httpclient_cli.c new file mode 100644 index 000000000..d81b728ce --- /dev/null +++ b/src/httpclient_cli.c @@ -0,0 +1,222 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* "httpclient" CLI command */ + + +#include +#include +#include +#include +#include +#include +#include + +#include + +/* --- This part of the file implement an HTTP client over the CLI --- + * The functions will be starting by "hc_cli" for "httpclient cli" + */ + +#define HC_F_RES_STLINE 0x01 +#define HC_F_RES_HDR 0x02 +#define HC_F_RES_BODY 0x04 +#define HC_F_RES_END 0x08 + +/* the CLI context for the httpclient command */ +struct hcli_svc_ctx { + struct httpclient *hc; /* the httpclient instance */ + uint flags; /* flags from HC_CLI_F_* above */ +}; + +/* These are the callback used by the HTTP Client when it needs to notify new + * data, we only sets a flag in the IO handler via the svcctx. + */ +void hc_cli_res_stline_cb(struct httpclient *hc) +{ + struct appctx *appctx = hc->caller; + struct hcli_svc_ctx *ctx; + + if (!appctx) + return; + + ctx = appctx->svcctx; + ctx->flags |= HC_F_RES_STLINE; + appctx_wakeup(appctx); +} + +void hc_cli_res_headers_cb(struct httpclient *hc) +{ + struct appctx *appctx = hc->caller; + struct hcli_svc_ctx *ctx; + + if (!appctx) + return; + + ctx = appctx->svcctx; + ctx->flags |= HC_F_RES_HDR; + appctx_wakeup(appctx); +} + +void hc_cli_res_body_cb(struct httpclient *hc) +{ + struct appctx *appctx = hc->caller; + struct hcli_svc_ctx *ctx; + + if (!appctx) + return; + + ctx = appctx->svcctx; + ctx->flags |= HC_F_RES_BODY; + appctx_wakeup(appctx); +} + +void hc_cli_res_end_cb(struct httpclient *hc) +{ + struct appctx *appctx = hc->caller; + struct hcli_svc_ctx *ctx; + + if (!appctx) + return; + + ctx = appctx->svcctx; + ctx->flags |= HC_F_RES_END; + appctx_wakeup(appctx); +} + +/* + * Parse an httpclient keyword on the cli: + * httpclient + */ +static int hc_cli_parse(char **args, char *payload, struct appctx *appctx, void *private) +{ + struct hcli_svc_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx)); + struct httpclient *hc; + char *err = NULL; + enum http_meth_t meth; + char *meth_str; + struct ist uri; + struct ist body = IST_NULL; + + if (!cli_has_level(appctx, ACCESS_LVL_ADMIN)) + return 1; + + if (!*args[1] || !*args[2]) { + memprintf(&err, ": not enough parameters"); + goto err; + } + + meth_str = args[1]; + uri = ist(args[2]); + + if (payload) + body = ist(payload); + + meth = find_http_meth(meth_str, strlen(meth_str)); + + hc = httpclient_new(appctx, meth, uri); + if (!hc) { + goto err; + } + + /* update the httpclient callbacks */ + hc->ops.res_stline = hc_cli_res_stline_cb; + hc->ops.res_headers = hc_cli_res_headers_cb; + hc->ops.res_payload = hc_cli_res_body_cb; + hc->ops.res_end = hc_cli_res_end_cb; + + ctx->hc = hc; /* store the httpclient ptr in the applet */ + ctx->flags = 0; + + if (httpclient_req_gen(hc, hc->req.url, hc->req.meth, NULL, body) != ERR_NONE) + goto err; + + + if (!httpclient_start(hc)) + goto err; + + return 0; + +err: + memprintf(&err, "Can't start the HTTP client%s.\n", err ? err : ""); + return cli_err(appctx, err); +} + +/* This function dumps the content of the httpclient receive buffer + * on the CLI output + * + * Return 1 when the processing is finished + * return 0 if it needs to be called again + */ +static int hc_cli_io_handler(struct appctx *appctx) +{ + struct hcli_svc_ctx *ctx = appctx->svcctx; + struct httpclient *hc = ctx->hc; + struct http_hdr *hdrs, *hdr; + + if (ctx->flags & HC_F_RES_STLINE) { + chunk_printf(&trash, "%.*s %d %.*s\n", (unsigned int)istlen(hc->res.vsn), istptr(hc->res.vsn), + hc->res.status, (unsigned int)istlen(hc->res.reason), istptr(hc->res.reason)); + if (applet_putchk(appctx, &trash) == -1) + goto more; + ctx->flags &= ~HC_F_RES_STLINE; + } + + if (ctx->flags & HC_F_RES_HDR) { + chunk_reset(&trash); + hdrs = hc->res.hdrs; + for (hdr = hdrs; isttest(hdr->v); hdr++) { + if (!h1_format_htx_hdr(hdr->n, hdr->v, &trash)) + goto too_many_hdrs; + } + if (!chunk_memcat(&trash, "\r\n", 2)) + goto too_many_hdrs; + if (applet_putchk(appctx, &trash) == -1) + goto more; + ctx->flags &= ~HC_F_RES_HDR; + } + + if (ctx->flags & HC_F_RES_BODY) { + httpclient_res_xfer(hc, &appctx->outbuf); + + /* remove the flag if the buffer was emptied */ + if (httpclient_data(hc)) + goto more; + ctx->flags &= ~HC_F_RES_BODY; + } + + /* we must close only if F_END is the last flag */ + if (ctx->flags == HC_F_RES_END) { + ctx->flags &= ~HC_F_RES_END; + goto end; + } + +more: + if (!ctx->flags) + applet_have_no_more_data(appctx); + return 0; +end: + return 1; + +too_many_hdrs: + return cli_err(appctx, "Too many headers.\n"); +} + +static void hc_cli_release(struct appctx *appctx) +{ + struct hcli_svc_ctx *ctx = appctx->svcctx; + struct httpclient *hc = ctx->hc; + + /* Everything possible was printed on the CLI, we can destroy the client */ + httpclient_stop_and_destroy(hc); + + return; +} + +/* register cli keywords */ +static struct cli_kw_list cli_kws = {{ },{ + { { "httpclient", NULL }, "httpclient : launch an HTTP request", hc_cli_parse, hc_cli_io_handler, hc_cli_release, NULL, ACCESS_EXPERT}, + { { NULL }, NULL, NULL, NULL } +}}; + +INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws); +