]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: filters: add "filter-sequence" directive
authorAurelien DARRAGON <adarragon@haproxy.com>
Wed, 11 Mar 2026 14:37:44 +0000 (15:37 +0100)
committerAurelien DARRAGON <adarragon@haproxy.com>
Fri, 3 Apr 2026 10:10:27 +0000 (12:10 +0200)
This is another pre-requisite work for upcoming decompression filter.

In this patch we implement the "filter-sequence" directive which can be
used in proxy section (frontend,backend,listen) and takes 2 parameters

The first one is the direction (request or response), the second one
is a comma separated list of filter names previously declared on the
proxy using the "filter" keyword.

The main goal of this directive is to be able to instruct haproxy in which
order the filters should be executed on request and response paths,
especially if the ordering between request and response handling must
differ, and without relying on the filter declaration ordering (within
the proxy) which is used by default by haproxy.

Another benefit of this feature is that it becomes possible to "ignore"
a previously declared filter on the proxy. Indeed, when filter-sequence
is defined for a given direction (request/response), then it will be used
over the implicit filter ordering, but if a filter which was previously
declared is not specified in the related filter-sequence, it will not be
executed on purpose. This can be used as a way to temporarily disable a
filter without completely removing its configuration.

Documentation was updated (check examples for more info)

doc/configuration.txt
include/haproxy/filters-t.h
include/haproxy/proxy-t.h
src/filters.c
src/proxy.c

index 5e385b7610458dd370883195ca1bc79c8c4504f1..a895caaaef5ee4aac5d86bcfd8734f8ff872cd92 100644 (file)
@@ -6001,6 +6001,7 @@ external-check path                       X          -         X         X
 force-persist                             -          -         X         X
 force-be-switch                           -          X         X         -
 filter                                    -          X         X         X
+filter-sequence                           -          X         X         X
 fullconn                                  X          -         X         X
 guid                                      -          X         X         X
 hash-balance-factor                       X          -         X         X
@@ -7891,8 +7892,41 @@ filter <name> [param*]
 
         server srv1 192.168.0.1:80
 
-  See also : section 9.
+  See also : section 9., "filter-sequence"
 
+filter-sequence { request | response } <filter_list>
+
+  Specifies in which order filters declared on the proxy should be
+  executed.
+
+  May be used in the following contexts: tcp, http
+
+  May be used in sections:    defaults | frontend | listen | backend
+                                 no    |    yes   |   yes  |   yes
+
+  Comma-separated list of filter names (<filter_list>) to specify in which
+  order filters declared on the proxy should be executed, for request
+  or response path, respectively.
+
+  When filter-sequence is not specified for a given path (ie: request vs
+  response), the order in which filters are declared on the proxy is used.
+
+  If filter-sequence omits some filters that were declared on the proxy,
+  they will not be executed. This is an effective way of temporarily
+  disabling a filter without removing it from the configuration.
+
+  Example:
+     global
+        lua-load my-filter.lua # defines custom "lua.my-filter"
+     frontend myfront
+        filter comp-req
+        filter comp-res
+        filter lua.my-filter
+
+        filter-sequence request lua.my-filter,comp-req
+        filter-sequence response lua.my-filter,comp-res
+
+  See also : "filter"
 
 fullconn <conns>
   Specify at what backend load the servers will reach their maxconn
index f5a49856bd07d3592012d627d06fa75c885538d2..317c46a9bb6ac07e786da604fb388780de3c5c3c 100644 (file)
@@ -215,6 +215,12 @@ struct flt_conf {
        unsigned int    flags; /* FLT_CFG_FL_* */
 };
 
+struct filter_sequence_elt {
+       char *flt_name; /* filter name (set during parsing) */
+       struct flt_conf *flt_conf; /* associated filter conf (set after parsing) */
+       struct list list; /* list element */
+};
+
 /*
  * Structure reprensenting a filter instance attached to a stream
  *
index 5e786da3ee94e3a488d2ad176d2935fed7622d5e..8914dc5cee8f15eddb999ddf6702dc6eab299e4c 100644 (file)
@@ -509,6 +509,12 @@ struct proxy {
                                                 * name is used
                                                 */
        struct list filter_configs;             /* list of the filters that are declared on this proxy */
+       struct {                                /* sequence in which declared filters on the proxy should be execute
+                                                * (list of filter_sequence_elt)
+                                                */
+               struct list req;                /* during request handling */
+               struct list res;                /* during response handling */
+       } filter_sequence;
 
        struct guid_node guid;                  /* GUID global tree node */
        struct mt_list watcher_list;            /* list of elems which currently references this proxy instance (currently only used with backends) */
index 61fcc8fe29977f9f2038ee0a1889b252acf404df..fc60063092ec68624db8f8c9e2e0796d8c97621d 100644 (file)
@@ -297,6 +297,136 @@ parse_filter(char **args, int section_type, struct proxy *curpx,
 
 }
 
