From ebd942545e4b902c2e4b5136a2ebf5d806e74a9d Mon Sep 17 00:00:00 2001 From: Andrea Cosentino Date: Thu, 6 Aug 2026 00:40:21 +0200 Subject: [PATCH 1/2] CAMEL-24360: camel-undertow - use UndertowHeaderFilterStrategy as the endpoint default DefaultUndertowHttpBinding installs an UndertowHeaderFilterStrategy in its constructor, but UndertowEndpoint defaulted its own headerFilterStrategy to the base HttpHeaderFilterStrategy and pushed that into the binding when lazily creating it, overwriting the undertow-specific strategy. Unless the user supplied a custom binding or a custom headerFilterStrategy, the binding ran the base strategy and the undertow-specific filtering never executed. Two behaviours that are documented as shipped were therefore inert on endpoint-configured routes: * the legacy websocket.* Exchange-header prefix added to the in and out filters by CAMEL-23588 (4.14.8 / 4.18.3 / 4.21.0); * the io.undertow.util.HttpString.tryFromString header-name validation in UndertowHeaderFilterStrategy.applyFilterToExternalHeaders, which skips header names undertow does not accept. The endpoint now defaults to UndertowHeaderFilterStrategy, so both take effect. Rest DSL consumers are unchanged: UndertowComponent already assigns UndertowRestHeaderFilterStrategy, which extends UndertowHeaderFilterStrategy. Endpoints that configure headerFilterStrategy or undertowHttpBinding explicitly keep their existing behaviour. Adds three UndertowEndpointTest cases covering the endpoint default, the strategy the lazily created binding ends up running together with the websocket prefixes actually being filtered, and an explicitly configured strategy still reaching the binding. Adds a 4.22 upgrade-guide entry describing the change and how to restore the previous behaviour. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Andrea Cosentino --- .../component/undertow/UndertowEndpoint.java | 3 +- .../undertow/UndertowEndpointTest.java | 36 +++++++++++++++++++ .../pages/camel-4x-upgrade-guide-4_22.adoc | 32 +++++++++++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowEndpoint.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowEndpoint.java index 358c154d1f7ab..d97ae90edb266 100644 --- a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowEndpoint.java +++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowEndpoint.java @@ -37,7 +37,6 @@ import org.apache.camel.component.undertow.UndertowConstants.EventType; import org.apache.camel.component.undertow.handlers.CamelWebSocketHandler; import org.apache.camel.component.undertow.spi.UndertowSecurityProvider; -import org.apache.camel.http.base.HttpHeaderFilterStrategy; import org.apache.camel.http.base.OAuthHttpSecuritySupport; import org.apache.camel.http.base.OAuthProfileAwareHttpEndpoint; import org.apache.camel.http.base.cookie.CookieHandler; @@ -85,7 +84,7 @@ public class UndertowEndpoint extends DefaultEndpoint @UriParam(label = "advanced") private AccessLogReceiver accessLogReceiver; @UriParam(label = "advanced") - private HeaderFilterStrategy headerFilterStrategy = new HttpHeaderFilterStrategy(); + private HeaderFilterStrategy headerFilterStrategy = new UndertowHeaderFilterStrategy(); @UriParam(label = "security") private SSLContextParameters sslContextParameters; @UriParam(label = "consumer") diff --git a/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/UndertowEndpointTest.java b/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/UndertowEndpointTest.java index 817a13a9da7fc..dfc02619b457c 100644 --- a/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/UndertowEndpointTest.java +++ b/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/UndertowEndpointTest.java @@ -18,10 +18,15 @@ import java.net.URI; +import org.apache.camel.http.base.HttpHeaderFilterStrategy; +import org.apache.camel.spi.HeaderFilterStrategy; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; public class UndertowEndpointTest { @@ -47,4 +52,35 @@ public void nonEmptyPathShouldBeKeptSame() { endpoint.setHttpURI(withSlash); assertEquals(withSlash, endpoint.getHttpURI()); } + + @Test + void defaultHeaderFilterStrategyIsUndertowSpecific() { + assertInstanceOf(UndertowHeaderFilterStrategy.class, endpoint.getHeaderFilterStrategy()); + } + + @Test + void defaultBindingKeepsUndertowHeaderFilterStrategy() { + // the endpoint pushes its own strategy into the lazily created binding, so the endpoint default + // decides which strategy the binding ends up running + DefaultUndertowHttpBinding binding + = assertInstanceOf(DefaultUndertowHttpBinding.class, endpoint.getUndertowHttpBinding()); + HeaderFilterStrategy strategy = binding.getHeaderFilterStrategy(); + assertInstanceOf(UndertowHeaderFilterStrategy.class, strategy); + + // the undertow-specific prefixes added by CAMEL-23588 must therefore be in effect + assertTrue(strategy.applyFilterToExternalHeaders(UndertowConstants.CONNECTION_KEY, "aValue", null)); + assertTrue(strategy.applyFilterToExternalHeaders(UndertowConstants.CONNECTION_KEY_LIST, "aValue", null)); + assertTrue(strategy.applyFilterToExternalHeaders(UndertowConstants.SEND_TO_ALL, "aValue", null)); + assertTrue(strategy.applyFilterToCamelHeaders(UndertowConstants.CONNECTION_KEY, "aValue", null)); + } + + @Test + void explicitHeaderFilterStrategyIsHandedToTheBinding() { + HeaderFilterStrategy custom = new HttpHeaderFilterStrategy(); + endpoint.setHeaderFilterStrategy(custom); + + DefaultUndertowHttpBinding binding + = assertInstanceOf(DefaultUndertowHttpBinding.class, endpoint.getUndertowHttpBinding()); + assertSame(custom, binding.getHeaderFilterStrategy()); + } } diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc index a6b8fb0663265..5bb33211e540a 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc @@ -1619,3 +1619,35 @@ only once. In a Spring Boot application, Spring AI's auto-configured `ToolCallbackResolver` is built from the `ToolCallback` and `ToolCallbackProvider` beans in the context. A bare `@Bean Function<...>` is no longer resolvable by bean name; expose such tools as `ToolCallback` or `ToolCallbackProvider` beans. + +=== camel-undertow - UndertowHeaderFilterStrategy is now the endpoint default + +`UndertowEndpoint` defaulted its `headerFilterStrategy` to the base +`HttpHeaderFilterStrategy`, and pushed that strategy into the `DefaultUndertowHttpBinding` +it creates lazily, overwriting the `UndertowHeaderFilterStrategy` that the binding installs +in its own constructor. The undertow-specific filtering was therefore not applied on +endpoint-configured routes. + +The endpoint now defaults to `UndertowHeaderFilterStrategy`, which makes two already +documented behaviours take effect: + +* The legacy `websocket.*` Exchange-header prefix, added to the in and out filters in + 4.14.8 / 4.18.3 / 4.21.0 (see the 4.18 upgrade guide), is now filtered at the undertow + transport boundary as described there. +* Header names that undertow does not accept (those for which + `io.undertow.util.HttpString.tryFromString` returns `null`) are skipped when mapping + external headers in, rather than being mapped onto the message. + +Ordinary application headers are unaffected, and Rest DSL consumers already used an +undertow-specific strategy (`UndertowRestHeaderFilterStrategy`) so their behaviour does not +change. Routes that relied on `websocket.*` headers crossing the undertow boundary in either +direction, and routes that relied on undertow-invalid header names being mapped, can restore +the previous behaviour by configuring `headerFilterStrategy` explicitly on the endpoint: + +[source,java] +---- +from("undertow:http://0.0.0.0:8080/foo?headerFilterStrategy=#myStrategy") +---- + +Routes that already supply a custom `headerFilterStrategy` or a custom `undertowHttpBinding` +are unaffected. From ff954f9b25955c16029d68dcd4fbd6677f4a721a Mon Sep 17 00:00:00 2001 From: Andrea Cosentino Date: Thu, 6 Aug 2026 10:45:20 +0200 Subject: [PATCH 2/2] CAMEL-24360: use AssertJ assertions in the new UndertowEndpointTest cases Addresses review feedback on #25367. The project convention is to prefer AssertJ over JUnit assertions in new test code. assertj-core was not on the camel-undertow test classpath, so it is declared here as a test dependency; the version comes from the parent dependencyManagement, matching how other modules declare it. Only the three new test methods are converted. The two pre-existing methods keep their JUnit assertions, per the convention that touched code migrates without sweeping the whole file. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Andrea Cosentino --- components/camel-undertow/pom.xml | 5 ++++ .../undertow/UndertowEndpointTest.java | 28 +++++++++---------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/components/camel-undertow/pom.xml b/components/camel-undertow/pom.xml index 5ed99a6b734e0..eb7010762def7 100644 --- a/components/camel-undertow/pom.xml +++ b/components/camel-undertow/pom.xml @@ -133,6 +133,11 @@ ${awaitility-version} test + + org.assertj + assertj-core + test + org.eclipse.jetty.http2 jetty-http2-client diff --git a/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/UndertowEndpointTest.java b/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/UndertowEndpointTest.java index dfc02619b457c..75a8be7ffd02e 100644 --- a/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/UndertowEndpointTest.java +++ b/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/UndertowEndpointTest.java @@ -23,10 +23,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; public class UndertowEndpointTest { @@ -55,23 +53,23 @@ public void nonEmptyPathShouldBeKeptSame() { @Test void defaultHeaderFilterStrategyIsUndertowSpecific() { - assertInstanceOf(UndertowHeaderFilterStrategy.class, endpoint.getHeaderFilterStrategy()); + assertThat(endpoint.getHeaderFilterStrategy()).isInstanceOf(UndertowHeaderFilterStrategy.class); } @Test void defaultBindingKeepsUndertowHeaderFilterStrategy() { // the endpoint pushes its own strategy into the lazily created binding, so the endpoint default // decides which strategy the binding ends up running - DefaultUndertowHttpBinding binding - = assertInstanceOf(DefaultUndertowHttpBinding.class, endpoint.getUndertowHttpBinding()); - HeaderFilterStrategy strategy = binding.getHeaderFilterStrategy(); - assertInstanceOf(UndertowHeaderFilterStrategy.class, strategy); + assertThat(endpoint.getUndertowHttpBinding()).isInstanceOf(DefaultUndertowHttpBinding.class); + HeaderFilterStrategy strategy + = ((DefaultUndertowHttpBinding) endpoint.getUndertowHttpBinding()).getHeaderFilterStrategy(); + assertThat(strategy).isInstanceOf(UndertowHeaderFilterStrategy.class); // the undertow-specific prefixes added by CAMEL-23588 must therefore be in effect - assertTrue(strategy.applyFilterToExternalHeaders(UndertowConstants.CONNECTION_KEY, "aValue", null)); - assertTrue(strategy.applyFilterToExternalHeaders(UndertowConstants.CONNECTION_KEY_LIST, "aValue", null)); - assertTrue(strategy.applyFilterToExternalHeaders(UndertowConstants.SEND_TO_ALL, "aValue", null)); - assertTrue(strategy.applyFilterToCamelHeaders(UndertowConstants.CONNECTION_KEY, "aValue", null)); + assertThat(strategy.applyFilterToExternalHeaders(UndertowConstants.CONNECTION_KEY, "aValue", null)).isTrue(); + assertThat(strategy.applyFilterToExternalHeaders(UndertowConstants.CONNECTION_KEY_LIST, "aValue", null)).isTrue(); + assertThat(strategy.applyFilterToExternalHeaders(UndertowConstants.SEND_TO_ALL, "aValue", null)).isTrue(); + assertThat(strategy.applyFilterToCamelHeaders(UndertowConstants.CONNECTION_KEY, "aValue", null)).isTrue(); } @Test @@ -79,8 +77,8 @@ void explicitHeaderFilterStrategyIsHandedToTheBinding() { HeaderFilterStrategy custom = new HttpHeaderFilterStrategy(); endpoint.setHeaderFilterStrategy(custom); - DefaultUndertowHttpBinding binding - = assertInstanceOf(DefaultUndertowHttpBinding.class, endpoint.getUndertowHttpBinding()); - assertSame(custom, binding.getHeaderFilterStrategy()); + assertThat(endpoint.getUndertowHttpBinding()).isInstanceOf(DefaultUndertowHttpBinding.class); + assertThat(((DefaultUndertowHttpBinding) endpoint.getUndertowHttpBinding()).getHeaderFilterStrategy()) + .isSameAs(custom); } }