From c9d47804d161d1d649abea4b03255385150029c1 Mon Sep 17 00:00:00 2001 From: Frederic Lecaille Date: Wed, 11 Feb 2026 15:05:42 +0100 Subject: [PATCH] MINOR: haterm: add haterm HTTP server Contrary to haproxy, httpterm does not support all the HTTP protocols. Furthermore, it has become easier to handle inbound/outbound connections / streams since the rework done at conn_stream level. This patch implements httpterm HTTP server services into haproxy. To do so, it proceeds the same way as for the TCP checks which use only one stream connector, but on frontend side. The makefile is modified to handle haterm.c in additions to all the C files for haproxy to build new haterm program into haproxy, the haterm server also instantiates a haterm stream (hstream struct) attached to a stream connector for each incoming connection without backend stream connector. This is the role of sc_new_from_endp() called by the muxes to instantiate streams/hstreams. As for stream_new(), hstream_new() instantiates a task named process_hstream() (see haterm.c) which has the same role as process_stream() but for haterm streams. haterm into haproxy takes advantage of the HTTP muxes and HTX API to support all the HTTP protocols supported by haproxy. --- Makefile | 2 +- doc/configuration.txt | 3 + include/haproxy/hstream-t.h | 36 ++ include/haproxy/hstream.h | 12 + include/haproxy/obj_type-t.h | 1 + include/haproxy/obj_type.h | 14 + include/haproxy/stconn.h | 18 + src/cfgparse-listen.c | 20 +- src/haterm.c | 1028 ++++++++++++++++++++++++++++++++++ src/stconn.c | 34 ++ 10 files changed, 1164 insertions(+), 4 deletions(-) create mode 100644 include/haproxy/hstream-t.h create mode 100644 include/haproxy/hstream.h create mode 100644 src/haterm.c diff --git a/Makefile b/Makefile index 508bd75d7..da50e2625 100644 --- a/Makefile +++ b/Makefile @@ -1003,7 +1003,7 @@ OBJS += src/mux_h2.o src/mux_h1.o src/mux_fcgi.o src/log.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/httpclient_cli.o src/version.o src/ncbmbuf.o src/ech.o \ - src/cfgparse-peers.o + src/cfgparse-peers.o src/haterm.o ifneq ($(TRACE),) OBJS += src/calltrace.o diff --git a/doc/configuration.txt b/doc/configuration.txt index 3f351c8f0..b91c886c9 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -9307,6 +9307,9 @@ mode { tcp|http|log|spop } processing and switching will be possible. This is the mode which brings HAProxy most of its value. + haterm The frontend will work in haterm HTTP benchmark mode. This is + not supported by backends. See doc/haterm.txt for details. + log When used in a backend section, it will turn the backend into a log backend. Such backend can be used as a log destination for any "log" directive by using the "backend@" syntax. Log diff --git a/include/haproxy/hstream-t.h b/include/haproxy/hstream-t.h new file mode 100644 index 000000000..e9ef52a20 --- /dev/null +++ b/include/haproxy/hstream-t.h @@ -0,0 +1,36 @@ +#ifndef _HAPROXY_HSTREAM_T_H +#define _HAPROXY_HSTREAM_T_H + +#include +#include +#include + +/* hastream stream */ +struct hstream { + enum obj_type obj_type; + struct session *sess; + + struct stconn *sc; + struct task *task; + + struct buffer req; + struct buffer res; + unsigned long long to_write; /* #of response data bytes to write after headers */ + struct buffer_wait buf_wait; /* Wait list for buffer allocation */ + + int flags; + + int ka; /* .0: keep-alive .1: forced .2: http/1.1, .3: was_reused */ + int req_cache; + unsigned long long req_size; /* values passed in the URI to override the server's */ + unsigned long long req_body; /* remaining body to be consumed from the request */ + int req_code; + int res_wait; /* time to wait before replying in ms */ + int res_time; + int req_chunked; + int req_random; + int req_after_res; /* Drain the request body after having sent the response */ + enum http_meth_t req_meth; +}; + +#endif /* _HAPROXY_HSTREAM_T_H */ diff --git a/include/haproxy/hstream.h b/include/haproxy/hstream.h new file mode 100644 index 000000000..893adff60 --- /dev/null +++ b/include/haproxy/hstream.h @@ -0,0 +1,12 @@ +#ifndef _HAPROXY_HSTREAM_H +#define _HAPROXY_HSTREAM_H + +#include +#include + +struct task *sc_hstream_io_cb(struct task *t, void *ctx, unsigned int state); +int hstream_wake(struct stconn *sc); +void hstream_shutdown(struct stconn *sc); +void *hstream_new(struct session *sess, struct stconn *sc, struct buffer *input); + +#endif /* _HAPROXY_HSTREAM_H */ diff --git a/include/haproxy/obj_type-t.h b/include/haproxy/obj_type-t.h index da2efbf86..fd232b347 100644 --- a/include/haproxy/obj_type-t.h +++ b/include/haproxy/obj_type-t.h @@ -46,6 +46,7 @@ enum obj_type { #ifdef USE_QUIC OBJ_TYPE_DGRAM, /* object is a struct quic_dgram */ #endif + OBJ_TYPE_HATERM, /* object is a struct hstream */ OBJ_TYPE_ENTRIES /* last one : number of entries */ } __attribute__((packed)) ; diff --git a/include/haproxy/obj_type.h b/include/haproxy/obj_type.h index 233f9d7ad..bd850b969 100644 --- a/include/haproxy/obj_type.h +++ b/include/haproxy/obj_type.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -189,6 +190,19 @@ static inline struct check *objt_check(enum obj_type *t) return __objt_check(t); } +static inline struct hstream *__objt_hstream(enum obj_type *t) +{ + return container_of(t, struct hstream, obj_type); +} + +static inline struct hstream *objt_hstream(enum obj_type *t) +{ + if (!t || *t != OBJ_TYPE_HATERM) + return NULL; + + return __objt_hstream(t); +} + #ifdef USE_QUIC static inline struct quic_dgram *__objt_dgram(enum obj_type *t) { diff --git a/include/haproxy/stconn.h b/include/haproxy/stconn.h index 3a2c5238c..5a906e623 100644 --- a/include/haproxy/stconn.h +++ b/include/haproxy/stconn.h @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -45,10 +46,12 @@ void se_shutdown(struct sedesc *sedesc, enum se_shut_mode mode); struct stconn *sc_new_from_endp(struct sedesc *sedesc, struct session *sess, struct buffer *input); struct stconn *sc_new_from_strm(struct stream *strm, unsigned int flags); struct stconn *sc_new_from_check(struct check *check, unsigned int flags); +struct stconn *sc_new_from_haterm(struct sedesc *sd, struct session *sess, struct buffer *input); void sc_free(struct stconn *sc); int sc_attach_mux(struct stconn *sc, void *target, void *ctx); int sc_attach_strm(struct stconn *sc, struct stream *strm); +int sc_attach_hstream(struct stconn *sc, struct hstream *hs); void sc_destroy(struct stconn *sc); int sc_reset_endp(struct stconn *sc); @@ -331,6 +334,21 @@ static inline struct check *sc_check(const struct stconn *sc) return NULL; } +/* Returns the haterm stream from a sc if the application is a + * haterm stream. Otherwise NULL is returned. __sc_hstream() returns the haterm + * stream without any control while sc_hstream() check the application type. + */ +static inline struct hstream *__sc_hstream(const struct stconn *sc) +{ + return __objt_hstream(sc->app); +} +static inline struct hstream *sc_hstream(const struct stconn *sc) +{ + if (obj_type(sc->app) == OBJ_TYPE_HATERM) + return __objt_hstream(sc->app); + return NULL; +} + /* Returns the name of the application layer's name for the stconn, * or "NONE" when none is attached. */ diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c index 9a680ef09..3bce9dd0b 100644 --- a/src/cfgparse-listen.c +++ b/src/cfgparse-listen.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -645,9 +646,22 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) mode = str_to_proxy_mode(args[1]); if (!mode) { - ha_alert("parsing [%s:%d] : unknown proxy mode '%s'.\n", file, linenum, args[1]); - err_code |= ERR_ALERT | ERR_FATAL; - goto out; + if (strcmp(args[1], "haterm") == 0) { + if (!(curproxy->cap & PR_CAP_FE)) { + ha_alert("parsing [%s:%d] : mode haterm is only applicable" + " on proxies with frontend capability.\n", file, linenum); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + mode = PR_MODE_HTTP; + curproxy->stream_new_from_sc = hstream_new; + } + else { + ha_alert("parsing [%s:%d] : unknown proxy mode '%s'.\n", file, linenum, args[1]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } } else if ((mode == PR_MODE_SYSLOG || mode == PR_MODE_SPOP) && !(curproxy->cap & PR_CAP_BE)) { diff --git a/src/haterm.c b/src/haterm.c new file mode 100644 index 000000000..672a69912 --- /dev/null +++ b/src/haterm.c @@ -0,0 +1,1028 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DECLARE_TYPED_POOL(pool_head_hstream, "hstream", struct hstream); + +/* haterm stream state flags */ +#define HS_ST_IN_ALLOC 0x0001 +#define HS_ST_OUT_ALLOC 0x0002 +#define HS_ST_CONN_ERROR 0x0004 +#define HS_ST_HTTP_GOT_HDRS 0x0008 +#define HS_ST_HTTP_HELP 0x0010 +#define HS_ST_HTTP_EXPECT 0x0020 +#define HS_ST_HTTP_RESP_SL_SENT 0x0040 + +const char *HTTP_HELP = + "HAProxy's dummy HTTP server for benchmarks - version " HAPROXY_VERSION ".\n" + "All integer argument values are in the form [digits]*[kmgr] (r=random(0..1)).\n" + "The following arguments are supported to override the default objects :\n" + " - /?s= return bytes.\n" + " E.g. /?s=20k\n" + " - /?r= present as the HTTP return code.\n" + " E.g. /?r=404\n" + " - /?c= set the return as not cacheable if <1.\n" + " E.g. /?c=0\n" + " - /?A= drain the request body after sending the response.\n" + " E.g. /?A=1\n" + " - /?C= force the response to use close if >0.\n" + " E.g. /?C=1\n" + " - /?K= force the response to use keep-alive if >0.\n" + " E.g. /?K=1\n" + " - /?t=