]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: httpclient/cli: add --htx option
authorWilliam Lallemand <wlallemand@haproxy.com>
Thu, 30 Jan 2025 16:15:36 +0000 (17:15 +0100)
committerWilliam Lallemand <wlallemand@haproxy.com>
Tue, 1 Jul 2025 14:33:38 +0000 (16:33 +0200)
Use the new HTTPCLIENT_O_RES_HTX flag when using the CLI httpclient with
--htx.

It allows to process directly the response in HTX, then the htx_dump()
function is used to display a debug output.

Example:

echo "httpclient --htx GET https://haproxy.org" | socat /tmp/haproxy.sock
 htx=0x79fd72a2e200(size=16336,data=139,used=6,wrap=NO,flags=0x00000010,extra=0,first=0,head=0,tail=5,tail_addr=139,head_addr=0,end_addr=0)
[0] type=HTX_BLK_RES_SL    - size=31     - addr=0      HTTP/2.0 301
[1] type=HTX_BLK_HDR       - size=15     - addr=31     content-length: 0
[2] type=HTX_BLK_HDR       - size=32     - addr=46     location: https://www.haproxy.org/
[3] type=HTX_BLK_HDR       - size=25     - addr=78     alt-svc: h3=":443"; ma=3600
[4] type=HTX_BLK_HDR       - size=35     - addr=103    set-cookie: served=2:TLSv1.3+TCP:IPv4
[5] type=HTX_BLK_EOH       - size=1      - addr=138    <empty>

doc/management.txt
src/httpclient_cli.c

index 133b2ea0f71dbd116e81f9c2b60d72e49957077d..9b65861326b0342468eb8c81ad9a6d8195bcb22d 100644 (file)
@@ -2309,7 +2309,7 @@ help [<command>]
   the requested one. The same help screen is also displayed for unknown
   commands.
 
-httpclient <method> <URI>
+httpclient [--htx] <method> <URI>
   Launch an HTTP client request and print the response on the CLI. Only
   supported on a CLI connection running in expert mode (see "expert-mode on").
   It's only meant for debugging. The httpclient is able to resolve a server
@@ -2318,6 +2318,9 @@ httpclient <method> <URI>
   able to resolve an host from /etc/hosts if you don't use a local dns daemon
   which can resolve those.
 
+  The --htx option allow to use the haproxy internal htx representation using
+  the htx_dump() function, mainly used for debugging.
+
 new ssl ca-file <cafile>
   Create a new empty CA file tree entry to be filled with a set of CA
   certificates and added to a crt-list. This command should be used in
index d81b728cee194a1f9e6d44394185ff4c49c3b661..1b41c9214ceaccc52e5fa6b8021dfa186614e592 100644 (file)
@@ -26,6 +26,7 @@
 struct hcli_svc_ctx {
        struct httpclient *hc;  /* the httpclient instance */
        uint flags;             /* flags from HC_CLI_F_* above */
+       uint is_htx:1;          /* is the response an htx buffer */
 };
 
 /* These are the callback used by the HTTP Client when it needs to notify new
@@ -85,7 +86,7 @@ void hc_cli_res_end_cb(struct httpclient *hc)
 
 /*
  * Parse an httpclient keyword on the cli:
- * httpclient <ID> <method> <URI>
+ * httpclient [--htx] <method> <URI>
  */
 static int hc_cli_parse(char **args, char *payload, struct appctx *appctx, void *private)
 {
@@ -96,17 +97,33 @@ static int hc_cli_parse(char **args, char *payload, struct appctx *appctx, void
        char *meth_str;
        struct ist uri;
        struct ist body = IST_NULL;
+       int cur_arg = 1;
 
        if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
                return 1;
 
-       if (!*args[1] || !*args[2]) {
+       /* look at optional keywords */
+       while (*args[cur_arg] == '-') {
+               if (strcmp(args[cur_arg], "--htx") == 0) {
+                       ctx->is_htx = 1;
+               }
+               else if (strcmp(args[cur_arg], "--") == 0) {
+                       cur_arg++;
+                       break;
+               } else {
+                       memprintf(&err, ": Unknown '%s' optional keyword", args[cur_arg]);
+                       goto err;
+               }
+               cur_arg++;
+       }
+
+       if (!*args[cur_arg] || !*args[cur_arg+1]) {
                memprintf(&err, ": not enough parameters");
                goto err;
        }
 
-       meth_str = args[1];
-       uri = ist(args[2]);
+       meth_str = args[cur_arg];
+       uri = ist(args[cur_arg+1]);
 
        if (payload)
                body = ist(payload);
@@ -127,6 +144,10 @@ static int hc_cli_parse(char **args, char *payload, struct appctx *appctx, void
        ctx->hc = hc; /* store the httpclient ptr in the applet */
        ctx->flags = 0;
 
+       /* enable the HTX mode for reception */
+       if (ctx->is_htx)
+               hc->options |= HTTPCLIENT_O_RES_HTX;
+
        if (httpclient_req_gen(hc, hc->req.url, hc->req.meth, NULL, body) != ERR_NONE)
                goto err;
 
@@ -152,10 +173,24 @@ 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;
+       struct htx *hc_htx = NULL;
+
+       if (ctx->is_htx && ctx->flags & (HC_F_RES_STLINE|HC_F_RES_HDR|HC_F_RES_BODY)) {
+               hc_htx = htxbuf(&hc->res.buf);
+
+               if (!hc_htx)
+                       goto error;
+
+               if (htx_is_empty(hc_htx))
+                       goto error;
+       }
 
        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));
+               chunk_reset(&trash);
+               if (!ctx->is_htx) {
+                       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;
@@ -163,25 +198,42 @@ static int hc_cli_io_handler(struct appctx *appctx)
 
        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))
+               if (!ctx->is_htx) {
+                       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 (!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);
+               if (!ctx->is_htx) {
+                       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;
+       }
+
+       if (ctx->is_htx && hc_htx) {
+               struct htx_blk *blk = NULL;
 
-               /* remove the flag if the buffer was emptied */
-               if (httpclient_data(hc))
+               chunk_reset(&trash);
+               htx_dump(&trash, hc_htx, 1);
+               if (applet_putchk(appctx, &trash) == -1)
                        goto more;
-               ctx->flags &= ~HC_F_RES_BODY;
+               blk = htx_get_head_blk(hc_htx);
+               while (blk)
+                       blk = htx_remove_blk(hc_htx, blk);
+               htx_to_buf(hc_htx, &hc->res.buf);
+
        }
 
        /* we must close only if F_END is the last flag */
@@ -199,6 +251,8 @@ end:
 
 too_many_hdrs:
        return cli_err(appctx, "Too many headers.\n");
+error:
+       return cli_err(appctx, "Unknown error.\n");
 }
 
 static void hc_cli_release(struct appctx *appctx)