From f850e5e1be272ca977561a25b4b75c8d1d496d84 Mon Sep 17 00:00:00 2001 From: Jorge Bescos Gascon Date: Wed, 22 Jun 2022 08:44:00 +0200 Subject: [PATCH] Support for proxy in HttpUrlConnector Signed-off-by: Jorge Bescos Gascon --- .../client/HttpUrlConnectorProvider.java | 8 ++- .../client/internal/HttpUrlConnector.java | 63 ++++++++++++++++--- .../client/internal/localization.properties | 3 +- .../jersey/client/HttpUrlConnectorTest.java | 18 +++--- .../connector/proxy/ProxySelectorTest.java | 46 ++++++++++++++ .../e2e/client/connector/proxy/ProxyTest.java | 5 +- .../ssl/SslHttpUrlConnectorTest.java | 16 ++--- 7 files changed, 130 insertions(+), 29 deletions(-) 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..0dab495299 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 @@ -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; @@ -263,17 +264,18 @@ public interface ConnectionFactory { *

* * @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. */ - public HttpURLConnection getConnection(URL url) throws IOException; + public HttpURLConnection getConnection(URL url, Proxy proxy) throws IOException; } private static class DefaultConnectionFactory implements ConnectionFactory { @Override - public HttpURLConnection getConnection(final URL url) throws IOException { - return (HttpURLConnection) url.openConnection(); + public HttpURLConnection getConnection(URL url, Proxy proxy) throws IOException { + return (HttpURLConnection) (proxy != null ? url.openConnection(proxy) : url.openConnection()); } } 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..21d2cae3ba 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; @@ -39,15 +44,15 @@ import java.util.logging.Logger; 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.client.ClientProperties; import org.glassfish.jersey.client.ClientRequest; import org.glassfish.jersey.client.ClientResponse; @@ -314,10 +319,54 @@ protected void secureConnection(final JerseyClient client, final HttpURLConnecti } } + private URI getProxyUri(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 String getFromConfigOrSystem(ClientRequest request, String clientProperty, String systemProperty) { + String result = request.resolveProperty(clientProperty, String.class); + if (result == null) { + result = System.getProperty(systemProperty); + } + return result; + } + + private URI getProxyUri(ClientRequest request) { + Configuration config = request.getConfiguration(); + Object proxyUri = config.getProperty(ClientProperties.PROXY_URI); + if (proxyUri == null) { + String proxyHost = System.getProperty("http.proxyHost"); + String proxyPort = System.getProperty("http.proxyPort"); + if (proxyHost != null && proxyPort != null) { + return URI.create(proxyHost + ":" + proxyPort); + } + } else { + return getProxyUri(proxyUri); + } + return null; + } + private ClientResponse _apply(final ClientRequest request) throws IOException { final HttpURLConnection uc; - - uc = this.connectionFactory.getConnection(request.getUri().toURL()); + Proxy proxy = null; + URI proxyUri = getProxyUri(request); + if (proxyUri != null) { + String username = getFromConfigOrSystem(request, ClientProperties.PROXY_USERNAME, "http.proxyUser"); + String password = getFromConfigOrSystem(request, ClientProperties.PROXY_PASSWORD, "http.proxyPassword"); + if (username != null && password != null) { + StringBuilder auth = new StringBuilder().append(username).append(":").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(proxyUri.getHost(), proxyUri.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..b7a7616b65 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 @@ -16,11 +16,14 @@ package org.glassfish.jersey.client; +import static org.junit.Assert.assertEquals; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.ProtocolException; +import java.net.Proxy; import java.net.SocketTimeoutException; import java.net.URI; import java.net.URL; @@ -30,6 +33,10 @@ import java.util.List; import java.util.Map; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSocketFactory; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; @@ -39,17 +46,10 @@ import javax.ws.rs.core.Link; import javax.ws.rs.core.Response; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSocketFactory; - import org.glassfish.jersey.client.internal.HttpUrlConnector; - import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; -import static org.junit.Assert.assertEquals; /** * Various tests for the default client connector. @@ -91,7 +91,7 @@ public void testConnectionTimeoutNoEntity() { public void testResolvedRequestUri() { HttpUrlConnectorProvider.ConnectionFactory factory = new HttpUrlConnectorProvider.ConnectionFactory() { @Override - public HttpURLConnection getConnection(URL endpointUrl) throws IOException { + public HttpURLConnection getConnection(URL endpointUrl, Proxy proxy) throws IOException { HttpURLConnection result = (HttpURLConnection) endpointUrl.openConnection(); return wrapRedirectedHttp(result); } @@ -435,7 +435,7 @@ public void testSSLConnection() { ClientRequest request = client.target("https://localhost:8080").request().buildGet().request(); HttpUrlConnectorProvider.ConnectionFactory factory = new HttpUrlConnectorProvider.ConnectionFactory() { @Override - public HttpURLConnection getConnection(URL endpointUrl) throws IOException { + public HttpURLConnection getConnection(URL endpointUrl, Proxy proxy) throws IOException { HttpURLConnection result = (HttpURLConnection) endpointUrl.openConnection(); return wrapNoContentHttps(result); } diff --git a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/proxy/ProxySelectorTest.java b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/proxy/ProxySelectorTest.java index 4580facf24..facbcf5f58 100644 --- a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/proxy/ProxySelectorTest.java +++ b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/connector/proxy/ProxySelectorTest.java @@ -24,6 +24,8 @@ import org.glassfish.jersey.apache.connector.ApacheConnectorProvider; 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; @@ -44,6 +46,7 @@ import javax.ws.rs.core.Response; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; +import java.util.Base64; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -57,6 +60,8 @@ @RunWith(Parameterized.class) public class ProxySelectorTest { private static final String NO_PASS = "no-pass"; + private static final String PROXY_USERNAME = "proxy-user"; + private static final String PROXY_PASSWORD = "proxy-password"; @Parameterized.Parameters(name = "{index}: {0}") public static List testData() { @@ -65,6 +70,7 @@ public static List testData() { // {Apache5ConnectorProvider.class}, // {JettyConnectorProvider.class}, {NettyConnectorProvider.class}, + {HttpUrlConnectorProvider.class}, }); } @@ -95,6 +101,23 @@ public void testGet407() { } } + @Test + public void testGetSuccess() { + // Next applies for HttpUrlConnectorProvider. It seems Netty is not supporting user/pass in System properties + if (connectorProvider.getClass() == HttpUrlConnectorProvider.class) { + try { + System.setProperty("http.proxyUser", PROXY_USERNAME); + System.setProperty("http.proxyPassword", PROXY_PASSWORD); + Response response = target("proxyTest").request().get(); + response.bufferEntity(); + assertEquals(response.readEntity(String.class), 200, response.getStatus()); + } finally { + System.clearProperty("http.proxyUser"); + System.clearProperty("http.proxyPassword"); + } + } + } + private static Server server; @BeforeClass public static void startFakeProxy() { @@ -148,6 +171,29 @@ public void handle(String target, HttpServletResponse response) { if (request.getHeader(NO_PASS) != null) { response.setStatus(Integer.parseInt(request.getHeader(NO_PASS))); + } else if (request.getHeader("Proxy-Authorization") != null) { + String proxyAuthorization = request.getHeader("Proxy-Authorization"); + String decoded = new String(Base64.getDecoder().decode(proxyAuthorization.substring(6).getBytes())); + final String[] split = decoded.split(":"); + final String username = split[0]; + final String password = split[1]; + + if (!username.equals(PROXY_USERNAME)) { + response.setStatus(400); + System.out.println("Found unexpected username: " + username); + } + + if (!password.equals(PROXY_PASSWORD)) { + response.setStatus(400); + System.out.println("Found unexpected password: " + username); + } + + if (response.getStatus() != 400) { + response.setStatus(200); + if ("CONNECT".equalsIgnoreCase(baseRequest.getMethod())) { // NETTY way of doing proxy + httpConnect.add(baseRequest.getHttpChannel()); + } + } } else { response.setStatus(407); response.addHeader("Proxy-Authenticate", "Basic"); 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/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 df3f1dcd4e..d81139588d 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 @@ -16,11 +16,15 @@ package org.glassfish.jersey.tests.e2e.client.connector.ssl; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.net.HttpURLConnection; import java.net.InetAddress; +import java.net.Proxy; import java.net.Socket; import java.net.URL; import java.util.List; @@ -30,13 +34,12 @@ 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 javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.core.Response; import org.glassfish.jersey.apache.connector.ApacheConnectorProvider; import org.glassfish.jersey.apache5.connector.Apache5ConnectorProvider; @@ -44,10 +47,7 @@ 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; /** * Test custom socket factory in HttpUrlConnection using SSL @@ -70,7 +70,7 @@ public void testSSLWithCustomSocketFactory() throws Exception { .connectorProvider(new HttpUrlConnectorProvider().connectionFactory( new HttpUrlConnectorProvider.ConnectionFactory() { @Override - public HttpURLConnection getConnection(final URL url) throws IOException { + public HttpURLConnection getConnection(URL url, Proxy proxy) throws IOException { HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); connection.setSSLSocketFactory(socketFactory); return connection;