]> git.kaiwu.me - haproxy.git/commitdiff
MINOR: haload: add -get/-head/-post options to select the HTTP method
authorFrederic Lecaille <flecaille@haproxy.com>
Wed, 19 Aug 2026 18:36:58 +0000 (20:36 +0200)
committerFrederic Lecaille <flecaille@haproxy.com>
Mon, 24 Aug 2026 17:16:05 +0000 (19:16 +0200)
Only GET (or HEAD globally via -I) could be sent before. Add -get,
-head and -post <size> as positional options, like -h(0|1|2|2c|3), to
pick the method and, for POST, the body size, per URL.

The method and body size are stored per URL path (struct hld_path) so
distinct paths sharing the same server address keep their own
settings. The POST body is filled from a static buffer, streamed
across several output buffers for large sizes, with a matching
Content-Length header.

Remove -I, superseded by -head. Update doc/haload.txt.

doc/haload.txt
include/haproxy/haload.h
include/haproxy/hldstream-t.h
src/haload.c
src/haload_init.c

index 66dafb7c3598887b38d31566923421574516bfbe..99e8a8a394f69fd9e21305538b3c3f13c738cda4 100644 (file)
@@ -61,6 +61,9 @@ HALoad displays its usage when run without argument or wrong arguments:
             -h(0|1|2|2c|3)   use h0 (hq-interop for QUIC), h1, h2, h2c or h3
                              (QUIC/TCP) protocols (*)
             -(0|1|2|2c|3)    same as above (*)