+/*
+ * Parses the "filter-sequence" keyword
+ */
+static int
+parse_filter_sequence(char **args, int section_type, struct proxy *curpx,
+                      const struct proxy *defpx, const char *file, int line, char **err)
+{
+       /* filter-sequence cannot be defined on a default proxy */
+       if (curpx == defpx) {
+               memprintf(err, "parsing [%s:%d] : %s is not allowed in a 'default' section.",
+                         file, line, args[0]);
+               return -1;
+       }
+       if (strcmp(args[0], "filter-sequence") == 0) {
+               struct list *list;
+               char *str;
+               size_t cur_sep;
+
+               if (!*args[1]) {
+                       memprintf(err,
+                                 "parsing [%s:%d] : missing argument for '%s' in %s '%s'.",
+                                 file, line, args[0], proxy_type_str(curpx), curpx->id);
+                       goto error;
+               }
+
+               if (!strcmp(args[1], "request"))
+                       list = &curpx->filter_sequence.req;
+               else if (!strcmp(args[1], "response"))
+                       list = &curpx->filter_sequence.res;
+               else {
+                       memprintf(err,
+                                 "parsing [%s:%d] : expected either 'request' or 'response' for '%s' in %s '%s'.",
+                                 file, line, args[0], proxy_type_str(curpx), curpx->id);
+                       goto error;
+               }
+
+               if (!*args[2]) {
+                       memprintf(err,
+                                 "parsing [%s:%d] : missing filter list for '%s' in %s '%s'.",
+                                 file, line, args[0], proxy_type_str(curpx), curpx->id);
+                       goto error;
+               }
+
+               str = args[2];
+               while (str[0]) {
+                       struct filter_sequence_elt *elt;
+
+                       elt = calloc(1, sizeof(*elt));
+                       if (!elt) {
+                               memprintf(err, "'%s %s' : out of memory", args[0], args[1]);
+                               goto error;
+                       }
+
+                       cur_sep = strcspn(str, ",");
+
+                       elt->flt_name = my_strndup(str, cur_sep);
+                       if (!elt->flt_name) {
+                               ha_free(&elt);
+                               goto error;
+                       }
+
+                       LIST_APPEND(list, &elt->list);
+
+                       if (str[cur_sep])
+                               str += cur_sep + 1;
+                       else
+                               str += cur_sep;
+               }
+       }
+
+       return 0;
+
+  error:
+       return -1;
+}
+
+static int compile_filter_sequence_elt(struct proxy *px, struct filter_sequence_elt *elt, char **errmsg)
+{
+       struct flt_conf *fconf;
+       int ret = ERR_NONE;
+
+       list_for_each_entry(fconf, &px->filter_configs, list) {
+               if (!strcmp(elt->flt_name, fconf->name)) {
+                       elt->flt_conf = fconf;
+                       break;
+               }
+       }
+       if (!elt->flt_conf) {
+               memprintf(errmsg, "invalid filter name: '%s' is not defined on the proxy", elt->flt_name);
+               ret = ERR_FATAL;
+       }
+
+       return ret;
+}
+
+/* after config is checked, time to resolve filter-sequence (both request and response)
+ * used on the proxy in order to associate filter names with valid flt_conf entries
+ * this will help decrease filter lookup time during runtime (filter ids are compared
+ * using their address, not string content)
+ */
+static int postcheck_filter_sequence(struct proxy *px)
+{
+       struct filter_sequence_elt *elt;
+       char *errmsg = NULL;
+       int ret = ERR_NONE;
+
+       list_for_each_entry(elt, &px->filter_sequence.req, list) {
+               ret = compile_filter_sequence_elt(px, elt, &errmsg);
+               if (ret & ERR_CODE) {
+                       memprintf(&errmsg, "error while postparsing request filter-sequence '%s' : %s", elt->flt_name, errmsg);
+                       goto error;
+               }
+       }
+       list_for_each_entry(elt, &px->filter_sequence.res, list) {
+               ret = compile_filter_sequence_elt(px, elt, &errmsg);
+               if (ret & ERR_CODE) {
+                       memprintf(&errmsg, "error while postparsing response filter-sequence '%s' : %s", elt->flt_name, errmsg);
+                       goto error;
+               }
+       }
+
+       return ret;
+
+ error:
+       ha_alert("%s: %s\n", px->id, errmsg);
+       ha_free(&errmsg);
+       return ret;
+}
+REGISTER_POST_PROXY_CHECK(postcheck_filter_sequence);
+
 /*
  * Calls 'init' callback for all filters attached to a proxy. This happens after
  * the configuration parsing. Filters can finish to fill their config. Returns
@@ -401,6 +531,7 @@ void
 flt_deinit(struct proxy *proxy)
 {
        struct flt_conf *fconf, *back;
+       struct filter_sequence_elt *fsequence, *fsequenceb;
 
        list_for_each_entry_safe(fconf, back, &proxy->filter_configs, list) {
                if (fconf->ops->deinit)
@@ -408,6 +539,16 @@ flt_deinit(struct proxy *proxy)
                LIST_DELETE(&fconf->list);
                free(fconf);
        }
+       list_for_each_entry_safe(fsequence, fsequenceb, &proxy->filter_sequence.req, list) {
+               LIST_DEL_INIT(&fsequence->list);
+               ha_free(&fsequence->flt_name);
+               ha_free(&fsequence);
+       }
+       list_for_each_entry_safe(fsequence, fsequenceb, &proxy->filter_sequence.res, list) {
+               LIST_DEL_INIT(&fsequence->list);
+               ha_free(&fsequence->flt_name);
+               ha_free(&fsequence);
+       }
 }
 
 /*
@@ -438,7 +579,7 @@ flt_deinit_all_per_thread()
 
 /* Attaches a filter to a stream. Returns -1 if an error occurs, 0 otherwise. */
 static int
