diff --git a/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java b/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java index f0eac62ca9..b19e366e03 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java @@ -40,6 +40,28 @@ package org.glassfish.jersey.client.internal; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.ClientRequest; +import org.glassfish.jersey.client.ClientResponse; +import org.glassfish.jersey.client.HttpUrlConnectorProvider; +import org.glassfish.jersey.client.JerseyClient; +import org.glassfish.jersey.client.RequestEntityProcessing; +import org.glassfish.jersey.client.spi.AsyncConnectorCallback; +import org.glassfish.jersey.client.spi.Connector; +import org.glassfish.jersey.internal.util.PropertiesHelper; +import org.glassfish.jersey.internal.util.collection.LazyValue; +import org.glassfish.jersey.internal.util.collection.UnsafeValue; +import org.glassfish.jersey.internal.util.collection.Value; +import org.glassfish.jersey.internal.util.collection.Values; +import org.glassfish.jersey.message.internal.Statuses; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSocketFactory; +import javax.ws.rs.ProcessingException; +import javax.ws.rs.client.Client; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -61,30 +83,6 @@ import java.util.logging.Logger; import java.util.stream.Collectors; -import javax.ws.rs.ProcessingException; -import javax.ws.rs.client.Client; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLSocketFactory; - -import org.glassfish.jersey.client.ClientProperties; -import org.glassfish.jersey.client.ClientRequest; -import org.glassfish.jersey.client.ClientResponse; -import org.glassfish.jersey.client.HttpUrlConnectorProvider; -import org.glassfish.jersey.client.JerseyClient; -import org.glassfish.jersey.client.RequestEntityProcessing; -import org.glassfish.jersey.client.spi.AsyncConnectorCallback; -import org.glassfish.jersey.client.spi.Connector; -import org.glassfish.jersey.internal.util.PropertiesHelper; -import org.glassfish.jersey.internal.util.collection.LazyValue; -import org.glassfish.jersey.internal.util.collection.UnsafeValue; -import org.glassfish.jersey.internal.util.collection.Value; -import org.glassfish.jersey.internal.util.collection.Values; -import org.glassfish.jersey.message.internal.Statuses; - /** * Default client transport connector using {@link HttpURLConnection}. * @@ -92,6 +90,12 @@ */ public class HttpUrlConnector implements Connector { + static { + // Initialize the default SSL socket factory to prevent a race condition in #secureConnection + // See https://github.com/jersey/jersey/issues/3293 + HttpsURLConnection.getDefaultSSLSocketFactory(); + } + private static final Logger LOGGER = Logger.getLogger(HttpUrlConnector.class.getName()); private static final String ALLOW_RESTRICTED_HEADERS_SYSTEM_PROPERTY = "sun.net.http.allowRestrictedHeaders"; // The list of restricted headers is extracted from sun.net.www.protocol.http.HttpURLConnection diff --git a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/ssl/SslHttpUrlConnectorTest.java b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/ssl/SslHttpUrlConnectorTest.java index a616937c5e..d4ff8b6b3a 100644 --- a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/ssl/SslHttpUrlConnectorTest.java +++ b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/ssl/SslHttpUrlConnectorTest.java @@ -40,26 +40,32 @@ package org.glassfish.jersey.tests.e2e.client.connector.ssl; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.HttpUrlConnectorProvider; +import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; +import org.glassfish.jersey.logging.LoggingFeature; +import org.junit.Test; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.core.Response; import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.Socket; import java.net.URL; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.core.Response; - -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; - -import org.glassfish.jersey.client.ClientConfig; -import org.glassfish.jersey.client.HttpUrlConnectorProvider; -import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; -import org.glassfish.jersey.logging.LoggingFeature; - -import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -103,6 +109,59 @@ public HttpURLConnection getConnection(final URL url) throws IOException { assertTrue(socketFactory.isVisited()); } + /** + * Test for https://github.com/jersey/jersey/issues/3293 + */ + @Test + public void testConcurrentRequestsWithCustomSSLContext() throws Exception { + final SSLContext sslContext = getSslContext(); + + final Client client = ClientBuilder.newBuilder() + .sslContext(sslContext) + .register(HttpAuthenticationFeature.basic("user", "password")) + .register(LoggingFeature.class) + .build(); + + int numThreads = 5; + CyclicBarrier barrier = new CyclicBarrier(numThreads); + ExecutorService service = Executors.newFixedThreadPool(numThreads); + List exceptions = new CopyOnWriteArrayList<>(); + + for (int i = 0; i < numThreads; i++) { + service.submit(() -> { + try { + barrier.await(1, TimeUnit.MINUTES); + for (int call = 0; call < 10; call++) { + final Response response = client.target(Server.BASE_URI).path("/").request().get(); + assertEquals(200, response.getStatus()); + } + } catch (Exception ex) { + exceptions.add(ex); + } + }); + } + + service.shutdown(); + + assertTrue( + service.awaitTermination(1, TimeUnit.MINUTES) + ); + + assertTrue( + toString(exceptions), + exceptions.isEmpty() + ); + } + + private String toString(List exceptions) { + StringWriter writer = new StringWriter(); + PrintWriter printWriter = new PrintWriter(writer); + + exceptions.forEach(e -> e.printStackTrace(printWriter)); + + return writer.toString(); + } + public static class CustomSSLSocketFactory extends SSLSocketFactory { private boolean visited = false;