]> git.kaiwu.me - haproxy.git/commitdiff
MEDIUM: filters: eval the legacy backend filters first on the response path
authorChristopher Faulet <cfaulet@haproxy.com>
Mon, 19 Oct 2026 13:42:44 +0000 (15:42 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Tue, 20 Oct 2026 09:13:30 +0000 (11:13 +0200)
In the legacy filter mode, the backend filter configurations are now
prepended to the response filter list when they are attached to a stream,
preserving their own response order, so a response is evaluated by the
backend filters before the frontend ones. The request path is unchanged.

This mirrors the behavior of the new filter mode on the response path: a
response travels in the opposite direction of a request and reaches the
backend filters first.

The regression tests relying on the response evaluation order of the legacy
mode were updated accordingly, including the mixed cases of the stream-order
test where a legacy frontend uses a instance-mode backend and the other way
around. The filters API documentation was also updated: the frontend filters
are called first on the request path, the backend ones on the response path.

doc/internals/api/filters.txt
reg-tests/filters/lua-eof.vtc
reg-tests/filters/lua-keepalive.vtc
reg-tests/filters/stream-order.vtc
src/filters.c

index e9592cd1135484675229f016ef0b29e17b965e7a..5a83505f55406ecaecf9c7ba800b0c07b1f5b275 100644 (file)
@@ -121,12 +121,15 @@ by a single stream.
 Filters are designed to be chained. It is possible to declare several filters in
 the same proxy section. The evaluation order is the declaration order in legacy
 mode, and the class order - possibly refined with "filter-sequence" - in the
-new mode. Frontend and backend filters are also chained, frontend ones called
-first. Even if the filters processing is serialized, each filter will behave
-as it was alone (unless it was developed to be aware of other filters). For
-all that, some constraints are imposed to filters, especially when data
-exchanged between the client and the server are processed. We will discuss
-again these constraints when we will tackle the subject of writing a filter.
+new mode. Frontend and backend filters are also chained: on the request
+path the frontend ones are called first, and on the response path the
+backend ones are called first. Even if the filters processing is
+serialized, each filter will behave as it was alone (unless it was
+developed to be aware of other filters). For all that, some constraints
+are imposed to filters, especially when data exchanged between the
+client and the server are processed. We will discuss again these
+constraints when we will tackle the subject of writing a filter.
+
 
 
 
index 740beabbffeba8fbc69e76304d4da6b279592750..751d042e0ad2d79870eb1a2aa9e2ebd119015930 100644 (file)
@@ -51,7 +51,7 @@ client c1 -connect ${h1_fe_sock} {
     chunked ""
     rxresp
     expect resp.status == 200
-    expect resp.http.x-probe-order == "F.a;F.b;B.a;B.b;"
+    expect resp.http.x-probe-order == "B.a;B.b;F.a;F.b;"
     expect resp.http.x-probe-errors == <undef>
     expect resp.body == "complete response"
 } -run
@@ -98,7 +98,7 @@ client c2 -connect ${h2_fe_sock} {
     chunked ""
     rxresp
     expect resp.status == 200
-    expect resp.http.x-probe-order == "F.a;F.b;B.a;B.b;"
+    expect resp.http.x-probe-order == "B.a;B.b;F.a;F.b;"
     expect resp.http.x-probe-errors == <undef>
     expect resp.body == "complete response"
 } -run
index 7c6c99e4f0659114462e5c7045a700701335dd4d..88e1d7e25567ae2b30a7517237562b442102c905 100644 (file)
@@ -106,9 +106,9 @@ client c1 -connect ${h1_fe_sock} {
     txreq -req POST -url /legacy -body "second"
     rxresp
     expect resp.status == 200
-    expect resp.http.x-probe-order == "F.b;F.a;L.a;L.c;"
-    expect resp.http.x-probe-instances == "F.b:3;F.a:4;L.a:1;L.c:2;"
-    expect resp.http.x-probe-request-bytes == "F.a:6;L.a:6;"
+    expect resp.http.x-probe-order == "L.a;L.c;F.b;F.a;"
+    expect resp.http.x-probe-instances == "L.a:1;L.c:2;F.b:3;F.a:4;"
+    expect resp.http.x-probe-request-bytes == "L.a:6;F.a:6;"
     expect resp.http.x-probe-errors == <undef>
     expect resp.body == "legacy response"
 
index 1d3108579e730dce62908863f89eae0f61445ad9..f7da84c4d1861bdc1bdaf868d30ce4e464184ff1 100644 (file)
@@ -142,7 +142,7 @@ client c3 -connect ${h3_fe_sock} {
     txreq
     rxresp
     expect resp.status == 200
-    expect resp.http.x-filter-order == "Fa:2;Fb:1;Ba:1;Bb:2;"
+    expect resp.http.x-filter-order == "Ba:1;Bb:2;Fa:2;Fb:1;"
     expect resp.http.x-filter-start == <undef>
 } -run
 
index 00a6c1c3fd3337c2826797f05ff4eaede0c5c759..fc521b811846f62ef754a42a04bf55bfbceb7b88 100644 (file)
@@ -1043,7 +1043,10 @@ static int flt_stream_add_proxy_filters(struct stream *s, struct proxy *px, unsi
        if (flt_use_legacy_filter(px)) {
                /* The attach callbacks are called in the global order, then
                 * the stream's request and response filter lists are filled
-                * with the proxy's request and response orders.
+                * with the proxy's request and response orders. The backend
+                * filters are prepended to the response list, so they are
+                * evaluated before the frontend filters: a response reaches
+                * the backend filters first.
                 */
                list_for_each_entry(fconf, &px->filter_configs, list) {
                        if (flt_stream_add_filter(s, fconf, flags) < 0)
@@ -1057,11 +1060,23 @@ static int flt_stream_add_proxy_filters(struct stream *s, struct proxy *px, unsi
                                }
                        }
                }
-               list_for_each_entry(fconf, &px->filters_res_configs, by_res) {
-                       list_for_each_entry(filter, &strm_flt(s)->filters, list) {
-                               if (filter->config == fconf) {
-                                       LIST_APPEND(&s->res.flt.filters, &filter->res_list);
-                                       break;
+               if (flags & FLT_FL_IS_BACKEND_FILTER) {
+                       list_for_each_entry_rev(fconf, &px->filters_res_configs, by_res) {
+                               list_for_each_entry(filter, &strm_flt(s)->filters, list) {
+                                       if (filter->config == fconf) {
+                                               LIST_INSERT(&s->res.flt.filters, &filter->res_list);
+                                               break;
+                                       }
+                               }
+                       }
+               }
+               else {
+                       list_for_each_entry(fconf, &px->filters_res_configs, by_res) {
+                               list_for_each_entry(filter, &strm_flt(s)->filters, list) {
+                                       if (filter->config == fconf) {
+                                               LIST_APPEND(&s->res.flt.filters, &filter->res_list);
+                                               break;
+                                       }
                                }
                        }
                }