From: Christopher Faulet Date: Mon, 19 Oct 2026 13:42:44 +0000 (+0200) Subject: MEDIUM: filters: eval the legacy backend filters first on the response path X-Git-Tag: v3.5-dev10~104 X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=fe75a62c006efaa27ed91624bed3d123b2483d21;p=haproxy.git MEDIUM: filters: eval the legacy backend filters first on the response path 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. --- diff --git a/doc/internals/api/filters.txt b/doc/internals/api/filters.txt index e9592cd11..5a83505f5 100644 --- a/doc/internals/api/filters.txt +++ b/doc/internals/api/filters.txt @@ -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. + diff --git a/reg-tests/filters/lua-eof.vtc b/reg-tests/filters/lua-eof.vtc index 740beabbf..751d042e0 100644 --- a/reg-tests/filters/lua-eof.vtc +++ b/reg-tests/filters/lua-eof.vtc @@ -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 == 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 == expect resp.body == "complete response" } -run diff --git a/reg-tests/filters/lua-keepalive.vtc b/reg-tests/filters/lua-keepalive.vtc index 7c6c99e4f..88e1d7e25 100644 --- a/reg-tests/filters/lua-keepalive.vtc +++ b/reg-tests/filters/lua-keepalive.vtc @@ -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 == expect resp.body == "legacy response" diff --git a/reg-tests/filters/stream-order.vtc b/reg-tests/filters/stream-order.vtc index 1d3108579..f7da84c4d 100644 --- a/reg-tests/filters/stream-order.vtc +++ b/reg-tests/filters/stream-order.vtc @@ -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 == } -run diff --git a/src/filters.c b/src/filters.c index 00a6c1c3f..fc521b811 100644 --- a/src/filters.c +++ b/src/filters.c @@ -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; + } } } }