From 787bea18e915f98a664dc451c196d1a15a0c5596 Mon Sep 17 00:00:00 2001 From: Ryan Baxter Date: Thu, 29 Jun 2017 15:11:27 -0400 Subject: [PATCH] OK HTTP client factory implementations --- ...aultOkHttpClientConnectionPoolFactory.java | 17 +++++ .../DefaultOkHttpClientFactory.java | 57 ++++++++++++++++ .../httpclient/HttpClientConfiguration.java | 21 ++++++ .../OkHttpClientConnectionPoolFactory.java | 21 ++++++ .../httpclient/OkHttpClientFactory.java | 65 +++++++++++++++++++ .../CustomHttpClientConfigurationTests.java | 56 +++++++++++++++- .../DefaultHttpClientConfigurationTests.java | 20 +++++- ...OkHttpClientConnectionPoolFactoryTest.java | 34 ++++++++++ .../DefaultOkHttpClientFactoryTest.java | 44 +++++++++++++ 9 files changed, 332 insertions(+), 3 deletions(-) create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/DefaultOkHttpClientConnectionPoolFactory.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/DefaultOkHttpClientFactory.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/OkHttpClientConnectionPoolFactory.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/OkHttpClientFactory.java create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/DefaultOkHttpClientConnectionPoolFactoryTest.java create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/DefaultOkHttpClientFactoryTest.java diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/DefaultOkHttpClientConnectionPoolFactory.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/DefaultOkHttpClientConnectionPoolFactory.java new file mode 100644 index 00000000..9c09dfcc --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/DefaultOkHttpClientConnectionPoolFactory.java @@ -0,0 +1,17 @@ +package org.springframework.cloud.commons.httpclient; + +import okhttp3.ConnectionPool; + +import java.util.concurrent.TimeUnit; + +/** + * Default implementation of {@link OkHttpClientConnectionPoolFactory}. + * @author Ryan Baxter + */ +public class DefaultOkHttpClientConnectionPoolFactory implements OkHttpClientConnectionPoolFactory { + + @Override + public ConnectionPool create(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) { + return new ConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit); + } +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/DefaultOkHttpClientFactory.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/DefaultOkHttpClientFactory.java new file mode 100644 index 00000000..0b0b61ae --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/DefaultOkHttpClientFactory.java @@ -0,0 +1,57 @@ +package org.springframework.cloud.commons.httpclient; + +import okhttp3.ConnectionPool; +import okhttp3.OkHttpClient; + +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.concurrent.TimeUnit; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Default implementation of {@link OkHttpClientFactory}. + * @author Ryan Baxter + */ +public class DefaultOkHttpClientFactory implements OkHttpClientFactory { + + private static final Log LOG = LogFactory.getLog(DefaultOkHttpClientFactory.class); + + @Override + public OkHttpClient create(boolean disableSslValidation, long connectTimeout, + TimeUnit connectTimeoutUnit, boolean followRedirects, long readTimeout, + TimeUnit readTimeoutUnit, ConnectionPool connectionPool, + SSLSocketFactory sslSocketFactory, X509TrustManager x509TrustManager) { + OkHttpClient.Builder builder = new OkHttpClient.Builder() + .connectTimeout(connectTimeout, connectTimeoutUnit) + .followRedirects(followRedirects) + .readTimeout(readTimeout, readTimeoutUnit).connectionPool(connectionPool) + .connectionPool(connectionPool); + if (disableSslValidation) { + try { + X509TrustManager disabledTrustManager = new DisableValidationTrustManager(); + TrustManager[] trustManagers = new TrustManager[1]; + trustManagers[0] = disabledTrustManager; + SSLContext sslContext = SSLContext.getInstance("SSL"); + sslContext.init(null, trustManagers, new java.security.SecureRandom()); + SSLSocketFactory disabledSSLSocketFactory = sslContext.getSocketFactory(); + builder.sslSocketFactory(disabledSSLSocketFactory, disabledTrustManager); + builder.hostnameVerifier(new TrustAllHostnames()); + } + catch (NoSuchAlgorithmException e) { + LOG.warn("Error setting SSLSocketFactory in OKHttpClient", e); + } + catch (KeyManagementException e) { + LOG.warn("Error setting SSLSocketFactory in OKHttpClient", e); + } + } + if (sslSocketFactory != null && x509TrustManager != null) { + builder.sslSocketFactory(sslSocketFactory, x509TrustManager); + } + return builder.build(); + } +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/HttpClientConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/HttpClientConfiguration.java index d0e70c3c..f64ab8a0 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/HttpClientConfiguration.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/HttpClientConfiguration.java @@ -1,5 +1,8 @@ package org.springframework.cloud.commons.httpclient; +import okhttp3.OkHttpClient; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; @@ -27,4 +30,22 @@ public class HttpClientConfiguration { return new DefaultApacheHttpClientFactory(); } } + + @Configuration + @ConditionalOnProperty(name = "spring.cloud.httpclient.ok.enabled") + @ConditionalOnClass(OkHttpClient.class) + static class OkHttpClientConfiguration { + + @Bean + @ConditionalOnMissingBean + public OkHttpClientConnectionPoolFactory connPoolFactory() { + return new DefaultOkHttpClientConnectionPoolFactory(); + } + + @Bean + @ConditionalOnMissingBean + public OkHttpClientFactory okHttpClientFactory() { + return new DefaultOkHttpClientFactory(); + } + } } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/OkHttpClientConnectionPoolFactory.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/OkHttpClientConnectionPoolFactory.java new file mode 100644 index 00000000..d7682cc1 --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/OkHttpClientConnectionPoolFactory.java @@ -0,0 +1,21 @@ +package org.springframework.cloud.commons.httpclient; + +import okhttp3.ConnectionPool; + +import java.util.concurrent.TimeUnit; + +/** + * Creates {@link ConnectionPool}s for {@link okhttp3.OkHttpClient}s + * @author Ryan Baxter + */ +public interface OkHttpClientConnectionPoolFactory { + + /** + * Creates a new {@link ConnectionPool}. + * @param maxIdleConnections number of max idle connections to allow + * @param keepAliveDuration amount of time to keep connections alive + * @param timeUnit the time unit for the keep alive duration + * @return A new {@link ConnectionPool} + */ + public ConnectionPool create(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit); +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/OkHttpClientFactory.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/OkHttpClientFactory.java new file mode 100644 index 00000000..b7857e99 --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/OkHttpClientFactory.java @@ -0,0 +1,65 @@ +package org.springframework.cloud.commons.httpclient; + +import okhttp3.ConnectionPool; +import okhttp3.OkHttpClient; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.concurrent.TimeUnit; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509TrustManager; + +/** + * Creates new {@link OkHttpClient}s. + * @author Ryan Baxter + */ +public interface OkHttpClientFactory { + + /** + * Creates a new {@link OkHttpClient}. + * @param disableSslValidation To disable SSL validation + * @param connectTimeout Connection timeout duration + * @param connectTimeoutUnit Connection timeout time unit + * @param followRedirects Whether to follow redirects + * @param readTimeout Read timeout duration + * @param readTimeoutUnit Read timeout time unit + * @param connectionPool The connection pool to use + * @param sslSocketFactory The socket factory to use, can be {@code null} + * @param x509TrustManager The trust manager to use, can be {@code null} + * @return A new {@link OkHttpClient} + */ + public OkHttpClient create(boolean disableSslValidation, long connectTimeout, + TimeUnit connectTimeoutUnit, boolean followRedirects, long readTimeout, + TimeUnit readTimeoutUnit, ConnectionPool connectionPool, + SSLSocketFactory sslSocketFactory, X509TrustManager x509TrustManager); + + /** + * A {@link X509TrustManager} that does not validate SSL certificates. + */ + public static class DisableValidationTrustManager implements X509TrustManager { + + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {} + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {} + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } + + /** + * A {@link HostnameVerifier} that does not validate any hostnames. + */ + public static class TrustAllHostnames implements HostnameVerifier { + + @Override + public boolean verify(String s, SSLSession sslSession) { + return true; + } + } +} diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/CustomHttpClientConfigurationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/CustomHttpClientConfigurationTests.java index 40729d41..879e4010 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/CustomHttpClientConfigurationTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/CustomHttpClientConfigurationTests.java @@ -1,6 +1,11 @@ package org.springframework.cloud.commons.httpclient; +import okhttp3.ConnectionPool; +import okhttp3.OkHttpClient; + import java.util.concurrent.TimeUnit; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509TrustManager; import org.apache.http.client.config.RequestConfig; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.HttpClientConnectionManager; @@ -21,7 +26,7 @@ import static org.junit.Assert.assertTrue; * @author Ryan Baxter */ @RunWith(SpringRunner.class) -@SpringBootTest(classes = CustomApplication.class) +@SpringBootTest(classes = CustomApplication.class, properties = {"spring.cloud.httpclient.ok.enabled: true"}) public class CustomHttpClientConfigurationTests { @Autowired @@ -30,6 +35,12 @@ public class CustomHttpClientConfigurationTests { @Autowired ApacheHttpClientConnectionManagerFactory connectionManagerFactory; + @Autowired + OkHttpClientFactory okHttpClientFactory; + + @Autowired + OkHttpClientConnectionPoolFactory okHttpClientConnectionPoolFactory; + @Test public void connManFactory() throws Exception { assertTrue(ApacheHttpClientConnectionManagerFactory.class @@ -45,6 +56,18 @@ public class CustomHttpClientConfigurationTests { .isInstance(httpClientFactory)); } + @Test + public void connectionPoolFactory() throws Exception { + assertTrue(OkHttpClientConnectionPoolFactory.class.isInstance(okHttpClientConnectionPoolFactory)); + assertTrue(CustomApplication.MyOkHttpConnectionPoolFactory.class.isInstance(okHttpClientConnectionPoolFactory)); + } + + @Test + public void okHttpClientFactory() throws Exception { + assertTrue(OkHttpClientFactory.class.isInstance(okHttpClientFactory)); + assertTrue(CustomApplication.MyOkHttpClientFactory.class.isInstance(okHttpClientFactory)); + } + } @Configuration @@ -64,10 +87,20 @@ class CustomApplication { } @Bean - ApacheHttpClientConnectionManagerFactory connectionManagerFactory() { + public ApacheHttpClientConnectionManagerFactory connectionManagerFactory() { return new MyApacheHttpClientConnectionManagerFactory(); } + @Bean + public OkHttpClientConnectionPoolFactory connectionPoolFactory() { + return new MyOkHttpConnectionPoolFactory(); + } + + @Bean + public OkHttpClientFactory okHttpClientFactory() { + return new MyOkHttpClientFactory(); + } + } static class MyApacheHttpClientFactory implements ApacheHttpClientFactory { @@ -90,4 +123,23 @@ class CustomApplication { return null; } } + + static class MyOkHttpClientFactory implements OkHttpClientFactory { + + @Override + public OkHttpClient create(boolean disableSslValidation, long connectTimeout, + TimeUnit connectTimeoutUnit, boolean followRedirects, long readTimeout, + TimeUnit readTimeoutUnit, ConnectionPool connectionPool, + SSLSocketFactory sslSocketFactory, X509TrustManager x509TrustManager) { + return null; + } + } + + static class MyOkHttpConnectionPoolFactory implements OkHttpClientConnectionPoolFactory { + + @Override + public ConnectionPool create(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) { + return null; + } + } } diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/DefaultHttpClientConfigurationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/DefaultHttpClientConfigurationTests.java index f07c51fd..d3f7d04f 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/DefaultHttpClientConfigurationTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/DefaultHttpClientConfigurationTests.java @@ -15,7 +15,7 @@ import static org.junit.Assert.*; * @author Ryan Baxter */ @RunWith(SpringRunner.class) -@SpringBootTest(classes = MyApplication.class) +@SpringBootTest(classes = MyApplication.class, properties = {"spring.cloud.httpclient.ok.enabled: true"}) public class DefaultHttpClientConfigurationTests { @Autowired ApacheHttpClientFactory httpClientFactory; @@ -23,6 +23,12 @@ public class DefaultHttpClientConfigurationTests { @Autowired ApacheHttpClientConnectionManagerFactory connectionManagerFactory; + @Autowired + OkHttpClientFactory okHttpClientFactory; + + @Autowired + OkHttpClientConnectionPoolFactory okHttpClientConnectionPoolFactory; + @Test public void connManFactory() throws Exception { assertTrue(ApacheHttpClientConnectionManagerFactory.class @@ -36,6 +42,18 @@ public class DefaultHttpClientConfigurationTests { assertTrue(ApacheHttpClientFactory.class.isInstance(httpClientFactory)); assertTrue(DefaultApacheHttpClientFactory.class.isInstance(httpClientFactory)); } + + @Test + public void connPoolFactory() throws Exception { + assertTrue(OkHttpClientConnectionPoolFactory.class.isInstance(okHttpClientConnectionPoolFactory)); + assertTrue(DefaultOkHttpClientConnectionPoolFactory.class.isInstance(okHttpClientConnectionPoolFactory)); + } + + @Test + public void setOkHttpClientFactory() throws Exception { + assertTrue(OkHttpClientFactory.class.isInstance(okHttpClientFactory)); + assertTrue(DefaultOkHttpClientFactory.class.isInstance(okHttpClientFactory)); + } } @Configuration diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/DefaultOkHttpClientConnectionPoolFactoryTest.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/DefaultOkHttpClientConnectionPoolFactoryTest.java new file mode 100644 index 00000000..a7658e3b --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/DefaultOkHttpClientConnectionPoolFactoryTest.java @@ -0,0 +1,34 @@ +package org.springframework.cloud.commons.httpclient; + +import okhttp3.ConnectionPool; + +import java.lang.reflect.Field; +import java.util.concurrent.TimeUnit; +import org.junit.Test; +import org.springframework.util.ReflectionUtils; + +import static org.junit.Assert.assertEquals; + +/** + * @author Ryan Baxter + */ +public class DefaultOkHttpClientConnectionPoolFactoryTest { + @Test + public void create() throws Exception { + DefaultOkHttpClientConnectionPoolFactory connectionPoolFactory = new DefaultOkHttpClientConnectionPoolFactory(); + ConnectionPool connectionPool = connectionPoolFactory.create(2, + 3, TimeUnit.MILLISECONDS); + int idleConnections = getField(connectionPool, "maxIdleConnections"); + long keepAliveDuration = getField(connectionPool, "keepAliveDurationNs"); + assertEquals(2, idleConnections); + assertEquals(TimeUnit.MILLISECONDS.toNanos(3), keepAliveDuration); + } + + protected T getField(Object target, String name) { + Field field = ReflectionUtils.findField(target.getClass(), name); + ReflectionUtils.makeAccessible(field); + Object value = ReflectionUtils.getField(field, target); + return (T) value; + } + +} \ No newline at end of file diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/DefaultOkHttpClientFactoryTest.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/DefaultOkHttpClientFactoryTest.java new file mode 100644 index 00000000..476caf21 --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/DefaultOkHttpClientFactoryTest.java @@ -0,0 +1,44 @@ +package org.springframework.cloud.commons.httpclient; + +import okhttp3.ConnectionPool; +import okhttp3.OkHttpClient; + +import java.lang.reflect.Field; +import java.util.concurrent.TimeUnit; +import javax.net.ssl.HostnameVerifier; +import org.junit.Test; +import org.springframework.util.ReflectionUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author Ryan Baxter + */ +public class DefaultOkHttpClientFactoryTest { + @Test + public void create() throws Exception { + DefaultOkHttpClientFactory okHttpClientFactory = new DefaultOkHttpClientFactory(); + DefaultOkHttpClientConnectionPoolFactory poolFactory = new DefaultOkHttpClientConnectionPoolFactory(); + ConnectionPool pool = poolFactory.create(4, 5, TimeUnit.DAYS); + OkHttpClient httpClient = okHttpClientFactory.create(true, 2, + TimeUnit.MILLISECONDS, true, 3, TimeUnit.HOURS, pool, null, null); + int connectTimeout = getField(httpClient, "connectTimeout"); + assertEquals(2, connectTimeout); + int readTimeout = getField(httpClient, "readTimeout"); + assertEquals(TimeUnit.HOURS.toMillis(3), readTimeout); + boolean followRedirects = getField(httpClient, "followRedirects"); + assertTrue(followRedirects); + ConnectionPool poolFromClient = getField(httpClient, "connectionPool"); + assertEquals(pool, poolFromClient); + HostnameVerifier hostnameVerifier = getField(httpClient, "hostnameVerifier"); + assertTrue(OkHttpClientFactory.TrustAllHostnames.class.isInstance(hostnameVerifier)); + } + + protected T getField(Object target, String name) { + Field field = ReflectionUtils.findField(target.getClass(), name); + ReflectionUtils.makeAccessible(field); + Object value = ReflectionUtils.getField(field, target); + return (T) value; + } +} \ No newline at end of file