diff --git a/jersey/connector/src/main/java/io/helidon/jersey/connector/HelidonConnector.java b/jersey/connector/src/main/java/io/helidon/jersey/connector/HelidonConnector.java
index 868d0a81fa6..5ed9b6030ae 100644
--- a/jersey/connector/src/main/java/io/helidon/jersey/connector/HelidonConnector.java
+++ b/jersey/connector/src/main/java/io/helidon/jersey/connector/HelidonConnector.java
@@ -46,6 +46,7 @@
import jakarta.ws.rs.core.Response;
import org.glassfish.jersey.client.ClientRequest;
import org.glassfish.jersey.client.ClientResponse;
+import org.glassfish.jersey.client.JerseyClient;
import org.glassfish.jersey.client.spi.AsyncConnectorCallback;
import org.glassfish.jersey.client.spi.Connector;
import org.glassfish.jersey.internal.util.PropertiesHelper;
@@ -82,7 +83,8 @@ class HelidonConnector implements Connector {
var builder = WebClientConfig.builder();
// use config for client
- builder.config(helidonConfig(config).orElse(Config.empty()));
+ Config helidonConfig = helidonConfig(config).orElse(Config.empty());
+ builder.config(helidonConfig);
// proxy support
proxy = ProxyBuilder.createProxy(config).orElse(Proxy.create());
@@ -98,11 +100,18 @@ class HelidonConnector implements Connector {
builder.followRedirects(getValue(properties, FOLLOW_REDIRECTS, true));
}
- // prefer Tls over SSLContext
- if (properties.containsKey(TLS)) {
- builder.tls(getValue(properties, TLS, Tls.class));
- } else if (client.getSslContext() != null) {
- builder.tls(Tls.builder().sslContext(client.getSslContext()).build());
+ //Whether WebClient TLS has been already set via config
+ boolean helidonConfigTlsSet = helidonConfig.map(hc -> hc.get("tls").exists()).orElse(false);
+ boolean isJerseyClient = client instanceof JerseyClient;
+ //Whether Jersey client has non-default SslContext set. If so, we should honor these settings
+ boolean jerseyHasDefaultSsl = isJerseyClient && ((JerseyClient) client).isDefaultSslContext();
+
+ if (!helidonConfigTlsSet || !isJerseyClient || !jerseyHasDefaultSsl) {// prefer Tls over SSLContext
+ if (properties.containsKey(TLS)) {
+ builder.tls(getValue(properties, TLS, Tls.class));
+ } else if (client.getSslContext() != null) {
+ builder.tls(Tls.builder().sslContext(client.getSslContext()).build());
+ }
}
// protocol configs
diff --git a/tests/integration/pom.xml b/tests/integration/pom.xml
index 31377fd7775..128661f8593 100644
--- a/tests/integration/pom.xml
+++ b/tests/integration/pom.xml
@@ -59,6 +59,7 @@
native-image
oidc
restclient
+ restclient-connector
security
vault
zipkin-mp-2.2
diff --git a/tests/integration/restclient-connector/pom.xml b/tests/integration/restclient-connector/pom.xml
new file mode 100644
index 00000000000..16c537519dc
--- /dev/null
+++ b/tests/integration/restclient-connector/pom.xml
@@ -0,0 +1,68 @@
+
+
+
+
+ 4.0.0
+
+ io.helidon.tests.integration
+ helidon-tests-integration
+ 4.0.0-SNAPSHOT
+
+
+ helidon-tests-integration-restclient-connector
+ Helidon Integration Test RestClient Webclient Connector
+
+
+
+ io.helidon.microprofile.bundles
+ helidon-microprofile
+
+
+ io.helidon.microprofile.rest-client
+ helidon-microprofile-rest-client
+
+
+ io.smallrye
+ jandex
+ runtime
+ true
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ test
+
+
+ io.helidon.microprofile.testing
+ helidon-microprofile-testing-junit5
+ test
+
+
+ io.helidon.jersey
+ helidon-jersey-connector
+ test
+
+
+
+
\ No newline at end of file
diff --git a/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/GreetResource.java b/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/GreetResource.java
new file mode 100644
index 00000000000..9d80a04f57b
--- /dev/null
+++ b/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/GreetResource.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates.
+ *
+ * Licensed 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 io.helidon.tests.integration.resclient.connector;
+
+import java.util.Collections;
+
+import jakarta.json.Json;
+import jakarta.json.JsonBuilderFactory;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+/**
+ * A typical greet resource that only handles a single GET for a default message.
+ */
+@Path("/greet")
+public class GreetResource {
+
+ private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap());
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public JsonObject getDefaultMessage() {
+ return createResponse("World");
+ }
+
+ private JsonObject createResponse(String who) {
+ String msg = String.format("%s %s!", "Hello", who);
+
+ return JSON.createObjectBuilder()
+ .add("message", msg)
+ .build();
+ }
+}
diff --git a/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/GreetResourceClient.java b/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/GreetResourceClient.java
new file mode 100644
index 00000000000..0ff3f8b3a47
--- /dev/null
+++ b/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/GreetResourceClient.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates.
+ *
+ * Licensed 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 io.helidon.tests.integration.resclient.connector;
+
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
+
+/**
+ * RestClient interface for a simple greet resource that includes a few FT annotations.
+ */
+@Path("/greet")
+@RegisterProvider(GreetResourceFilter.class)
+public interface GreetResourceClient {
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ JsonObject getDefaultMessage();
+
+}
diff --git a/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/GreetResourceFilter.java b/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/GreetResourceFilter.java
new file mode 100644
index 00000000000..ccfaf09bf69
--- /dev/null
+++ b/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/GreetResourceFilter.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates.
+ *
+ * Licensed 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 io.helidon.tests.integration.resclient.connector;
+
+import java.io.IOException;
+import java.net.URI;
+
+import io.helidon.common.context.Contexts;
+
+import jakarta.ws.rs.client.ClientRequestContext;
+import jakarta.ws.rs.client.ClientRequestFilter;
+
+/**
+ * A client request filter that replaces port 8080 by the ephemeral port allocated for the
+ * webserver in each run. This is necessary since {@link GreetResourceClient} uses an annotation
+ * to specify the base URI, and its value cannot be changed dynamically.
+ */
+public class GreetResourceFilter implements ClientRequestFilter {
+
+ @Override
+ public void filter(ClientRequestContext requestContext) throws IOException {
+ URI uri = requestContext.getUri();
+ String fixedUri = uri.toString().replace("8080", extractDynamicPort());
+ requestContext.setUri(URI.create(fixedUri));
+ }
+
+ private String extractDynamicPort() {
+ URI uri = Contexts.globalContext().get(getClass(), URI.class).orElseThrow();
+ String uriString = uri.toString();
+ int k = uriString.lastIndexOf(":");
+ int j = uriString.indexOf("/", k);
+ j = j < 0 ? uriString.length() : j; //Prevent failing if / is missing after the port
+ return uriString.substring(k + 1, j);
+ }
+}
diff --git a/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/package-info.java b/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/package-info.java
new file mode 100644
index 00000000000..510c7bd1a2b
--- /dev/null
+++ b/tests/integration/restclient-connector/src/main/java/io/helidon/tests/integration/resclient/connector/package-info.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates.
+ *
+ * Licensed 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 io.helidon.tests.integration.resclient.connector;
\ No newline at end of file
diff --git a/tests/integration/restclient-connector/src/main/resources/META-INF/beans.xml b/tests/integration/restclient-connector/src/main/resources/META-INF/beans.xml
new file mode 100644
index 00000000000..79fe65a8048
--- /dev/null
+++ b/tests/integration/restclient-connector/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
diff --git a/tests/integration/restclient-connector/src/test/java/io/helidon/tests/integration/restclient/connector/TlsTest.java b/tests/integration/restclient-connector/src/test/java/io/helidon/tests/integration/restclient/connector/TlsTest.java
new file mode 100644
index 00000000000..0cc4add9a47
--- /dev/null
+++ b/tests/integration/restclient-connector/src/test/java/io/helidon/tests/integration/restclient/connector/TlsTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates.
+ *
+ * Licensed 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 io.helidon.tests.integration.restclient.connector;
+
+import java.io.UncheckedIOException;
+import java.net.URI;
+import java.security.NoSuchAlgorithmException;
+import java.util.Map;
+
+import javax.net.ssl.SSLContext;
+
+import io.helidon.common.context.Contexts;
+import io.helidon.config.Config;
+import io.helidon.config.ConfigSources;
+import io.helidon.jersey.connector.HelidonProperties;
+import io.helidon.microprofile.testing.junit5.Configuration;
+import io.helidon.microprofile.testing.junit5.HelidonTest;
+import io.helidon.tests.integration.resclient.connector.GreetResourceClient;
+import io.helidon.tests.integration.resclient.connector.GreetResourceFilter;
+
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.client.WebTarget;
+import org.eclipse.microprofile.rest.client.RestClientBuilder;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.endsWith;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@HelidonTest
+@Configuration(configSources = "tls-config.properties")
+public class TlsTest {
+
+ @Test
+ void testHelloWorld(WebTarget target) {
+ Config config = Config.create(ConfigSources.create(Map.of("tls.trust-all", "true")));
+ Contexts.globalContext().register(GreetResourceFilter.class, target.getUri());
+
+ GreetResourceClient client = RestClientBuilder.newBuilder()
+ .baseUri(URI.create("https://localhost:8080"))
+ .property(HelidonProperties.CONFIG, config)
+ .build(GreetResourceClient.class);
+
+ JsonObject defaultMessage = client.getDefaultMessage();
+ assertThat(defaultMessage.toString(), is("{\"message\":\"Hello World!\"}"));
+ }
+
+ @Test
+ void restClientSslContextPriority(WebTarget target) throws NoSuchAlgorithmException {
+ Config config = Config.create(ConfigSources.create(Map.of("tls.trust-all", "true")));
+ Contexts.globalContext().register(GreetResourceFilter.class, target.getUri());
+
+ GreetResourceClient client = RestClientBuilder.newBuilder()
+ .baseUri(URI.create("https://localhost:8080"))
+ .property(HelidonProperties.CONFIG, config)
+ .sslContext(SSLContext.getDefault())
+ .build(GreetResourceClient.class);
+
+ ProcessingException exception = assertThrows(ProcessingException.class, client::getDefaultMessage);
+ assertThat(exception.getCause(), instanceOf(UncheckedIOException.class));
+ assertThat(exception.getCause().getMessage(), endsWith("Failed to execute SSL handshake"));
+ }
+
+
+}
diff --git a/tests/integration/restclient-connector/src/test/resources/server.p12 b/tests/integration/restclient-connector/src/test/resources/server.p12
new file mode 100644
index 00000000000..c5e409b4433
Binary files /dev/null and b/tests/integration/restclient-connector/src/test/resources/server.p12 differ
diff --git a/tests/integration/restclient-connector/src/test/resources/tls-config.properties b/tests/integration/restclient-connector/src/test/resources/tls-config.properties
new file mode 100644
index 00000000000..b6342e06215
--- /dev/null
+++ b/tests/integration/restclient-connector/src/test/resources/tls-config.properties
@@ -0,0 +1,26 @@
+#
+# Copyright (c) 2024 Oracle and/or its affiliates.
+#
+# Licensed 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.
+#
+
+server.host=0.0.0.0
+
+#Truststore setup
+server.tls.trust.keystore.resource.resource-path=server.p12
+server.tls.trust.keystore.passphrase=toChange
+server.tls.trust.keystore.trust-store=true
+
+#Keystore with private key and server certificate
+server.tls.private-key.keystore.resource.resource-path=server.p12
+server.tls.private-key.keystore.passphrase=toChange
\ No newline at end of file