From ff4001554a4e95d4cf866aeb0aba222f92485df8 Mon Sep 17 00:00:00 2001 From: croway Date: Sun, 6 Sep 2026 11:58:41 +0200 Subject: [PATCH 1/2] CAMEL-24625: camel-platform-http-starter - wire up stripUriPrefix consumer option The platform-http consumer option stripUriPrefix (added to PlatformHttpEndpoint in apache/camel via CAMEL-24625 / apache/camel#26114) was a no-op on the Spring Boot (servlet) platform-http engine: nothing stripped the registered consumer path from CamelHttpPath. Wire it up in SpringBootPlatformHttpConsumer, mirroring the Vert.x engine, so that combined with the http producer's bridgeEndpoint option a platform-http route becomes a path-based reverse proxy: from("platform-http:/reverse-proxy?matchOnUriPrefix=true&stripUriPrefix=true") .to("http://backend?bridgeEndpoint=true") // /reverse-proxy/get -> http://backend/get Only CamelHttpPath is rewritten; CamelHttpUri/CamelHttpUrl are left untouched. --- .../SpringBootPlatformHttpConsumer.java | 5 + ...formHttpStripUriPrefixContextPathTest.java | 105 +++++++++++++++ ...ingBootPlatformHttpStripUriPrefixTest.java | 125 ++++++++++++++++++ 3 files changed, 235 insertions(+) create mode 100644 components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpStripUriPrefixContextPathTest.java create mode 100644 components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpStripUriPrefixTest.java diff --git a/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpConsumer.java b/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpConsumer.java index 26fc2756998..b0ee498677f 100644 --- a/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpConsumer.java +++ b/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpConsumer.java @@ -150,6 +150,11 @@ protected void handleService(HttpServletRequest request, HttpServletResponse res msg.init(exchange, binding, request, response); String contextPath = getEndpoint().getPath(); exchange.getIn().setHeader(SpringBootPlatformHttpConstants.CONTEXT_PATH, contextPath); + if (getEndpoint().isStripUriPrefix()) { + String httpPath = (String) exchange.getIn().getHeader(Exchange.HTTP_PATH); + exchange.getIn().setHeader(Exchange.HTTP_PATH, + org.apache.camel.http.base.HttpHelper.stripUriPrefix(httpPath, contextPath)); + } if (getEndpoint().isUseCookieHandler()) { exchange.setProperty(Exchange.COOKIE_HANDLER, new SpringBootCookieHandler(request, response)); } diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpStripUriPrefixContextPathTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpStripUriPrefixContextPathTest.java new file mode 100644 index 00000000000..d24bab1ff39 --- /dev/null +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpStripUriPrefixContextPathTest.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.platform.http.springboot; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.equalTo; + +import io.restassured.RestAssured; +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.http.springboot.HttpComponentAutoConfiguration; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit6.CamelSpringBootTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; + +/** + * Verifies that stripUriPrefix keeps working correctly when the Spring Boot application also has a non-default + * {@code server.servlet.context-path} configured (see {@link SpringBootPlatformHttpContextPathTest} for the same setup + * applied to plain platform-http/REST DSL routes). The servlet context path is already excluded from CamelHttpPath by + * the servlet container itself, before Camel sees the request; stripUriPrefix then additionally removes the + * platform-http consumer's own registered path on top of that - so a client request under both prefixes ends up with + * neither at the backend. + */ +@EnableAutoConfiguration +@CamelSpringBootTest +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, + SpringBootPlatformHttpStripUriPrefixContextPathTest.class, + SpringBootPlatformHttpStripUriPrefixContextPathTest.TestConfiguration.class, + PlatformHttpComponentAutoConfiguration.class, SpringBootPlatformHttpAutoConfiguration.class, + HttpComponentAutoConfiguration.class }, properties = { "server.servlet.context-path=/test" }) +public class SpringBootPlatformHttpStripUriPrefixContextPathTest { + + @Autowired + private Environment env; + + @Autowired + private CamelContext camelContext; + + @BeforeEach + void setUp() throws Exception { + RestAssured.port = env.getRequiredProperty("local.server.port", Integer.class); + + // the bridged target includes the context path, since that path prefix is required for every servlet + // mapping of this application, backend included - only the platform-http consumer path is stripped by + // stripUriPrefix + if (camelContext.getRoute("reverse-proxy-strip") == null) { + final String backend = "http://localhost:" + RestAssured.port + "/test"; + camelContext.addRoutes(new RouteBuilder() { + @Override + public void configure() { + from("platform-http:/reverse-proxy?matchOnUriPrefix=true&stripUriPrefix=true") + .routeId("reverse-proxy-strip").to(backend + "?bridgeEndpoint=true"); + } + }); + } + } + + @Configuration + public static class TestConfiguration { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http.authorizeHttpRequests(auth -> auth.anyRequest().permitAll()).csrf(csrf -> csrf.disable()); + return http.build(); + } + + @Bean + public RouteBuilder backendRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("platform-http:/get").routeId("backend-get").setBody().simple("get:${header.CamelHttpQuery}"); + } + }; + } + } + + @Test + void stripUriPrefixCombinesWithTheServletContextPath() { + given().when().get("/test/reverse-proxy/get?arg1=val1").then().statusCode(200).body(equalTo("get:arg1=val1")); + } +} diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpStripUriPrefixTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpStripUriPrefixTest.java new file mode 100644 index 00000000000..5fa572bc616 --- /dev/null +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpStripUriPrefixTest.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.platform.http.springboot; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.equalTo; + +import io.restassured.RestAssured; +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.http.springboot.HttpComponentAutoConfiguration; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.test.spring.junit6.CamelSpringBootTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; + +/** + * Verifies the stripUriPrefix consumer option on the Spring Boot (servlet) platform-http engine: combined with the http + * producer's bridgeEndpoint=true, it turns a platform-http route into a path-based reverse proxy that forwards only the + * path relative to the consumer, with zero header manipulation - matching the behavior already wired for the Vert.x + * platform-http engine. + *

+ * The "backend" is a second platform-http route deployed on the very same embedded server: this avoids relying on + * WireMock, whose Spring Boot integration is currently incompatible with Spring Boot 4's Jetty version in this module + * (see the disabled {@link SpringBootPlatformHttpBridgedEndpointTest} and {@link SpringBootPlatformHttpProxyTest}). + */ +@EnableAutoConfiguration +@CamelSpringBootTest +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, + SpringBootPlatformHttpStripUriPrefixTest.class, + SpringBootPlatformHttpStripUriPrefixTest.TestConfiguration.class, PlatformHttpComponentAutoConfiguration.class, + SpringBootPlatformHttpAutoConfiguration.class, HttpComponentAutoConfiguration.class }) +public class SpringBootPlatformHttpStripUriPrefixTest { + + @Autowired + private Environment env; + + @Autowired + private CamelContext camelContext; + + @BeforeEach + void setUp() throws Exception { + RestAssured.port = env.getRequiredProperty("local.server.port", Integer.class); + + // the reverse-proxy routes bridge to a real loopback HTTP call, so they need the actual (random) server + // port and are therefore added once the port is known, rather than as a statically configured @Bean route + if (camelContext.getRoute("reverse-proxy-strip") == null) { + final String backend = "http://localhost:" + RestAssured.port; + camelContext.addRoutes(new RouteBuilder() { + @Override + public void configure() { + from("platform-http:/reverse-proxy?matchOnUriPrefix=true&stripUriPrefix=true") + .routeId("reverse-proxy-strip").to(backend + "?bridgeEndpoint=true"); + + from("platform-http:/reverse-proxy-control?matchOnUriPrefix=true").routeId("reverse-proxy-control") + .to(backend + "?bridgeEndpoint=true"); + } + }); + } + } + + @Configuration + public static class TestConfiguration { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http.authorizeHttpRequests(auth -> auth.anyRequest().permitAll()).csrf(csrf -> csrf.disable()); + return http.build(); + } + + @Bean + public RouteBuilder backendRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + // the "downstream" backend service, reachable only at these exact paths + from("platform-http:/get").routeId("backend-get").setBody().simple("get:${header.CamelHttpQuery}"); + + from("platform-http:/reverse-proxy-control/get").routeId("backend-control-get").setBody() + .simple("control:${header.CamelHttpQuery}"); + + from("platform-http:/").routeId("backend-root").setBody().constant("root"); + } + }; + } + } + + @Test + void stripUriPrefixRemovesTheConsumerPathBeforeBridging() { + given().when().get("/reverse-proxy/get?arg1=val1").then().statusCode(200).body(equalTo("get:arg1=val1")); + } + + @Test + void withoutStripUriPrefixTheFullPathIsForwardedUnchanged() { + given().when().get("/reverse-proxy-control/get?arg1=val1").then().statusCode(200) + .body(equalTo("control:arg1=val1")); + } + + @Test + void stripUriPrefixOnAnExactMatchLeavesTheRootPath() { + given().when().get("/reverse-proxy").then().statusCode(200).body(equalTo("root")); + } +} From 7f9e87c9421657e73c76bbc5d32d45c3d1ed7497 Mon Sep 17 00:00:00 2001 From: croway Date: Sun, 6 Sep 2026 12:04:32 +0200 Subject: [PATCH 2/2] CAMEL-24625: Simplify stripUriPrefix tests to a single integration test Drop the duplicate servlet-context-path test class and the two extra scenarios in favor of one end-to-end integration test covering the reverse-proxy usecase (stripUriPrefix + matchOnUriPrefix + bridgeEndpoint). --- ...formHttpStripUriPrefixContextPathTest.java | 105 ------------------ ...ingBootPlatformHttpStripUriPrefixTest.java | 49 +++----- 2 files changed, 18 insertions(+), 136 deletions(-) delete mode 100644 components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpStripUriPrefixContextPathTest.java diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpStripUriPrefixContextPathTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpStripUriPrefixContextPathTest.java deleted file mode 100644 index d24bab1ff39..00000000000 --- a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpStripUriPrefixContextPathTest.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.camel.component.platform.http.springboot; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.Matchers.equalTo; - -import io.restassured.RestAssured; -import org.apache.camel.CamelContext; -import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.component.http.springboot.HttpComponentAutoConfiguration; -import org.apache.camel.spring.boot.CamelAutoConfiguration; -import org.apache.camel.test.spring.junit6.CamelSpringBootTest; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.web.SecurityFilterChain; - -/** - * Verifies that stripUriPrefix keeps working correctly when the Spring Boot application also has a non-default - * {@code server.servlet.context-path} configured (see {@link SpringBootPlatformHttpContextPathTest} for the same setup - * applied to plain platform-http/REST DSL routes). The servlet context path is already excluded from CamelHttpPath by - * the servlet container itself, before Camel sees the request; stripUriPrefix then additionally removes the - * platform-http consumer's own registered path on top of that - so a client request under both prefixes ends up with - * neither at the backend. - */ -@EnableAutoConfiguration -@CamelSpringBootTest -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class, - SpringBootPlatformHttpStripUriPrefixContextPathTest.class, - SpringBootPlatformHttpStripUriPrefixContextPathTest.TestConfiguration.class, - PlatformHttpComponentAutoConfiguration.class, SpringBootPlatformHttpAutoConfiguration.class, - HttpComponentAutoConfiguration.class }, properties = { "server.servlet.context-path=/test" }) -public class SpringBootPlatformHttpStripUriPrefixContextPathTest { - - @Autowired - private Environment env; - - @Autowired - private CamelContext camelContext; - - @BeforeEach - void setUp() throws Exception { - RestAssured.port = env.getRequiredProperty("local.server.port", Integer.class); - - // the bridged target includes the context path, since that path prefix is required for every servlet - // mapping of this application, backend included - only the platform-http consumer path is stripped by - // stripUriPrefix - if (camelContext.getRoute("reverse-proxy-strip") == null) { - final String backend = "http://localhost:" + RestAssured.port + "/test"; - camelContext.addRoutes(new RouteBuilder() { - @Override - public void configure() { - from("platform-http:/reverse-proxy?matchOnUriPrefix=true&stripUriPrefix=true") - .routeId("reverse-proxy-strip").to(backend + "?bridgeEndpoint=true"); - } - }); - } - } - - @Configuration - public static class TestConfiguration { - - @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - http.authorizeHttpRequests(auth -> auth.anyRequest().permitAll()).csrf(csrf -> csrf.disable()); - return http.build(); - } - - @Bean - public RouteBuilder backendRouteBuilder() { - return new RouteBuilder() { - @Override - public void configure() { - from("platform-http:/get").routeId("backend-get").setBody().simple("get:${header.CamelHttpQuery}"); - } - }; - } - } - - @Test - void stripUriPrefixCombinesWithTheServletContextPath() { - given().when().get("/test/reverse-proxy/get?arg1=val1").then().statusCode(200).body(equalTo("get:arg1=val1")); - } -} diff --git a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpStripUriPrefixTest.java b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpStripUriPrefixTest.java index 5fa572bc616..d2e8ed89980 100644 --- a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpStripUriPrefixTest.java +++ b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpStripUriPrefixTest.java @@ -37,14 +37,20 @@ import org.springframework.security.web.SecurityFilterChain; /** - * Verifies the stripUriPrefix consumer option on the Spring Boot (servlet) platform-http engine: combined with the http - * producer's bridgeEndpoint=true, it turns a platform-http route into a path-based reverse proxy that forwards only the - * path relative to the consumer, with zero header manipulation - matching the behavior already wired for the Vert.x - * platform-http engine. - *

- * The "backend" is a second platform-http route deployed on the very same embedded server: this avoids relying on - * WireMock, whose Spring Boot integration is currently incompatible with Spring Boot 4's Jetty version in this module - * (see the disabled {@link SpringBootPlatformHttpBridgedEndpointTest} and {@link SpringBootPlatformHttpProxyTest}). + * Integration test for the stripUriPrefix consumer option on the Spring Boot (servlet) platform-http engine. Combined + * with matchOnUriPrefix and the http producer's bridgeEndpoint, it turns a platform-http route into a path-based + * reverse proxy that forwards only the path relative to the consumer: + * + *

+ * from("platform-http:/reverse-proxy?matchOnUriPrefix=true&stripUriPrefix=true")
+ *         .to("http://backend?bridgeEndpoint=true");
+ * 
+ * + * The "backend" is a second platform-http route on the same embedded server: WireMock's Spring Boot integration is + * incompatible with this module's Spring Boot 4/Jetty version (see the disabled + * {@link SpringBootPlatformHttpBridgedEndpointTest}). A request that was not stripped would not match the + * {@code /get} backend route and would fall back into this very reverse-proxy consumer, so the successful response + * below proves the consumer path was removed before the request was bridged. */ @EnableAutoConfiguration @CamelSpringBootTest @@ -64,8 +70,8 @@ public class SpringBootPlatformHttpStripUriPrefixTest { void setUp() throws Exception { RestAssured.port = env.getRequiredProperty("local.server.port", Integer.class); - // the reverse-proxy routes bridge to a real loopback HTTP call, so they need the actual (random) server - // port and are therefore added once the port is known, rather than as a statically configured @Bean route + // the reverse proxy bridges to a real loopback HTTP call, so it needs the actual (random) server port and is + // added once that port is known, rather than as a statically configured @Bean route if (camelContext.getRoute("reverse-proxy-strip") == null) { final String backend = "http://localhost:" + RestAssured.port; camelContext.addRoutes(new RouteBuilder() { @@ -73,9 +79,6 @@ void setUp() throws Exception { public void configure() { from("platform-http:/reverse-proxy?matchOnUriPrefix=true&stripUriPrefix=true") .routeId("reverse-proxy-strip").to(backend + "?bridgeEndpoint=true"); - - from("platform-http:/reverse-proxy-control?matchOnUriPrefix=true").routeId("reverse-proxy-control") - .to(backend + "?bridgeEndpoint=true"); } }); } @@ -95,31 +98,15 @@ public RouteBuilder backendRouteBuilder() { return new RouteBuilder() { @Override public void configure() { - // the "downstream" backend service, reachable only at these exact paths + // the "downstream" backend service, reachable only at its own path (not under /reverse-proxy) from("platform-http:/get").routeId("backend-get").setBody().simple("get:${header.CamelHttpQuery}"); - - from("platform-http:/reverse-proxy-control/get").routeId("backend-control-get").setBody() - .simple("control:${header.CamelHttpQuery}"); - - from("platform-http:/").routeId("backend-root").setBody().constant("root"); } }; } } @Test - void stripUriPrefixRemovesTheConsumerPathBeforeBridging() { + void reverseProxyStripsTheConsumerPathBeforeBridging() { given().when().get("/reverse-proxy/get?arg1=val1").then().statusCode(200).body(equalTo("get:arg1=val1")); } - - @Test - void withoutStripUriPrefixTheFullPathIsForwardedUnchanged() { - given().when().get("/reverse-proxy-control/get?arg1=val1").then().statusCode(200) - .body(equalTo("control:arg1=val1")); - } - - @Test - void stripUriPrefixOnAnExactMatchLeavesTheRootPath() { - given().when().get("/reverse-proxy").then().statusCode(200).body(equalTo("root")); - } }