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

davsclaus pushed a commit to branch camel-4.22.x
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/camel-4.22.x by this push:
     new b621838972c7 [camel-4.22.x] CAMEL-26065: camel-netty-http - key 
multiplex channel handlers by listener address (#28516)
b621838972c7 is described below

commit b621838972c758ea66667b106c6290a1363356bb
Author: Guillaume Nodet <[email protected]>
AuthorDate: Tue Oct 27 07:28:21 2026 +0100

    [camel-4.22.x] CAMEL-26065: camel-netty-http - key multiplex channel 
handlers by listener address (#28516)
    
    Key the HttpServerConsumerChannelFactory map by listener address
    (host:port) rather than by port alone, so that multiple consumers
    on distinct IP addresses sharing the same port do not cross-dispatch
    requests to the wrong route handlers.
    
    Backport of #28352 to camel-4.22.x.
    
    Co-Authored-By: Claude Opus 4.8 <[email protected]>
---
 .../netty/http/HttpServerInitializerFactory.java   |  4 +-
 .../component/netty/http/NettyHttpComponent.java   | 24 +++++++-
 .../http/NettyHttpDifferentHostSamePortTest.java   | 72 ++++++++++++++++++++++
 3 files changed, 96 insertions(+), 4 deletions(-)

diff --git 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpServerInitializerFactory.java
 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpServerInitializerFactory.java
index 982fad1dcbe8..e27639e2ff39 100644
--- 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpServerInitializerFactory.java
+++ 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpServerInitializerFactory.java
@@ -124,8 +124,10 @@ public class HttpServerInitializerFactory extends 
ServerInitializerFactory {
             pipeline.addLast("deflater", new HttpContentCompressor());
         }
 
+        String address = consumer.getConfiguration().getAddress();
         int port = consumer.getConfiguration().getPort();
-        ChannelHandler handler = 
consumer.getEndpoint().getComponent().getMultiplexChannelHandler(port).getChannelHandler();
+        ChannelHandler handler
+                = 
consumer.getEndpoint().getComponent().getMultiplexChannelHandler(address, 
port).getChannelHandler();
 
         if (consumer.getConfiguration().isUsingExecutorService()) {
             EventExecutorGroup applicationExecutor = 
consumer.getEndpoint().getComponent().getExecutorService();
diff --git 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java
 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java
index 9070c291c568..8427a21a5f26 100644
--- 
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java
+++ 
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java
@@ -64,7 +64,7 @@ public class NettyHttpComponent extends NettyComponent
     private static final Logger LOG = 
LoggerFactory.getLogger(NettyHttpComponent.class);
 
     // factories which is created by this component and therefore manage their 
lifecycles
-    private final Map<Integer, HttpServerConsumerChannelFactory> 
multiplexChannelHandlers = new ConcurrentHashMap<>();
+    private final Map<String, HttpServerConsumerChannelFactory> 
multiplexChannelHandlers = new ConcurrentHashMap<>();
     private final Map<String, HttpServerBootstrapFactory> bootstrapFactories = 
new ConcurrentHashMap<>();
     @Metadata(label = "advanced")
     private NettyHttpBinding nettyHttpBinding;
@@ -328,8 +328,26 @@ public class NettyHttpComponent extends NettyComponent
         this.muteException = muteException;
     }
 
+    /**
+     * Gets, creating it on first use, the multiplex channel handler for the 
consumer bound to the given
+     * {@code address}. Handlers are keyed by {@code address}, so consumers 
bound to different interfaces on the same
+     * port each get their own handler and do not cross-dispatch.
+     *
+     * @param  address the listener address in {@code host:port} form (for 
example {@code 127.0.0.1:8080})
+     * @param  port    the listener port, used only to initialise a newly 
created handler for this address
+     * @return         the handler for the address, creating a new one if none 
exists yet
+     */
+    public HttpServerConsumerChannelFactory getMultiplexChannelHandler(String 
address, int port) {
+        return multiplexChannelHandlers.computeIfAbsent(address, s -> 
newHttpServerConsumerChannelFactory(port));
+    }
+
+    /**
+     * @deprecated use {@link #getMultiplexChannelHandler(String, int)}. This 
method only returns the handler of the
+     *             listener on {@code 0.0.0.0} for the given port.
+     */
+    @Deprecated
     public HttpServerConsumerChannelFactory getMultiplexChannelHandler(int 
port) {
-        return multiplexChannelHandlers.computeIfAbsent(port, s -> 
newHttpServerConsumerChannelFactory(port));
+        return getMultiplexChannelHandler("0.0.0.0:" + port, port);
     }
 
     private static HttpServerConsumerChannelFactory 
newHttpServerConsumerChannelFactory(int port) {
@@ -364,7 +382,7 @@ public class NettyHttpComponent extends NettyComponent
 
     private HttpServerBootstrapFactory 
newHttpServerBootstrapFactory(NettyHttpConsumer consumer) {
         final HttpServerConsumerChannelFactory channelFactory
-                = 
getMultiplexChannelHandler(consumer.getConfiguration().getPort());
+                = 
getMultiplexChannelHandler(consumer.getConfiguration().getAddress(), 
consumer.getConfiguration().getPort());
         final HttpServerBootstrapFactory answer = new 
HttpServerBootstrapFactory(channelFactory);
 
         answer.init(getCamelContext(), consumer.getConfiguration(), new 
HttpServerInitializerFactory(consumer));
diff --git 
a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpDifferentHostSamePortTest.java
 
b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpDifferentHostSamePortTest.java
new file mode 100644
index 000000000000..f64dbd6ba77d
--- /dev/null
+++ 
b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpDifferentHostSamePortTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.netty.http;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+
+@DisabledOnOs(value = OS.MAC, disabledReason = "macOS only configures 
127.0.0.1 on the loopback interface")
+public class NettyHttpDifferentHostSamePortTest extends BaseNettyTestSupport {
+
+    @Test
+    public void testDifferentHostSamePortHandlersAreDistinct() {
+        NettyHttpComponent component = context.getComponent("netty-http", 
NettyHttpComponent.class);
+        int port = getPort();
+        HttpServerConsumerChannelFactory handler1 = 
component.getMultiplexChannelHandler("127.0.0.1:" + port, port);
+        HttpServerConsumerChannelFactory handler2 = 
component.getMultiplexChannelHandler("127.0.0.2:" + port, port);
+        assertNotSame(handler1, handler2);
+    }
+
+    @Test
+    public void testRoutesOnDifferentHostsSamePortDoNotCrossDispatch() {
+        int port = getPort();
+
+        String fooResponse = 
template.requestBody("netty-http:http://127.0.0.1:"; + port + "/foo", "request", 
String.class);
+        assertEquals("foo", fooResponse);
+
+        String barResponse = 
template.requestBody("netty-http:http://127.0.0.2:"; + port + "/bar", "request", 
String.class);
+        assertEquals("bar", barResponse);
+
+        Exchange out1 = template.request("netty-http:http://127.0.0.1:"; + port 
+ "/bar?throwExceptionOnFailure=false",
+                exchange -> exchange.getIn().setBody("request"));
+        assertEquals(404, 
out1.getMessage().getHeader(Exchange.HTTP_RESPONSE_CODE));
+
+        Exchange out2 = template.request("netty-http:http://127.0.0.2:"; + port 
+ "/foo?throwExceptionOnFailure=false",
+                exchange -> exchange.getIn().setBody("request"));
+        assertEquals(404, 
out2.getMessage().getHeader(Exchange.HTTP_RESPONSE_CODE));
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("netty-http:http://127.0.0.1:{{port}}/foo";)
+                        .transform().constant("foo");
+
+                from("netty-http:http://127.0.0.2:{{port}}/bar";)
+                        .transform().constant("bar");
+            }
+        };
+    }
+}

Reply via email to