]> git.kaiwu.me - haproxy.git/commitdiff
MEDIUM: filters: eval the backend instances first on the response path
authorChristopher Faulet <cfaulet@haproxy.com>
Mon, 19 Oct 2026 13:41:43 +0000 (15:41 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Tue, 20 Oct 2026 09:13:30 +0000 (11:13 +0200)
When the backend filters are attached to a stream, they are appended to the
global and request filter lists after the frontend ones, which is the
expected order: the request reaches the frontend filters first. But they
were also appended to the response filter list, while a response travels in
the opposite direction and reaches the backend filters first.

In the new filter mode, the backend instances are now prepended to the
response filter list, preserving their own response order, so a response is
evaluated by the backend instances before the frontend ones. The request
path is unchanged.

The regression tests relying on the response evaluation order of the
instance mode were updated accordingly.

reg-tests/filters/lua-defaults.vtc
reg-tests/filters/lua-keepalive.vtc
reg-tests/filters/lua-per-thread.vtc
reg-tests/filters/lua-resume.vtc
reg-tests/filters/lua-retry.vtc
reg-tests/filters/stream-order.vtc
src/filters.c

index 073df7d771060b0a36cc7bc98622c821567c9e88..dca28667e46e5db0f3befbb77d9ecacedafe355d 100644 (file)
@@ -108,7 +108,7 @@ client c1 -connect ${h1_first_sock} {
     txreq -req POST -body "ping"
     rxresp
     expect resp.status == 200
-    expect resp.http.x-probe-order == "F.base-b;F1.local-c;F1.local-a;BA.local-c;BA.local-d;"
+    expect resp.http.x-probe-order == "BA.local-c;BA.local-d;F.base-b;F1.local-c;F1.local-a;"
     expect resp.http.x-probe-errors == <undef>
     expect resp.body == "pong"
 } -run
@@ -119,7 +119,7 @@ client c2 -connect ${h1_sibling_sock} {
     txreq
     rxresp
     expect resp.status == 200
-    expect resp.http.x-probe-order == "F.base-a;B.base-b;B.base-c;"
+    expect resp.http.x-probe-order == "B.base-b;B.base-c;F.base-a;"
     expect resp.http.x-probe-errors == <undef>
     expect resp.body == "sibling"
 } -run
index 44cf44364be8e86aa4b4484f7d55ec23fe3ac9d7..7c6c99e4f0659114462e5c7045a700701335dd4d 100644 (file)
@@ -97,9 +97,9 @@ client c1 -connect ${h1_fe_sock} {
     txreq -req POST -url /instances -body "first"
     rxresp
     expect resp.status == 200
-    expect resp.http.x-probe-order == "F.b;F.a;D.c;D.a;"
-    expect resp.http.x-probe-instances == "F.b:1;F.a:2;D.c:1;D.a:2;"
-    expect resp.http.x-probe-request-bytes == "F.a:5;D.c:5;"
+    expect resp.http.x-probe-order == "D.c;D.a;F.b;F.a;"
+    expect resp.http.x-probe-instances == "D.c:1;D.a:2;F.b:1;F.a:2;"
+    expect resp.http.x-probe-request-bytes == "D.c:5;F.a:5;"
     expect resp.http.x-probe-errors == <undef>
     expect resp.body == "first response"
 
@@ -127,9 +127,9 @@ client c1 -connect ${h1_fe_sock} {
     txreq -req POST -url /instances -body "fourth"
     rxresp
     expect resp.status == 200
-    expect resp.http.x-probe-order == "F.b;F.a;D.c;D.a;"
-    expect resp.http.x-probe-instances == "F.b:7;F.a:8;D.c:3;D.a:4;"
-    expect resp.http.x-probe-request-bytes == "F.a:6;D.c:6;"
+    expect resp.http.x-probe-order == "D.c;D.a;F.b;F.a;"
+    expect resp.http.x-probe-instances == "D.c:3;D.a:4;F.b:7;F.a:8;"
+    expect resp.http.x-probe-request-bytes == "D.c:6;F.a:6;"
     expect resp.http.x-probe-errors == <undef>
     expect resp.body == "fourth response"
 } -run
@@ -138,8 +138,8 @@ client c2 -connect ${h1_fe_sock} {
     txreq -req POST -url /instances -body "new"
     rxresp
     expect resp.status == 200
-    expect resp.http.x-probe-instances == "F.b:9;F.a:10;D.c:5;D.a:6;"
-    expect resp.http.x-probe-request-bytes == "F.a:3;D.c:3;"
+    expect resp.http.x-probe-instances == "D.c:5;D.a:6;F.b:9;F.a:10;"
+    expect resp.http.x-probe-request-bytes == "D.c:3;F.a:3;"
     expect resp.http.x-probe-errors == <undef>
     expect resp.body == "new connection"
 } -run
index 0404e371308fb61d5d25df04b627a056cd17f37c..86620661255a692429c602f0e9ee9f01a5602059 100644 (file)
@@ -72,9 +72,9 @@ client c1 -connect ${h1_one_sock} {
     txreq
     rxresp
     expect resp.status == 200
-    expect resp.http.x-probe-order == "F.b;F.a;B.c;B.a;"
-    expect resp.http.x-probe-threads == "F.b:1;F.a:1;B.c:1;B.a:1;"
-    expect resp.http.x-probe-instances == "F.b:1;F.a:2;B.c:2;B.a:1;"
+    expect resp.http.x-probe-order == "B.c;B.a;F.b;F.a;"
+    expect resp.http.x-probe-threads == "B.c:1;B.a:1;F.b:1;F.a:1;"
+    expect resp.http.x-probe-instances == "B.c:2;B.a:1;F.b:1;F.a:2;"
     expect resp.http.x-probe-errors == <undef>
     expect resp.body == "thread one"
 } -run
@@ -83,9 +83,9 @@ client c2 -connect ${h1_two_sock} {
     txreq
     rxresp
     expect resp.status == 200
-    expect resp.http.x-probe-order == "F.b;F.a;B.c;B.a;"
-    expect resp.http.x-probe-threads == "F.b:2;F.a:2;B.c:2;B.a:2;"
-    expect resp.http.x-probe-instances == "F.b:1;F.a:2;B.c:2;B.a:1;"
+    expect resp.http.x-probe-order == "B.c;B.a;F.b;F.a;"
+    expect resp.http.x-probe-threads == "B.c:2;B.a:2;F.b:2;F.a:2;"
+    expect resp.http.x-probe-instances == "B.c:2;B.a:1;F.b:1;F.a:2;"
     expect resp.http.x-probe-errors == <undef>
     expect resp.body == "thread two"
 } -run
@@ -94,8 +94,8 @@ client c3 -connect ${h1_one_sock} {
     txreq
     rxresp
     expect resp.status == 200
-    expect resp.http.x-probe-threads == "F.b:1;F.a:1;B.c:1;B.a:1;"
-    expect resp.http.x-probe-instances == "F.b:3;F.a:4;B.c:4;B.a:3;"
+    expect resp.http.x-probe-threads == "B.c:1;B.a:1;F.b:1;F.a:1;"
+    expect resp.http.x-probe-instances == "B.c:4;B.a:3;F.b:3;F.a:4;"
     expect resp.http.x-probe-errors == <undef>
     expect resp.body == "thread one again"
 } -run
@@ -104,8 +104,8 @@ client c4 -connect ${h1_two_sock} {
     txreq
     rxresp
     expect resp.status == 200
-    expect resp.http.x-probe-threads == "F.b:2;F.a:2;B.c:2;B.a:2;"
-    expect resp.http.x-probe-instances == "F.b:3;F.a:4;B.c:4;B.a:3;"
+    expect resp.http.x-probe-threads == "B.c:2;B.a:2;F.b:2;F.a:2;"
+    expect resp.http.x-probe-instances == "B.c:4;B.a:3;F.b:3;F.a:4;"
     expect resp.http.x-probe-errors == <undef>
     expect resp.body == "thread two again"
 } -run
index e99a6ac2f1c5b07bb8164bb59a78c6dc50195102..3781be3182aff958378a4d6cbd4b1ab9c46cfa35 100644 (file)
@@ -64,9 +64,9 @@ client c1 -connect ${h1_fe_sock} {
     txreq -req POST -body "abcdefgh"
     rxresp
     expect resp.status == 200
-    expect resp.http.x-probe-order == "F.c;F.a;F.b;B.a;B.c;B.b;"
-    expect resp.http.x-probe-instances == "F.c:2;F.a:1;F.b:3;B.a:2;B.c:1;B.b:3;"
-    expect resp.http.x-probe-request-bytes == "F.b:8;B.a:8;B.c:8;"
+    expect resp.http.x-probe-order == "B.a;B.c;B.b;F.c;F.a;F.b;"
+    expect resp.http.x-probe-instances == "B.a:2;B.c:1;B.b:3;F.c:2;F.a:1;F.b:3;"
+    expect resp.http.x-probe-request-bytes == "B.a:8;B.c:8;F.b:8;"
     expect resp.http.x-probe-errors == <undef>
     expect resp.body == "response after waits"
 
@@ -76,9 +76,9 @@ client c1 -connect ${h1_fe_sock} {
     chunked ""
     rxresp
     expect resp.status == 200
-    expect resp.http.x-probe-order == "F.c;F.a;F.b;B.a;B.c;B.b;"
-    expect resp.http.x-probe-instances == "F.c:5;F.a:4;F.b:6;B.a:5;B.c:4;B.b:6;"
-    expect resp.http.x-probe-request-bytes == "F.b:8;B.a:8;B.c:8;"
+    expect resp.http.x-probe-order == "B.a;B.c;B.b;F.c;F.a;F.b;"
+    expect resp.http.x-probe-instances == "B.a:5;B.c:4;B.b:6;F.c:5;F.a:4;F.b:6;"
+    expect resp.http.x-probe-request-bytes == "B.a:8;B.c:8;F.b:8;"
     expect resp.http.x-probe-errors == <undef>
     expect resp.body == "another response"
     barrier b_drained sync
index afc5b6f8313e73660f22f18580a06cf59bc104a9..56a66103031046dc139899f6b68f82b63b84136e 100644 (file)
@@ -66,8 +66,8 @@ client c1 -connect ${h1_fe_sock} {
     expect resp.status == 200
     expect resp.http.x-retries == "1"
     expect resp.http.x-redispatched == "1"
-    expect resp.http.x-probe-order == "F.b;F.a;B.a;B.c;"
-    expect resp.http.x-probe-instances == "F.b:1;F.a:2;B.a:2;B.c:1;"
+    expect resp.http.x-probe-order == "B.a;B.c;F.b;F.a;"
+    expect resp.http.x-probe-instances == "B.a:2;B.c:1;F.b:1;F.a:2;"
     expect resp.http.x-probe-errors == <undef>
     expect resp.body == "successful retry"
     barrier b_drained sync
index f2f16847077172cf0916762feffd5e387c9203c5..1d3108579e730dce62908863f89eae0f61445ad9 100644 (file)
@@ -58,7 +58,7 @@ client c1 -connect ${h1_fe_sock} {
     txreq -req POST -body "ping"
     rxresp
     expect resp.status == 200
-    expect resp.http.x-filter-order == "Fb:3;Fc:1;Fa:2;Ba:2;Bb:1;Bc:3;"
+    expect resp.http.x-filter-order == "Ba:2;Bb:1;Bc:3;Fb:3;Fc:1;Fa:2;"
     expect resp.http.x-filter-start == <undef>
     expect resp.body == "pong"
 } -run
@@ -100,7 +100,7 @@ client c2 -connect ${h2_fe_sock} {
     txreq
     rxresp
     expect resp.status == 200
-    expect resp.http.x-filter-order == "Fa:1;Fb:2;Ba:1;Bb:2;"
+    expect resp.http.x-filter-order == "Ba:1;Bb:2;Fa:1;Fb:2;"
     expect resp.http.x-filter-start == <undef>
 } -run
 
index cd3a82817b6394d3b23df30cdfa48a67573e8e3c..00a6c1c3fd3337c2826797f05ff4eaede0c5c759 100644 (file)
@@ -1028,6 +1028,11 @@ flt_stream_add_filter(struct stream *s, struct flt_conf *fconf, unsigned int fla
  * configuration pointers to build the channel lists; disabled, incompatible
  * or declined filters have no stream filter instance to link. Never store
  * per-stream pointers in shared filter instances.
+ *
+ * The request list is always filled in the proxy's request order. The
+ * response list is filled in the proxy's response order for the frontend
+ * filters, but the backend filters are prepended, so a response reaches the
+ * backend filters before the frontend ones.
  */
 static int flt_stream_add_proxy_filters(struct stream *s, struct proxy *px, unsigned int flags)
 {
@@ -1080,13 +1085,27 @@ static int flt_stream_add_proxy_filters(struct stream *s, struct proxy *px, unsi
                        }
                }
        }
-       list_for_each_entry(inst, &px->filter_res_instances, res.list) {
-               if (!inst->enabled || !inst->fconf)
-                       continue;
-               list_for_each_entry(filter, &strm_flt(s)->filters, list) {
-                       if (filter->config == inst->fconf) {
-                               LIST_APPEND(&s->res.flt.filters, &filter->res_list);
-                               break;
+       if (flags & FLT_FL_IS_BACKEND_FILTER) {
+               list_for_each_entry_rev(inst, &px->filter_res_instances, res.list) {
+                       if (!inst->enabled || !inst->fconf)
+                               continue;
+                       list_for_each_entry(filter, &strm_flt(s)->filters, list) {
+                               if (filter->config == inst->fconf) {
+                                       LIST_INSERT(&s->res.flt.filters, &filter->res_list);
+                                       break;
+                               }
+                       }
+               }
+       }
+       else {
+               list_for_each_entry(inst, &px->filter_res_instances, res.list) {
+                       if (!inst->enabled || !inst->fconf)
+                               continue;
+                       list_for_each_entry(filter, &strm_flt(s)->filters, list) {
+                               if (filter->config == inst->fconf) {
+                                       LIST_APPEND(&s->res.flt.filters, &filter->res_list);
+                                       break;
+                               }
                        }
                }
        }