+            -get             use GET method (default) (*)
+            -head            use HEAD method instead of GET (*)
+            -post <size>     use POST method, sending <size> bytes of body (*)
             -hs              show HTTP status codes distribution
             -hsv             show HTTP status codes distribution ((global + per HTTP version)
             -l               enable long output format; double for raw values
@@ -73,7 +76,6 @@ HALoad displays its usage when run without argument or wrong arguments:
             -w <time>        I/O timeout in milliseconds (10000)
             -C               dump the configuration and exit
             -H "foo:bar"     add this header name and value
-            -I               use HEAD instead of GET
             -R <rate>        limit to this many request attempts per second (0)
             -v               shows version
             --defaults <str> add a string to default section
@@ -107,6 +109,10 @@ Examples:
     # HTTP/3 test limiting concurrent streams to 3 per connection (-m3)
     $ ./haload -m3 quic://127.0.0.1:8889/
 
+    # POST 500 bytes to /a, then GET /b, on the same server: each path keeps
+    # its own method and body size even though they share the same address
+    $ ./haload -post 500 http://127.0.0.1:8888/a -get http://127.0.0.1:8888/b -u 10 -d 30
+
     # Dump the generated memory configuration and exit (-C)
     $ ./haload https://127.0.0.1:4443 -C
     global
index 69d7f1d3581040830ec8acdd1ea28eea19f80cad..b66c0e4f636cfd2c7de5f92fb738dc4dba14ec1b 100644 (file)
@@ -24,6 +24,9 @@ enum hld_http_ver {
 
 struct hld_path {
        char *path;
+       enum http_meth_t http_meth;
+       struct ist meth_ist;
+       int post_sz;
        struct hld_path *next;
 };
 
@@ -70,10 +73,10 @@ extern struct proxy hld_proxy;
 extern int arg_accu;
 extern int arg_dura;
 extern int arg_fast;
-extern int arg_head;
 extern int arg_hscd;
 extern int arg_long;
 extern int arg_mreqs;
+extern int arg_post_sz;
 extern int arg_rate;
 extern int arg_reqs;
 extern int arg_rcon;
index 5f743aa561ba33837b92e54462e3ada650e266c1..c593d64cf117e100f8e1968198b8bf0a7a4b4f33 100644 (file)
@@ -1,9 +1,11 @@
 #ifndef _HAPROXY_HDLSTREAM_T_H
 #define _HAPROXY_HDLSTREAM_T_H
 
+#include <import/ist.h>
 #include <haproxy/buf-t.h>
 #include <haproxy/connection-t.h>
 #include <haproxy/dynbuf-t.h>
+#include <haproxy/http-t.h>
 #include <haproxy/obj_type-t.h>
 #include <haproxy/session-t.h>
 #include <haproxy/stconn-t.h>
@@ -17,6 +19,9 @@ struct hldstream {
        struct hld_usr *usr;
        struct hld_url *url;
        const char *path;
+       enum http_meth_t http_meth;
+       struct ist meth_ist;
+       unsigned long long post_sz; /* total size of the POST body (hs->to_send starts at this value) */
        struct stconn *sc;
        struct buffer bi, bo;
        struct buffer_wait buf_wait; /* wait list for buffer allocation */
index c68eb12b6b3e12f14893cce17a7519b315900ff6..17cded45158e9a9567a6fc3af82c2aca684f745c 100644 (file)
@@ -1,6 +1,7 @@
 #include <openssl/ssl.h>
 
 #include <haproxy/api.h>
+#include <haproxy/chunk.h>
 #include <haproxy/dynbuf.h>
 #include <haproxy/errors.h>
 #include <haproxy/http.h>
@@ -17,6 +18,7 @@
 #include <haproxy/stats.h>
 #include <haproxy/stconn.h>
 #include <haproxy/stream.h>
+#include <haproxy/tools.h>
 
 /* haload stream state flags */
 #define HLD_STRM_ST_IN_ALLOC     0x0001
@@ -93,10 +95,10 @@ const char *arg_path;
 int arg_accu;          // more accurate req/time measurements in keep-alive
 int arg_dura;          // test duration in sec if non-nul
 int arg_fast;          // merge send with connect's ACK
-int arg_head;          // use HEAD
 int arg_hscd;          // HTTP status code distribution
 int arg_long;          // long output format; 2=raw values
 int arg_mreqs = 1;     // max concurrent streams by connection
+int arg_post_sz;       // number of bytes to POST
 int arg_rate;          // connection & request rate limit
 int arg_rcon = -1;     // max requests per conn
 int arg_reqs = -1;     // max total requests
@@ -114,6 +116,10 @@ unsigned int hld_ver_flags; // flags to identify the HTTP versions used
 
 char *hld_args[MAX_LINE_ARGS + 1];
 
+#define HLD_POST_DATA_SZ       16384 // bytes
+#define HLD_POST_DATA_LINE_SZ     50 // bytes
+static char hld_post_data[HLD_POST_DATA_SZ];
+
 volatile unsigned long global_req; // global (started) req counter to sync user tasks.
 
 /************ time manipulation functions ***************/
@@ -348,9 +354,9 @@ static void hld_trace(enum trace_level level, uint64_t mask, const struct trace_
        if (!hs || src->verbosity < HALOAD_VERB_CLEAN)
                return;
 
-       chunk_appendf(&trace_buf, " hs@%p conn@%p se@%p to_send=%u tot_req=%lu",
+       chunk_appendf(&trace_buf, " hs@%p conn@%p se@%p to_send=%llu tot_req=%lu",
                      hs, __sc_conn(hs->sc), __sc_endp(hs->sc),
-                     htxbuf(&hs->bo)->data, hs->url->tot_req);
+                     hs->to_send, hs->url->tot_req);
        if (hs->sc) {
                struct connection *conn = sc_conn(hs->sc);
                chunk_appendf(&trace_buf, " - conn=%p(0x%08x)", conn, conn ? conn->flags : 0);
@@ -916,16 +922,31 @@ leave:
        return t;
 }
 
-static int hldstream_build_http_req(struct hldstream *hs, struct ist path, int eom)
+/* Add up to <to_send> bytes of the POST body to <htx>, shifting the starting
+ * offset in hld_post_data by up to one line's width based on how many bytes
+ * were already sent, so consecutive chunks don't start with the exact same
+ * bytes. Mirrors hstream_add_htx_data() in haterm.c. Returns the number of
+ * bytes actually added.
+ */
+static size_t hldstream_add_htx_data(struct hldstream *hs, struct htx *htx,
+                                     unsigned long long to_send)
+{
+       size_t offset = (hs->post_sz - to_send) % HLD_POST_DATA_LINE_SZ;
+       size_t len = MIN(to_send, (unsigned long long)(sizeof(hld_post_data) - offset));
+
+       return htx_add_data(htx, ist2(hld_post_data + offset, len));
+}
+
+static int hldstream_build_http_req(struct hldstream *hs, struct ist path)
 {
        int ret = 0;
        struct buffer *buf;
        struct htx *htx;
        struct htx_sl *sl;
-       struct ist meth_ist;
        struct hld_hdr *hdr;
+       struct ist meth_ist = hs->meth_ist;
        unsigned int flags = HTX_SL_F_VER_11 | HTX_SL_F_XFER_LEN |
-               (!hs->to_send ? HTX_SL_F_BODYLESS : 0);
+               (!hs->to_send ? HTX_SL_F_BODYLESS : HTX_SL_F_CLEN);
 
        TRACE_ENTER(HLD_STRM_EV_TX, hs);
        buf = hldstream_get_obuf(hs);
@@ -936,12 +957,11 @@ static int hldstream_build_http_req(struct hldstream *hs, struct ist path, int e
        }
 
        htx = htx_from_buf(buf);
-       meth_ist = !arg_head ? ist("GET") : ist("HEAD");
        sl = htx_add_stline(htx, HTX_BLK_REQ_SL, flags, meth_ist, path, ist("HTTP/1.1"));
        if (!sl)
                goto err;
 
-       sl->info.req.meth = !arg_head ? HTTP_METH_GET : HTTP_METH_HEAD;
+       sl->info.req.meth = hs->http_meth;
        list_for_each_entry(hdr, &hld_hdrs, list)
                if (!htx_add_header(htx, hdr->name, hdr->value)) {
                        TRACE_ERROR("could not add a header", HLD_STRM_EV_TX, hs);
@@ -959,10 +979,22 @@ static int hldstream_build_http_req(struct hldstream *hs, struct ist path, int e
                goto err;
        }
 
+       if (hs->to_send > 0) {
+               char *end = ultoa_o(hs->to_send, trash.area, trash.size);
+
+               if (!end || !htx_add_header(htx, ist("Content-Length"), ist2(trash.area, end - trash.area))) {
+                       TRACE_ERROR("could not add content-length header", HLD_STRM_EV_TX, hs);
+                       goto err;
+               }
+       }
+
        if (!htx_add_endof(htx, HTX_BLK_EOH))
                goto err;
 
-       if (eom)
+       if (hs->to_send > 0)
+               hs->to_send -= hldstream_add_htx_data(hs, htx, hs->to_send);
+
+       if (!hs->to_send)
                htx->flags |= HTX_FL_EOM;
        htx_to_buf(htx, &hs->bo);
  leave:
@@ -975,6 +1007,35 @@ static int hldstream_build_http_req(struct hldstream *hs, struct ist path, int e
        goto leave;
 }
 
+/* Continue sending the POST body once the previous output buffer was fully
+ * drained but hs->to_send is still > 0. Sets HTX_FL_EOM once it reaches 0.
+ * Return 1 if succeeded, 0 if not.
+ */
+static int hldstream_build_http_body(struct hldstream *hs)
+{
+       int ret = 0;
+       struct buffer *buf;
+       struct htx *htx;
+
+       TRACE_ENTER(HLD_STRM_EV_TX, hs);
+       buf = hldstream_get_obuf(hs);
+       if (!buf) {
+               TRACE_STATE("waiting for ouput buffer", HLD_STRM_EV_TX|HLD_STRM_EV_TX_BLK, hs);
+               hs->flags |= HLD_STRM_ST_OUT_ALLOC;
+               goto leave;
+       }
+
+       htx = htx_from_buf(buf);
+       hs->to_send -= hldstream_add_htx_data(hs, htx, hs->to_send);
+       if (!hs->to_send)
+               htx->flags |= HTX_FL_EOM;
+       htx_to_buf(htx, &hs->bo);
+ leave:
+       ret = 1;
+       TRACE_LEAVE(HLD_STRM_EV_TX, hs);
+       return ret;
+}
+
 /* Send HTX data prepared for <hs> haload stream from <conn> connection */
 static int hldstream_htx_buf_snd(struct connection *conn, struct hldstream *hs)
 {
@@ -1330,11 +1391,18 @@ struct task *hld_strm_task(struct task *t, void *context, unsigned int state)
        }
 
        if (hs->flags & HLD_STRM_ST_REQ_TO_BUILD) {
-               if (!hldstream_build_http_req(hs, ist(hs->path), 1))
+               if (!hldstream_build_http_req(hs, ist(hs->path)))
                        goto out;
 
                hs->flags &= ~HLD_STRM_ST_REQ_TO_BUILD;
        }
+       else if (hs->to_send && htx_is_empty(htxbuf(&hs->bo))) {
+               /* The request headers were already sent but the whole POST body
+                * did not fit in the first buffer. Continue feeding it.
+                */
+               if (!hldstream_build_http_body(hs))
+                       goto out;
+       }
 
        if (!hldstream_htx_buf_snd(conn, hs))
                goto out;
@@ -1474,13 +1542,16 @@ static struct hldstream *hld_new_strm(struct hld_usr *usr,
        hs->usr = usr;
        hs->url = url;
        hs->path = path->path;
+       hs->http_meth = path->http_meth;
+       hs->meth_ist = path->meth_ist;
        hs->sc = sc;
        hs->bi = hs->bo = BUF_NULL;
        LIST_INIT(&hs->buf_wait.list);
        hs->task = t;
        hs->flags = conn ? HLD_STRM_ST_REQ_TO_BUILD : HLD_STRM_ST_REQ_TO_BUILD;
        hs->state = 0;
-       hs->to_send = 0;
+       hs->to_send = path->post_sz;
+       hs->post_sz = path->post_sz;
        hs->req_date = tv_unset();
        LIST_APPEND(&usr->strms, &hs->list);
        task_wakeup(t, TASK_WOKEN_INIT);
@@ -2062,3 +2133,23 @@ static int hld_init(void)
        goto leave;
 }
 REGISTER_POST_CHECK(hld_init);
+
+/* Build the POST data buffer.
+ * Always succeeds.
+ */
+static int hldstream_build_post_data(void)
+{
+       int i;
+
+       for (i = 0; i < sizeof(hld_post_data); i++) {
+               if (i % HLD_POST_DATA_LINE_SZ == HLD_POST_DATA_LINE_SZ - 1)
+                       hld_post_data[i] = '\n';
+               else if (i % 10 == 0)
+                       hld_post_data[i] = '.';
+               else
+                       hld_post_data[i] = '0' + i % 10;
+       }
+
+       return 1;
+}
+REGISTER_POST_CHECK(hldstream_build_post_data);
index 94db6f4af200557f56add893f3b1af0eb828bc38..15b2e6616694b99708dada5b71a54f4d8fb600dc 100644 (file)
@@ -12,6 +12,8 @@
 static int hld_debug;
 struct hld_url_cfg *hld_url_cfgs;
 char *srv_opts, *tls_ciphers, *tls_ciphersuites, *tls_curves, *alpn;
+enum http_meth_t http_meth = HTTP_METH_GET;
+struct ist meth_ist = IST("GET");
 int h2c;
 
 static void  hld_usage(char *name, int argc)
@@ -23,6 +25,9 @@ static void  hld_usage(char *name, int argc)
                "        -e               stop upon first connection error\n"
                "        -h(0|1|2|2c|3)   use h0 (hq-interop for QUIC), h1, h2, h2c or h3 (QUIC/TCP) protocols (*)\n"
                "        -(0|1|2|2c|3)    same as above (*)\n"
+               "        -get             use GET method (default) (*)\n"
+               "        -head            use HEAD method instead of GET (*)\n"
+               "        -post <size>     use POST method, sending <size> bytes of body (*)\n"
                "        -hs              show HTTP status codes distribution\n"
                "        -hsv             show HTTP status codes distribution ((global + per HTTP version)\n"
                "        -l               enable long output format; double for raw values\n"
@@ -37,7 +42,6 @@ static void  hld_usage(char *name, int argc)
                "        -C               dump the configuration and exit\n"
                "        -F               merge send() with connect's ACK\n"
                "        -H \"foo:bar\"   add this header name and value\n"
-               "        -I               use HEAD instead of GET\n"
                "        -R <rate>        limit to this many request attempts per second (0)\n"
                "        -v               shows version\n"
                "        --defaults <str> add a string to default section\n"
@@ -250,6 +254,9 @@ static struct hld_url_cfg *hld_alloc_url(char *url, int is_quic,
 
                        /* Append a new path to this URL */
                        p->path = path;
+                       p->http_meth = http_meth;
+                       p->meth_ist = meth_ist;
+                       p->post_sz = arg_post_sz;
                        p->next = hld_url_cfg->paths;
                        hld_url_cfg->paths = p;
 
@@ -285,6 +292,9 @@ static struct hld_url_cfg *hld_alloc_url(char *url, int is_quic,
                goto err;
 
        p->path = path;
+       p->http_meth = http_meth;
+       p->meth_ist = meth_ist;
+       p->post_sz = arg_post_sz;
        p->next = NULL;
 
        hld_url_cfg->ssl = ssl;
@@ -530,6 +540,26 @@ void haproxy_init_args(int argc, char **argv)
                                goto leave;
 #endif
                        }
+                       else if (strcmp(opt, "get") == 0) {
+                               http_meth = HTTP_METH_GET;
+                               meth_ist = ist("GET");
+                               arg_post_sz = 0;
+                       }
+                       else if (strcmp(opt, "head") == 0) {
+                               http_meth = HTTP_METH_HEAD;
+                               meth_ist = ist("HEAD");
+                               arg_post_sz = 0;
+                       }
+                       else if (strcmp(opt, "post") == 0) {
+                               argv++, argc--;
+                               if ((argc <= 0 || **argv == '-'))
+                                       hld_usage(progname, argc);
+
+                               http_meth = HTTP_METH_POST;
+                               meth_ist = ist("POST");
+                               opt = *argv;
+                               hld_parse_long(&arg_post_sz, opt, &argc, &argv);
+                       }
                        else if (*opt == 'd') {
                                opt++;
                                hld_parse_long(&arg_dura, opt, &argc, &argv);
@@ -624,13 +654,6 @@ void haproxy_init_args(int argc, char **argv)
 
                                LIST_APPEND(&hld_hdrs, &hdr->list);
                        }
-                       else if (*opt == 'I') {
-                               /* empty option */
-                               if (*(opt + 1))
-                                       hld_usage(progname, argc);
-
-                               arg_head = 1;
-                       }
                        else if (*opt == 'R') {
                                opt++;
                                hld_parse_long(&arg_rate, opt, &argc, &argv);