From 1b6103befa5e113ff67c4b47df942f1165865ba1 Mon Sep 17 00:00:00 2001 From: Ryan Baxter Date: Thu, 1 Feb 2018 19:12:48 -0500 Subject: [PATCH] Allows for custom Http Client builders (#313) --- .../main/asciidoc/spring-cloud-commons.adoc | 4 +- spring-cloud-commons/pom.xml | 2 +- .../DefaultApacheHttpClientFactory.java | 8 +- .../DefaultOkHttpClientFactory.java | 7 +- .../httpclient/HttpClientConfiguration.java | 21 ++++- ...omHttpClientBuilderConfigurationTests.java | 70 +++++++++++++++++ ...OkHttpClientBuilderConfigurationTests.java | 77 +++++++++++++++++++ .../DefaultApacheHttpClientFactoryTests.java | 2 +- .../DefaultOkHttpClientFactoryTest.java | 2 +- 9 files changed, 183 insertions(+), 10 deletions(-) create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/CustomHttpClientBuilderConfigurationTests.java create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/CustomOkHttpClientBuilderConfigurationTests.java diff --git a/docs/src/main/asciidoc/spring-cloud-commons.adoc b/docs/src/main/asciidoc/spring-cloud-commons.adoc index d0f2f07e..eb7c3352 100644 --- a/docs/src/main/asciidoc/spring-cloud-commons.adoc +++ b/docs/src/main/asciidoc/spring-cloud-commons.adoc @@ -612,6 +612,8 @@ if the OK HTTP jar is on the classpath. In addition, Spring Cloud Commons provi the connection managers used by both clients, `ApacheHttpClientConnectionManagerFactory` for the Apache HTTP client and `OkHttpClientConnectionPoolFactory` for the OK HTTP client. You can provide your own implementation of these beans if you would like to customize how the HTTP clients are created -in downstream projects. You can also disable the creation of these beans by setting +in downstream projects. In addition, if you provide a bean of type `HttpClientBuilder` and/or `OkHttpClient.Builder`, +the default factories will use these builders as the basis for the builders returned to downstream projects. +You can also disable the creation of these beans by setting `spring.cloud.httpclientfactories.apache.enabled` or `spring.cloud.httpclientfactories.ok.enabled` to `false`. diff --git a/spring-cloud-commons/pom.xml b/spring-cloud-commons/pom.xml index 34837d1d..cf52eb20 100644 --- a/spring-cloud-commons/pom.xml +++ b/spring-cloud-commons/pom.xml @@ -66,7 +66,7 @@ 3.6.0 - 4.5.1 + 4.5.4 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 index da90c05e..1b54aa04 100644 --- 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 @@ -8,13 +8,19 @@ import org.apache.http.impl.client.HttpClientBuilder; */ public class DefaultApacheHttpClientFactory implements ApacheHttpClientFactory { + private HttpClientBuilder builder; + + public DefaultApacheHttpClientFactory(HttpClientBuilder builder) { + this.builder = builder; + } + /** * A default {@link HttpClientBuilder}. The {@link HttpClientBuilder} returned will * have content compression disabled, cookie management disabled, and use system properties. */ @Override public HttpClientBuilder createBuilder() { - return HttpClientBuilder.create().disableContentCompression() + return this.builder.disableContentCompression() .disableCookieManagement().useSystemProperties(); } } 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 index 4c5fd074..a30234e2 100644 --- 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 @@ -19,9 +19,14 @@ public class DefaultOkHttpClientFactory implements OkHttpClientFactory { private static final Log LOG = LogFactory.getLog(DefaultOkHttpClientFactory.class); + private OkHttpClient.Builder builder; + + public DefaultOkHttpClientFactory(OkHttpClient.Builder builder) { + this.builder = builder; + } + @Override public OkHttpClient.Builder createBuilder(boolean disableSslValidation) { - OkHttpClient.Builder builder = new OkHttpClient.Builder(); if (disableSslValidation) { try { X509TrustManager disabledTrustManager = new DisableValidationTrustManager(); 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 df3de178..1ab488fb 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 @@ -2,6 +2,7 @@ package org.springframework.cloud.commons.httpclient; import okhttp3.OkHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -26,8 +27,14 @@ public class HttpClientConfiguration { @Bean @ConditionalOnMissingBean - public ApacheHttpClientFactory apacheHttpClientFactory() { - return new DefaultApacheHttpClientFactory(); + public HttpClientBuilder apacheHttpClientBuilder() { + return HttpClientBuilder.create(); + } + + @Bean + @ConditionalOnMissingBean + public ApacheHttpClientFactory apacheHttpClientFactory(HttpClientBuilder builder) { + return new DefaultApacheHttpClientFactory(builder); } } @@ -44,8 +51,14 @@ public class HttpClientConfiguration { @Bean @ConditionalOnMissingBean - public OkHttpClientFactory okHttpClientFactory() { - return new DefaultOkHttpClientFactory(); + public OkHttpClient.Builder okHttpClientBuilder() { + return new OkHttpClient.Builder(); + } + + @Bean + @ConditionalOnMissingBean + public OkHttpClientFactory okHttpClientFactory(OkHttpClient.Builder builder) { + return new DefaultOkHttpClientFactory(builder); } } } diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/CustomHttpClientBuilderConfigurationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/CustomHttpClientBuilderConfigurationTests.java new file mode 100644 index 00000000..a55c6dc0 --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/CustomHttpClientBuilderConfigurationTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2013-2018 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.impl.client.HttpClientBuilder; +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.AutoConfigureBefore; +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 = CustomHttpClientBuilderApplication.class) +public class CustomHttpClientBuilderConfigurationTests { + + @Autowired + ApacheHttpClientFactory apacheHttpClientFactory; + + @Test + public void testCustomBuilder() { + HttpClientBuilder builder = apacheHttpClientFactory.createBuilder(); + assertTrue(CustomHttpClientBuilderApplication.MyHttpClientBuilder.class.isInstance(builder)); + } +} + +@Configuration +@EnableAutoConfiguration +class CustomHttpClientBuilderApplication { + + public static void main(String[] args) { + SpringApplication.run(MyApplication.class, args); + } + + @Configuration + @AutoConfigureBefore(value = HttpClientConfiguration.class) + static class MyConfig { + + @Bean + public MyHttpClientBuilder builder() { + return new MyHttpClientBuilder(); + } + + } + + static class MyHttpClientBuilder extends HttpClientBuilder { + } +} diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/CustomOkHttpClientBuilderConfigurationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/CustomOkHttpClientBuilderConfigurationTests.java new file mode 100644 index 00000000..ac8afa5c --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/commons/httpclient/CustomOkHttpClientBuilderConfigurationTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2013-2018 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 okhttp3.OkHttpClient; + +import java.lang.reflect.Field; +import java.util.concurrent.TimeUnit; +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 org.springframework.util.ReflectionUtils; + +import static org.junit.Assert.assertEquals; + +/** + * @author Ryan Baxter + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = CustomOkHttpClientBuilderApplication.class) +public class CustomOkHttpClientBuilderConfigurationTests { + + @Autowired + private OkHttpClientFactory okHttpClientFactory; + + @Test + public void testCustomBuilder() { + OkHttpClient.Builder builder = okHttpClientFactory.createBuilder(false); + Integer timeout = getField(builder, "connectTimeout"); + assertEquals(1, timeout.intValue()); + } + + 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; + } + +} + +@Configuration +@EnableAutoConfiguration +class CustomOkHttpClientBuilderApplication { + + public static void main(String[] args) { + SpringApplication.run(MyApplication.class, args); + } + + @Configuration + static class MyConfig { + + @Bean + public OkHttpClient.Builder builder() { + return new OkHttpClient.Builder().connectTimeout(1, TimeUnit.MILLISECONDS); + } + } +} 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 index 3a69ea72..03810b0a 100644 --- 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 @@ -22,7 +22,7 @@ public class DefaultApacheHttpClientFactoryTests { public void createClient() throws Exception { final RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(100) .setConnectTimeout(200).setCookieSpec(CookieSpecs.IGNORE_COOKIES).build(); - CloseableHttpClient httpClient = new DefaultApacheHttpClientFactory().createBuilder(). + CloseableHttpClient httpClient = new DefaultApacheHttpClientFactory(HttpClientBuilder.create()).createBuilder(). setConnectionManager(mock(HttpClientConnectionManager.class)). setDefaultRequestConfig(requestConfig).build(); Assertions.assertThat(httpClient).isInstanceOf(Configurable.class); 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 index 10c40516..b14e7f05 100644 --- 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 @@ -18,7 +18,7 @@ import static org.junit.Assert.assertTrue; public class DefaultOkHttpClientFactoryTest { @Test public void create() throws Exception { - DefaultOkHttpClientFactory okHttpClientFactory = new DefaultOkHttpClientFactory(); + DefaultOkHttpClientFactory okHttpClientFactory = new DefaultOkHttpClientFactory(new OkHttpClient.Builder()); DefaultOkHttpClientConnectionPoolFactory poolFactory = new DefaultOkHttpClientConnectionPoolFactory(); ConnectionPool pool = poolFactory.create(4, 5, TimeUnit.DAYS); OkHttpClient httpClient = okHttpClientFactory.createBuilder(true).