]> git.kaiwu.me - haproxy.git/commitdiff
MINOR: haload: add FastCGI support
authorFrederic Lecaille <flecaille@haproxy.com>
Mon, 31 Aug 2026 09:25:16 +0000 (11:25 +0200)
committerFrederic Lecaille <flecaille@haproxy.com>
Tue, 1 Sep 2026 15:15:08 +0000 (17:15 +0200)
Add -fcgi (protocol selector) and --fcgi-app <str> (raw passthrough
into a generated "fcgi-app haload" section, mainly for "docroot").

The FCGI filter is attached from hld_cfg_finalize() (post-check),
not hld_pre_check(): servers don't exist yet at pre-check time, and
HAProxy's own postparser then wrongly reports no FCGI server found.

"set-param"/"pass-header" have no effect (no struct stream to run
them); use -H ":fcgi-<param>:<value>" instead.

doc/haload.txt
include/haproxy/haload.h
src/haload.c
src/haload_init.c

index e875cf6a77c8775b48614647d2de07dedd12ea01..98b36e72123762001aa5047a4d814d32ccf9e68b 100644 (file)
@@ -65,6 +65,7 @@ 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 (*)
+            -fcgi            use FastCGI protocol (*)
             -get             use GET method (default) (*)
             -head            use HEAD method instead of GET (*)
             -post <size>     use POST method, sending <size> bytes of body (*)
@@ -88,7 +89,12 @@ HALoad displays its usage when run without argument or wrong arguments:
             --global <str>   add a string to global section
             --server <opts>  set server <opt> options as defined for "server"
                              haproxy keyword
-            --traces         enable the traces for all the HTTP protocols
+            --fcgi-app <str> add a string to the generated "fcgi-app haload"
+                             section (docroot, index...). Note: "set-param" and
+                             "pass-header" rules have no effect, since HALoad
+                             does not use HAProxy streams to run them; use "-H"
+                             to set FCGI params directly (e.g. -H ":fcgi-script_filename:...")
+            --traces         enable the traces for all the protocols
     SSL options:
             --tls-ciphers <ciphers>      for TLS1.2 and below (*)
             --tls-ciphersuites <ciphers> for TLS1.3 and above (*)
@@ -145,6 +151,9 @@ Examples:
     defaults
             timeout connect 5s
 
+    # FastCGI target, with a docroot for the generated "fcgi-app" section
+    $ ./haload --fcgi-app "docroot /var/www" -fcgi -u 10 -d 30 http://127.0.0.1:9000/index.php
+
 
 4. Limitations
 --------------
@@ -158,3 +167,11 @@ For example, when targeting a single URL, specifying "-u 10 -m 5" might lead
 to opening more than 10 concurrent connections to the target server under
 heavy load, instead of strictly capping the total connections to the requested
 number of users.
+
+FastCGI (-fcgi) requires a "docroot" set via --fcgi-app, and only supports a
+single "fcgi-app" configuration for the whole run: HAProxy allows only one
+per backend, and HALoad uses a single internal backend for every URL. Also,
+"set-param" and "pass-header" rules in that section have no effect, since
+they are only evaluated through an HAProxy stream filter and HALoad drives
+its connections without ever creating a stream; use "-H :fcgi-<param>:<val>"
+instead to override an FCGI parameter.
index 5983d31f7a26600547ac36a1662b2d2ff280e8e2..825d64481149be2df447f6f534dc7fa613a5377b 100644 (file)
 #define HLD_HAS_PROTO_H1   (1U << HLD_PROTO_H1)
 #define HLD_HAS_PROTO_H2   (1U << HLD_PROTO_H2)
 #define HLD_HAS_PROTO_H3   (1U << HLD_PROTO_H3)
+#define HLD_HAS_PROTO_FCGI (1U << HLD_PROTO_FCGI)
 
 enum hld_proto {
        HLD_PROTO_H0,
        HLD_PROTO_H1,
        HLD_PROTO_H2,
        HLD_PROTO_H3,
+       HLD_PROTO_FCGI,
        /* Do not add more enum below */
        HLD_PROTO_MAX,
 };
