]> git.kaiwu.me - haproxy.git/commitdiff
MINOR: filters: Introduce filter classes and per-side class ordering
authorChristopher Faulet <cfaulet@haproxy.com>
Wed, 30 Sep 2026 16:10:21 +0000 (18:10 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Fri, 9 Oct 2026 14:17:35 +0000 (16:17 +0200)
Introduce a class registry distinct from the legacy filter keyword registry.
A class has a unique name, some flags, a instance parser and independent
request/response placement relative to other classes.

Export the built-in class names from their implementations and establish
their default order centrally. Provide registration and placement helpers
for additional classes, and reject classes placed on neither side.

Leave built-in instance parsers unset until the parser adapters are added.
This is configuration infrastructure only; stream evaluation still uses the
legacy filters. This WIP registry also unconditionally references the Lua
class: support for builds without Lua remains to be completed.

include/haproxy/filters-t.h
include/haproxy/filters.h
src/cache.c
src/fcgi-app.c
src/filters.c
src/flt_bwlim.c
src/flt_decomp.c
src/flt_http_comp.c
src/flt_spoe.c
src/flt_trace.c
src/hlua.c

index ff1d82be3eca3a2527abda3940a9ec1d07277a7b..6716fd0f6f300505f9a0a13c41b1d58de6fb2493 100644 (file)
@@ -43,6 +43,7 @@ struct stream;
 struct channel;
 struct flt_conf;
 struct filter;
+struct filter_instance;
 
 /* Descriptor for a "filter" keyword. The ->parse() function returns 0 in case
  * of success, or a combination of ERR_* flags if an error is encountered. The
@@ -257,6 +258,41 @@ struct chn_flt {
        unsigned long long offset;
 };
 
+/**************************************************************/
+
+#define FLT_SIDE_REQ  0x01
+#define FLT_SIDE_RES  0x02
+
+enum flt_pos {
+       FLT_POS_BEFORE,
+       FLT_POS_AFTER,
+};
+
+/* filter class flags */
+#define FLT_CLS_FL_MULTI  0x01   /* Set if several class instances are allowed per proxy */
+
+struct filter_class {
+       const char *name;           /* The filter class name, must be unique */
+       unsigned int flags;         /* FLT_CLS_FL_* */
+
+       /* Callback to parse the arguments of a filter instance and produce
+        * the filter configuration (inst->fconf).
+        */
+       int (*parse)(char **args, struct proxy *px, struct filter_instance *inst, char **err);
+       struct {
+               struct list before; /* classes moved before current class for request */
+               struct list after;  /* classes moved after current class for request */
+               struct list list;   /* classes registered on request analysis */
+       } req;
+       struct {
+               struct list before; /* classes moved before current class on response */
+               struct list after;  /* classes moved after current class on response */
+               struct list list;   /* classes registered on response analysis */
+       } res;
+
+       struct list list; /* Link in global list of filter classes */
+};
+
 #endif /* _HAPROXY_FILTERS_T_H */
 
 /*
index 636cb85db292a4ebe81ea5a99331974371d47b3f..6e133abc1df2622315c6391b81156c5ea76e3f2b 100644 (file)
@@ -32,10 +32,40 @@ extern const char *http_comp_req_flt_id;
 extern const char *http_comp_res_flt_id;
 extern const char *decomp_req_flt_id;
 extern const char *decomp_res_flt_id;
-
 extern const char *cache_store_flt_id;
+
+extern const char *trace_filter_cls_name;
+extern const char *cache_store_filter_cls_name;
+extern const char *http_comp_req_filter_cls_name;
+extern const char *http_comp_res_filter_cls_name;
+extern const char *decomp_req_filter_cls_name;
+extern const char *decomp_res_filter_cls_name;
+extern const char *bwlim_in_filter_cls_name;
+extern const char *bwlim_out_filter_cls_name;
+
+extern struct filter_class flt_trace_cls;
+extern struct filter_class flt_cache_store_cls;
+extern struct filter_class flt_http_comp_req_cls;
+extern struct filter_class flt_http_comp_res_cls;
+extern struct filter_class flt_decomp_req_cls;
+extern struct filter_class flt_decomp_res_cls;
+extern struct filter_class flt_bwlim_in_cls;
+extern struct filter_class flt_bwlim_out_cls;
+
+#if defined(USE_SPOE)
 extern const char *spoe_filter_id;
+extern const char *spoe_filter_cls_name;
+extern struct filter_class flt_spoe_cls;
+#endif
+#if defined(USE_LUA)
+extern const char *hlua_filter_cls_name;
+extern struct filter_class flt_lua_cls;
+#endif
+#if defined(USE_FCGI)
 extern const char *fcgi_flt_id;
+extern const char *fcgi_filter_cls_name;
+extern struct filter_class flt_fcgi_cls;
+#endif
 
 #define FLT_ID(flt)   (flt)->config->id
 #define FLT_CONF(flt) (flt)->config->conf
@@ -234,4 +264,19 @@ flt_update_offsets(struct filter *filter, struct channel *chn, int len)
        }
 }
 
+
+/**************************************************************/
+
+struct filter_class *filter_find_class(const char *name);
+
+int filter_register_class(struct filter_class *cls, const char *name,
+                         int (*parse)(char **args, struct proxy *px,
+                                      struct filter_instance *inst, char **err));
+int filter_place_class(struct filter_class *cls, unsigned int side,
+                      const char *ref_name, enum flt_pos pos);
+int filter_register_class_full(struct filter_class *cls, const char *name,
+                              int (*parse)(char **args, struct proxy *px,
+                                           struct filter_instance *inst, char **err),
+                              const char *ref_name, enum flt_pos pos);
+
 #endif /* _HAPROXY_FILTERS_H */
index 1235ffbd82dbdde3651cb23fa6d5be6da349837f..ed8729dcd1ede75ad17172320742954b72c6ab8f 100644 (file)
@@ -56,6 +56,7 @@
 static uint64_t cache_hash_seed = 0;
 
 const char *cache_store_flt_id = "cache store filter";
+const char *cache_store_filter_cls_name = "cache-store";
 
 extern struct applet http_cache_applet;
 
index 71c579ddb229bee289523f655c8dfceace50ee74..caecc129eddf01f69baabf9170405aab1d99e312 100644 (file)
@@ -34,6 +34,7 @@ static struct fcgi_app *fcgi_apps = NULL;
 
 struct flt_ops fcgi_flt_ops;
 const char *fcgi_flt_id = "FCGI filter";
+const char *fcgi_filter_cls_name = "fcgi";
 
 /* The fcgi-app filter was implicitly declared (ie without the filter keyword,
  * via use-fcgi-app).
index d985de3fa52b81bf8dbc80abcf9abc9f9c928e5b..f85156f22641281b0bca9fec39599f72ce292550 100644 (file)
 
 #define TRACE_SOURCE &trace_strm
 
+/* All internal filter classes (should be extended if new filters are added) */
+struct filter_class flt_trace_cls;
+struct filter_class flt_cache_store_cls;
+struct filter_class flt_http_comp_req_cls;
+struct filter_class flt_http_comp_res_cls;
+struct filter_class flt_decomp_req_cls;
+struct filter_class flt_decomp_res_cls;
+#if defined(USE_SPOE)
+struct filter_class flt_spoe_cls;
+#endif
+#if defined(USE_LUA)
+struct filter_class flt_lua_cls;
+#endif
+struct filter_class flt_bwlim_in_cls;
+struct filter_class flt_bwlim_out_cls;
+#if defined(USE_FCGI)
+struct filter_class flt_fcgi_cls;
+#endif
+
+/* Global list of all filter classes */
+static struct list filter_classes = LIST_HEAD_INIT(filter_classes);
+
+/* Ordered list of filter classes dedicated to the request/response processing */
+static struct list req_filter_classes = LIST_HEAD_INIT(req_filter_classes);
+static struct list res_filter_classes = LIST_HEAD_INIT(res_filter_classes);
+
+/* Used to be sure internal filters classes are initialized before any other ones */
+static int filter_classes_initialized = 0;
+
 /* Pool used to allocate filters */
 DECLARE_STATIC_TYPED_POOL(pool_head_filter, "filter", struct filter);
 
@@ -1251,10 +1280,283 @@ static struct cfg_kw_list cfg_kws = {ILH, {
 
 INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
 
+
+/* Checks that all registered filter classes are placed on at least one
+ * side. The classes are placed with filter_place_class() (see
+ * filter_init_classes() for the internal classes), so a class without
+ * placement is useless and most probably comes from a module registration
+ * bug.
+ * Returns a combination of ERR_* flags, ERR_NONE on success.
+ */
+static int flt_precheck_classes()
+{
+       struct filter_class *cls;
+       int err_code = ERR_NONE;
+
+       list_for_each_entry(cls, &filter_classes, list) {
+               if (!LIST_INLIST(&cls->req.list) && !LIST_INLIST(&cls->res.list)) {
+                       ha_alert("filters: class '%s' is placed neither on the request side nor on the response side.\n",
+                                cls->name);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+               }
+       }
+       return err_code;
+}
+
+/* Helper function used to initialized a filter class with the given name,
+ * flags (FLT_CLS_FL_*) and instance parsing function (may be NULL).
+ */
+static inline void flt_init_class(struct filter_class *cls, const char *name, unsigned int flags,
+                                 int (*parse)(char **args, struct proxy *px,
+                                              struct filter_instance *inst, char **err))
+{
+       cls->name = name;
+       cls->flags = flags;
+       cls->parse = parse;
+       LIST_INIT(&cls->req.before);
+       LIST_INIT(&cls->req.after);
+       LIST_INIT(&cls->req.list);
+
+       LIST_INIT(&cls->res.before);
+       LIST_INIT(&cls->res.after);
+       LIST_INIT(&cls->res.list);
+
+       LIST_INIT(&cls->list);
+}
+
+/* Finds a filter class by name in the global list, NULL if unknown. */
+struct filter_class *filter_find_class(const char *name)
+{
+       struct filter_class *cls;
+
+       list_for_each_entry(cls, &filter_classes, list) {
+               if (strcmp(cls->name, name) == 0)
+                       return cls;
+       }
+       return NULL;
+}
+
+/* Registers a new filter class, with the parsing function <parse> used to
+ * finalize the filter instances of this class (may be NULL if the class does
+ * not support the "filter-config" directive). The class is initialized and
+ * added to the global "filter_classes" list only — it is not usable on any side
+ * until placed with filter_place_class(). Must be called after
+ * filter_init_classes().
+ * Returns 0 on success, -1 on error (too early call, duplicate name).
+ */
+int filter_register_class(struct filter_class *cls, const char *name,
+                         int (*parse)(char **args, struct proxy *px,
+                                      struct filter_instance *inst, char **err))
+{
+       const char *err;
+       int ret = -1;
+
+       if (!filter_classes_initialized) {
+               ha_alert("filters: class '%s' registered before internal classes were initialized\n", name);
+               goto out;
+       }
+       if (filter_find_class(name)) {
+               ha_alert("filters: duplicate filter class name '%s'\n", name);
+               goto out;
+       }
+       err = invalid_prefix_char(name);
+       if (err) {
+               ha_alert("filters: character '%c' is not permitted in filter class name.\n", *err);
+               goto out;
+       }
+
+       flt_init_class(cls, name, 0, parse);
+       LIST_APPEND(&filter_classes, &cls->list);
+       ret = 0;
+  out:
+       return ret;
+}
+
+/* Places <cls> on <side> (FLT_SIDE_REQ or FLT_SIDE_RES), before or after the
+ * reference class <ref_name>. The class is appended in the reference class'
+ * req/res .before or .after constraint list; the final evaluation order is
+ * resolved later. The global ordered lists are never touched here.  Call it
+ * once per side; the positions may differ per side.
+ * Returns 0 on success, -1 on error (unknown ref, ref not present on that side,
+ * class already placed on that side).
+ */
+int filter_place_class(struct filter_class *cls, unsigned int side,
+                      const char *ref_name, enum flt_pos pos)
+{
+       struct filter_class *ref;
+       int ret = -1;
+
+       if (!filter_classes_initialized) {
+               ha_alert("filters: class '%s' placed before internal classes were initialized\n", cls->name);
+               goto out;
+       }
+       ref = filter_find_class(ref_name);
+       if (!ref) {
+               ha_alert("filters: unknown reference class '%s' for class '%s'\n", ref_name, cls->name);
+               goto out;
+       }
+
+       if (side == FLT_SIDE_REQ) {
+               if (LIST_INLIST(&cls->req.list)) {
+                       ha_alert("filters: class '%s' already placed on request side\n", cls->name);
+                       goto out;
+               }
+               if (!LIST_INLIST(&ref->req.list)) {
+                       ha_alert("filters: reference class '%s' has no request side\n", ref_name);
+                       goto out;
+               }
+               LIST_APPEND((pos == FLT_POS_BEFORE) ? &ref->req.before : &ref->req.after,
+                           &cls->req.list);
+       }
+       else {
+               if (LIST_INLIST(&cls->res.list)) {
+                       ha_alert("filters: class '%s' already placed on response side\n", cls->name);
+                       goto out;
+               }
+               if (!LIST_INLIST(&ref->res.list)) {
+                       ha_alert("filters: reference class '%s' has no response side\n", ref_name);
+                       goto out;
+               }
+               LIST_APPEND((pos == FLT_POS_BEFORE) ? &ref->res.before : &ref->res.after,
+                           &cls->res.list);
+       }
+       ret = 0;
+  out:
+       return ret;
+}
+
+/* Registers a new filter class named <name> and places it on both sides,
+ * before or after the reference class <ref_name>. Convenience function for
+ * the common symmetric case, equivalent to filter_register_class() followed
+ * by filter_place_class() on FLT_SIDE_REQ and FLT_SIDE_RES.
+ * Returns 0 on success, -1 on error. */
+int filter_register_class_full(struct filter_class *cls, const char *name,
+                              int (*parse)(char **args, struct proxy *px,
+                                           struct filter_instance *inst, char **err),
+                              const char *ref_name, enum flt_pos pos)
+{
+       if (filter_register_class(cls, name, parse) < 0)
+               goto err;
+       if (filter_place_class(cls, FLT_SIDE_REQ, ref_name, pos) < 0)
+               goto cleanup_on_err;
+       if (filter_place_class(cls, FLT_SIDE_RES, ref_name, pos) < 0)
+               goto cleanup_on_err;
+
+       return 0;
+
+  cleanup_on_err:
+       LIST_DELETE(&cls->list);
+       LIST_INIT(&cls->list);
+       /* fallthrough */
+  err:
+       return -1;
+}
+
+/* Init function responsible to initialize all intenral filter classes and to
+ * insert them is global list above. The default evaluation order of all
+ * internal filters is defined in this function.
+ *
+ * Exemple to register a new filter class:
+ *
+ * static struct filter_class flt_myfilter_cls;
+ *
+ * static int myfilter_register_class(void)
+ * {
+ *    if (filter_register_class(&flt_myfilter_cls, "myfilter", myfilter_parse_def) < 0)
+ *       return -1;
+ *
+ *    // before spoe on request, after lua on response
+ *     if (filter_place_class(&flt_myfilter_cls, FLT_SIDE_REQ, "spoe", FLT_POS_BEFORE) < 0)
+ *       return -1;
+ *    if (filter_place_class(&flt_myfilter_cls, FLT_SIDE_RES, "lua", FLT_POS_AFTER) < 0)
+ *       return -1;
+ *    return 0;
+ * }
+ * INITCALL1(STG_INIT, myfilter_register_class);
+ */
+static void filter_init_classes(void)
+{
+       /* TODO: the filter names must come from the filters (the filter id most probably) */
+       flt_init_class(&flt_trace_cls,         trace_filter_cls_name,         FLT_CLS_FL_MULTI, NULL);
+       flt_init_class(&flt_cache_store_cls,   cache_store_filter_cls_name,   FLT_CLS_FL_MULTI, NULL);
+       flt_init_class(&flt_http_comp_req_cls, http_comp_req_filter_cls_name, 0,                NULL);
+       flt_init_class(&flt_http_comp_res_cls, http_comp_res_filter_cls_name, 0,                NULL);
+       flt_init_class(&flt_decomp_req_cls,    decomp_req_filter_cls_name,    0,                NULL);
+       flt_init_class(&flt_decomp_res_cls,    decomp_res_filter_cls_name,    0,                NULL);
+#if defined(USE_SPOE)
+       flt_init_class(&flt_spoe_cls,          spoe_filter_cls_name,          FLT_CLS_FL_MULTI, NULL);
+#endif
+#if defined(USE_LUA)
+       flt_init_class(&flt_lua_cls,           hlua_filter_cls_name,          FLT_CLS_FL_MULTI, NULL);
+#endif
+       flt_init_class(&flt_bwlim_in_cls,      bwlim_in_filter_cls_name,      FLT_CLS_FL_MULTI, NULL);
+       flt_init_class(&flt_bwlim_out_cls,     bwlim_out_filter_cls_name,     FLT_CLS_FL_MULTI, NULL);
+#if defined(USE_FCGI)
+       flt_init_class(&flt_fcgi_cls,          fcgi_filter_cls_name,          0,                NULL);
+#endif
+
+       LIST_APPEND(&filter_classes, &flt_trace_cls.list);
+       LIST_APPEND(&filter_classes, &flt_cache_store_cls.list);
+       LIST_APPEND(&filter_classes, &flt_decomp_req_cls.list);
+       LIST_APPEND(&filter_classes, &flt_decomp_res_cls.list);
+#if defined(USE_SPOE)
+       LIST_APPEND(&filter_classes, &flt_spoe_cls.list);
+#endif
+#if defined(USE_LUA)
+       LIST_APPEND(&filter_classes, &flt_lua_cls.list);
+#endif
+       LIST_APPEND(&filter_classes, &flt_http_comp_req_cls.list);
+       LIST_APPEND(&filter_classes, &flt_http_comp_res_cls.list);
+       LIST_APPEND(&filter_classes, &flt_bwlim_in_cls.list);
+       LIST_APPEND(&filter_classes, &flt_bwlim_out_cls.list);
+#if defined(USE_FCGI)
+       LIST_APPEND(&filter_classes, &flt_fcgi_cls.list);
+#endif
+
+       LIST_APPEND(&req_filter_classes, &flt_trace_cls.req.list);
+       LIST_APPEND(&req_filter_classes, &flt_decomp_req_cls.req.list);
+#if defined(USE_LUA)
+       LIST_APPEND(&req_filter_classes, &flt_lua_cls.req.list);
+#endif
+#if defined(USE_SPOE)
+       LIST_APPEND(&req_filter_classes, &flt_spoe_cls.req.list);
+#endif
+       LIST_APPEND(&req_filter_classes, &flt_http_comp_res_cls.req.list);
+       LIST_APPEND(&req_filter_classes, &flt_decomp_res_cls.req.list);
+       LIST_APPEND(&req_filter_classes, &flt_http_comp_req_cls.req.list);
+       LIST_APPEND(&req_filter_classes, &flt_bwlim_in_cls.req.list);
+#if defined(USE_FCGI)
+       LIST_APPEND(&req_filter_classes, &flt_fcgi_cls.req.list);
+#endif
+
+#if defined(USE_FCGI)
+       LIST_APPEND(&res_filter_classes, &flt_fcgi_cls.res.list);
+#endif
+       LIST_APPEND(&res_filter_classes, &flt_trace_cls.res.list);
+       LIST_APPEND(&res_filter_classes, &flt_decomp_res_cls.res.list);
+       LIST_APPEND(&res_filter_classes, &flt_cache_store_cls.res.list);
+#if defined(USE_SPOE)
+       LIST_APPEND(&res_filter_classes, &flt_spoe_cls.res.list);
+#endif
+#if defined(USE_LUA)
+       LIST_APPEND(&res_filter_classes, &flt_lua_cls.res.list);
+#endif
+       LIST_APPEND(&res_filter_classes, &flt_http_comp_res_cls.res.list);
+       LIST_APPEND(&res_filter_classes, &flt_decomp_req_cls.res.list);
+       LIST_APPEND(&res_filter_classes, &flt_bwlim_out_cls.res.list);
+
+       filter_classes_initialized = 1;
+}
+
+
+
+REGISTER_PRE_CHECK(flt_precheck_classes);
 REGISTER_POST_CHECK(flt_init_all);
 REGISTER_PER_THREAD_INIT(flt_init_all_per_thread);
 REGISTER_PER_THREAD_DEINIT(flt_deinit_all_per_thread);
 
+INITCALL0(STG_REGISTER, filter_init_classes);
+
 /*
  * Local variables:
  *  c-indent-level: 8
index f94706423e5b00849946e37d81519cb66757db24..6c61c43df8ba7fe7071f7d251a6bd8407e881961 100644 (file)
@@ -28,6 +28,8 @@
 
 const char *bwlim_in_flt_id = "input bandwidth limitation filter";
 const char *bwlim_out_flt_id = "output bandwidth limitation filter";
+const char *bwlim_in_filter_cls_name = "bwlim-in";
+const char *bwlim_out_filter_cls_name = "bwlim-out";
 
 struct flt_ops bwlim_ops;
 
index 16b4c1a7e0145c4cf0ee5c744750a7cdb0df8a8b..72ee1c836082f59ba15b36eed4d0c54777abf5cf 100644 (file)
@@ -38,6 +38,8 @@
 
 const char *decomp_req_flt_id = "decomp-req filter";
 const char *decomp_res_flt_id = "decomp-res filter";
+const char *decomp_req_filter_cls_name = "decomp-req";
+const char *decomp_res_filter_cls_name = "decomp-res";
 
 struct flt_ops decomp_req_ops;
 struct flt_ops decomp_res_ops;
index 0c3650e978313d9fa5982ed971c770adbb0dd56b..e281932226f54ab69995d35cea20af9ec86b3884 100644 (file)
@@ -29,6 +29,8 @@
 
 const char *http_comp_req_flt_id = "comp-req filter";
 const char *http_comp_res_flt_id = "comp-res filter";
+const char *http_comp_req_filter_cls_name = "http-comp-req";
+const char *http_comp_res_filter_cls_name = "http-comp-res";
 
 struct flt_ops comp_req_ops;
 struct flt_ops comp_res_ops;
index 64a330089b3629f8f2bf1d0c940fce2ce28d904d..aa5287d50e3cad64cfbbe8db3b32579151bdb2a5 100644 (file)
@@ -221,6 +221,7 @@ struct spoe_config {
 
 /* SPOE filter id. Used to identify SPOE filters */
 const char *spoe_filter_id = "SPOE filter";
+const char *spoe_filter_cls_name = "spoe";
 
 /* The name of the SPOE engine, used during the parsing */
 char *curengine = NULL;
index cd9e5dbc373cbe3fc1c581760b6271aaf1a689c1..3284f37d18da28619a2a10238c0a335b27a9ce8e 100644 (file)
@@ -26,6 +26,7 @@
 #include <haproxy/tools.h>
 
 const char *trace_flt_id = "trace filter";
+const char *trace_filter_cls_name = "trace";
 
 struct flt_ops trace_ops;
 
index 58b6bbe8e69601ffd8efaac6ab58c6301904f403..3c54791b21ebd2bb7fa8a3b96ca1c3898697856a 100644 (file)
@@ -416,6 +416,8 @@ static lua_State *hlua_states[MAX_THREADS + 1];
 
 #define HLUA_FLT_CTX_FL_PAYLOAD  0x00000001
 
+const char *hlua_filter_cls_name = "lua";
+
 struct hlua_reg_filter  {
        char *name;
        int flt_ref[MAX_THREADS + 1];