This is an automated email from the ASF dual-hosted git repository.

Croway pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new bda1be58a6cd CAMEL-24625: camel-platform-http: add stripUriPrefix 
option for path-based reverse proxies (#26114)
bda1be58a6cd is described below

commit bda1be58a6cd61025ba3a942c7836ab0688466ce
Author: Federico Mariani <[email protected]>
AuthorDate: Mon Sep 7 18:52:13 2026 +0200

    CAMEL-24625: camel-platform-http: add stripUriPrefix option for path-based 
reverse proxies (#26114)
    
    * CAMEL-24625: camel-platform-http - add stripUriPrefix option for 
path-based reverse proxies
    
    platform-http is the only HTTP consumer (unlike camel-servlet, camel-jetty,
    camel-netty-http and camel-undertow) that does not make CamelHttpPath
    relative to the consumer's own registered path, so combining it with the
    http producer's bridgeEndpoint option to build a path-based reverse proxy
    forwards the full raw request path instead of the path relative to the
    consumer.
    
    Add a new consumer option, stripUriPrefix (default false), and a shared
    HttpHelper.stripUriPrefix(requestPath, consumerPath) helper in
    camel-http-base that other HTTP consumers could reuse. The helper is a
    defensive pure function: it only strips a full, boundary-respecting match
    of the consumer path (REST-DSL {name} placeholders included) and otherwise
    returns the request path unchanged - in particular a consumer path of "/"
    (the platform-http:proxy pseudo-path) is always a no-op, so the existing
    platform-http:proxy forward-proxy mode is unaffected.
    
    Co-Authored-By: Claude Sonnet 5 <[email protected]>
    Claude-Session: https://claude.ai/code/session_01Tgb2b9DSh1ncG7C82rwzsE
    
    * CAMEL-24625: camel-platform-http-vertx - wire up stripUriPrefix
    
    Apply HttpHelper.stripUriPrefix to CamelHttpPath in
    VertxPlatformHttpConsumer.populateCamelMessage when the new consumer
    option is enabled, using the endpoint's own registered path as the
    consumer path. This is the single place headers are (re)populated for
    a request that reaches the route - the OAuth security handler path
    resets the message and lets processHttpRequest/populateCamelMessage
    repopulate headers afterwards, so no separate handling is needed there.
    CamelHttpUri/CamelHttpUrl are untouched, only CamelHttpPath is rewritten.
    
    Co-Authored-By: Claude Sonnet 5 <[email protected]>
    Claude-Session: https://claude.ai/code/session_01Tgb2b9DSh1ncG7C82rwzsE
    
    * CAMEL-24625: camel-platform-http - document stripUriPrefix path-based 
reverse proxy
    
    Add a short section next to the existing reverse-proxy documentation
    showing the stripUriPrefix + bridgeEndpoint combination, with a
    before/after CamelHttpPath example and a cross-reference clarifying
    this is distinct from the platform-http:proxy Host-header forward
    proxy.
    
    Co-Authored-By: Claude Sonnet 5 <[email protected]>
    Claude-Session: https://claude.ai/code/session_01Tgb2b9DSh1ncG7C82rwzsE
    
    * Regen
    
    * CAMEL-24625: stripUriPrefix - case-sensitive segment match and 
exact-match docs/test
    
    Address review feedback: match literal path segments case-sensitively (the
    Vert.x router only ever routes case-exact prefixes, so this aligns the 
helper
    with reachable behaviour and HTTP path semantics), drop the two unit cases
    that asserted unreachable case-insensitive matching, verify exact-match
    stripping in the live Vert.x test without matchOnUriPrefix, and document 
that
    matchOnUriPrefix is not a prerequisite for stripUriPrefix.
    
    Co-authored-by: Claude Code <[email protected]>
    
    ---------
    
    Co-authored-by: Claude Sonnet 5 <[email protected]>
    Co-authored-by: github-actions[bot] 
<github-actions[bot]@users.noreply.github.com>
---
 .../camel/catalog/components/platform-http.json    |  21 ++--
 .../catalog/docs/platform-http-component.adoc      |  31 +++++
 .../org/apache/camel/http/base/HttpHelper.java     |  86 +++++++++++++
 .../http/base/HttpHelperStripUriPrefixTest.java    |  70 +++++++++++
 components/camel-platform-http-vertx/pom.xml       |   4 +
 .../http/vertx/VertxPlatformHttpConsumer.java      |   5 +
 .../vertx/VertxPlatformHttpStripUriPrefixTest.java | 140 +++++++++++++++++++++
 .../http/PlatformHttpEndpointConfigurer.java       |   6 +
 .../http/PlatformHttpEndpointUriFactory.java       |   3 +-
 .../component/platform/http/platform-http.json     |  21 ++--
 .../src/main/docs/platform-http-component.adoc     |  31 +++++
 .../platform/http/PlatformHttpEndpoint.java        |  27 ++++
 .../PlatformHttpEndpointStripUriPrefixTest.java    |  69 ++++++++++
 .../dsl/PlatformHttpEndpointBuilderFactory.java    |  54 ++++++++
 14 files changed, 547 insertions(+), 21 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/platform-http.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/platform-http.json
index e2cdf3cf26fb..d9dc24f3a08c 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/platform-http.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/platform-http.json
@@ -50,15 +50,16 @@
     "produces": { "index": 13, "kind": "parameter", "displayName": "Produces", 
"group": "consumer", "label": "consumer", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "description": "The content type this endpoint produces, such 
as application\/xml or application\/json." },
     "requestTimeout": { "index": 14, "kind": "parameter", "displayName": 
"Request Timeout", "group": "consumer", "label": "advanced,consumer", 
"required": false, "type": "integer", "javaType": "long", "deprecated": false, 
"autowired": false, "secret": false, "description": "The period in milliseconds 
after which the request should be timed out." },
     "returnHttpRequestHeaders": { "index": 15, "kind": "parameter", 
"displayName": "Return Http Request Headers", "group": "consumer", "label": 
"advanced,consumer", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether to include HTTP request headers 
(Accept, User-Agent, etc.) into HTTP response produced by this endpoint." },
-    "useBodyHandler": { "index": 16, "kind": "parameter", "displayName": "Use 
Body Handler", "group": "consumer", "label": "advanced,consumer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether to use BodyHandler for the request. If set to false then the request 
will no be read and parsed." },
-    "useCookieHandler": { "index": 17, "kind": "parameter", "displayName": 
"Use Cookie Handler", "group": "consumer", "label": "advanced,consumer", 
"required": false, "type": "boolean", "javaType": "boolean", "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": false, 
"description": "Whether to enable the Cookie Handler that allows Cookie 
addition, expiry, and retrieval (currently only supported by 
camel-platform-http-vertx)" },
-    "useStreaming": { "index": 18, "kind": "parameter", "displayName": "Use 
Streaming", "group": "consumer", "label": "advanced,consumer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Whether to use streaming for large requests and responses (currently only 
supported by camel-platform-http-vertx)" },
-    "bridgeErrorHandler": { "index": 19, "kind": "parameter", "displayName": 
"Bridge Error Handler", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Allows for bridging the consumer to the 
Camel routing Error Handler, which mean any exceptions (if possible) occurred 
while the Camel consumer is trying to pickup incoming [...]
-    "exceptionHandler": { "index": 20, "kind": "parameter", "displayName": 
"Exception Handler", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "object", "javaType": 
"org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", 
"deprecated": false, "autowired": false, "secret": false, "description": "To 
let the consumer use a custom ExceptionHandler. Notice if the option 
bridgeErrorHandler is enabled then this option is not in use. By de [...]
-    "exchangePattern": { "index": 21, "kind": "parameter", "displayName": 
"Exchange Pattern", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "enum", "javaType": 
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], 
"deprecated": false, "autowired": false, "secret": false, "description": "Sets 
the exchange pattern when the consumer creates an exchange." },
-    "fileNameExtWhitelist": { "index": 22, "kind": "parameter", "displayName": 
"File Name Ext Whitelist", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"description": "A comma or whitespace separated list of file extensions. 
Uploads having these extensions will be stored locally. Null value or asterisk 
() will allow all files." },
-    "headerFilterStrategy": { "index": 23, "kind": "parameter", "displayName": 
"Header Filter Strategy", "group": "advanced", "label": "advanced", "required": 
false, "type": "object", "javaType": 
"org.apache.camel.spi.HeaderFilterStrategy", "deprecated": false, "autowired": 
false, "secret": false, "description": "To use a custom HeaderFilterStrategy to 
filter headers to and from Camel message." },
-    "platformHttpEngine": { "index": 24, "kind": "parameter", "displayName": 
"Platform Http Engine", "group": "advanced", "label": "advanced", "required": 
false, "type": "object", "javaType": 
"org.apache.camel.component.platform.http.spi.PlatformHttpEngine", 
"deprecated": false, "autowired": false, "secret": false, "description": "An 
HTTP Server engine implementation to serve the requests of this endpoint." },
-    "oauthProfile": { "index": 25, "kind": "parameter", "displayName": "OAuth 
Profile", "group": "security", "label": "consumer,security", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "description": "OAuth profile name for 
validating incoming Authorization: Bearer tokens. When set, the request is 
authenticated before the route is processed. This requires an 
OAuthTokenValidationFactory; camel-oauth provides  [...]
+    "stripUriPrefix": { "index": 16, "kind": "parameter", "displayName": 
"Strip Uri Prefix", "group": "consumer", "label": "consumer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Whether to strip the registered consumer path from CamelHttpPath after the 
request has been matched, so the exchange sees the path relative to this 
consumer instead of the full raw request path. Comb [...]
+    "useBodyHandler": { "index": 17, "kind": "parameter", "displayName": "Use 
Body Handler", "group": "consumer", "label": "advanced,consumer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether to use BodyHandler for the request. If set to false then the request 
will no be read and parsed." },
+    "useCookieHandler": { "index": 18, "kind": "parameter", "displayName": 
"Use Cookie Handler", "group": "consumer", "label": "advanced,consumer", 
"required": false, "type": "boolean", "javaType": "boolean", "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": false, 
"description": "Whether to enable the Cookie Handler that allows Cookie 
addition, expiry, and retrieval (currently only supported by 
camel-platform-http-vertx)" },
+    "useStreaming": { "index": 19, "kind": "parameter", "displayName": "Use 
Streaming", "group": "consumer", "label": "advanced,consumer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Whether to use streaming for large requests and responses (currently only 
supported by camel-platform-http-vertx)" },
+    "bridgeErrorHandler": { "index": 20, "kind": "parameter", "displayName": 
"Bridge Error Handler", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Allows for bridging the consumer to the 
Camel routing Error Handler, which mean any exceptions (if possible) occurred 
while the Camel consumer is trying to pickup incoming [...]
+    "exceptionHandler": { "index": 21, "kind": "parameter", "displayName": 
"Exception Handler", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "object", "javaType": 
"org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", 
"deprecated": false, "autowired": false, "secret": false, "description": "To 
let the consumer use a custom ExceptionHandler. Notice if the option 
bridgeErrorHandler is enabled then this option is not in use. By de [...]
+    "exchangePattern": { "index": 22, "kind": "parameter", "displayName": 
"Exchange Pattern", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "enum", "javaType": 
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], 
"deprecated": false, "autowired": false, "secret": false, "description": "Sets 
the exchange pattern when the consumer creates an exchange." },
+    "fileNameExtWhitelist": { "index": 23, "kind": "parameter", "displayName": 
"File Name Ext Whitelist", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"description": "A comma or whitespace separated list of file extensions. 
Uploads having these extensions will be stored locally. Null value or asterisk 
() will allow all files." },
+    "headerFilterStrategy": { "index": 24, "kind": "parameter", "displayName": 
"Header Filter Strategy", "group": "advanced", "label": "advanced", "required": 
false, "type": "object", "javaType": 
"org.apache.camel.spi.HeaderFilterStrategy", "deprecated": false, "autowired": 
false, "secret": false, "description": "To use a custom HeaderFilterStrategy to 
filter headers to and from Camel message." },
+    "platformHttpEngine": { "index": 25, "kind": "parameter", "displayName": 
"Platform Http Engine", "group": "advanced", "label": "advanced", "required": 
false, "type": "object", "javaType": 
"org.apache.camel.component.platform.http.spi.PlatformHttpEngine", 
"deprecated": false, "autowired": false, "secret": false, "description": "An 
HTTP Server engine implementation to serve the requests of this endpoint." },
+    "oauthProfile": { "index": 26, "kind": "parameter", "displayName": "OAuth 
Profile", "group": "security", "label": "consumer,security", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "description": "OAuth profile name for 
validating incoming Authorization: Bearer tokens. When set, the request is 
authenticated before the route is processed. This requires an 
OAuthTokenValidationFactory; camel-oauth provides  [...]
   }
 }
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/platform-http-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/platform-http-component.adoc
index 1b5790d7d0f1..0b551804037e 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/platform-http-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/platform-http-component.adoc
@@ -201,6 +201,37 @@ Platform HTTP component can act as a reverse proxy. In 
that case, some headers a
 
 At this moment, this feature is only supported for Quarkus implemented in 
`camel-platform-http-vertx` component.
 
+=== Forwarding requests under a path prefix (path-based reverse proxy)
+
+This is a different scenario from the `platform-http:proxy` forward proxy 
described above, which is a `Host` header
+based forward proxy and only supported in `camel-platform-http-vertx`. Here, 
the consumer is registered under an
+ordinary path, e.g. `/reverse-proxy`, and requests received under that path 
should be forwarded to a fixed backend
+with the prefix stripped, using the xref:components::http-component.adoc[http] 
producer's `bridgeEndpoint` option.
+
+Enable the consumer option `stripUriPrefix` so the matched consumer path is 
removed from `CamelHttpPath` before the
+route runs:
+
+[source,java]
+----
+from("platform-http:/reverse-proxy?matchOnUriPrefix=true&stripUriPrefix=true")
+    .to("http://backend?bridgeEndpoint=true";);
+----
+
+`matchOnUriPrefix` is not required for `stripUriPrefix`; it is only needed to 
also forward requests under sub-paths.
+On an exact match (e.g. `GET /reverse-proxy`) the path is stripped to `/`.
+
+|===
+| Incoming request | `CamelHttpPath` (`stripUriPrefix=false`, default) | 
`CamelHttpPath` (`stripUriPrefix=true`) | Forwarded to
+
+| `GET /reverse-proxy/get`
+| `/reverse-proxy/get`
+| `/get`
+| `http://backend/get`
+|===
+
+`CamelHttpUri`/`CamelHttpUrl` still hold the full original request, only 
`CamelHttpPath` is rewritten. This option is
+currently implemented in `camel-platform-http-vertx`.
+
 === File Attachments handling
 
 Since Apache Camel 4.10, multipart file uploads are easier and harmonized 
across all runtimes. When a single file is uploaded, the Apache Camel framework 
provides the following:
diff --git 
a/components/camel-http-base/src/main/java/org/apache/camel/http/base/HttpHelper.java
 
b/components/camel-http-base/src/main/java/org/apache/camel/http/base/HttpHelper.java
index 1a9198796eb1..b12cb19ee633 100644
--- 
a/components/camel-http-base/src/main/java/org/apache/camel/http/base/HttpHelper.java
+++ 
b/components/camel-http-base/src/main/java/org/apache/camel/http/base/HttpHelper.java
@@ -135,6 +135,92 @@ public final class HttpHelper {
         return HttpUtil.isStatusCodeOk(statusCode, okStatusCodeRange);
     }
 
+    /**
+     * Removes the leading portion of a request path that was matched by a 
consumer's registered path, so that what
+     * remains is the path relative to that consumer - the same way {@code 
camel-servlet}, {@code camel-jetty},
+     * {@code camel-netty-http} and {@code camel-undertow} already behave by 
default.
+     * <p/>
+     * This is a pure, defensive function: it never throws for malformed 
input, and whenever the match is anything less
+     * than a full, boundary-respecting match of every consumer path segment, 
it returns {@code requestPath} unchanged
+     * rather than risk producing a partial or incorrect result. In particular 
a {@code consumerPath} of {@code null},
+     * blank, {@code "/"} or {@code ""} (once normalized) is treated as "no 
prefix to strip" and always returns
+     * {@code requestPath} unchanged - this is what makes the platform-http 
{@code proxy} pseudo-path (whose consumer
+     * path is {@code "/"}) provably unaffected by callers of this method.
+     * <p/>
+     * The consumer path may contain REST-DSL style {@code {name}} placeholder 
segments, which match any single
+     * non-empty request segment.
+     *
+     * @param  requestPath  the incoming request path, e.g. {@code 
/reverse-proxy/get}
+     * @param  consumerPath the path the consumer is registered under, e.g. 
{@code /reverse-proxy} or
+     *                      {@code /reverse-proxy*}
+     * @return              the remaining path after stripping the matched 
consumer path, always starting with a
+     *                      {@code /}, or {@code requestPath} unchanged if the 
consumer path does not match (or there is
+     *                      nothing to strip)
+     */
+    public static String stripUriPrefix(String requestPath, String 
consumerPath) {
+        if (requestPath == null) {
+            return null;
+        }
+        if (consumerPath == null || consumerPath.isBlank()) {
+            return requestPath;
+        }
+
+        String normalized = consumerPath.trim();
+        if (!normalized.startsWith("/")) {
+            normalized = "/" + normalized;
+        }
+        if (normalized.endsWith("*")) {
+            normalized = normalized.substring(0, normalized.length() - 1);
+        }
+        while (normalized.length() > 1 && normalized.endsWith("/")) {
+            normalized = normalized.substring(0, normalized.length() - 1);
+        }
+        if (normalized.isEmpty() || "/".equals(normalized)) {
+            return requestPath;
+        }
+
+        // split using single char / is optimized in the jdk
+        final String[] consumerSegments = normalized.split("/");
+        final String[] requestSegments = requestPath.split("/", -1);
+
+        if (requestSegments.length < consumerSegments.length) {
+            return requestPath;
+        }
+
+        for (int i = 0; i < consumerSegments.length; i++) {
+            String consumerSegment = consumerSegments[i];
+            if (consumerSegment.isEmpty()) {
+                // leading empty segment produced by the split on the initial 
'/'
+                continue;
+            }
+            String requestSegment = i < requestSegments.length ? 
requestSegments[i] : null;
+            boolean placeholder = consumerSegment.startsWith("{") && 
consumerSegment.endsWith("}");
+            if (placeholder) {
+                if (requestSegment == null || requestSegment.isEmpty()) {
+                    return requestPath;
+                }
+            } else if (requestSegment == null || 
!consumerSegment.equals(requestSegment)) {
+                return requestPath;
+            }
+        }
+
+        int matchedSegments = consumerSegments.length;
+        if (matchedSegments == requestSegments.length) {
+            // exact match, nothing remains
+            return "/";
+        }
+
+        // boundary check: the next request segment marks a '/' boundary, so 
this is always safe - anything else
+        // (e.g. /reverse-proxyfoo when the consumer path is /reverse-proxy) 
was already rejected above because the
+        // last matched request segment would not have equaled the consumer's 
last segment
+        StringBuilder remainder = new StringBuilder();
+        for (int i = matchedSegments; i < requestSegments.length; i++) {
+            remainder.append('/').append(requestSegments[i]);
+        }
+        String result = remainder.toString();
+        return result.isEmpty() ? "/" : result;
+    }
+
     /**
      * In the endpoint the user may have defined rest {} placeholders. This 
helper method map those placeholders with
      * data from the incoming request context path
diff --git 
a/components/camel-http-base/src/test/java/org/apache/camel/http/base/HttpHelperStripUriPrefixTest.java
 
b/components/camel-http-base/src/test/java/org/apache/camel/http/base/HttpHelperStripUriPrefixTest.java
new file mode 100644
index 000000000000..09dfae56a0d0
--- /dev/null
+++ 
b/components/camel-http-base/src/test/java/org/apache/camel/http/base/HttpHelperStripUriPrefixTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.http.base;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+class HttpHelperStripUriPrefixTest {
+
+    static Stream<Arguments> stripCases() {
+        return Stream.of(
+                Arguments.of("/reverse-proxy/get", "/reverse-proxy", "/get"),
+                Arguments.of("/reverse-proxy", "/reverse-proxy", "/"),
+                Arguments.of("/reverse-proxy/", "/reverse-proxy", "/"),
+                Arguments.of("/reverse-proxy/a/b/", "/reverse-proxy", "/a/b/"),
+                // leading slash on the consumer path is optional
+                Arguments.of("/reverse-proxy/get", "reverse-proxy", "/get"),
+                // Vert.x style trailing wildcard on the consumer path
+                Arguments.of("/reverse-proxy/get", "/reverse-proxy*", "/get"),
+                // boundary: only strip on a '/' (or end of string) boundary
+                Arguments.of("/reverse-proxyfoo", "/reverse-proxy", 
"/reverse-proxyfoo"),
+                Arguments.of("/other/x", "/reverse-proxy", "/other/x"),
+                // REST-DSL {name} placeholder segment matches any single 
non-empty segment
+                Arguments.of("/user/123/orders", "/user/{id}", "/orders"),
+                Arguments.of("/user/123", "/user/{id}", "/"),
+                // request shorter than consumer path never matches
+                Arguments.of("/reverse", "/reverse-proxy", "/reverse"),
+                Arguments.of("/", "/reverse-proxy/sub", "/"),
+                // "no prefix to strip" cases: consumer path of "/", "" or 
null always returns input unchanged -
+                // this is what makes platform-http:proxy (whose getPath() is 
"/") provably unaffected
+                Arguments.of("/reverse-proxy/get", "/", "/reverse-proxy/get"),
+                Arguments.of("/reverse-proxy/get", "", "/reverse-proxy/get"),
+                Arguments.of("/reverse-proxy/get", null, "/reverse-proxy/get"),
+                Arguments.of("/reverse-proxy/get", "   ", 
"/reverse-proxy/get"));
+    }
+
+    @ParameterizedTest
+    @MethodSource("stripCases")
+    void stripsAsExpected(String requestPath, String consumerPath, String 
expected) {
+        assertEquals(expected, HttpHelper.stripUriPrefix(requestPath, 
consumerPath));
+    }
+
+    @Test
+    void nullRequestPathReturnsNull() {
+        assertNull(HttpHelper.stripUriPrefix(null, "/reverse-proxy"));
+        assertNull(HttpHelper.stripUriPrefix(null, "/"));
+        assertNull(HttpHelper.stripUriPrefix(null, null));
+    }
+}
diff --git a/components/camel-platform-http-vertx/pom.xml 
b/components/camel-platform-http-vertx/pom.xml
index e3b1190aaef9..97e98671af4d 100644
--- a/components/camel-platform-http-vertx/pom.xml
+++ b/components/camel-platform-http-vertx/pom.xml
@@ -42,6 +42,10 @@
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-platform-http</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-http-base</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-attachments</artifactId>
diff --git 
a/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpConsumer.java
 
b/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpConsumer.java
index 2744a63fb1d9..9fdaa9a5784e 100644
--- 
a/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpConsumer.java
+++ 
b/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpConsumer.java
@@ -54,6 +54,7 @@ import 
org.apache.camel.component.platform.http.cookie.CookieHandler;
 import org.apache.camel.component.platform.http.spi.Method;
 import org.apache.camel.component.platform.http.spi.PlatformHttpConsumer;
 import 
org.apache.camel.component.platform.http.spi.PlatformHttpSecurityHandler;
+import org.apache.camel.http.base.HttpHelper;
 import org.apache.camel.spi.HeaderFilterStrategy;
 import org.apache.camel.spi.RestRegistry;
 import org.apache.camel.spi.RestRegistry.RestService;
@@ -487,6 +488,10 @@ public class VertxPlatformHttpConsumer extends 
DefaultConsumer
     protected Future<Void> populateCamelMessage(RoutingContext ctx, Exchange 
exchange, Message message) {
         final HeaderFilterStrategy headerFilterStrategy = 
getEndpoint().getHeaderFilterStrategy();
         populateCamelHeaders(ctx, message.getHeaders(), exchange, 
headerFilterStrategy);
+        if (getEndpoint().isStripUriPrefix()) {
+            String httpPath = (String) message.getHeader(Exchange.HTTP_PATH);
+            message.setHeader(Exchange.HTTP_PATH, 
HttpHelper.stripUriPrefix(httpPath, getEndpoint().getPath()));
+        }
         if (securityHandler != null) {
             message.removeHeader(AUTHORIZATION);
         }
diff --git 
a/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpStripUriPrefixTest.java
 
b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpStripUriPrefixTest.java
new file mode 100644
index 000000000000..f89b3b9cb1b6
--- /dev/null
+++ 
b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpStripUriPrefixTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.vertx;
+
+import com.github.tomakehurst.wiremock.WireMockServer;
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.AvailablePortFinder;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import static 
com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.Matchers.equalTo;
+
+/**
+ * Tests the stripUriPrefix consumer option, which lets a platform-http route 
combined with the http producer's
+ * bridgeEndpoint option act as a path-based reverse proxy: the registered 
consumer path is stripped from CamelHttpPath
+ * before the request reaches the route, so the backend only sees the path 
relative to the consumer.
+ */
+public class VertxPlatformHttpStripUriPrefixTest {
+
+    @RegisterExtension
+    AvailablePortFinder.Port backendPort = AvailablePortFinder.find();
+    @RegisterExtension
+    AvailablePortFinder.Port camelPort = AvailablePortFinder.find();
+
+    private WireMockServer wireMockServer;
+
+    @BeforeEach
+    void before() {
+        wireMockServer = new 
WireMockServer(options().port(backendPort.getPort()));
+        // stubbed as it should be received once the /reverse-proxy prefix has 
been stripped
+        wireMockServer.stubFor(get(urlEqualTo("/get?arg1=val1"))
+                .willReturn(aResponse().withStatus(200).withBody("stripped")));
+        // stubbed as it should be received when stripUriPrefix is NOT enabled 
(control)
+        wireMockServer.stubFor(get(urlEqualTo("/reverse-proxy/get?arg1=val1"))
+                
.willReturn(aResponse().withStatus(200).withBody("unstripped")));
+        // stubbed for an exact (non-prefixed) match, which should strip down 
to "/"
+        wireMockServer.stubFor(get(urlEqualTo("/"))
+                .willReturn(aResponse().withStatus(200).withBody("root")));
+        wireMockServer.start();
+    }
+
+    @AfterEach
+    void after() {
+        if (wireMockServer != null) {
+            wireMockServer.stop();
+        }
+    }
+
+    @Test
+    void stripUriPrefixRemovesTheConsumerPathBeforeBridging() throws Exception 
{
+        final CamelContext context = 
VertxPlatformHttpEngineTest.createCamelContext(camelPort.getPort());
+        try {
+            context.addRoutes(new RouteBuilder() {
+                @Override
+                public void configure() {
+                    
from("platform-http:/reverse-proxy?matchOnUriPrefix=true&stripUriPrefix=true")
+                            .to("http://localhost:"; + backendPort.getPort() + 
"?bridgeEndpoint=true");
+                }
+            });
+            context.start();
+
+            given()
+                    .when().get("http://localhost:"; + camelPort.getPort() + 
"/reverse-proxy/get?arg1=val1")
+                    .then()
+                    .statusCode(200)
+                    .body(equalTo("stripped"));
+        } finally {
+            context.stop();
+        }
+    }
+
+    @Test
+    void withoutStripUriPrefixTheFullPathIsForwardedUnchanged() throws 
Exception {
+        final CamelContext context = 
VertxPlatformHttpEngineTest.createCamelContext(camelPort.getPort());
+        try {
+            context.addRoutes(new RouteBuilder() {
+                @Override
+                public void configure() {
+                    from("platform-http:/reverse-proxy?matchOnUriPrefix=true")
+                            .to("http://localhost:"; + backendPort.getPort() + 
"?bridgeEndpoint=true");
+                }
+            });
+            context.start();
+
+            given()
+                    .when().get("http://localhost:"; + camelPort.getPort() + 
"/reverse-proxy/get?arg1=val1")
+                    .then()
+                    .statusCode(200)
+                    .body(equalTo("unstripped"));
+        } finally {
+            context.stop();
+        }
+    }
+
+    @Test
+    void stripUriPrefixOnAnExactMatchLeavesTheRootPath() throws Exception {
+        // matchOnUriPrefix is not required for stripUriPrefix: on an exact 
match the path is stripped to "/"
+        final CamelContext context = 
VertxPlatformHttpEngineTest.createCamelContext(camelPort.getPort());
+        try {
+            context.addRoutes(new RouteBuilder() {
+                @Override
+                public void configure() {
+                    from("platform-http:/reverse-proxy?stripUriPrefix=true")
+                            .to("http://localhost:"; + backendPort.getPort() + 
"?bridgeEndpoint=true");
+                }
+            });
+            context.start();
+
+            given()
+                    .when().get("http://localhost:"; + camelPort.getPort() + 
"/reverse-proxy")
+                    .then()
+                    .statusCode(200)
+                    .body(equalTo("root"));
+        } finally {
+            context.stop();
+        }
+    }
+}
diff --git 
a/components/camel-platform-http/src/generated/java/org/apache/camel/component/platform/http/PlatformHttpEndpointConfigurer.java
 
b/components/camel-platform-http/src/generated/java/org/apache/camel/component/platform/http/PlatformHttpEndpointConfigurer.java
index aa5c1cb72e71..d546aa9ddbb2 100644
--- 
a/components/camel-platform-http/src/generated/java/org/apache/camel/component/platform/http/PlatformHttpEndpointConfigurer.java
+++ 
b/components/camel-platform-http/src/generated/java/org/apache/camel/component/platform/http/PlatformHttpEndpointConfigurer.java
@@ -65,6 +65,8 @@ public class PlatformHttpEndpointConfigurer extends 
PropertyConfigurerSupport im
         case "requestTimeout": target.setRequestTimeout(property(camelContext, 
long.class, value)); return true;
         case "returnhttprequestheaders":
         case "returnHttpRequestHeaders": 
target.setReturnHttpRequestHeaders(property(camelContext, boolean.class, 
value)); return true;
+        case "stripuriprefix":
+        case "stripUriPrefix": target.setStripUriPrefix(property(camelContext, 
boolean.class, value)); return true;
         case "usebodyhandler":
         case "useBodyHandler": target.setUseBodyHandler(property(camelContext, 
boolean.class, value)); return true;
         case "usecookiehandler":
@@ -120,6 +122,8 @@ public class PlatformHttpEndpointConfigurer extends 
PropertyConfigurerSupport im
         case "requestTimeout": return long.class;
         case "returnhttprequestheaders":
         case "returnHttpRequestHeaders": return boolean.class;
+        case "stripuriprefix":
+        case "stripUriPrefix": return boolean.class;
         case "usebodyhandler":
         case "useBodyHandler": return boolean.class;
         case "usecookiehandler":
@@ -176,6 +180,8 @@ public class PlatformHttpEndpointConfigurer extends 
PropertyConfigurerSupport im
         case "requestTimeout": return target.getRequestTimeout();
         case "returnhttprequestheaders":
         case "returnHttpRequestHeaders": return 
target.isReturnHttpRequestHeaders();
+        case "stripuriprefix":
+        case "stripUriPrefix": return target.isStripUriPrefix();
         case "usebodyhandler":
         case "useBodyHandler": return target.isUseBodyHandler();
         case "usecookiehandler":
diff --git 
a/components/camel-platform-http/src/generated/java/org/apache/camel/component/platform/http/PlatformHttpEndpointUriFactory.java
 
b/components/camel-platform-http/src/generated/java/org/apache/camel/component/platform/http/PlatformHttpEndpointUriFactory.java
index 4a7a05b1fcdb..e4a06590ddda 100644
--- 
a/components/camel-platform-http/src/generated/java/org/apache/camel/component/platform/http/PlatformHttpEndpointUriFactory.java
+++ 
b/components/camel-platform-http/src/generated/java/org/apache/camel/component/platform/http/PlatformHttpEndpointUriFactory.java
@@ -24,7 +24,7 @@ public class PlatformHttpEndpointUriFactory extends 
org.apache.camel.support.com
     private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES;
     private static final Map<String, String> MULTI_VALUE_PREFIXES;
     static {
-        Set<String> props = new HashSet<>(26);
+        Set<String> props = new HashSet<>(27);
         props.add("bridgeErrorHandler");
         props.add("consumes");
         props.add("cookieDomain");
@@ -48,6 +48,7 @@ public class PlatformHttpEndpointUriFactory extends 
org.apache.camel.support.com
         props.add("produces");
         props.add("requestTimeout");
         props.add("returnHttpRequestHeaders");
+        props.add("stripUriPrefix");
         props.add("useBodyHandler");
         props.add("useCookieHandler");
         props.add("useStreaming");
diff --git 
a/components/camel-platform-http/src/generated/resources/META-INF/org/apache/camel/component/platform/http/platform-http.json
 
b/components/camel-platform-http/src/generated/resources/META-INF/org/apache/camel/component/platform/http/platform-http.json
index e2cdf3cf26fb..d9dc24f3a08c 100644
--- 
a/components/camel-platform-http/src/generated/resources/META-INF/org/apache/camel/component/platform/http/platform-http.json
+++ 
b/components/camel-platform-http/src/generated/resources/META-INF/org/apache/camel/component/platform/http/platform-http.json
@@ -50,15 +50,16 @@
     "produces": { "index": 13, "kind": "parameter", "displayName": "Produces", 
"group": "consumer", "label": "consumer", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "description": "The content type this endpoint produces, such 
as application\/xml or application\/json." },
     "requestTimeout": { "index": 14, "kind": "parameter", "displayName": 
"Request Timeout", "group": "consumer", "label": "advanced,consumer", 
"required": false, "type": "integer", "javaType": "long", "deprecated": false, 
"autowired": false, "secret": false, "description": "The period in milliseconds 
after which the request should be timed out." },
     "returnHttpRequestHeaders": { "index": 15, "kind": "parameter", 
"displayName": "Return Http Request Headers", "group": "consumer", "label": 
"advanced,consumer", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether to include HTTP request headers 
(Accept, User-Agent, etc.) into HTTP response produced by this endpoint." },
-    "useBodyHandler": { "index": 16, "kind": "parameter", "displayName": "Use 
Body Handler", "group": "consumer", "label": "advanced,consumer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether to use BodyHandler for the request. If set to false then the request 
will no be read and parsed." },
-    "useCookieHandler": { "index": 17, "kind": "parameter", "displayName": 
"Use Cookie Handler", "group": "consumer", "label": "advanced,consumer", 
"required": false, "type": "boolean", "javaType": "boolean", "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": false, 
"description": "Whether to enable the Cookie Handler that allows Cookie 
addition, expiry, and retrieval (currently only supported by 
camel-platform-http-vertx)" },
-    "useStreaming": { "index": 18, "kind": "parameter", "displayName": "Use 
Streaming", "group": "consumer", "label": "advanced,consumer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Whether to use streaming for large requests and responses (currently only 
supported by camel-platform-http-vertx)" },
-    "bridgeErrorHandler": { "index": 19, "kind": "parameter", "displayName": 
"Bridge Error Handler", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Allows for bridging the consumer to the 
Camel routing Error Handler, which mean any exceptions (if possible) occurred 
while the Camel consumer is trying to pickup incoming [...]
-    "exceptionHandler": { "index": 20, "kind": "parameter", "displayName": 
"Exception Handler", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "object", "javaType": 
"org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", 
"deprecated": false, "autowired": false, "secret": false, "description": "To 
let the consumer use a custom ExceptionHandler. Notice if the option 
bridgeErrorHandler is enabled then this option is not in use. By de [...]
-    "exchangePattern": { "index": 21, "kind": "parameter", "displayName": 
"Exchange Pattern", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "enum", "javaType": 
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], 
"deprecated": false, "autowired": false, "secret": false, "description": "Sets 
the exchange pattern when the consumer creates an exchange." },
-    "fileNameExtWhitelist": { "index": 22, "kind": "parameter", "displayName": 
"File Name Ext Whitelist", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"description": "A comma or whitespace separated list of file extensions. 
Uploads having these extensions will be stored locally. Null value or asterisk 
() will allow all files." },
-    "headerFilterStrategy": { "index": 23, "kind": "parameter", "displayName": 
"Header Filter Strategy", "group": "advanced", "label": "advanced", "required": 
false, "type": "object", "javaType": 
"org.apache.camel.spi.HeaderFilterStrategy", "deprecated": false, "autowired": 
false, "secret": false, "description": "To use a custom HeaderFilterStrategy to 
filter headers to and from Camel message." },
-    "platformHttpEngine": { "index": 24, "kind": "parameter", "displayName": 
"Platform Http Engine", "group": "advanced", "label": "advanced", "required": 
false, "type": "object", "javaType": 
"org.apache.camel.component.platform.http.spi.PlatformHttpEngine", 
"deprecated": false, "autowired": false, "secret": false, "description": "An 
HTTP Server engine implementation to serve the requests of this endpoint." },
-    "oauthProfile": { "index": 25, "kind": "parameter", "displayName": "OAuth 
Profile", "group": "security", "label": "consumer,security", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "description": "OAuth profile name for 
validating incoming Authorization: Bearer tokens. When set, the request is 
authenticated before the route is processed. This requires an 
OAuthTokenValidationFactory; camel-oauth provides  [...]
+    "stripUriPrefix": { "index": 16, "kind": "parameter", "displayName": 
"Strip Uri Prefix", "group": "consumer", "label": "consumer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Whether to strip the registered consumer path from CamelHttpPath after the 
request has been matched, so the exchange sees the path relative to this 
consumer instead of the full raw request path. Comb [...]
+    "useBodyHandler": { "index": 17, "kind": "parameter", "displayName": "Use 
Body Handler", "group": "consumer", "label": "advanced,consumer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether to use BodyHandler for the request. If set to false then the request 
will no be read and parsed." },
+    "useCookieHandler": { "index": 18, "kind": "parameter", "displayName": 
"Use Cookie Handler", "group": "consumer", "label": "advanced,consumer", 
"required": false, "type": "boolean", "javaType": "boolean", "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": false, 
"description": "Whether to enable the Cookie Handler that allows Cookie 
addition, expiry, and retrieval (currently only supported by 
camel-platform-http-vertx)" },
+    "useStreaming": { "index": 19, "kind": "parameter", "displayName": "Use 
Streaming", "group": "consumer", "label": "advanced,consumer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Whether to use streaming for large requests and responses (currently only 
supported by camel-platform-http-vertx)" },
+    "bridgeErrorHandler": { "index": 20, "kind": "parameter", "displayName": 
"Bridge Error Handler", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Allows for bridging the consumer to the 
Camel routing Error Handler, which mean any exceptions (if possible) occurred 
while the Camel consumer is trying to pickup incoming [...]
+    "exceptionHandler": { "index": 21, "kind": "parameter", "displayName": 
"Exception Handler", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "object", "javaType": 
"org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", 
"deprecated": false, "autowired": false, "secret": false, "description": "To 
let the consumer use a custom ExceptionHandler. Notice if the option 
bridgeErrorHandler is enabled then this option is not in use. By de [...]
+    "exchangePattern": { "index": 22, "kind": "parameter", "displayName": 
"Exchange Pattern", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "enum", "javaType": 
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], 
"deprecated": false, "autowired": false, "secret": false, "description": "Sets 
the exchange pattern when the consumer creates an exchange." },
+    "fileNameExtWhitelist": { "index": 23, "kind": "parameter", "displayName": 
"File Name Ext Whitelist", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"description": "A comma or whitespace separated list of file extensions. 
Uploads having these extensions will be stored locally. Null value or asterisk 
() will allow all files." },
+    "headerFilterStrategy": { "index": 24, "kind": "parameter", "displayName": 
"Header Filter Strategy", "group": "advanced", "label": "advanced", "required": 
false, "type": "object", "javaType": 
"org.apache.camel.spi.HeaderFilterStrategy", "deprecated": false, "autowired": 
false, "secret": false, "description": "To use a custom HeaderFilterStrategy to 
filter headers to and from Camel message." },
+    "platformHttpEngine": { "index": 25, "kind": "parameter", "displayName": 
"Platform Http Engine", "group": "advanced", "label": "advanced", "required": 
false, "type": "object", "javaType": 
"org.apache.camel.component.platform.http.spi.PlatformHttpEngine", 
"deprecated": false, "autowired": false, "secret": false, "description": "An 
HTTP Server engine implementation to serve the requests of this endpoint." },
+    "oauthProfile": { "index": 26, "kind": "parameter", "displayName": "OAuth 
Profile", "group": "security", "label": "consumer,security", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "description": "OAuth profile name for 
validating incoming Authorization: Bearer tokens. When set, the request is 
authenticated before the route is processed. This requires an 
OAuthTokenValidationFactory; camel-oauth provides  [...]
   }
 }
diff --git 
a/components/camel-platform-http/src/main/docs/platform-http-component.adoc 
b/components/camel-platform-http/src/main/docs/platform-http-component.adoc
index 1b5790d7d0f1..0b551804037e 100644
--- a/components/camel-platform-http/src/main/docs/platform-http-component.adoc
+++ b/components/camel-platform-http/src/main/docs/platform-http-component.adoc
@@ -201,6 +201,37 @@ Platform HTTP component can act as a reverse proxy. In 
that case, some headers a
 
 At this moment, this feature is only supported for Quarkus implemented in 
`camel-platform-http-vertx` component.
 
+=== Forwarding requests under a path prefix (path-based reverse proxy)
+
+This is a different scenario from the `platform-http:proxy` forward proxy 
described above, which is a `Host` header
+based forward proxy and only supported in `camel-platform-http-vertx`. Here, 
the consumer is registered under an
+ordinary path, e.g. `/reverse-proxy`, and requests received under that path 
should be forwarded to a fixed backend
+with the prefix stripped, using the xref:components::http-component.adoc[http] 
producer's `bridgeEndpoint` option.
+
+Enable the consumer option `stripUriPrefix` so the matched consumer path is 
removed from `CamelHttpPath` before the
+route runs:
+
+[source,java]
+----
+from("platform-http:/reverse-proxy?matchOnUriPrefix=true&stripUriPrefix=true")
+    .to("http://backend?bridgeEndpoint=true";);
+----
+
+`matchOnUriPrefix` is not required for `stripUriPrefix`; it is only needed to 
also forward requests under sub-paths.
+On an exact match (e.g. `GET /reverse-proxy`) the path is stripped to `/`.
+
+|===
+| Incoming request | `CamelHttpPath` (`stripUriPrefix=false`, default) | 
`CamelHttpPath` (`stripUriPrefix=true`) | Forwarded to
+
+| `GET /reverse-proxy/get`
+| `/reverse-proxy/get`
+| `/get`
+| `http://backend/get`
+|===
+
+`CamelHttpUri`/`CamelHttpUrl` still hold the full original request, only 
`CamelHttpPath` is rewritten. This option is
+currently implemented in `camel-platform-http-vertx`.
+
 === File Attachments handling
 
 Since Apache Camel 4.10, multipart file uploads are easier and harmonized 
across all runtimes. When a single file is uploaded, the Apache Camel framework 
provides the following:
diff --git 
a/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/PlatformHttpEndpoint.java
 
b/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/PlatformHttpEndpoint.java
index 8574380b9d6e..793027e5f4eb 100644
--- 
a/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/PlatformHttpEndpoint.java
+++ 
b/components/camel-platform-http/src/main/java/org/apache/camel/component/platform/http/PlatformHttpEndpoint.java
@@ -104,6 +104,18 @@ public class PlatformHttpEndpoint extends DefaultEndpoint
               description = "Whether or not the consumer should try to find a 
target consumer "
                             + "by matching the URI prefix if no exact match is 
found.")
     private boolean matchOnUriPrefix;
+    @UriParam(label = "consumer", defaultValue = "false",
+              description = "Whether to strip the registered consumer path 
from CamelHttpPath after the request has been"
+                            + " matched, so the exchange sees the path 
relative to this consumer instead of the full raw"
+                            + " request path. Combined with the http 
producer's bridgeEndpoint option this allows building a"
+                            + " path-based reverse proxy without manual header 
manipulation, e.g. a route on"
+                            + " platform-http:/reverse-proxy with 
matchOnUriPrefix=true and stripUriPrefix=true bridged to"
+                            + " http://backend forwards /reverse-proxy/get to 
http://backend/get instead of"
+                            + " http://backend/reverse-proxy/get. CamelHttpUri 
and CamelHttpUrl are left untouched. The other"
+                            + " HTTP consumers (camel-servlet, camel-jetty, 
camel-netty-http, camel-undertow) already behave"
+                            + " this way by default; this option brings 
platform-http in line with them without changing its"
+                            + " default behavior.")
+    private boolean stripUriPrefix;
     @UriParam(label = "consumer", description = "A comma separated list of 
HTTP methods to serve, e.g. GET,POST ."
                                                 + " If no methods are 
specified, all methods will be served.")
     private String httpMethodRestrict;
@@ -271,6 +283,21 @@ public class PlatformHttpEndpoint extends DefaultEndpoint
         this.matchOnUriPrefix = matchOnUriPrefix;
     }
 
+    public boolean isStripUriPrefix() {
+        return stripUriPrefix;
+    }
+
+    /**
+     * If the option is true, the registered consumer path is stripped from 
the CamelHttpPath header after the request
+     * has been matched, so the exchange sees the path relative to this 
consumer instead of the full raw request path.
+     * This is useful in reverse proxy applications built with the http 
producer's bridgeEndpoint option, where the
+     * downstream target should receive the path with this consumer's own 
prefix removed. CamelHttpUri and CamelHttpUrl
+     * are left untouched.
+     */
+    public void setStripUriPrefix(boolean stripUriPrefix) {
+        this.stripUriPrefix = stripUriPrefix;
+    }
+
     public String getHttpMethodRestrict() {
         return httpMethodRestrict;
     }
diff --git 
a/components/camel-platform-http/src/test/java/org/apache/camel/component/platform/http/PlatformHttpEndpointStripUriPrefixTest.java
 
b/components/camel-platform-http/src/test/java/org/apache/camel/component/platform/http/PlatformHttpEndpointStripUriPrefixTest.java
new file mode 100644
index 000000000000..fd4957d56ad6
--- /dev/null
+++ 
b/components/camel-platform-http/src/test/java/org/apache/camel/component/platform/http/PlatformHttpEndpointStripUriPrefixTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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;
+
+import org.apache.camel.impl.DefaultCamelContext;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The stripUriPrefix consumer option must be provably inert for 
platform-http:proxy mode: proxy mode makes
+ * {@link PlatformHttpEndpoint#getPath()} return "/", and {@link 
org.apache.camel.http.base.HttpHelper#stripUriPrefix}
+ * treats a consumer path of "/" as "no prefix to strip", so enabling 
stripUriPrefix on a proxy endpoint must never
+ * change its behavior.
+ */
+class PlatformHttpEndpointStripUriPrefixTest {
+
+    @Test
+    void stripUriPrefixDefaultsToFalse() throws Exception {
+        assertFalse(stripUriPrefixOf("platform-http:/reverse-proxy"));
+        
assertFalse(stripUriPrefixOf("platform-http:/reverse-proxy?matchOnUriPrefix=true"));
+    }
+
+    @Test
+    void stripUriPrefixCanBeEnabled() throws Exception {
+        
assertTrue(stripUriPrefixOf("platform-http:/reverse-proxy?stripUriPrefix=true"));
+        
assertTrue(stripUriPrefixOf("platform-http:/reverse-proxy?matchOnUriPrefix=true&stripUriPrefix=true"));
+    }
+
+    @Test
+    void proxyModeIsUnaffectedByStripUriPrefix() throws Exception {
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            context.start();
+            PlatformHttpComponent component = new 
PlatformHttpComponent(context);
+            PlatformHttpEndpoint endpoint
+                    = (PlatformHttpEndpoint) 
component.createEndpoint("platform-http:proxy?stripUriPrefix=true");
+
+            // the option is honored as a plain bean property...
+            assertTrue(endpoint.isStripUriPrefix());
+            // ...but proxy mode detection and the effective consumer path are 
unaffected by it
+            assertTrue(endpoint.isHttpProxy());
+            assertEquals("/", endpoint.getPath());
+        }
+    }
+
+    private static boolean stripUriPrefixOf(String uri) throws Exception {
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            context.start();
+            PlatformHttpComponent component = new 
PlatformHttpComponent(context);
+            return ((PlatformHttpEndpoint) 
component.createEndpoint(uri)).isStripUriPrefix();
+        }
+    }
+}
diff --git 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/PlatformHttpEndpointBuilderFactory.java
 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/PlatformHttpEndpointBuilderFactory.java
index 9eba4146a388..d955fcc8965a 100644
--- 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/PlatformHttpEndpointBuilderFactory.java
+++ 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/PlatformHttpEndpointBuilderFactory.java
@@ -311,6 +311,60 @@ public interface PlatformHttpEndpointBuilderFactory {
             doSetProperty("produces", produces);
             return this;
         }
+        /**
+         * Whether to strip the registered consumer path from CamelHttpPath
+         * after the request has been matched, so the exchange sees the path
+         * relative to this consumer instead of the full raw request path.
+         * Combined with the http producer's bridgeEndpoint option this allows
+         * building a path-based reverse proxy without manual header
+         * manipulation, e.g. a route on platform-http:/reverse-proxy with
+         * matchOnUriPrefix=true and stripUriPrefix=true bridged to
+         * http://backend forwards /reverse-proxy/get to http://backend/get
+         * instead of http://backend/reverse-proxy/get. CamelHttpUri and
+         * CamelHttpUrl are left untouched. The other HTTP consumers
+         * (camel-servlet, camel-jetty, camel-netty-http, camel-undertow)
+         * already behave this way by default; this option brings platform-http
+         * in line with them without changing its default behavior.
+         * 
+         * The option is a: <code>boolean</code> type.
+         * 
+         * Default: false
+         * Group: consumer
+         * 
+         * @param stripUriPrefix the value to set
+         * @return the dsl builder
+         */
+        default PlatformHttpEndpointBuilder stripUriPrefix(boolean 
stripUriPrefix) {
+            doSetProperty("stripUriPrefix", stripUriPrefix);
+            return this;
+        }
+        /**
+         * Whether to strip the registered consumer path from CamelHttpPath
+         * after the request has been matched, so the exchange sees the path
+         * relative to this consumer instead of the full raw request path.
+         * Combined with the http producer's bridgeEndpoint option this allows
+         * building a path-based reverse proxy without manual header
+         * manipulation, e.g. a route on platform-http:/reverse-proxy with
+         * matchOnUriPrefix=true and stripUriPrefix=true bridged to
+         * http://backend forwards /reverse-proxy/get to http://backend/get
+         * instead of http://backend/reverse-proxy/get. CamelHttpUri and
+         * CamelHttpUrl are left untouched. The other HTTP consumers
+         * (camel-servlet, camel-jetty, camel-netty-http, camel-undertow)
+         * already behave this way by default; this option brings platform-http
+         * in line with them without changing its default behavior.
+         * 
+         * The option will be converted to a <code>boolean</code> type.
+         * 
+         * Default: false
+         * Group: consumer
+         * 
+         * @param stripUriPrefix the value to set
+         * @return the dsl builder
+         */
+        default PlatformHttpEndpointBuilder stripUriPrefix(String 
stripUriPrefix) {
+            doSetProperty("stripUriPrefix", stripUriPrefix);
+            return this;
+        }
         /**
          * OAuth profile name for validating incoming Authorization: Bearer
          * tokens. When set, the request is authenticated before the route is

Reply via email to