diff --git a/spring-cloud-commons/pom.xml b/spring-cloud-commons/pom.xml
index 7b120d36..82a678a9 100644
--- a/spring-cloud-commons/pom.xml
+++ b/spring-cloud-commons/pom.xml
@@ -35,6 +35,11 @@
+
+
+ 3.6.0
+ 4.5.1
+
org.springframework.boot
@@ -102,6 +107,24 @@
compile
true
+
+ com.squareup.okhttp3
+ okhttp
+ ${okhttp3.version}
+ true
+
+
+ com.squareup.okhttp3
+ logging-interceptor
+ ${okhttp3.version}
+ true
+
+
+ org.apache.httpcomponents
+ httpclient
+ ${apachehttpclient.version}
+ true
+
org.springframework.boot
spring-boot-starter-test
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/ApacheHttpClientConnectionManagerFactory.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/ApacheHttpClientConnectionManagerFactory.java
new file mode 100644
index 00000000..55e1b167
--- /dev/null
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/ApacheHttpClientConnectionManagerFactory.java
@@ -0,0 +1,47 @@
+/*
+ *
+ * * Copyright 2013-2016 the original author or authors.
+ * *
+ * * 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 org.springframework.cloud.commons.httpclient;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.HttpClientConnectionManager;
+
+/**
+ * Interface for creating an {@link HttpClientConnectionManager}.
+ * @author Ryan Baxter
+ */
+public interface ApacheHttpClientConnectionManagerFactory {
+ public static final String HTTP_SCHEME = "http";
+ public static final String HTTPS_SCHEME = "https";
+
+ /**
+ * Creates a new {@link HttpClientConnectionManager}.
+ * @param disableSslValidation True to disable SSL validation, false otherwise
+ * @param maxTotalConnections The total number of connections
+ * @param maxConnectionsPerRoute The total number of connections per route
+ * @param timeToLive The time a connection is allowed to exist
+ * @param timeUnit The time unit for the time to live value
+ * @param registryBuilder The {@link RegistryBuilder} to use in the connection manager
+ * @return A new {@link HttpClientConnectionManager}
+ */
+ public HttpClientConnectionManager newConnectionManager(boolean disableSslValidation,
+ int maxTotalConnections, int maxConnectionsPerRoute,
+ long timeToLive, TimeUnit timeUnit, RegistryBuilder registryBuilder);
+}
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/ApacheHttpClientFactory.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/ApacheHttpClientFactory.java
new file mode 100644
index 00000000..6dfa3a3c
--- /dev/null
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/ApacheHttpClientFactory.java
@@ -0,0 +1,38 @@
+/*
+ *
+ * * Copyright 2013-2016 the original author or authors.
+ * *
+ * * 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 org.springframework.cloud.commons.httpclient;
+
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.impl.client.CloseableHttpClient;
+
+/**
+ * Factory for creating a new {@link CloseableHttpClient}.
+ * @author Ryan Baxter
+ */
+public interface ApacheHttpClientFactory {
+
+ /**
+ * Creates a new {@link CloseableHttpClient}.
+ * @param requestConfig Configuration to be used for all requests by default
+ * @param connectionManager The {@link HttpClientConnectionManager} to use for the client
+ * @return A new {@link CloseableHttpClient}
+ */
+ public CloseableHttpClient createClient(RequestConfig requestConfig, HttpClientConnectionManager connectionManager);
+}
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/DefaultApacheHttpClientConnectionManagerFactory.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/DefaultApacheHttpClientConnectionManagerFactory.java
new file mode 100644
index 00000000..18939595
--- /dev/null
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/DefaultApacheHttpClientConnectionManagerFactory.java
@@ -0,0 +1,85 @@
+package org.springframework.cloud.commons.httpclient;
+
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.concurrent.TimeUnit;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.config.Registry;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.commons.logging.Log;
+
+/**
+ * Default implementation of {@link ApacheHttpClientConnectionManagerFactory}.
+ * @author Ryan Baxter
+ */
+public class DefaultApacheHttpClientConnectionManagerFactory implements ApacheHttpClientConnectionManagerFactory{
+
+ private static final Log LOG = LogFactory.getLog(DefaultApacheHttpClientConnectionManagerFactory.class);
+
+ public HttpClientConnectionManager newConnectionManager(boolean disableSslValidation,
+ int maxTotalConnections,
+ int maxConnectionsPerRoute) {
+ return newConnectionManager(disableSslValidation, maxTotalConnections, maxConnectionsPerRoute,
+ -1, TimeUnit.MILLISECONDS, null);
+ }
+
+ @Override
+ public HttpClientConnectionManager newConnectionManager(boolean disableSslValidation,
+ int maxTotalConnections, int maxConnectionsPerRoute, long timeToLive,
+ TimeUnit timeUnit, RegistryBuilder registryBuilder) {
+ if (registryBuilder == null) {
+ registryBuilder = RegistryBuilder. create()
+ .register(HTTP_SCHEME, PlainConnectionSocketFactory.INSTANCE);
+ }
+ if (disableSslValidation) {
+ try {
+ final SSLContext sslContext = SSLContext.getInstance("SSL");
+ sslContext.init(null, new TrustManager[] { new 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 null;
+ }
+ } }, new SecureRandom());
+ registryBuilder.register(HTTPS_SCHEME, new SSLConnectionSocketFactory(
+ sslContext, NoopHostnameVerifier.INSTANCE));
+ }
+ catch (NoSuchAlgorithmException e) {
+ LOG.warn("Error creating SSLContext", e);
+ }
+ catch (KeyManagementException e) {
+ LOG.warn("Error creating SSLContext", e);
+ }
+
+ }
+ final Registry registry = registryBuilder.build();
+
+ PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(
+ registry, null, null, null, timeToLive, timeUnit);
+ connectionManager.setMaxTotal(maxTotalConnections);
+ connectionManager.setDefaultMaxPerRoute(maxConnectionsPerRoute);
+
+ return connectionManager;
+ }
+}
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/DefaultApacheHttpClientFactory.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/DefaultApacheHttpClientFactory.java
new file mode 100644
index 00000000..c4e895d8
--- /dev/null
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/DefaultApacheHttpClientFactory.java
@@ -0,0 +1,22 @@
+package org.springframework.cloud.commons.httpclient;
+
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+
+/**
+ * Default implementation of {@link ApacheHttpClientFactory}.
+ * @author Ryan Baxter
+ */
+public class DefaultApacheHttpClientFactory implements ApacheHttpClientFactory {
+
+ public CloseableHttpClient createClient(RequestConfig requestConfig, HttpClientConnectionManager connectionManager) {
+ return HttpClientBuilder.create().disableContentCompression()
+ .setDefaultRequestConfig(requestConfig)
+ .setConnectionManager(connectionManager)
+ .disableCookieManagement()
+ .useSystemProperties()
+ .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
new file mode 100644
index 00000000..d0e70c3c
--- /dev/null
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/commons/httpclient/HttpClientConfiguration.java
@@ -0,0 +1,30 @@
+package org.springframework.cloud.commons.httpclient;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Ryan Baxter
+ */
+@Configuration
+public class HttpClientConfiguration {
+
+ @Configuration
+ @ConditionalOnProperty(name = "spring.cloud.httpclient.apache.enabled", matchIfMissing = true)
+ static class ApacheHttpClientConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public ApacheHttpClientConnectionManagerFactory connManFactory() {
+ return new DefaultApacheHttpClientConnectionManagerFactory();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public ApacheHttpClientFactory apacheHttpClientFactory() {
+ return new DefaultApacheHttpClientFactory();
+ }
+ }
+}
diff --git a/spring-cloud-commons/src/main/resources/META-INF/spring.factories b/spring-cloud-commons/src/main/resources/META-INF/spring.factories
index 4e17ff9a..5a433f60 100644
--- a/spring-cloud-commons/src/main/resources/META-INF/spring.factories
+++ b/spring-cloud-commons/src/main/resources/META-INF/spring.factories
@@ -7,7 +7,8 @@ org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\
org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration,\
org.springframework.cloud.commons.util.UtilAutoConfiguration,\
-org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration
+org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration,\
+org.springframework.cloud.commons.httpclient.HttpClientConfiguration
# Environment Post Processors
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
new file mode 100644
index 00000000..4bd1167c
--- /dev/null
+++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/CustomHttpClientConfigurationTests.java
@@ -0,0 +1,87 @@
+package org.springframework.cloud.commons.httpclient;
+
+import java.util.concurrent.TimeUnit;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Ryan Baxter
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = CustomApplication.class)
+public class CustomHttpClientConfigurationTests {
+
+ @Autowired
+ ApacheHttpClientFactory httpClientFactory;
+
+ @Autowired
+ ApacheHttpClientConnectionManagerFactory connectionManagerFactory;
+
+ @Test
+ public void connManFactory() throws Exception {
+ assertTrue(ApacheHttpClientConnectionManagerFactory.class.isInstance(connectionManagerFactory));
+ assertTrue(CustomApplication.MyApacheHttpClientConnectionManagerFactory.class.isInstance(connectionManagerFactory));
+ }
+
+ @Test
+ public void apacheHttpClientFactory() throws Exception {
+ assertTrue(ApacheHttpClientFactory.class.isInstance(httpClientFactory));
+ assertTrue(CustomApplication.MyApacheHttpClientFactory.class.isInstance(httpClientFactory));
+ }
+
+
+}
+
+@Configuration
+@EnableAutoConfiguration
+class CustomApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(MyApplication.class, args);
+ }
+
+ @Configuration
+ static class MyConfig {
+
+ @Bean
+ public ApacheHttpClientFactory clientFactory() {
+ return new MyApacheHttpClientFactory();
+ }
+
+ @Bean ApacheHttpClientConnectionManagerFactory connectionManagerFactory() {
+ return new MyApacheHttpClientConnectionManagerFactory();
+ }
+
+ }
+
+ static class MyApacheHttpClientFactory implements ApacheHttpClientFactory {
+
+ @Override
+ public CloseableHttpClient createClient(RequestConfig requestConfig, HttpClientConnectionManager connectionManager) {
+ return null;
+ }
+ }
+
+ static class MyApacheHttpClientConnectionManagerFactory implements ApacheHttpClientConnectionManagerFactory {
+
+ @Override
+ public HttpClientConnectionManager newConnectionManager(boolean disableSslValidation, int maxTotalConnections, int maxConnectionsPerRoute, long timeToLive, TimeUnit timeUnit, RegistryBuilder registryBuilder) {
+ return null;
+ }
+ }
+}
+
+
diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/DefaultApacheHttpClientConnectionManagerFactoryTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/DefaultApacheHttpClientConnectionManagerFactoryTests.java
new file mode 100644
index 00000000..a202438a
--- /dev/null
+++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/DefaultApacheHttpClientConnectionManagerFactoryTests.java
@@ -0,0 +1,46 @@
+package org.springframework.cloud.commons.httpclient;
+
+import java.lang.reflect.Field;
+import java.util.concurrent.TimeUnit;
+import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.junit.Test;
+import org.springframework.util.ReflectionUtils;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Ryan Baxter
+ */
+public class DefaultApacheHttpClientConnectionManagerFactoryTests {
+ @Test
+ public void newConnectionManager() throws Exception {
+ HttpClientConnectionManager connectionManager = new DefaultApacheHttpClientConnectionManagerFactory().newConnectionManager(
+ false, 2, 6);
+ assertEquals(6, ((PoolingHttpClientConnectionManager)connectionManager).getDefaultMaxPerRoute());
+ assertEquals(2,((PoolingHttpClientConnectionManager)connectionManager).getMaxTotal());
+ Object pool = getField(((PoolingHttpClientConnectionManager)connectionManager), "pool");
+ assertEquals(-1l, getField(pool, "timeToLive"));
+ TimeUnit timeUnit = getField(pool, "tunit");
+ assertEquals(TimeUnit.MILLISECONDS, timeUnit);
+ }
+
+ @Test
+ public void newConnectionManagerWithTTL() throws Exception {
+ HttpClientConnectionManager connectionManager = new DefaultApacheHttpClientConnectionManagerFactory().newConnectionManager(
+ false, 2, 6, 56l, TimeUnit.DAYS, null);
+ assertEquals(6, ((PoolingHttpClientConnectionManager)connectionManager).getDefaultMaxPerRoute());
+ assertEquals(2,((PoolingHttpClientConnectionManager)connectionManager).getMaxTotal());
+ Object pool = getField(((PoolingHttpClientConnectionManager)connectionManager), "pool");
+ assertEquals(56l, getField(pool, "timeToLive"));
+ TimeUnit timeUnit = getField(pool, "tunit");
+ assertEquals(TimeUnit.DAYS, timeUnit);
+ }
+
+ 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/DefaultApacheHttpClientFactoryTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/DefaultApacheHttpClientFactoryTests.java
new file mode 100644
index 00000000..dd7d2398
--- /dev/null
+++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/DefaultApacheHttpClientFactoryTests.java
@@ -0,0 +1,42 @@
+package org.springframework.cloud.commons.httpclient;
+
+import java.lang.reflect.Field;
+import org.apache.http.client.config.CookieSpecs;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.Configurable;
+import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+import org.springframework.util.ReflectionUtils;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+/**
+ * @author Ryan Baxter
+ */
+public class DefaultApacheHttpClientFactoryTests {
+ @Test
+ public void createClient() throws Exception {
+ final RequestConfig requestConfig = RequestConfig.custom()
+ .setSocketTimeout(100)
+ .setConnectTimeout(200)
+ .setCookieSpec(CookieSpecs.IGNORE_COOKIES).build();
+ CloseableHttpClient httpClient = new DefaultApacheHttpClientFactory().createClient(requestConfig,
+ mock(HttpClientConnectionManager.class));
+ Assertions.assertThat(httpClient).isInstanceOf(Configurable.class);
+ RequestConfig config = ((Configurable) httpClient).getConfig();
+ assertEquals(100, config.getSocketTimeout());
+ assertEquals(200, config.getConnectTimeout());
+ assertEquals(CookieSpecs.IGNORE_COOKIES, config.getCookieSpec());
+ }
+
+ 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/DefaultHttpClientConfigurationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/DefaultHttpClientConfigurationTests.java
new file mode 100644
index 00000000..b4cfe27a
--- /dev/null
+++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/DefaultHttpClientConfigurationTests.java
@@ -0,0 +1,46 @@
+package org.springframework.cloud.commons.httpclient;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Ryan Baxter
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = MyApplication.class)
+public class DefaultHttpClientConfigurationTests {
+ @Autowired
+ ApacheHttpClientFactory httpClientFactory;
+
+ @Autowired
+ ApacheHttpClientConnectionManagerFactory connectionManagerFactory;
+
+ @Test
+ public void connManFactory() throws Exception {
+ assertTrue(ApacheHttpClientConnectionManagerFactory.class.isInstance(connectionManagerFactory));
+ assertTrue(DefaultApacheHttpClientConnectionManagerFactory.class.isInstance(connectionManagerFactory));
+ }
+
+ @Test
+ public void apacheHttpClientFactory() throws Exception {
+ assertTrue(ApacheHttpClientFactory.class.isInstance(httpClientFactory));
+ assertTrue(DefaultApacheHttpClientFactory.class.isInstance(httpClientFactory));
+ }
+}
+
+@Configuration
+@EnableAutoConfiguration
+class MyApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(MyApplication.class, args);
+ }
+}
\ No newline at end of file