diff --git a/docs/src/main/asciidoc/_configprops.adoc b/docs/src/main/asciidoc/_configprops.adoc index b73525ae..5062c749 100644 --- a/docs/src/main/asciidoc/_configprops.adoc +++ b/docs/src/main/asciidoc/_configprops.adoc @@ -26,6 +26,7 @@ |feign.httpclient.hc5.socket-timeout-unit | | Default value for socket timeout unit. |feign.httpclient.max-connections | `200` | |feign.httpclient.max-connections-per-route | `50` | +|feign.httpclient.ok-http-client-properties.read-timeout | `60s` | {@link OkHttpClient} read timeout; defaults to 60 seconds. |feign.httpclient.time-to-live | `900` | |feign.httpclient.time-to-live-unit | | |feign.metrics.enabled | `true` | Enables metrics capability for Feign. diff --git a/docs/src/main/asciidoc/spring-cloud-openfeign.adoc b/docs/src/main/asciidoc/spring-cloud-openfeign.adoc index 24885451..ddc2595f 100644 --- a/docs/src/main/asciidoc/spring-cloud-openfeign.adoc +++ b/docs/src/main/asciidoc/spring-cloud-openfeign.adoc @@ -739,7 +739,7 @@ To work around this problem you can use an `ObjectProvider` when autowiring your [source,java,indent=0] ---- @Autowired -ObjectProvider testFeginClient; +ObjectProvider testFeignClient; ---- === Spring Data Support diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java index 68473e6e..f556fa78 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java @@ -18,6 +18,7 @@ package org.springframework.cloud.openfeign; import java.io.IOException; import java.lang.reflect.Method; +import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Timer; @@ -274,8 +275,8 @@ public class FeignAutoConfiguration { @ConditionalOnMissingBean(ConnectionPool.class) public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties, OkHttpClientConnectionPoolFactory connectionPoolFactory) { - Integer maxTotalConnections = httpClientProperties.getMaxConnections(); - Long timeToLive = httpClientProperties.getTimeToLive(); + int maxTotalConnections = httpClientProperties.getMaxConnections(); + long timeToLive = httpClientProperties.getTimeToLive(); TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit(); return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit); } @@ -283,12 +284,13 @@ public class FeignAutoConfiguration { @Bean public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) { - Boolean followRedirects = httpClientProperties.isFollowRedirects(); - Integer connectTimeout = httpClientProperties.getConnectionTimeout(); - Boolean disableSslValidation = httpClientProperties.isDisableSslValidation(); + boolean followRedirects = httpClientProperties.isFollowRedirects(); + int connectTimeout = httpClientProperties.getConnectionTimeout(); + boolean disableSslValidation = httpClientProperties.isDisableSslValidation(); + Duration readTimeout = httpClientProperties.getOkHttpClientProperties().getReadTimeout(); this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation) .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).followRedirects(followRedirects) - .connectionPool(connectionPool).build(); + .readTimeout(readTimeout).connectionPool(connectionPool).build(); return this.okHttpClient; } diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/clientconfig/OkHttpFeignConfiguration.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/clientconfig/OkHttpFeignConfiguration.java index 13acef02..1f8a12d5 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/clientconfig/OkHttpFeignConfiguration.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/clientconfig/OkHttpFeignConfiguration.java @@ -16,6 +16,7 @@ package org.springframework.cloud.openfeign.clientconfig; +import java.time.Duration; import java.util.concurrent.TimeUnit; import javax.annotation.PreDestroy; @@ -48,8 +49,8 @@ public class OkHttpFeignConfiguration { @ConditionalOnMissingBean(ConnectionPool.class) public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties, OkHttpClientConnectionPoolFactory connectionPoolFactory) { - Integer maxTotalConnections = httpClientProperties.getMaxConnections(); - Long timeToLive = httpClientProperties.getTimeToLive(); + int maxTotalConnections = httpClientProperties.getMaxConnections(); + long timeToLive = httpClientProperties.getTimeToLive(); TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit(); return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit); } @@ -57,11 +58,12 @@ public class OkHttpFeignConfiguration { @Bean public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) { - Boolean followRedirects = httpClientProperties.isFollowRedirects(); - Integer connectTimeout = httpClientProperties.getConnectionTimeout(); + boolean followRedirects = httpClientProperties.isFollowRedirects(); + int connectTimeout = httpClientProperties.getConnectionTimeout(); + Duration reaTimeout = httpClientProperties.getOkHttpClientProperties().getReadTimeout(); this.okHttpClient = httpClientFactory.createBuilder(httpClientProperties.isDisableSslValidation()) .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).followRedirects(followRedirects) - .connectionPool(connectionPool).build(); + .readTimeout(reaTimeout).connectionPool(connectionPool).build(); return this.okHttpClient; } diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/FeignHttpClientProperties.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/FeignHttpClientProperties.java index 0eae400d..b6bcf588 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/FeignHttpClientProperties.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/FeignHttpClientProperties.java @@ -16,13 +16,17 @@ package org.springframework.cloud.openfeign.support; +import java.time.Duration; import java.util.concurrent.TimeUnit; +import feign.okhttp.OkHttpClient; + import org.springframework.boot.context.properties.ConfigurationProperties; /** * @author Ryan Baxter * @author Nguyen Ky Thanh + * @author Olga Maciaszek-Sharma */ @ConfigurationProperties(prefix = "feign.httpclient") public class FeignHttpClientProperties { @@ -88,8 +92,13 @@ public class FeignHttpClientProperties { */ private Hc5Properties hc5 = new Hc5Properties(); + /** + * Additional {@link OkHttpClient}-specific properties. + */ + private OkHttpClientProperties okHttpClientProperties = new OkHttpClientProperties(); + public int getConnectionTimerRepeat() { - return this.connectionTimerRepeat; + return connectionTimerRepeat; } public void setConnectionTimerRepeat(int connectionTimerRepeat) { @@ -97,7 +106,7 @@ public class FeignHttpClientProperties { } public boolean isDisableSslValidation() { - return this.disableSslValidation; + return disableSslValidation; } public void setDisableSslValidation(boolean disableSslValidation) { @@ -105,7 +114,7 @@ public class FeignHttpClientProperties { } public int getMaxConnections() { - return this.maxConnections; + return maxConnections; } public void setMaxConnections(int maxConnections) { @@ -113,7 +122,7 @@ public class FeignHttpClientProperties { } public int getMaxConnectionsPerRoute() { - return this.maxConnectionsPerRoute; + return maxConnectionsPerRoute; } public void setMaxConnectionsPerRoute(int maxConnectionsPerRoute) { @@ -121,7 +130,7 @@ public class FeignHttpClientProperties { } public long getTimeToLive() { - return this.timeToLive; + return timeToLive; } public void setTimeToLive(long timeToLive) { @@ -129,7 +138,7 @@ public class FeignHttpClientProperties { } public TimeUnit getTimeToLiveUnit() { - return this.timeToLiveUnit; + return timeToLiveUnit; } public void setTimeToLiveUnit(TimeUnit timeToLiveUnit) { @@ -137,7 +146,7 @@ public class FeignHttpClientProperties { } public boolean isFollowRedirects() { - return this.followRedirects; + return followRedirects; } public void setFollowRedirects(boolean followRedirects) { @@ -145,7 +154,7 @@ public class FeignHttpClientProperties { } public int getConnectionTimeout() { - return this.connectionTimeout; + return connectionTimeout; } public void setConnectionTimeout(int connectionTimeout) { @@ -160,6 +169,14 @@ public class FeignHttpClientProperties { this.hc5 = hc5; } + public OkHttpClientProperties getOkHttpClientProperties() { + return okHttpClientProperties; + } + + public void setOkHttpClientProperties(OkHttpClientProperties okHttpClientProperties) { + this.okHttpClientProperties = okHttpClientProperties; + } + public static class Hc5Properties { /** @@ -203,7 +220,7 @@ public class FeignHttpClientProperties { private TimeUnit socketTimeoutUnit = DEFAULT_SOCKET_TIMEOUT_UNIT; public PoolConcurrencyPolicy getPoolConcurrencyPolicy() { - return this.poolConcurrencyPolicy; + return poolConcurrencyPolicy; } public void setPoolConcurrencyPolicy(PoolConcurrencyPolicy poolConcurrencyPolicy) { @@ -272,4 +289,24 @@ public class FeignHttpClientProperties { } + /** + * {@link OkHttpClient}-specific properties. + */ + public static class OkHttpClientProperties { + + /** + * {@link OkHttpClient} read timeout; defaults to 60 seconds. + */ + private Duration readTimeout = Duration.ofSeconds(60); + + public Duration getReadTimeout() { + return readTimeout; + } + + public void setReadTimeout(Duration readTimeout) { + this.readTimeout = readTimeout; + } + + } + } diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignOkHttpConfigurationTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignOkHttpConfigurationTests.java index 5e3f8a48..8f0ebfad 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignOkHttpConfigurationTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignOkHttpConfigurationTests.java @@ -45,26 +45,33 @@ class FeignOkHttpConfigurationTests { void setUp() { this.context = new SpringApplicationBuilder() .properties("debug=true", "feign.httpclient.disableSslValidation=true", "feign.okhttp.enabled=true", - "feign.httpclient.enabled=false") + "feign.httpclient.enabled=false", "feign.httpclient.okhttp-client-properties.read-timeout=9s") .web(WebApplicationType.NONE).sources(HttpClientConfiguration.class, FeignAutoConfiguration.class) .run(); } @AfterEach void tearDown() { - if (this.context != null) { - this.context.close(); + if (context != null) { + context.close(); } } @Test void disableSslTest() { - OkHttpClient httpClient = this.context.getBean(OkHttpClient.class); + OkHttpClient httpClient = context.getBean(OkHttpClient.class); HostnameVerifier hostnameVerifier = (HostnameVerifier) this.getField(httpClient, "hostnameVerifier"); assertThat(hostnameVerifier instanceof OkHttpClientFactory.TrustAllHostnames).isTrue(); } - protected Object getField(Object target, String name) { + @Test + void shouldConfigureReadTimeout() { + OkHttpClient httpClient = context.getBean(OkHttpClient.class); + + assertThat(httpClient.readTimeoutMillis()).isEqualTo(9000); + } + + protected Object getField(Object target, String name) { Field field = ReflectionUtils.findField(target.getClass(), name); ReflectionUtils.makeAccessible(field); Object value = ReflectionUtils.getField(field, target); diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/loadbalancer/FeignLoadBalancerAutoConfigurationTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/loadbalancer/FeignLoadBalancerAutoConfigurationTests.java index 40258367..69c8d5bb 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/loadbalancer/FeignLoadBalancerAutoConfigurationTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/loadbalancer/FeignLoadBalancerAutoConfigurationTests.java @@ -33,6 +33,7 @@ import org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfigurati import org.springframework.context.ConfigurableApplicationContext; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.util.ReflectionTestUtils.getField; /** * @author Olga Maciaszek-Sharma @@ -58,9 +59,18 @@ class FeignLoadBalancerAutoConfigurationTests { @Test void shouldInstantiateOkHttpFeignClientWhenEnabled() { ConfigurableApplicationContext context = initContext("feign.httpclient.enabled=false", - "feign.okhttp.enabled=true", "spring.cloud.loadbalancer.retry.enabled=false"); + "feign.okhttp.enabled=true", "spring.cloud.loadbalancer.retry.enabled=false", + "feign.httpclient.okhttp-client-properties.read-timeout=9s"); assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class); - assertLoadBalanced(context, OkHttpClient.class); + Map beans = context + .getBeansOfType(FeignBlockingLoadBalancerClient.class); + assertThat(beans).as("Missing bean of type %s", OkHttpClient.class).hasSize(1); + Client client = beans.get("feignClient").getDelegate(); + assertThat(client).isInstanceOf(OkHttpClient.class); + OkHttpClient okHttpClient = (OkHttpClient) client; + okhttp3.OkHttpClient httpClient = (okhttp3.OkHttpClient) getField(okHttpClient, "delegate"); + assertThat(httpClient.readTimeoutMillis()).isEqualTo(9000); + } @Test