This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-26487-netty-http-equals-4.18.x in repository https://gitbox.apache.org/repos/asf/camel.git
commit 959cbfa97eec761ba20abf2f17b0c95723a9d9e4 Author: Claus Ibsen <[email protected]> AuthorDate: Tue Nov 3 15:17:10 2026 +0100 CAMEL-26487: camel-netty-http - endpoints that differ only in their options are not equal NettyHttpEndpoint compares the full endpoint URI, with the endpoint options, in equals and hashCode. Endpoints that share an HTTP address but differ in their options then get a producer of their own in a ProducerTemplate, toD or another EIP with a dynamic URI. The endpoint URI is unchanged (the HTTP address), so JMX names, auto-mock names, tracing and metrics are the same. On main the endpoint URI itself became the full URI (#28863); this is the backport of the behaviour without that visible change. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../component/netty/http/NettyHttpComponent.java | 2 + .../component/netty/http/NettyHttpEndpoint.java | 29 +++++++ .../NettyHttpEndpointDifferentOptionsTest.java | 90 ++++++++++++++++++++++ .../ROOT/pages/camel-4x-upgrade-guide-4_18.adoc | 14 ++++ 4 files changed, 135 insertions(+) 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 5ef61be7fca7..d1647ddb24a5 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 @@ -182,6 +182,8 @@ public class NettyHttpComponent extends NettyComponent String addressUri = URISupport.createRemainingURI(u, parameters).toString(); NettyHttpEndpoint answer = new NettyHttpEndpoint(addressUri, this, config); + // endpoints that differ only in their options must not be equal, so the full uri is the identity + answer.setIdentityUri(uri); answer.getConfiguration().setMuteException(muteException); setProperties(answer, parameters); diff --git a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpEndpoint.java b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpEndpoint.java index 5dc0300ef7a2..4c706b025298 100644 --- a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpEndpoint.java +++ b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpEndpoint.java @@ -54,6 +54,9 @@ public class NettyHttpEndpoint extends NettyEndpoint implements HeaderFilterStra private static final Logger LOG = LoggerFactory.getLogger(NettyHttpEndpoint.class); static final String PROXY_NOT_SUPPORTED_MESSAGE = "Netty Http Producer does not support proxy mode"; + // the full uri with the endpoint options, which is the identity of this endpoint in equals and hashCode + private String identityUri; + @UriParam private NettyHttpConfiguration configuration; @UriParam(label = "advanced", name = "configuration", @@ -255,6 +258,32 @@ public class NettyHttpEndpoint extends NettyEndpoint implements HeaderFilterStra } } + void setIdentityUri(String identityUri) { + this.identityUri = identityUri; + } + + private String getIdentityUri() { + return identityUri != null ? identityUri : getEndpointUri(); + } + + @Override + public int hashCode() { + return getIdentityUri().hashCode() * 37 + 1; + } + + @Override + public boolean equals(Object object) { + if (object instanceof NettyHttpEndpoint that) { + // the endpoint uri is the http address, which endpoints that differ only in their options share, + // so compare the full uri; and as DefaultEndpoint, the CamelContext must also be the same + String thisContextName = this.getCamelContext() != null ? this.getCamelContext().getName() : null; + String thatContextName = that.getCamelContext() != null ? that.getCamelContext().getName() : null; + return ObjectHelper.equal(this.getIdentityUri(), that.getIdentityUri()) + && ObjectHelper.equal(thisContextName, thatContextName); + } + return false; + } + private boolean isProxyProtocol() { URI baseUri = URI.create(getEndpointBaseUri()); String protocol = baseUri.getScheme(); diff --git a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpEndpointDifferentOptionsTest.java b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpEndpointDifferentOptionsTest.java new file mode 100644 index 000000000000..9762153a223a --- /dev/null +++ b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpEndpointDifferentOptionsTest.java @@ -0,0 +1,90 @@ +/* + * 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.CamelExecutionException; +import org.apache.camel.Endpoint; +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.support.DefaultExchange; +import org.junit.jupiter.api.Test; + +import static org.apache.camel.test.junit5.TestSupport.assertIsInstanceOf; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class NettyHttpEndpointDifferentOptionsTest extends BaseNettyTestSupport { + + @Test + public void testEndpointsWithDifferentOptionsAreNotEqual() { + Endpoint plain = context.getEndpoint("netty-http:http://localhost:{{port}}/foo?bar=123"); + Endpoint option = context.getEndpoint("netty-http:http://localhost:{{port}}/foo?bar=123&throwExceptionOnFailure=false"); + + assertNotEquals(plain, option); + assertNotEquals(plain.hashCode(), option.hashCode()); + // the endpoint uri is unchanged: the http address + assertEquals(plain.getEndpointUri(), option.getEndpointUri()); + + // the same uri is the same endpoint + assertEquals(plain, context.getEndpoint("netty-http:http://localhost:{{port}}/foo?bar=123")); + + // both send to the same address, which keeps the query parameters that are not endpoint options + String url = "http://localhost:" + getPort() + "/foo?bar=123"; + assertEquals(url, NettyHttpHelper.createURL(new DefaultExchange(context), (NettyHttpEndpoint) plain)); + assertEquals(url, NettyHttpHelper.createURL(new DefaultExchange(context), (NettyHttpEndpoint) option)); + } + + @Test + public void testProducerTemplateUsesTheOptionsOfEachEndpoint() { + CamelExecutionException e = assertThrows(CamelExecutionException.class, + () -> template.requestBody("netty-http:http://localhost:{{port}}/fail", "Hello World", String.class)); + NettyHttpOperationFailedException cause = assertIsInstanceOf(NettyHttpOperationFailedException.class, e.getCause()); + assertEquals(500, cause.getStatusCode()); + + // the same address with another option is another endpoint, with a producer of its own + String out = template.requestBody("netty-http:http://localhost:{{port}}/fail?throwExceptionOnFailure=false", + "Hello World", String.class); + assertEquals("Failed", out); + } + + @Test + public void testToDUsesTheOptionsOfEachEndpoint() { + CamelExecutionException e = assertThrows(CamelExecutionException.class, + () -> template.requestBodyAndHeader("direct:start", "Hello World", "throwOnFailure", "true", String.class)); + NettyHttpOperationFailedException cause = assertIsInstanceOf(NettyHttpOperationFailedException.class, e.getCause()); + assertEquals(500, cause.getStatusCode()); + + String out = template.requestBodyAndHeader("direct:start", "Hello World", "throwOnFailure", "false", String.class); + assertEquals("Failed", out); + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("netty-http:http://0.0.0.0:{{port}}/fail") + .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(500)) + .transform().constant("Failed"); + + from("direct:start") + .toD("netty-http:http://localhost:{{port}}/fail?throwExceptionOnFailure=${header.throwOnFailure}"); + } + }; + } +} diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc index a9727cfadb38..81f482869000 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc @@ -190,6 +190,20 @@ CamelContext of that provider. It now closes each of them only when no other `JC it returned the closed cache, whose operations throw `IllegalStateException`. This matters for code that calls a `JCacheManager` (for example one created by a custom `JCacheManagerFactory`) after it was closed. +=== camel-netty-http - endpoints that differ only in their options are distinct endpoints + +Two `netty-http` endpoints whose URIs differ only in their endpoint options, for example +`netty-http:http://localhost:8080/foo` and `netty-http:http://localhost:8080/foo?throwExceptionOnFailure=false`, are no +longer equal. Each of them now gets its own producer when a `ProducerTemplate`, `toD` or another EIP with a dynamic URI +sends to them. Before, these endpoints used the producer of the endpoint that was used first, with the options of that +endpoint. + +The endpoint URI does not change: it is still the HTTP address, such as `http://localhost:8080/foo`, so JMX names, +auto-mock names, tracing and metrics are the same as before. + +Two routes that consume from the same address with different endpoint options are no longer rejected at startup as +"Multiple consumers for the same endpoint is not allowed". Use one consumer per address. + == Upgrading from 4.18.4 to 4.18.5 === camel-docling - output path handling