@@ -34,6 +36,7 @@ struct hld_url_cfg {
        int ssl;
        int is_quic;
        int h2c;
+       int fcgi;
        int thnk_time;
        enum hld_proto proto;
        char *addr;
index 99194ab6f877794b731abe76cdb0d7fcac923024..8f90a5ff40f6d3dde75cf2aa403298ec330d0fdb 100644 (file)
@@ -4,6 +4,9 @@
 #include <haproxy/chunk.h>
 #include <haproxy/dynbuf.h>
 #include <haproxy/errors.h>
+#include <haproxy/fcgi-app.h>
+#include <haproxy/fcgi-app-t.h>
+#include <haproxy/filters.h>
 #include <haproxy/http.h>
 #include <haproxy/http_htx.h>
 #include <haproxy/htx.h>
@@ -33,6 +36,9 @@ static inline struct hld_usr *hld_new_usr(int nreqs, int tid);
 static void hld_report_percentiles(void);
 static void hld_dealloc_thrs_info(void);
 
+/* not declared in any header */
+extern struct flt_ops fcgi_flt_ops;
+
 struct hld_mtask {
        struct task *t;
        unsigned int show_time;
@@ -1001,6 +1007,8 @@ static void update_err_ramp(void)
        HA_ATOMIC_STORE(&arg_rate, (uint64_t)err_rate_floor << step);
 }
 
+static const char *hld_proto_names[HLD_PROTO_MAX] = { "h0", "h1", "h2", "h3", "fcgi" };
+
 /* main task */
 static struct task *mtask_cb(struct task *t, void *context, unsigned int state)
 {
@@ -1026,10 +1034,10 @@ static struct task *mtask_cb(struct task *t, void *context, unsigned int state)
                if (arg_hscd == 2) {
                        int i;
 
-                       for (i = 0 ; i < 4; i++) {
+                       for (i = 0 ; i < HLD_PROTO_MAX; i++) {
                                if (!(hld_proto_flags & (1 << i)))
                                        continue;
-                               printf("   (h%d)1xx 2xx 3xx 4xx 5xx", i);
+                               printf("   (%s)1xx 2xx 3xx 4xx 5xx", hld_proto_names[i]);
                        }
                }
 
@@ -2246,6 +2254,7 @@ static int hld_srv_parse_opts(char *opts, struct server *s)
 static int hld_cfg_finalize(void)
 {
        int ret = 0;
+       int fcgi_used = 0;
        struct hld_url_cfg *cfg;
 
        for (cfg = hld_url_cfgs; cfg; cfg = cfg->next) {
@@ -2314,6 +2323,8 @@ static int hld_cfg_finalize(void)
                                srv->mux_proto = get_mux_proto(ist("quic"));
                        else if (cfg->h2c)
                                srv->mux_proto = get_mux_proto(ist("h2"));
+                       else if (cfg->fcgi)
+                               srv->mux_proto = get_mux_proto(ist("fcgi"));
                }
 
                if (srv->mux_proto) {
@@ -2383,6 +2394,42 @@ static int hld_cfg_finalize(void)
 
                /* Attach the server to the URL */
                cfg->srv = srv;
+               if (cfg->fcgi)
+                       fcgi_used = 1;
+       }
+
+       if (fcgi_used) {
+               /* same as "use-fcgi-app haload" at config parsing time */
+               struct fcgi_flt_conf *fcgi_conf;
+               struct flt_conf *fconf;
+
+               fcgi_conf = calloc(1, sizeof(*fcgi_conf));
+               fconf = calloc(1, sizeof(*fconf));
+               if (!fcgi_conf || !fconf) {
+                       ha_alert("could not allocate the FCGI filter config\n");
+                       goto leave;
+               }
+
+               fcgi_conf->name = strdup("haload");
+               if (!fcgi_conf->name) {
+                       ha_alert("could not allocate the FCGI app name\n");
+                       goto leave;
+               }
+
+               LIST_INIT(&fcgi_conf->param_rules);
+               LIST_INIT(&fcgi_conf->hdr_rules);
+
+               /* same as fcgi_flt_check() does at post-parsing time */
+               fcgi_conf->app = fcgi_app_find_by_name(fcgi_conf->name);
+               if (!fcgi_conf->app) {
+                       ha_alert("fcgi-app 'haload' not found. Did you pass --fcgi-app with a \"docroot\"?\n");
+                       goto leave;
+               }
+
+               fconf->id = fcgi_flt_id;
+               fconf->conf = fcgi_conf;
+               fconf->ops = &fcgi_flt_ops;
+               LIST_APPEND(&hld_proxy.filter_configs, &fconf->list);
        }
 
        ret = 1;
index d0dc565e10b61370b0d7f99e7abbeb795acaab36..74dac49bbcb2076d09755e6f2e4a237f4b681315 100644 (file)
@@ -16,6 +16,7 @@ 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;
+int fcgi;
 
 static void  hld_usage(char *name, int argc)
 {
@@ -30,6 +31,7 @@ static void  hld_usage(char *name, int argc)
                "                         then resume the load asked for on the command line\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"
+               "        -fcgi            use FastCGI protocol (*)\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"
@@ -54,7 +56,8 @@ static void  hld_usage(char *name, int argc)
                "        --defaults <str> add a string to default section\n"
                "        --global <str>   add a string to global section\n"
                "        --server <opts>  set server <opt> options as defined for \"server\" haproxy keyword\n"
-               "        --traces         enable the traces for all the HTTP protocols\n"
+               "        --fcgi-app <str> add a string to the generated \"fcgi-app haload\" section\n"
+               "        --traces         enable the traces for all the protocols\n"
                "SSL options:\n"
                "        --tls-ciphers <ciphers>       for TLS1.2 and below (*)\n"
                "        --tls-ciphersuites <ciphers>  for TLS1.3 and above (*)\n"
@@ -80,7 +83,8 @@ static const char *hld_cfg_traces_str =
 #endif
                "\ttrace haload sink stderr level developer start now verbosity clean\n"
                "\ttrace h1 sink stderr level developer start now verbosity minimal\n"
-               "\ttrace h2 sink stderr level developer start now verbosity minimal\n";
+               "\ttrace h2 sink stderr level developer start now verbosity minimal\n"
+               "\ttrace fcgi sink stderr level developer start now verbosity minimal\n";
 
 /* Allocate <hdr_str> header with "<name>:<value>" form and
  * returs it.
@@ -195,7 +199,7 @@ int hld_url_cfg_path_exist(struct hld_url_cfg *u, const char *path)
  */
 static struct hld_url_cfg *hld_alloc_url(char *url, int is_quic,
                                          char *alpn_param, int h2c_param,
-                                         enum hld_proto proto)
+                                         int fcgi_param, enum hld_proto proto)
 {
        int ssl = 0;
        char *addr = NULL, *raw_addr = NULL, *path = NULL;
@@ -242,7 +246,7 @@ static struct hld_url_cfg *hld_alloc_url(char *url, int is_quic,
 
        for (purl = hld_url_cfgs; purl; purl = purl->next) {
                if (purl->is_quic == is_quic && purl->ssl == ssl &&
-                   purl->h2c == h2c_param &&
+                   purl->h2c == h2c_param && purl->fcgi == fcgi_param &&
                    strcmp(purl->raw_addr, addr) == 0 &&
                    ((!purl->alpn && !alpn_param) ||
                     (purl->alpn && alpn_param && strcmp(purl->alpn, alpn_param) == 0))) {
@@ -307,6 +311,7 @@ static struct hld_url_cfg *hld_alloc_url(char *url, int is_quic,
        hld_url_cfg->ssl = ssl;
        hld_url_cfg->is_quic = is_quic;
        hld_url_cfg->h2c = h2c_param;
+       hld_url_cfg->fcgi = fcgi_param;
        hld_url_cfg->thnk_time = arg_thnk;
        hld_url_cfg->proto = proto;
        hld_url_cfg->addr = addr;
@@ -416,6 +421,7 @@ void haproxy_init_args(int argc, char **argv)
        struct hbuf gbuf = HBUF_NULL; // "global" section
        struct hbuf tbuf = HBUF_NULL; // "traces" section
        struct hbuf dbuf = HBUF_NULL; // "default" section
+       struct hbuf fbuf = HBUF_NULL; // "fcgi-app" section
        int proto = HLD_PROTO_H1;
 
        if (argc <= 1)
@@ -472,6 +478,22 @@ void haproxy_init_args(int argc, char **argv)
 
                                        hbuf_str_append(&dbuf, *argv);
                                }
+                               else if (strcmp(opt, "fcgi-app") == 0) {
+                                       argv++; argc--;
+                                       if (argc <= 0 || **argv == '-')
+                                               hld_usage(progname, argc);
+
+                                       if (hbuf_is_null(&fbuf)) {
+                                               if (hbuf_alloc(&fbuf) == NULL) {
+                                                       ha_alert("failed to allocate a buffer.\n");
+                                                       goto leave;
+                                               }
+
+                                               hbuf_appendf(&fbuf, "fcgi-app haload\n");
+                                       }
+
+                                       hbuf_str_append(&fbuf, *argv);
+                               }
                                else if (strcmp(opt, "global") == 0) {
                                        argv++; argc--;
                                        if (argc <= 0 || **argv == '-')
@@ -522,24 +544,28 @@ void haproxy_init_args(int argc, char **argv)
                                 strcmp(opt, "h0") == 0) {
                                alpn = "hq-interop";
                                h2c = 0;
+                               fcgi = 0;
                                proto = HLD_PROTO_H0;
                        }
                        else if (strcmp(opt, "1") == 0 ||
                                 strcmp(opt, "h1") == 0) {
                                alpn = "http/1.1";
                                h2c = 0;
+                               fcgi = 0;
                                proto = HLD_PROTO_H1;
                        }
                        else if (strcmp(opt, "2") == 0 ||
                                 strcmp(opt, "h2") == 0) {
                                alpn = "h2";
                                h2c = 0;
+                               fcgi = 0;
                                proto = HLD_PROTO_H2;
                        }
                        else if (strcmp(opt, "2c") == 0 ||
                                 strcmp(opt, "h2c") == 0) {
                                alpn = NULL;
                                h2c = 1;
+                               fcgi = 0;
                                proto = HLD_PROTO_H2;
                        }
                        else if (strcmp(opt, "3") == 0 ||
@@ -547,12 +573,19 @@ void haproxy_init_args(int argc, char **argv)
 #if defined(USE_QUIC)
                                alpn = "h3";
                                h2c = 0;
+                               fcgi = 0;
                                proto = HLD_PROTO_H3;
 #else
                                ha_warning("QUIC support not compiled in. Rebuild with USE_QUIC=1.\n");
                                goto leave;
 #endif
                        }
+                       else if (strcmp(opt, "fcgi") == 0) {
+                               alpn = NULL;
+                               h2c = 0;
+                               fcgi = 1;
+                               proto = HLD_PROTO_FCGI;
+                       }
                        else if (strcmp(opt, "get") == 0) {
                                http_meth = HTTP_METH_GET;
                                meth_ist = ist("GET");
@@ -711,7 +744,8 @@ void haproxy_init_args(int argc, char **argv)
                        hld_proto_flags |= (1 << proto);
                        url = hld_alloc_url(*argv, is_quic,
                                            is_quic ? "h3" : alpn,
-                                           is_quic ? 0 : h2c, proto);
+                                           is_quic ? 0 : h2c,
+                                           is_quic ? 0 : fcgi, proto);
                        if (!url) {
                                ha_alert("could not parse a new URL\n");
                                goto leave;
@@ -756,6 +790,9 @@ void haproxy_init_args(int argc, char **argv)
        /* "default section */
        if (!hbuf_is_null(&dbuf))
                hbuf_appendf(&buf, "%.*s\n", (int)dbuf.data, dbuf.area);
+       /* "fcgi-app" section */
+       if (!hbuf_is_null(&fbuf))
+               hbuf_appendf(&buf, "%.*s\n", (int)fbuf.data, fbuf.area);
 
        fileless_cfg.filename = strdup("haterm cfgfile");
        fileless_cfg.content = strdup(buf.area);
@@ -777,6 +814,7 @@ void haproxy_init_args(int argc, char **argv)
        err = 0;
 leave:
        free_hbuf(&dbuf);
+       free_hbuf(&fbuf);
        free_hbuf(&gbuf);
        free_hbuf(&buf);
        if (err)