Implementation of factories to provide Apache HTTP Clients

This commit is contained in:
Ryan Baxter
2017-06-27 15:47:11 -04:00
parent 111bd1ccb2
commit 9bf1fe4685
11 changed files with 468 additions and 1 deletions

View File

@@ -35,6 +35,11 @@
</plugin>
</plugins>
</build>
<properties>
<okhttp3.version>3.6.0</okhttp3.version>
<apachehttpclient.version>4.5.1</apachehttpclient.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -102,6 +107,24 @@
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp3.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>${okhttp3.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${apachehttpclient.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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.<ConnectionSocketFactory> 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<ConnectionSocketFactory> registry = registryBuilder.build();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(
registry, null, null, null, timeToLive, timeUnit);
connectionManager.setMaxTotal(maxTotalConnections);
connectionManager.setDefaultMaxPerRoute(maxConnectionsPerRoute);
return connectionManager;
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}
}

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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> 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;
}
}

View File

@@ -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> 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;
}
}

View File

@@ -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);
}
}