From 9dda4cac9de9b0b0d736dd99c72a8c14c26154dc Mon Sep 17 00:00:00 2001 From: croway Date: Fri, 4 Sep 2026 09:47:01 +0200 Subject: [PATCH 1/2] Fix platform-http-proxy example: real target instead of self-referential CamelHttpUrl The example was broken in three ways: - The README's curl --proxy command never reached the route: servlet containers normalize an absolute-form request line down to just the path, discarding the target host, so the request never matched platform-http:proxy/*. - toD("${headers.CamelHttpUrl}") used the incoming request's own URL (CamelHttpUrl is set from request.getRequestURL(), i.e. this application's own address), causing the route to call itself in an infinite loop until the HTTP connection pool exhausted (503s). - The consumer path literal "proxy" collides with a reserved marker in camel-platform-http that turns the endpoint into a catch-all consumer for a Host-header-based forward proxy - a mode only implemented by the Vert.x platform-http engine, not by camel-platform-http-starter (the servlet/Spring MVC engine this example uses). Left as-is it silently degrades into an unguarded catch-all matching unrelated paths instead of 404ing. Redesigned as a real path-based reverse proxy: requests under /reverse-proxy/** are forwarded to a configurable backend (reverse-proxy.target-base-uri) using camel-http's bridgeEndpoint mode, which appends CamelHttpPath/CamelHttpQuery onto the fixed target automatically instead of trusting a self-referential header. Verified end-to-end against httpbin.org: query strings, root path, and trailing-slash requests all forward correctly, and unrelated paths now 404 instead of being swallowed by the route. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Tgb2b9DSh1ncG7C82rwzsE --- platform-http-proxy/README.adoc | 20 ++++++++----- .../camel/example/springboot/CamelRouter.java | 29 +++++++++++++++---- .../src/main/resources/application.properties | 3 ++ 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/platform-http-proxy/README.adoc b/platform-http-proxy/README.adoc index 7ab369bee..9502fbef6 100644 --- a/platform-http-proxy/README.adoc +++ b/platform-http-proxy/README.adoc @@ -2,9 +2,11 @@ === Introduction -This example illustrates how to use https://projects.spring.io/spring-boot/[Spring Boot] with http://camel.apache.org[Camel]. It implements a reverse proxy using https://camel.apache.org/components/latest/platform-http-component.html[platform-http]. +This example illustrates how to use https://projects.spring.io/spring-boot/[Spring Boot] with http://camel.apache.org[Camel]. It implements a *path-based reverse proxy* using https://camel.apache.org/components/latest/platform-http-component.html[platform-http]: requests received under `/reverse-proxy/**` are forwarded to a fixed backend, with the `/reverse-proxy` prefix stripped and the remaining path and query string preserved. -The project uses `camel-platform-http-starter` component as the implementation for platform-http-engine and `camel-http-starter` to implement the producer calling the http endpoint. +The project uses `camel-platform-http-starter` component as the implementation for platform-http-engine and `camel-http-starter` to implement the producer calling the http endpoint. The producer uses the http component's `bridgeEndpoint` option, which is the idiomatic way to build a reverse proxy with `camel-http`: the endpoint URI supplies the fixed backend (configured via the `reverse-proxy.target-base-uri` property, defaulting to `http://httpbin.org`), and the `CamelHttpPath`/`CamelHttpQuery` headers from the incoming request are appended to it automatically. + +NOTE: This is a reverse proxy reachable at a known path (`/reverse-proxy/...`), not a forward/HTTP-CONNECT proxy. Servlet containers such as the one backing `platform-http` normalize an absolute-form request line (as sent by `curl --proxy`) down to just the request path, discarding the target host - so a client can't point an HTTP forward proxy at this application. Talk to it directly instead, as shown below. === Run @@ -15,21 +17,25 @@ You can run this example using: mvn spring-boot:run ---- -After the Spring Boot application is started, you can execute the following HTTP requests: +After the Spring Boot application is started, you can execute the following HTTP request: [source,bash] ---- -curl --proxy http://localhost:8080 -L http://httpbin.org/get?arg1=val1 -H 'accept: application/json' +curl http://localhost:8080/reverse-proxy/get?arg1=val1 -H 'accept: application/json' ---- -The command will call a test endpoint using the application as reverse proxy, you should see the Camel headers from both request and response in the log. Something similar to: +The command calls `/reverse-proxy/get` on the application, which forwards it to `http://httpbin.org/get`, you should see the Camel headers from both request and response in the log. Something similar to: ---- -INFO 70370 --- [ad #1 - WireTap] header-request : {accept=application/json, arg1=val1, CamelHttpCharacterEncoding=UTF-8, CamelHttpMethod=GET, CamelHttpPath=/get, CamelHttpQuery=arg1=val1, CamelHttpServletRequest=org.apache.catalina.connector.RequestFacade@4f31b074, CamelHttpServletResponse=org.springframework.web.context.request.async.StandardServletAsyncWebRequest$LifecycleHttpServletResponse@158137c0, CamelHttpUri=/get, CamelHttpUrl=http://httpbin.org/get, CamelPlatformHttpContextPath=/, host=httpbin.org, proxy-connection=Keep-Alive, user-agent=curl/8.9.1} -INFO 70370 --- [ad #2 - WireTap] header-response : {accept=application/json, Access-Control-Allow-Credentials=true, Access-Control-Allow-Origin=*, arg1=val1, CamelHttpCharacterEncoding=UTF-8, CamelHttpMethod=GET, CamelHttpQuery=arg1=val1, CamelHttpResponseCode=200, CamelHttpResponseText=OK, CamelHttpServletRequest=org.apache.catalina.connector.RequestFacade@4f31b074, CamelHttpServletResponse=org.springframework.web.context.request.async.StandardServletAsyncWebRequest$LifecycleHttpServletResponse@158137c0, CamelHttpUri=/get, CamelHttpUrl=http://httpbin.org/get, CamelPlatformHttpContextPath=/, Connection=keep-alive, Content-Length=387, Content-Type=application/json, Date=Thu, 04 Sep 2025 11:59:30 GMT, proxy-connection=Keep-Alive, Server=gunicorn/19.9.0} +INFO 70370 --- [ad #1 - WireTap] header-request : {accept=application/json, arg1=val1, CamelHttpCharacterEncoding=UTF-8, CamelHttpMethod=GET, CamelHttpPath=/reverse-proxy/get, CamelHttpQuery=arg1=val1, CamelHttpServletRequest=org.apache.catalina.connector.RequestFacade@4f31b074, CamelHttpServletResponse=org.springframework.web.context.request.async.StandardServletAsyncWebRequest$LifecycleHttpServletResponse@158137c0, CamelHttpUri=/reverse-proxy/get, CamelHttpUrl=http://localhost:8080/reverse-proxy/get, CamelPlatformHttpContextPath=/, host=localhost:8080, user-agent=curl/8.9.1} +INFO 70370 --- [ad #2 - WireTap] header-response : {accept=application/json, Access-Control-Allow-Credentials=true, Access-Control-Allow-Origin=*, arg1=val1, CamelHttpCharacterEncoding=UTF-8, CamelHttpMethod=GET, CamelHttpPath=/get, CamelHttpQuery=arg1=val1, CamelHttpResponseCode=200, CamelHttpResponseText=OK, CamelHttpServletRequest=org.apache.catalina.connector.RequestFacade@4f31b074, CamelHttpServletResponse=org.springframework.web.context.request.async.StandardServletAsyncWebRequest$LifecycleHttpServletResponse@158137c0, CamelHttpUri=/get, CamelHttpUrl=http://localhost:8080/reverse-proxy/get, CamelPlatformHttpContextPath=/, Connection=keep-alive, Content-Length=387, Content-Type=application/json, Date=Thu, 04 Sep 2025 11:59:30 GMT, Server=gunicorn/19.9.0} ---- +Note that `CamelHttpUrl` always reflects the *incoming* request's own URL (this application's own address), not the backend - it is set by the servlet container from the inbound request and is unrelated to where the request is forwarded. Do not use it to build the forwarding target, as that leads to the proxy calling itself. + +CAUTION: The consumer path deliberately avoids the literal segment `proxy` (i.e. it is *not* `platform-http:proxy`). That exact path is a reserved marker in `camel-platform-http`: matching it turns the endpoint into a catch-all consumer that is meant to build a `Host`-header-based forward proxy, but that behavior is only implemented by the Vert.x platform-http engine - `camel-platform-http-starter` (the Spring MVC/servlet engine used by this example) has no such logic. Using `platform-http:proxy` here silently degrades into an unguarded catch-all consumer with no forwarding logic at all, matching unrelated paths (even `/error`) instead of returning a normal 404. + The Spring Boot application can be stopped pressing `[CTRL] + [C]` in the shell. === Help and contributions diff --git a/platform-http-proxy/src/main/java/org/apache/camel/example/springboot/CamelRouter.java b/platform-http-proxy/src/main/java/org/apache/camel/example/springboot/CamelRouter.java index 400838da6..05387542b 100644 --- a/platform-http-proxy/src/main/java/org/apache/camel/example/springboot/CamelRouter.java +++ b/platform-http-proxy/src/main/java/org/apache/camel/example/springboot/CamelRouter.java @@ -21,22 +21,39 @@ import org.springframework.stereotype.Component; /** - * Reverse proxy route with routes that logs heders around the backend call + * Reverse proxy route that logs headers around the backend call. + * + * Requests are accepted under the {@code /reverse-proxy} path and forwarded to the fixed backend + * configured via {@code reverse-proxy.target-base-uri}. The {@code /reverse-proxy} prefix is stripped + * from the request path so that, for example, {@code /reverse-proxy/get} is forwarded to + * {@code /get}. The remaining path and query string are then appended + * automatically by the http producer's {@code bridgeEndpoint} mode. + * + * NOTE: the consumer path must not be the literal string "proxy" (e.g. "platform-http:proxy"). + * That exact path is a reserved marker in camel-platform-http: it turns the endpoint into a + * catch-all consumer meant to build a Host-header-based forward proxy, but that mode is only + * implemented by the Vert.x platform-http engine, not by camel-platform-http-starter (the + * servlet/Spring-MVC engine used here). On this engine it silently degrades into an unguarded + * catch-all with no forwarding logic, so a genuinely different path is used instead. */ @Component public class CamelRouter extends RouteBuilder { + private static final String PROXY_PATH = "/reverse-proxy"; + @Override public void configure() throws Exception { // @formatter:off - from("platform-http:proxy/*?matchOnUriPrefix=true") + from("platform-http:reverse-proxy?matchOnUriPrefix=true") .routeId("reverse-proxy") .wireTap("direct:request") - .removeHeader(Exchange.HTTP_PATH) - .log("calling ${headers." + Exchange.HTTP_URL + "}") - .toD("${headers." + Exchange.HTTP_URL + "}" - + "?throwExceptionOnFailure=false&bridgeEndpoint=true") + .process(exchange -> { + String path = exchange.getIn().getHeader(Exchange.HTTP_PATH, String.class); + exchange.getIn().setHeader(Exchange.HTTP_PATH, path.substring(PROXY_PATH.length())); + }) + .log("calling ${properties:reverse-proxy.target-base-uri}${headers." + Exchange.HTTP_PATH + "}") + .to("{{reverse-proxy.target-base-uri}}?bridgeEndpoint=true&throwExceptionOnFailure=false") .wireTap("direct:response"); from("direct:request") diff --git a/platform-http-proxy/src/main/resources/application.properties b/platform-http-proxy/src/main/resources/application.properties index a4ed5c28b..f78869f0b 100644 --- a/platform-http-proxy/src/main/resources/application.properties +++ b/platform-http-proxy/src/main/resources/application.properties @@ -16,3 +16,6 @@ ## --------------------------------------------------------------------------- # the name of Camel camel.main.name = MyCamelReverseProxy + +# base URI of the backend service requests under /reverse-proxy are forwarded to +reverse-proxy.target-base-uri = http://httpbin.org From 8925a10e6322704bab063b29ce558ee58c5b72d7 Mon Sep 17 00:00:00 2001 From: croway Date: Fri, 4 Sep 2026 09:59:54 +0200 Subject: [PATCH 2/2] Strip the reverse-proxy prefix with a Simple expression, not a Processor Use setHeader with a Simple/OGNL expression (${header.CamelHttpPath.substring(n)}, which invokes the real java.lang.String.substring(int) per Camel's OGNL support) instead of a Java lambda Processor, keeping the route declarative. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Tgb2b9DSh1ncG7C82rwzsE --- .../org/apache/camel/example/springboot/CamelRouter.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/platform-http-proxy/src/main/java/org/apache/camel/example/springboot/CamelRouter.java b/platform-http-proxy/src/main/java/org/apache/camel/example/springboot/CamelRouter.java index 05387542b..cf8718a95 100644 --- a/platform-http-proxy/src/main/java/org/apache/camel/example/springboot/CamelRouter.java +++ b/platform-http-proxy/src/main/java/org/apache/camel/example/springboot/CamelRouter.java @@ -48,10 +48,7 @@ public void configure() throws Exception { from("platform-http:reverse-proxy?matchOnUriPrefix=true") .routeId("reverse-proxy") .wireTap("direct:request") - .process(exchange -> { - String path = exchange.getIn().getHeader(Exchange.HTTP_PATH, String.class); - exchange.getIn().setHeader(Exchange.HTTP_PATH, path.substring(PROXY_PATH.length())); - }) + .setHeader(Exchange.HTTP_PATH, simple("${header." + Exchange.HTTP_PATH + ".substring(" + PROXY_PATH.length() + ")}")) .log("calling ${properties:reverse-proxy.target-base-uri}${headers." + Exchange.HTTP_PATH + "}") .to("{{reverse-proxy.target-base-uri}}?bridgeEndpoint=true&throwExceptionOnFailure=false") .wireTap("direct:response");