This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch camel-4.18.x
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.18.x by this push:
new 88e1632efd09 [camel-4.18.x] CAMEL-26065: camel-netty-http - key
multiplex channel handlers by listener address (#28517)
88e1632efd09 is described below
commit 88e1632efd09a1c412c97e076d566a049ea2d1dd
Author: Guillaume Nodet <[email protected]>
AuthorDate: Tue Oct 27 07:28:30 2026 +0100
[camel-4.18.x] CAMEL-26065: camel-netty-http - key multiplex channel
handlers by listener address (#28517)
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.18.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 43623e9f79aa..4a73003b7257 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
@@ -125,8 +125,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 65a9506a82ac..5dbe747e4bdf 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;
@@ -329,8 +329,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) {
@@ -352,7 +370,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");
+ }
+ };
+ }
+}