From a83d288f3952b92a6a713ffc74ac22165a973fbd Mon Sep 17 00:00:00 2001 From: jbescos Date: Fri, 1 Jul 2022 14:48:57 +0200 Subject: [PATCH] Support ClientProperties.PROXY_URI & al in HttpUrlConnector (#5091) * Support for proxy in HttpUrlConnector Signed-off-by: Jorge Bescos Gascon --- .../jersey/client/ClientProperties.java | 17 ++++++- .../client/HttpUrlConnectorProvider.java | 20 +++++++- .../client/internal/HttpUrlConnector.java | 48 ++++++++++++++++--- .../client/internal/localization.properties | 3 +- .../jersey/client/HttpUrlConnectorTest.java | 2 +- .../glassfish/jersey/ExternalProperties.java | 14 +++++- .../e2e/client/connector/proxy/ProxyTest.java | 5 +- .../externalproperties/HttpProxyTest.java | 2 +- .../jersey4003/LostResponseTest.java | 40 ++++++++-------- 9 files changed, 116 insertions(+), 35 deletions(-) diff --git a/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java b/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java index 7f1752ec71..4b42f10297 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -520,4 +520,19 @@ public static T getValue(final Map properties, final String key, public static T getValue(final Map properties, final String key, final Class type) { return PropertiesHelper.getValue(properties, key, type, null); } + + /** + * Get the value of the specified property. If null, it will obtain it from System property. + *

+ * If the property is not set the method will return {@code null}. + * + * @param properties Map of properties to get the property value from. + * @param key Name of the property. + * @param systemKey Name of the System property. + * @return Value of the property or {@code null}. + * @since 2.37 + */ + public static String getValue(Map properties, String key, String systemKey) { + return PropertiesHelper.getValue(properties, key, System.getProperty(systemKey), String.class, null); + } } diff --git a/core-client/src/main/java/org/glassfish/jersey/client/HttpUrlConnectorProvider.java b/core-client/src/main/java/org/glassfish/jersey/client/HttpUrlConnectorProvider.java index 7389781ece..25a6d78cf5 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/HttpUrlConnectorProvider.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/HttpUrlConnectorProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -18,6 +18,7 @@ import java.io.IOException; import java.net.HttpURLConnection; +import java.net.Proxy; import java.net.URL; import java.util.Map; import java.util.logging.Logger; @@ -267,6 +268,23 @@ public interface ConnectionFactory { * @throws java.io.IOException in case the connection cannot be provided. */ public HttpURLConnection getConnection(URL url) throws IOException; + + /** + * Get a {@link java.net.HttpURLConnection} for a given URL. + *

+ * Implementation of the method MUST be thread-safe and MUST ensure that + * a dedicated {@link java.net.HttpURLConnection} instance is returned for concurrent + * requests. + *

+ * + * @param url the endpoint URL. + * @param proxy the configured proxy or null. + * @return the {@link java.net.HttpURLConnection}. + * @throws java.io.IOException in case the connection cannot be provided. + */ + default HttpURLConnection getConnection(URL url, Proxy proxy) throws IOException { + return (proxy == null) ? getConnection(url) : (HttpURLConnection) url.openConnection(proxy); + } } private static class DefaultConnectionFactory implements ConnectionFactory { 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 443c8573ed..ef85fa8eaf 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -21,13 +21,18 @@ import java.io.InputStream; import java.lang.reflect.Field; import java.net.HttpURLConnection; +import java.net.InetSocketAddress; import java.net.ProtocolException; +import java.net.Proxy; +import java.net.Proxy.Type; import java.net.SocketTimeoutException; import java.net.URI; import java.net.URISyntaxException; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; +import java.util.Arrays; +import java.util.Base64; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -37,17 +42,19 @@ import java.util.concurrent.Future; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Pattern; import java.util.stream.Collectors; +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.Configuration; 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.ExternalProperties; import org.glassfish.jersey.client.ClientProperties; import org.glassfish.jersey.client.ClientRequest; import org.glassfish.jersey.client.ClientResponse; @@ -314,10 +321,37 @@ protected void secureConnection(final JerseyClient client, final HttpURLConnecti } } + private static URI getProxyUriValue(Object proxy) { + if (proxy instanceof URI) { + return (URI) proxy; + } else if (proxy instanceof String) { + return URI.create((String) proxy); + } else { + throw new ProcessingException(LocalizationMessages.WRONG_PROXY_URI_TYPE(ClientProperties.PROXY_URI)); + } + } + private ClientResponse _apply(final ClientRequest request) throws IOException { final HttpURLConnection uc; - - uc = this.connectionFactory.getConnection(request.getUri().toURL()); + Proxy proxy = null; + Object proxyUri = request.getConfiguration().getProperties().get(ClientProperties.PROXY_URI); + if (proxyUri != null) { + URI uri = getProxyUriValue(proxyUri); + String username = ClientProperties.getValue(request.getConfiguration().getProperties(), + ClientProperties.PROXY_USERNAME, ExternalProperties.HTTP_PROXY_USER); + String password = ClientProperties.getValue(request.getConfiguration().getProperties(), + ClientProperties.PROXY_PASSWORD, ExternalProperties.HTTP_PROXY_PASSWORD); + if (username != null) { + StringBuilder auth = new StringBuilder().append(username).append(":"); + if (password != null) { + auth.append(password); + } + String encoded = "Basic " + Base64.getEncoder().encodeToString(auth.toString().getBytes()); + request.getHeaders().put("Proxy-Authorization", Arrays.asList(encoded)); + } + proxy = new Proxy(Type.HTTP, new InetSocketAddress(uri.getHost(), uri.getPort())); + } + uc = this.connectionFactory.getConnection(request.getUri().toURL(), proxy); uc.setDoInput(true); final String httpMethod = request.getMethod(); diff --git a/core-client/src/main/resources/org/glassfish/jersey/client/internal/localization.properties b/core-client/src/main/resources/org/glassfish/jersey/client/internal/localization.properties index 4bc0ae8e30..8e3823cde3 100644 --- a/core-client/src/main/resources/org/glassfish/jersey/client/internal/localization.properties +++ b/core-client/src/main/resources/org/glassfish/jersey/client/internal/localization.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. # # This program and the accompanying materials are made available under the # terms of the Eclipse Public License v. 2.0, which is available at @@ -84,3 +84,4 @@ error.request.cancelled=Request cancelled by the client call. error.listener.init=ClientLifecycleListener {0} failed to initialize properly. error.listener.close=ClientLifecycleListener {0} failed to close properly. error.shutdownhook.close=Client shutdown hook {0} failed. +wrong.proxy.uri.type=The proxy URI ("{0}") property MUST be an instance of String or URI. diff --git a/core-client/src/test/java/org/glassfish/jersey/client/HttpUrlConnectorTest.java b/core-client/src/test/java/org/glassfish/jersey/client/HttpUrlConnectorTest.java index 7cd91213bd..5bb7a78780 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/HttpUrlConnectorTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/HttpUrlConnectorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/core-common/src/main/java/org/glassfish/jersey/ExternalProperties.java b/core-common/src/main/java/org/glassfish/jersey/ExternalProperties.java index 04ef1bd323..a8f6ec6eee 100644 --- a/core-common/src/main/java/org/glassfish/jersey/ExternalProperties.java +++ b/core-common/src/main/java/org/glassfish/jersey/ExternalProperties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -32,11 +32,21 @@ public final class ExternalProperties { public static final String HTTP_PROXY_PORT = "http.proxyPort"; /** - * Property used to indicates the hosts that should be accessed + * Property used to indicate the hosts that should be accessed * without going through the proxy. */ public static final String HTTP_NON_PROXY_HOSTS = "http.nonProxyHosts"; + /** + * Property used to specify the user name to authenticate with the proxy. + */ + public static final String HTTP_PROXY_USER = "http.proxyUser"; + + /** + * Property used to specify the password to authenticate with the proxy. + */ + public static final String HTTP_PROXY_PASSWORD = "http.proxyPassword"; + /** * Prevent instantiation. */ diff --git a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/proxy/ProxyTest.java b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/proxy/ProxyTest.java index 93e69c2124..b30d39e29c 100644 --- a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/proxy/ProxyTest.java +++ b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/proxy/ProxyTest.java @@ -25,6 +25,7 @@ import org.glassfish.jersey.apache5.connector.Apache5ConnectorProvider; import org.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.HttpUrlConnectorProvider; import org.glassfish.jersey.client.spi.ConnectorProvider; import org.glassfish.jersey.jetty.connector.JettyConnectorProvider; import org.glassfish.jersey.netty.connector.NettyConnectorProvider; @@ -72,6 +73,7 @@ public static List testData() { {Apache5ConnectorProvider.class}, {JettyConnectorProvider.class}, {NettyConnectorProvider.class}, + {HttpUrlConnectorProvider.class}, }); } @@ -110,7 +112,8 @@ public void testGetSuccess() { client().property(ClientProperties.PROXY_USERNAME, ProxyTest.PROXY_USERNAME); client().property(ClientProperties.PROXY_PASSWORD, ProxyTest.PROXY_PASSWORD); Response response = target("proxyTest").request().get(); - assertEquals(200, response.getStatus()); + response.bufferEntity(); + assertEquals(response.readEntity(String.class), 200, response.getStatus()); } private static Server server; diff --git a/tests/integration/externalproperties/src/test/java/org/glassfish/jersey/tests/externalproperties/HttpProxyTest.java b/tests/integration/externalproperties/src/test/java/org/glassfish/jersey/tests/externalproperties/HttpProxyTest.java index 94100297f0..f74a55da14 100644 --- a/tests/integration/externalproperties/src/test/java/org/glassfish/jersey/tests/externalproperties/HttpProxyTest.java +++ b/tests/integration/externalproperties/src/test/java/org/glassfish/jersey/tests/externalproperties/HttpProxyTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at diff --git a/tests/integration/jersey-4003/src/test/java/org/glassfish/jersey/tests/integration/jersey4003/LostResponseTest.java b/tests/integration/jersey-4003/src/test/java/org/glassfish/jersey/tests/integration/jersey4003/LostResponseTest.java index 2ba8042961..1780d05a8d 100644 --- a/tests/integration/jersey-4003/src/test/java/org/glassfish/jersey/tests/integration/jersey4003/LostResponseTest.java +++ b/tests/integration/jersey-4003/src/test/java/org/glassfish/jersey/tests/integration/jersey4003/LostResponseTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,28 +16,11 @@ package org.glassfish.jersey.tests.integration.jersey4003; -import org.glassfish.jersey.client.ClientConfig; -import org.glassfish.jersey.client.HttpUrlConnectorProvider; -import org.glassfish.jersey.client.JerseyClientBuilder; -import org.glassfish.jersey.client.JerseyCompletionStageRxInvoker; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import org.mockito.Mockito; - -import javax.ws.rs.client.Client; -import javax.ws.rs.client.Entity; -import javax.ws.rs.client.InvocationCallback; -import javax.ws.rs.client.ResponseProcessingException; -import javax.ws.rs.core.GenericType; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; +import java.net.Proxy; import java.net.URL; import java.util.concurrent.CompletionStage; import java.util.concurrent.CountDownLatch; @@ -46,6 +29,23 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.InvocationCallback; +import javax.ws.rs.client.ResponseProcessingException; +import javax.ws.rs.core.GenericType; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.HttpUrlConnectorProvider; +import org.glassfish.jersey.client.JerseyClientBuilder; +import org.glassfish.jersey.client.JerseyCompletionStageRxInvoker; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + public class LostResponseTest { private static final String DUMMY_URL = "http://foo"; @@ -59,7 +59,7 @@ public void setup() throws IOException { HttpUrlConnectorProvider.ConnectionFactory connectionFactory = Mockito.mock(HttpUrlConnectorProvider.ConnectionFactory.class); HttpURLConnection connection = Mockito.mock(HttpURLConnection.class); - Mockito.when(connectionFactory.getConnection(Mockito.any(URL.class))).thenReturn(connection); + Mockito.when(connectionFactory.getConnection(Mockito.any(URL.class), Mockito.any())).thenReturn(connection); OutputStream outputStream = Mockito.mock(OutputStream.class); Mockito.when(connection.getOutputStream()).thenReturn(outputStream);