-flt_stream_add_filter(struct stream *s, struct flt_conf *fconf, unsigned int flags)
+flt_stream_add_filter(struct stream *s, struct proxy *px, struct flt_conf *fconf, unsigned int flags)
 {
        struct filter *f;
 
@@ -461,18 +602,38 @@ flt_stream_add_filter(struct stream *s, struct flt_conf *fconf, unsigned int fla
        }
 
        LIST_APPEND(&strm_flt(s)->filters, &f->list);
+       LIST_INIT(&f->req_list);
+       LIST_INIT(&f->res_list);
 
-       /* for now f->req_list == f->res_list to preserve
-        * historical behavior, but the ordering will change
-        * in the future
-        */
-       LIST_APPEND(&s->req.flt.filters, &f->req_list);
-       LIST_APPEND(&s->res.flt.filters, &f->res_list);
+       /* use filter config ordering unless filter-sequence says otherwise */
+       if (LIST_ISEMPTY(&px->filter_sequence.req))
+               LIST_APPEND(&s->req.flt.filters, &f->req_list);
+       if (LIST_ISEMPTY(&px->filter_sequence.res))
+               LIST_APPEND(&s->res.flt.filters, &f->res_list);
 
        strm_flt(s)->flags |= STRM_FLT_FL_HAS_FILTERS;
        return 0;
 }
 
+static void flt_stream_organize_filters(struct stream *s, struct proxy *px)
+{
+       struct filter_sequence_elt *fsequence;
+       struct filter *filter;
+
+       list_for_each_entry(fsequence, &px->filter_sequence.req, list) {
+               list_for_each_entry(filter, &strm_flt(s)->filters, list) {
+                       if (filter->config == fsequence->flt_conf && !LIST_INLIST(&filter->req_list))
+                               LIST_APPEND(&s->req.flt.filters, &filter->req_list);
+               }
+       }
+       list_for_each_entry(fsequence, &px->filter_sequence.res, list) {
+               list_for_each_entry(filter, &strm_flt(s)->filters, list) {
+                       if (filter->config == fsequence->flt_conf && !LIST_INLIST(&filter->res_list))
+                               LIST_APPEND(&s->res.flt.filters, &filter->res_list);
+               }
+       }
+}
+
 /*
  * Called when a stream is created. It attaches all frontend filters to the
  * stream. Returns -1 if an error occurs, 0 otherwise.
@@ -489,9 +650,10 @@ flt_stream_init(struct stream *s)
        memset(&s->res.flt, 0, sizeof(s->res.flt));
        LIST_INIT(&s->res.flt.filters);
        list_for_each_entry(fconf, &strm_fe(s)->filter_configs, list) {
-               if (flt_stream_add_filter(s, fconf, 0) < 0)
+               if (flt_stream_add_filter(s, strm_fe(s), fconf, 0) < 0)
                        return -1;
        }
+       flt_stream_organize_filters(s, strm_fe(s));
        return 0;
 }
 
@@ -605,10 +767,12 @@ flt_set_stream_backend(struct stream *s, struct proxy *be)
                goto end;
 
        list_for_each_entry(fconf, &be->filter_configs, list) {
-               if (flt_stream_add_filter(s, fconf, FLT_FL_IS_BACKEND_FILTER) < 0)
+               if (flt_stream_add_filter(s, be, fconf, FLT_FL_IS_BACKEND_FILTER) < 0)
                        return -1;
        }
 
+       flt_stream_organize_filters(s, be);
+
   end:
        list_for_each_entry(filter, &strm_flt(s)->filters, list) {
                if (FLT_OPS(filter)->stream_set_backend) {
@@ -1231,6 +1395,7 @@ handle_analyzer_result(struct stream *s, struct channel *chn,
  * not enabled. */
 static struct cfg_kw_list cfg_kws = {ILH, {
                { CFG_LISTEN, "filter", parse_filter },
+               { CFG_LISTEN, "filter-sequence", parse_filter_sequence },
                { 0, NULL, NULL },
        }
 };
index 8dd10fb6f9f1a08ada6d21c6d7ba08a4b5fde692..c826397bf31aa902a44f4169ebbfe7399e85a1e1 100644 (file)
@@ -1578,6 +1578,8 @@ void init_new_proxy(struct proxy *p)
        LIST_INIT(&p->conf.lf_checks);
        LIST_INIT(&p->filter_configs);
        LIST_INIT(&p->tcpcheck.preset_vars);
+       LIST_INIT(&p->filter_sequence.req);
+       LIST_INIT(&p->filter_sequence.res);
 
        MT_LIST_INIT(&p->lbprm.lb_free_list);