]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: httpclient: split the CLI from the actual httpclient API
authorWilliam Lallemand <wlallemand@haproxy.com>
Tue, 1 Jul 2025 13:03:46 +0000 (15:03 +0200)
committerWilliam Lallemand <wlallemand@haproxy.com>
Tue, 1 Jul 2025 13:46:04 +0000 (15:46 +0200)
This patch split the httpclient code to prevent confusion between the
httpclient CLI command and the actual httpclient API.

Indeed there was a confusion between the flag used internally by the
CLI command, and the actual httpclient API.

hc_cli_* functions as well as HC_C_F_* defines were moved to
httpclient_cli.c.

Makefile
include/haproxy/http_client-t.h
src/http_client.c
src/httpclient_cli.c [new file with mode: 0644]

index ba3fd26f392db8dad36b669f607d47eb3dc6d791..b874fa32fd2f3165bbed5447dfd157b824850bc4 100644 (file)
--- 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
index 350a301a45a6f694ebf438f273f4ad26a97b75e0..1b273827d9acfa7e0fb0c8c297af7daf1cad88da 100644 (file)
@@ -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 */
index 1fe14a0e5e93b620fbeb4f6fe10779ae423b445b..fd09c697c00abe21b5489e5fad85bc88ec22a863 100644 (file)
@@ -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 <ID> <method> <URI>
- */
-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 <method> <URI>               : 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 <url> and
diff --git a/src/httpclient_cli.c b/src/httpclient_cli.c
new file mode 100644 (file)
index 0000000..d81b728
--- /dev/null
@@ -0,0 +1,222 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* "httpclient" CLI command */
+
+
+#include <haproxy/cli.h>
+#include <haproxy/cfgparse.h>
+#include <haproxy/global.h>
+#include <haproxy/istbuf.h>
+#include <haproxy/h1_htx.h>
+#include <haproxy/http_client.h>
+#include <haproxy/tools.h>
+
+#include <string.h>
+
+/* --- 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 <ID> <method> <URI>
+ */
+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 <method> <URI>               : 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);
+