diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignAutoConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignAutoConfiguration.java index c643df8a15..4393ea3c10 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignAutoConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignAutoConfiguration.java @@ -18,14 +18,24 @@ import java.util.ArrayList; import java.util.List; +import java.util.Timer; +import java.util.TimerTask; import org.apache.http.client.HttpClient; +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.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.actuator.HasFeatures; +import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory; +import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory; +import org.springframework.cloud.netflix.feign.support.FeignHttpClientProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -34,12 +44,17 @@ import feign.httpclient.ApacheHttpClient; import feign.okhttp.OkHttpClient; +import javax.annotation.PreDestroy; +import com.netflix.client.config.CommonClientConfigKey; +import com.netflix.client.config.DefaultClientConfigImpl; + /** * @author Spencer Gibb * @author Julien Roy */ @Configuration @ConditionalOnClass(Feign.class) +@EnableConfigurationProperties({FeignHttpClientProperties.class}) public class FeignAutoConfiguration { @Autowired(required = false) @@ -86,17 +101,51 @@ public Targeter feignTargeter() { @ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer") @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true) protected static class HttpClientFeignConfiguration { + private final Timer connectionManagerTimer = new Timer( + "FeignApacheHttpClientConfiguration.connectionManagerTimer", true); @Autowired(required = false) - private HttpClient httpClient; + private RegistryBuilder registryBuilder; + + private CloseableHttpClient httpClient; + + @Bean + public HttpClientConnectionManager connectionManager(ApacheHttpClientConnectionManagerFactory connectionManagerFactory, + FeignHttpClientProperties httpClientProperties) { + final HttpClientConnectionManager connectionManager = connectionManagerFactory.newConnectionManager(false, httpClientProperties.getMaxConnections(), + httpClientProperties.getMaxConnectionsPerRoute(), httpClientProperties.getTimeToLive(), + httpClientProperties.getTimeToLiveUnit(), + registryBuilder); + this.connectionManagerTimer.schedule(new TimerTask() { + @Override + public void run() { + connectionManager.closeExpiredConnections(); + } + }, 30000, httpClientProperties.getConnectionTimerRepeat()); + return connectionManager; + } + + @Bean + public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory, HttpClientConnectionManager httpClientConnectionManager, + FeignHttpClientProperties httpClientProperties) { + RequestConfig defaultRequestConfig = RequestConfig.custom(). + setConnectTimeout(httpClientProperties.getConnectionTimeout()). + setRedirectsEnabled(httpClientProperties.isFollowRedirects()). + build(); + this.httpClient = httpClientFactory.createClient(defaultRequestConfig, httpClientConnectionManager); + return this.httpClient; + } @Bean @ConditionalOnMissingBean(Client.class) - public Client feignClient() { - if (this.httpClient != null) { - return new ApacheHttpClient(this.httpClient); - } - return new ApacheHttpClient(); + public Client feignClient(HttpClient httpClient) { + return new ApacheHttpClient(httpClient); + } + + @PreDestroy + public void destroy() throws Exception { + connectionManagerTimer.cancel(); + httpClient.close(); } } diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/FeignRibbonClientAutoConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/FeignRibbonClientAutoConfiguration.java index 4210fa6c59..137e6440d1 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/FeignRibbonClientAutoConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/ribbon/FeignRibbonClientAutoConfiguration.java @@ -17,14 +17,22 @@ package org.springframework.cloud.netflix.feign.ribbon; import org.apache.http.client.HttpClient; +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.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; +import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory; +import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory; import org.springframework.cloud.netflix.feign.FeignAutoConfiguration; +import org.springframework.cloud.netflix.feign.support.FeignHttpClientProperties; import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -38,6 +46,10 @@ import feign.httpclient.ApacheHttpClient; import feign.okhttp.OkHttpClient; +import java.util.Timer; +import java.util.TimerTask; +import javax.annotation.PreDestroy; + /** * Autoconfiguration to be activated if Feign is in use and needs to be use Ribbon as a * load balancer. @@ -47,6 +59,7 @@ @ConditionalOnClass({ ILoadBalancer.class, Feign.class }) @Configuration @AutoConfigureBefore(FeignAutoConfiguration.class) +@EnableConfigurationProperties({FeignHttpClientProperties.class}) public class FeignRibbonClientAutoConfiguration { @Bean @@ -84,22 +97,54 @@ public Request.Options feignRequestOptions() { @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true) protected static class HttpClientFeignLoadBalancedConfiguration { + private final Timer connectionManagerTimer = new Timer( + "FeignApacheHttpClientConfiguration.connectionManagerTimer", true); + + private CloseableHttpClient httpClient; + @Autowired(required = false) - private HttpClient httpClient; + private RegistryBuilder registryBuilder; + + @Bean + public HttpClientConnectionManager connectionManager(ApacheHttpClientConnectionManagerFactory connectionManagerFactory, + FeignHttpClientProperties httpClientProperties) { + final HttpClientConnectionManager connectionManager = connectionManagerFactory.newConnectionManager(false, httpClientProperties.getMaxConnections(), + httpClientProperties.getMaxConnectionsPerRoute(), httpClientProperties.getTimeToLive(), + httpClientProperties.getTimeToLiveUnit(), + registryBuilder); + this.connectionManagerTimer.schedule(new TimerTask() { + @Override + public void run() { + connectionManager.closeExpiredConnections(); + } + }, 30000, httpClientProperties.getConnectionTimerRepeat()); + return connectionManager; + } + + @Bean + public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory, HttpClientConnectionManager httpClientConnectionManager, + FeignHttpClientProperties httpClientProperties) { + RequestConfig defaultRequestConfig = RequestConfig.custom(). + setConnectTimeout(httpClientProperties.getConnectionTimeout()). + setRedirectsEnabled(httpClientProperties.isFollowRedirects()). + build(); + this.httpClient = httpClientFactory.createClient(defaultRequestConfig, httpClientConnectionManager); + return this.httpClient; + } @Bean @ConditionalOnMissingBean(Client.class) public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, - SpringClientFactory clientFactory) { - ApacheHttpClient delegate; - if (this.httpClient != null) { - delegate = new ApacheHttpClient(this.httpClient); - } - else { - delegate = new ApacheHttpClient(); - } + SpringClientFactory clientFactory, HttpClient httpClient) { + ApacheHttpClient delegate = new ApacheHttpClient(httpClient); return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory); } + + @PreDestroy + public void destroy() throws Exception { + connectionManagerTimer.cancel(); + httpClient.close(); + } } @Configuration diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/support/FeignHttpClientProperties.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/support/FeignHttpClientProperties.java new file mode 100644 index 0000000000..501e3e7d54 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/support/FeignHttpClientProperties.java @@ -0,0 +1,110 @@ +/* + * + * * 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.netflix.feign.support; + +import java.util.concurrent.TimeUnit; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author Ryan Baxter + */ +@ConfigurationProperties(prefix = "feign.httpclient") +public class FeignHttpClientProperties { + public static final boolean DEFAULT_DISABLE_SSL_VALIDATION = false; + public static final int DEFAULT_MAX_CONNECTIONS = 200; + public static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 50; + public static final long DEFAULT_TIME_TO_LIVE = 900L; + public static final TimeUnit DEFAULT_TIME_TO_LIVE_UNIT = TimeUnit.SECONDS; + public static final boolean DEFAULT_FOLLOW_REDIRECTS = true; + public static final int DEFAULT_CONNECTION_TIMEOUT = 2000; + public static final int DEFAULT_CONNECTION_TIMER_REPEAT = 3000; + + private boolean disableSslValidation = DEFAULT_DISABLE_SSL_VALIDATION; + private int maxConnections = DEFAULT_MAX_CONNECTIONS; + private int maxConnectionsPerRoute = DEFAULT_MAX_CONNECTIONS_PER_ROUTE; + private long timeToLive = DEFAULT_TIME_TO_LIVE; + private TimeUnit timeToLiveUnit = DEFAULT_TIME_TO_LIVE_UNIT; + private boolean followRedirects = DEFAULT_FOLLOW_REDIRECTS; + private int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT; + private int connectionTimerRepeat = DEFAULT_CONNECTION_TIMER_REPEAT; + + public int getConnectionTimerRepeat() { + return connectionTimerRepeat; + } + + public void setConnectionTimerRepeat(int connectionTimerRepeat) { + this.connectionTimerRepeat = connectionTimerRepeat; + } + + public boolean isDisableSslValidation() { + return disableSslValidation; + } + + public void setDisableSslValidation(boolean disableSslValidation) { + this.disableSslValidation = disableSslValidation; + } + + public int getMaxConnections() { + return maxConnections; + } + + public void setMaxConnections(int maxConnections) { + this.maxConnections = maxConnections; + } + + public int getMaxConnectionsPerRoute() { + return maxConnectionsPerRoute; + } + + public void setMaxConnectionsPerRoute(int maxConnectionsPerRoute) { + this.maxConnectionsPerRoute = maxConnectionsPerRoute; + } + + public long getTimeToLive() { + return timeToLive; + } + + public void setTimeToLive(long timeToLive) { + this.timeToLive = timeToLive; + } + + public TimeUnit getTimeToLiveUnit() { + return timeToLiveUnit; + } + + public void setTimeToLiveUnit(TimeUnit timeToLiveUnit) { + this.timeToLiveUnit = timeToLiveUnit; + } + + public boolean isFollowRedirects() { + return followRedirects; + } + + public void setFollowRedirects(boolean followRedirects) { + this.followRedirects = followRedirects; + } + + public int getConnectionTimeout() { + return connectionTimeout; + } + + public void setConnectionTimeout(int connectionTimeout) { + this.connectionTimeout = connectionTimeout; + } +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfiguration.java index d33c214e81..cb1bcf385e 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/RibbonClientConfiguration.java @@ -17,11 +17,19 @@ package org.springframework.cloud.netflix.ribbon; import java.net.URI; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.TimeUnit; import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import org.apache.http.client.config.RequestConfig; import org.apache.http.client.params.ClientPNames; import org.apache.http.client.params.CookiePolicy; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.impl.client.CloseableHttpClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -30,17 +38,22 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; +import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory; +import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory; +import org.springframework.cloud.commons.httpclient.HttpClientConfiguration; import org.springframework.cloud.netflix.ribbon.apache.RetryableRibbonLoadBalancingHttpClient; import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient; import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpLoadBalancingClient; import org.springframework.cloud.netflix.ribbon.okhttp.RetryableOkHttpLoadBalancingClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Lazy; import com.netflix.client.AbstractLoadBalancerAwareClient; import com.netflix.client.DefaultLoadBalancerRetryHandler; import com.netflix.client.RetryHandler; +import com.netflix.client.config.CommonClientConfigKey; import com.netflix.client.config.DefaultClientConfigImpl; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.ConfigurationBasedServerList; @@ -70,6 +83,7 @@ @SuppressWarnings("deprecation") @Configuration @EnableConfigurationProperties +@Import(HttpClientConfiguration.class) public class RibbonClientConfiguration { @Value("${ribbon.client.name}") @@ -121,6 +135,71 @@ public ServerList ribbonServerList(IClientConfig config) { return serverList; } + @Configuration + @ConditionalOnProperty(name = "ribbon.httpclient.enabled", matchIfMissing = true) + protected static class ApacheHttpClientConfiguration { + private final Timer connectionManagerTimer = new Timer( + "RibbonApacheHttpClientConfiguration.connectionManagerTimer", true); + private CloseableHttpClient httpClient; + + @Autowired(required = false) + private RegistryBuilder registryBuilder; + + @Bean + public HttpClientConnectionManager httpClientConnectionManager(IClientConfig config, + ApacheHttpClientConnectionManagerFactory connectionManagerFactory, + ApacheHttpClientFactory httpClientFactory) { + Integer maxTotalConnections = config.getPropertyAsInteger(CommonClientConfigKey.MaxTotalConnections, + DefaultClientConfigImpl.DEFAULT_MAX_TOTAL_CONNECTIONS); + Integer maxConnectionsPerHost = config.getPropertyAsInteger(CommonClientConfigKey.MaxConnectionsPerHost, + DefaultClientConfigImpl.DEFAULT_MAX_CONNECTIONS_PER_HOST); + Integer timerRepeat = config.getPropertyAsInteger(CommonClientConfigKey.ConnectionCleanerRepeatInterval, + DefaultClientConfigImpl.DEFAULT_CONNECTION_IDLE_TIMERTASK_REPEAT_IN_MSECS); + Object timeToLiveObj = config.getProperty(CommonClientConfigKey.PoolKeepAliveTime); + Long timeToLive = DefaultClientConfigImpl.DEFAULT_POOL_KEEP_ALIVE_TIME; + Object ttlUnitObj = config.getProperty(CommonClientConfigKey.PoolKeepAliveTimeUnits); + TimeUnit ttlUnit = DefaultClientConfigImpl.DEFAULT_POOL_KEEP_ALIVE_TIME_UNITS; + if(timeToLiveObj instanceof Long) { + timeToLive = (Long)timeToLiveObj; + } + if(ttlUnitObj instanceof TimeUnit) { + ttlUnit = (TimeUnit)ttlUnitObj; + } + final HttpClientConnectionManager connectionManager = connectionManagerFactory.newConnectionManager(false, maxTotalConnections, + maxConnectionsPerHost, timeToLive, ttlUnit, registryBuilder); + this.connectionManagerTimer.schedule(new TimerTask() { + @Override + public void run() { + connectionManager.closeExpiredConnections(); + } + }, 30000, timerRepeat); + return connectionManager; + } + + @Bean + public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory, + HttpClientConnectionManager connectionManager, IClientConfig config) { + Boolean followRedirects = config.getPropertyAsBoolean( + CommonClientConfigKey.FollowRedirects, + DefaultClientConfigImpl.DEFAULT_FOLLOW_REDIRECTS); + Integer connectTimeout = config.getPropertyAsInteger( + CommonClientConfigKey.ConnectTimeout, + DefaultClientConfigImpl.DEFAULT_CONNECT_TIMEOUT); + RequestConfig defaultRequestConfig = RequestConfig.custom(). + setConnectTimeout(connectTimeout). + setRedirectsEnabled(followRedirects). + build(); + this.httpClient = httpClientFactory.createClient(defaultRequestConfig, connectionManager); + return httpClient; + } + + @PreDestroy + public void destroy() throws Exception { + connectionManagerTimer.cancel(); + httpClient.close(); + } + } + @Configuration @ConditionalOnProperty(name = "ribbon.httpclient.enabled", matchIfMissing = true) protected static class HttpClientRibbonConfiguration { @@ -132,8 +211,8 @@ protected static class HttpClientRibbonConfiguration { @ConditionalOnMissingClass(value = "org.springframework.retry.support.RetryTemplate") public RibbonLoadBalancingHttpClient ribbonLoadBalancingHttpClient( IClientConfig config, ServerIntrospector serverIntrospector, - ILoadBalancer loadBalancer, RetryHandler retryHandler) { - RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient( + ILoadBalancer loadBalancer, RetryHandler retryHandler, CloseableHttpClient httpClient) { + RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient(httpClient, config, serverIntrospector); client.setLoadBalancer(loadBalancer); client.setRetryHandler(retryHandler); @@ -147,8 +226,9 @@ public RibbonLoadBalancingHttpClient ribbonLoadBalancingHttpClient( public RetryableRibbonLoadBalancingHttpClient retryableRibbonLoadBalancingHttpClient( IClientConfig config, ServerIntrospector serverIntrospector, ILoadBalancer loadBalancer, RetryHandler retryHandler, - LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { - RetryableRibbonLoadBalancingHttpClient client = new RetryableRibbonLoadBalancingHttpClient( + LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory, + CloseableHttpClient httpClient) { + RetryableRibbonLoadBalancingHttpClient client = new RetryableRibbonLoadBalancingHttpClient(httpClient, config, serverIntrospector, loadBalancedRetryPolicyFactory); client.setLoadBalancer(loadBalancer); client.setRetryHandler(retryHandler); diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RetryableRibbonLoadBalancingHttpClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RetryableRibbonLoadBalancingHttpClient.java index a46eb0116b..c17855fcd8 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RetryableRibbonLoadBalancingHttpClient.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RetryableRibbonLoadBalancingHttpClient.java @@ -22,6 +22,7 @@ import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.impl.client.CloseableHttpClient; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy; @@ -50,10 +51,17 @@ public class RetryableRibbonLoadBalancingHttpClient extends RibbonLoadBalancingHttpClient implements ServiceInstanceChooser { private LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory = new LoadBalancedRetryPolicyFactory.NeverRetryFactory(); - public RetryableRibbonLoadBalancingHttpClient(IClientConfig config, ServerIntrospector serverIntrospector, LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { + + public RetryableRibbonLoadBalancingHttpClient(IClientConfig config, ServerIntrospector serverIntrospector, + LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { super(config, serverIntrospector); this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory; } + public RetryableRibbonLoadBalancingHttpClient(CloseableHttpClient delegate, IClientConfig config, ServerIntrospector serverIntrospector, + LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory) { + super(delegate, config, serverIntrospector); + this.loadBalancedRetryPolicyFactory = loadBalancedRetryPolicyFactory; + } @Override public RibbonApacheHttpResponse execute(final RibbonApacheHttpRequest request, final IClientConfig configOverride) throws Exception { diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClient.java index 18db9b683d..008d09ec3f 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClient.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClient.java @@ -20,12 +20,11 @@ import com.netflix.client.RetryHandler; import com.netflix.client.config.CommonClientConfigKey; import com.netflix.client.config.IClientConfig; -import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.springframework.cloud.netflix.ribbon.ServerIntrospector; import org.springframework.cloud.netflix.ribbon.support.AbstractLoadBalancingClient; @@ -41,27 +40,17 @@ */ //TODO: rename (ie new class that extends this in Dalston) to ApacheHttpLoadBalancingClient public class RibbonLoadBalancingHttpClient extends - AbstractLoadBalancingClient { - - @Deprecated - public RibbonLoadBalancingHttpClient() { - super(); - } - - @Deprecated - public RibbonLoadBalancingHttpClient(final ILoadBalancer lb) { - super(lb); - } + AbstractLoadBalancingClient { public RibbonLoadBalancingHttpClient(IClientConfig config, ServerIntrospector serverIntrospector) { super(config, serverIntrospector); } - public RibbonLoadBalancingHttpClient(HttpClient delegate, IClientConfig config, ServerIntrospector serverIntrospector) { + public RibbonLoadBalancingHttpClient(CloseableHttpClient delegate, IClientConfig config, ServerIntrospector serverIntrospector) { super(delegate, config, serverIntrospector); } - protected HttpClient createDelegate(IClientConfig config) { + protected CloseableHttpClient createDelegate(IClientConfig config) { return HttpClientBuilder.create() // already defaults to 0 in builder, so resetting to 0 won't hurt .setMaxConnTotal(config.getPropertyAsInteger(CommonClientConfigKey.MaxTotalConnections, 0)) diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyAutoConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyAutoConfiguration.java index 1f11d57190..82f474393e 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyAutoConfiguration.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/ZuulProxyAutoConfiguration.java @@ -32,6 +32,9 @@ import org.springframework.cloud.client.discovery.event.HeartbeatMonitor; import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent; import org.springframework.cloud.client.discovery.event.ParentHeartbeatEvent; +import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory; +import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory; +import org.springframework.cloud.commons.httpclient.HttpClientConfiguration; import org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer; import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper; import org.springframework.cloud.netflix.zuul.filters.RouteLocator; @@ -59,7 +62,8 @@ @Configuration @Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class, RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class, - RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class }) + RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class, + HttpClientConfiguration.class}) @ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class) public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration { @@ -102,8 +106,10 @@ public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper, @Bean @ConditionalOnMissingBean(SimpleHostRoutingFilter.class) - public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper, ZuulProperties zuulProperties) { - return new SimpleHostRoutingFilter(helper, zuulProperties); + public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper, ZuulProperties zuulProperties, + ApacheHttpClientConnectionManagerFactory connectionManagerFactory, + ApacheHttpClientFactory httpClientFactory) { + return new SimpleHostRoutingFilter(helper, zuulProperties, connectionManagerFactory, httpClientFactory); } @Bean diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/SimpleHostRoutingFilter.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/SimpleHostRoutingFilter.java index f6b9909c17..f117320597 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/SimpleHostRoutingFilter.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/route/SimpleHostRoutingFilter.java @@ -19,9 +19,6 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; -import java.security.SecureRandom; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -30,9 +27,6 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; @@ -41,32 +35,21 @@ import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; -import org.apache.http.ProtocolException; -import org.apache.http.client.RedirectStrategy; import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPatch; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.config.Registry; -import org.apache.http.config.RegistryBuilder; -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.conn.HttpClientConnectionManager; import org.apache.http.entity.ContentType; import org.apache.http.entity.InputStreamEntity; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHttpEntityEnclosingRequest; import org.apache.http.message.BasicHttpRequest; -import org.apache.http.protocol.HttpContext; +import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory; +import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory; import org.springframework.cloud.context.environment.EnvironmentChangeEvent; import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; @@ -80,8 +63,6 @@ import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; -import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.HTTPS_SCHEME; -import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.HTTP_SCHEME; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.ROUTE_TYPE; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SIMPLE_HOST_ROUTING_FILTER_ORDER; @@ -105,7 +86,9 @@ public class SimpleHostRoutingFilter extends ZuulFilter { private ProxyRequestHelper helper; private Host hostProperties; - private PoolingHttpClientConnectionManager connectionManager; + private ApacheHttpClientConnectionManagerFactory connectionManagerFactory; + private ApacheHttpClientFactory httpClientFactory; + private HttpClientConnectionManager connectionManager; private CloseableHttpClient httpClient; @EventListener @@ -130,16 +113,23 @@ public void onPropertyChange(EnvironmentChangeEvent event) { } } - public SimpleHostRoutingFilter(ProxyRequestHelper helper, ZuulProperties properties) { + public SimpleHostRoutingFilter(ProxyRequestHelper helper, ZuulProperties properties, + ApacheHttpClientConnectionManagerFactory connectionManagerFactory, + ApacheHttpClientFactory httpClientFactory) { this.helper = helper; this.hostProperties = properties.getHost(); this.sslHostnameValidationEnabled = properties.isSslHostnameValidationEnabled(); this.forceOriginalQueryStringEncoding = properties .isForceOriginalQueryStringEncoding(); + this.connectionManagerFactory = connectionManagerFactory; + this.httpClientFactory = httpClientFactory; } @PostConstruct private void initialize() { + this.connectionManager = connectionManagerFactory.newConnectionManager(this.sslHostnameValidationEnabled, + this.hostProperties.getMaxTotalConnections(), this.hostProperties.getMaxPerRouteConnections(), + this.hostProperties.getTimeToLive(), this.hostProperties.getTimeUnit(), null); this.httpClient = newClient(); this.connectionManagerTimer.schedule(new TimerTask() { @Override @@ -201,50 +191,8 @@ public Object run() { return null; } - protected PoolingHttpClientConnectionManager newConnectionManager() { - 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 registryBuilder = RegistryBuilder - . create() - .register(HTTP_SCHEME, PlainConnectionSocketFactory.INSTANCE); - if (this.sslHostnameValidationEnabled) { - registryBuilder.register(HTTPS_SCHEME, - new SSLConnectionSocketFactory(sslContext)); - } - else { - registryBuilder.register(HTTPS_SCHEME, new SSLConnectionSocketFactory( - sslContext, NoopHostnameVerifier.INSTANCE)); - } - final Registry registry = registryBuilder.build(); - - this.connectionManager = new PoolingHttpClientConnectionManager(registry, null, null, null, - hostProperties.getTimeToLive(), hostProperties.getTimeUnit()); - this.connectionManager - .setMaxTotal(this.hostProperties.getMaxTotalConnections()); - this.connectionManager.setDefaultMaxPerRoute( - this.hostProperties.getMaxPerRouteConnections()); - return this.connectionManager; - } - catch (Exception ex) { - throw new RuntimeException(ex); - } + protected HttpClientConnectionManager getConnectionManager() { + return connectionManager; } protected CloseableHttpClient newClient() { @@ -252,30 +200,7 @@ protected CloseableHttpClient newClient() { .setSocketTimeout(this.hostProperties.getSocketTimeoutMillis()) .setConnectTimeout(this.hostProperties.getConnectTimeoutMillis()) .setCookieSpec(CookieSpecs.IGNORE_COOKIES).build(); - - HttpClientBuilder httpClientBuilder = HttpClients.custom(); - if (!this.sslHostnameValidationEnabled) { - httpClientBuilder.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE); - } - return httpClientBuilder.setConnectionManager(newConnectionManager()) - .disableContentCompression() - .useSystemProperties().setDefaultRequestConfig(requestConfig) - .setRetryHandler(new DefaultHttpRequestRetryHandler(0, false)) - .setRedirectStrategy(new RedirectStrategy() { - @Override - public boolean isRedirected(HttpRequest request, - HttpResponse response, HttpContext context) - throws ProtocolException { - return false; - } - - @Override - public HttpUriRequest getRedirect(HttpRequest request, - HttpResponse response, HttpContext context) - throws ProtocolException { - return null; - } - }).build(); + return httpClientFactory.createClient(requestConfig, this.connectionManager); } private CloseableHttpResponse forward(CloseableHttpClient httpclient, String verb, diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/HttpClientConfigurationTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/HttpClientConfigurationTests.java new file mode 100644 index 0000000000..7468f109c6 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/HttpClientConfigurationTests.java @@ -0,0 +1,203 @@ +/* + * + * * 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.netflix; + +import feign.Client; +import feign.httpclient.ApacheHttpClient; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; +import org.apache.http.Header; +import org.apache.http.StatusLine; +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.message.BasicHeader; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockingDetails; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory; +import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory; +import org.springframework.cloud.commons.httpclient.DefaultApacheHttpClientConnectionManagerFactory; +import org.springframework.cloud.commons.httpclient.DefaultApacheHttpClientFactory; +import org.springframework.cloud.netflix.feign.EnableFeignClients; +import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.cloud.netflix.feign.ribbon.LoadBalancerFeignClient; +import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient; +import org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer; +import org.springframework.cloud.netflix.zuul.EnableZuulProxy; +import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext; +import org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter; +import org.springframework.cloud.netflix.zuul.filters.route.apache.HttpClientRibbonCommand; +import org.springframework.cloud.netflix.zuul.filters.route.apache.HttpClientRibbonCommandFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockingDetails; + +/** + * @author Ryan Baxter + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = HttpClientConfigurationTestApp.class, value = {"feign.okhttp.enabled: false", + "ribbon.eureka.enabled = false"}) +@DirtiesContext +public class HttpClientConfigurationTests { + + @Autowired + ApacheHttpClientConnectionManagerFactory connectionManagerFactory; + + @Autowired + ApacheHttpClientFactory httpClientFactory; + + @Autowired + SimpleHostRoutingFilter simpleHostRoutingFilter; + + @Autowired + LoadBalancerFeignClient feignClient; + + @Autowired + HttpClientRibbonCommandFactory httpClientRibbonCommandFactory; + + @Test + public void testFactories() { + assertTrue(ApacheHttpClientConnectionManagerFactory.class.isInstance(connectionManagerFactory)); + assertTrue(HttpClientConfigurationTestApp.MyApacheHttpClientConnectionManagerFactory.class.isInstance(connectionManagerFactory)); + assertTrue(ApacheHttpClientFactory.class.isInstance(httpClientFactory)); + assertTrue(HttpClientConfigurationTestApp.MyApacheHttpClientFactory.class.isInstance(httpClientFactory)); + } + + @Test + public void testHttpClientSimpleHostRoutingFilter() { + PoolingHttpClientConnectionManager connectionManager = getField(simpleHostRoutingFilter, "connectionManager"); + MockingDetails connectionManagerDetails = mockingDetails(connectionManager); + assertTrue(connectionManagerDetails.isMock()); + CloseableHttpClient httpClient = getField(simpleHostRoutingFilter, "httpClient"); + MockingDetails httpClientDetails = mockingDetails(httpClient); + assertTrue(httpClientDetails.isMock()); + } + + @Test + public void testHttpClientWithFeign() { + Client delegate = feignClient.getDelegate(); + assertTrue(ApacheHttpClient.class.isInstance(delegate)); + ApacheHttpClient apacheHttpClient = (ApacheHttpClient)delegate; + HttpClient httpClient = getField(apacheHttpClient, "client"); + MockingDetails httpClientDetails = mockingDetails(httpClient); + assertTrue(httpClientDetails.isMock()); + } + + @Test + public void testRibbonLoadBalancingHttpClient() { + RibbonCommandContext context = new RibbonCommandContext("foo"," GET", "http://localhost", + false, new LinkedMultiValueMap(), new LinkedMultiValueMap(), + null, new ArrayList(), 0l); + HttpClientRibbonCommand command = httpClientRibbonCommandFactory.create(context); + RibbonLoadBalancingHttpClient ribbonClient = command.getClient(); + CloseableHttpClient httpClient = getField(ribbonClient, "delegate"); + MockingDetails httpClientDetails = mockingDetails(httpClient); + assertTrue(httpClientDetails.isMock()); + } + + 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 +@RestController +@EnableFeignClients(clients = {HttpClientConfigurationTestApp.FooClient.class}) +@EnableZuulProxy +class HttpClientConfigurationTestApp { + + @RequestMapping + public String index() { + return "hello"; + } + + static class MyApacheHttpClientConnectionManagerFactory extends DefaultApacheHttpClientConnectionManagerFactory { + @Override + public HttpClientConnectionManager newConnectionManager(boolean disableSslValidation, int maxTotalConnections, int maxConnectionsPerRoute, long timeToLive, TimeUnit timeUnit, RegistryBuilder registry) { + return mock(PoolingHttpClientConnectionManager.class); + } + } + + static class MyApacheHttpClientFactory extends DefaultApacheHttpClientFactory { + @Override + public CloseableHttpClient createClient(RequestConfig requestConfig, HttpClientConnectionManager connectionManager) { + CloseableHttpClient client = mock(CloseableHttpClient.class); + CloseableHttpResponse response = mock(CloseableHttpResponse.class); + StatusLine statusLine = mock(StatusLine.class); + doReturn(200).when(statusLine).getStatusCode(); + doReturn(statusLine).when(response).getStatusLine(); + Header[] headers = new BasicHeader[0]; + doReturn(headers).when(response).getAllHeaders(); + try { + doReturn(response).when(client).execute(any(HttpUriRequest.class)); + } catch (IOException e) { + e.printStackTrace(); + } + return client; + } + } + + @Configuration + static class MyConfig { + + @Bean + public ApacheHttpClientFactory apacheHttpClientFactory() { + return new MyApacheHttpClientFactory(); + } + + @Bean + public ApacheHttpClientConnectionManagerFactory connectionManagerFactory() { + return new MyApacheHttpClientConnectionManagerFactory(); + } + + } + + @FeignClient(name="foo", serviceId = "foo") + static interface FooClient {} +} + + diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/FeignCompressionTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/FeignCompressionTests.java index 17ba30d1eb..515d04018b 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/FeignCompressionTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/FeignCompressionTests.java @@ -32,6 +32,7 @@ import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.ClassPathExclusions; import org.springframework.cloud.FilteredClassPathRunner; +import org.springframework.cloud.commons.httpclient.HttpClientConfiguration; import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration; import org.springframework.cloud.netflix.feign.encoding.FeignAcceptGzipEncodingAutoConfiguration; import org.springframework.cloud.netflix.feign.encoding.FeignAcceptGzipEncodingInterceptor; @@ -58,7 +59,7 @@ public void setUp() { context = new SpringApplicationBuilder().properties("feign.compression.response.enabled=true", "feign.compression.request.enabled=true", "feign.okhttp.enabled=false").sources(PropertyPlaceholderAutoConfiguration.class, ArchaiusAutoConfiguration.class, FeignAutoConfiguration.class, PlainConfig.class, FeignContentGzipEncodingAutoConfiguration.class, - FeignAcceptGzipEncodingAutoConfiguration.class).web(false).run(); + FeignAcceptGzipEncodingAutoConfiguration.class, HttpClientConfiguration.class).web(false).run(); } @After diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/support/FeignHttpClientPropertiesTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/support/FeignHttpClientPropertiesTests.java new file mode 100644 index 0000000000..6431c6a49b --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/support/FeignHttpClientPropertiesTests.java @@ -0,0 +1,98 @@ +/* + * + * * 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.netflix.feign.support; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.springframework.boot.test.util.EnvironmentTestUtils.addEnvironment; + +/** + * @author Ryan Baxter + */ +@RunWith(SpringRunner.class) +@DirtiesContext +public class FeignHttpClientPropertiesTests { + + private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + + @After + public void clear() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void testDefaults() { + setupContext(); + assertEquals(FeignHttpClientProperties.DEFAULT_CONNECTION_TIMEOUT, getProperties().getConnectionTimeout()); + assertEquals(FeignHttpClientProperties.DEFAULT_MAX_CONNECTIONS, getProperties().getMaxConnections()); + assertEquals(FeignHttpClientProperties.DEFAULT_MAX_CONNECTIONS_PER_ROUTE, getProperties().getMaxConnectionsPerRoute()); + assertEquals(FeignHttpClientProperties.DEFAULT_TIME_TO_LIVE, getProperties().getTimeToLive()); + assertEquals(FeignHttpClientProperties.DEFAULT_DISABLE_SSL_VALIDATION, getProperties().isDisableSslValidation()); + assertEquals(FeignHttpClientProperties.DEFAULT_FOLLOW_REDIRECTS, getProperties().isFollowRedirects()); + } + + @Test + public void testCustomization() { + addEnvironment(this.context, "feign.httpclient.maxConnections=2", + "feign.httpclient.connectionTimeout=2", + "feign.httpclient.maxConnectionsPerRoute=2", + "feign.httpclient.timeToLive=2", + "feign.httpclient.disableSslValidation=true", + "feign.httpclient.followRedirects=false"); + setupContext(); + assertEquals(2, getProperties().getMaxConnections()); + assertEquals(2, getProperties().getConnectionTimeout()); + assertEquals(2, getProperties().getMaxConnectionsPerRoute()); + assertEquals(2L, getProperties().getTimeToLive()); + assertTrue(getProperties().isDisableSslValidation()); + assertFalse(getProperties().isFollowRedirects()); + } + + private void setupContext() { + this.context.register(PropertyPlaceholderAutoConfiguration.class, TestConfiguration.class); + this.context.refresh(); + } + + private FeignHttpClientProperties getProperties() { + return this.context.getBean(FeignHttpClientProperties.class); + } + + @Configuration + @EnableConfigurationProperties + protected static class TestConfiguration { + @Bean + FeignHttpClientProperties zuulProperties() { + return new FeignHttpClientProperties() ; + } + } +} \ No newline at end of file diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/valid/FeignClientValidationTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/valid/FeignClientValidationTests.java index 26be6f78ef..a3e7a50f92 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/valid/FeignClientValidationTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/valid/FeignClientValidationTests.java @@ -18,6 +18,7 @@ import org.junit.Test; import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; +import org.springframework.cloud.commons.httpclient.HttpClientConfiguration; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.feign.FeignAutoConfiguration; import org.springframework.cloud.netflix.feign.FeignClient; @@ -45,7 +46,7 @@ public void validNotLoadBalanced() { } @Configuration - @Import(FeignAutoConfiguration.class) + @Import({FeignAutoConfiguration.class, HttpClientConfiguration.class}) @EnableFeignClients(clients = GoodUrlConfiguration.Client.class) protected static class GoodUrlConfiguration { @@ -67,7 +68,7 @@ public void validPlaceholder() { } @Configuration - @Import(FeignAutoConfiguration.class) + @Import({FeignAutoConfiguration.class, HttpClientConfiguration.class}) @EnableFeignClients(clients = PlaceholderUrlConfiguration.Client.class) protected static class PlaceholderUrlConfiguration { @@ -92,7 +93,7 @@ public void validLoadBalanced() { } @Configuration - @Import(FeignAutoConfiguration.class) + @Import({FeignAutoConfiguration.class, HttpClientConfiguration.class}) @EnableFeignClients(clients = GoodServiceIdConfiguration.Client.class) protected static class GoodServiceIdConfiguration { diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonClientsPreprocessorIntegrationTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonClientsPreprocessorIntegrationTests.java index 302a6f906a..18c25b3a0c 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonClientsPreprocessorIntegrationTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/RibbonClientsPreprocessorIntegrationTests.java @@ -23,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.commons.httpclient.HttpClientConfiguration; import org.springframework.cloud.commons.util.UtilAutoConfiguration; import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration; import org.springframework.cloud.netflix.ribbon.RibbonClientsPreprocessorIntegrationTests.TestConfiguration; @@ -66,7 +67,7 @@ public void serverListFilterOverride() throws Exception { @Configuration @RibbonClients(@RibbonClient(name = "foo", configuration = FooConfiguration.class)) @Import({ UtilAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, - ArchaiusAutoConfiguration.class, RibbonAutoConfiguration.class }) + ArchaiusAutoConfiguration.class, RibbonAutoConfiguration.class, HttpClientConfiguration.class}) protected static class TestConfiguration { } diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/SpringClientFactoryTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/SpringClientFactoryTests.java index 2dd65e9c2e..b927b5f872 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/SpringClientFactoryTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/SpringClientFactoryTests.java @@ -19,6 +19,7 @@ import org.apache.http.client.params.ClientPNames; import org.apache.http.client.params.CookiePolicy; import org.junit.Test; +import org.springframework.cloud.commons.httpclient.HttpClientConfiguration; import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -69,7 +70,7 @@ public NoClientConfigAware() { public void testConfigureRetry() { SpringClientFactory factory = new SpringClientFactory(); AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext( - RibbonAutoConfiguration.class, ArchaiusAutoConfiguration.class); + RibbonAutoConfiguration.class, ArchaiusAutoConfiguration.class, HttpClientConfiguration.class); addEnvironment(parent, "foo.ribbon.MaxAutoRetries:2"); factory.setApplicationContext(parent); DefaultLoadBalancerRetryHandler retryHandler = (DefaultLoadBalancerRetryHandler) factory diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/SpringRetryEnabledTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/SpringRetryEnabledTests.java index ea93d0e344..b15cb38b0d 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/SpringRetryEnabledTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/SpringRetryEnabledTests.java @@ -25,6 +25,7 @@ import org.springframework.beans.BeansException; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; +import org.springframework.cloud.commons.httpclient.HttpClientConfiguration; import org.springframework.cloud.netflix.feign.ribbon.CachingSpringLoadBalancerFactory; import org.springframework.cloud.netflix.feign.ribbon.FeignLoadBalancer; import org.springframework.cloud.netflix.feign.ribbon.FeignRibbonClientAutoConfiguration; @@ -45,7 +46,7 @@ */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {RibbonAutoConfiguration.class, RibbonClientConfiguration.class, LoadBalancerAutoConfiguration.class, - FeignRibbonClientAutoConfiguration.class}) + FeignRibbonClientAutoConfiguration.class, HttpClientConfiguration.class}) public class SpringRetryEnabledTests implements ApplicationContextAware { private ApplicationContext context; diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClientTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClientTests.java index b77318f07a..a263a2f488 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClientTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/apache/RibbonLoadBalancingHttpClientTests.java @@ -24,6 +24,7 @@ import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.junit.After; import org.junit.Before; @@ -169,7 +170,7 @@ public void testUpdatedTimeouts() @Test public void testNeverRetry() throws Exception { ServerIntrospector introspector = mock(ServerIntrospector.class); - HttpClient delegate = mock(HttpClient.class); + CloseableHttpClient delegate = mock(CloseableHttpClient.class); HttpResponse response = mock(HttpResponse.class); doThrow(new IOException("boom")).when(delegate).execute(any(HttpUriRequest.class)); DefaultClientConfigImpl clientConfig = new DefaultClientConfigImpl(); @@ -220,8 +221,8 @@ public void testRetrySameServerOnly() throws Exception { int port = 80; HttpMethod method = HttpMethod.GET; URI uri = new URI("http://" + host + ":" + port); - HttpClient delegate = mock(HttpClient.class); - final HttpResponse response = mock(HttpResponse.class); + CloseableHttpClient delegate = mock(CloseableHttpClient.class); + final CloseableHttpResponse response = mock(CloseableHttpResponse.class); StatusLine statusLine = mock(StatusLine.class); doReturn(200).when(statusLine).getStatusCode(); doReturn(statusLine).when(response).getStatusLine(); @@ -252,8 +253,8 @@ public void testRetryNextServer() throws Exception { int port = 80; HttpMethod method = HttpMethod.GET; URI uri = new URI("http://" + host + ":" + port); - HttpClient delegate = mock(HttpClient.class); - final HttpResponse response = mock(HttpResponse.class); + CloseableHttpClient delegate = mock(CloseableHttpClient.class); + final CloseableHttpResponse response = mock(CloseableHttpResponse.class); StatusLine statusLine = mock(StatusLine.class); doReturn(200).when(statusLine).getStatusCode(); doReturn(statusLine).when(response).getStatusLine(); @@ -285,7 +286,7 @@ public void testRetryOnPost() throws Exception { int port = 80; HttpMethod method = HttpMethod.POST; URI uri = new URI("http://" + host + ":" + port); - HttpClient delegate = mock(HttpClient.class); + CloseableHttpClient delegate = mock(CloseableHttpClient.class); final CloseableHttpResponse response = mock(CloseableHttpResponse.class); StatusLine statusLine = mock(StatusLine.class); doReturn(200).when(statusLine).getStatusCode(); @@ -318,7 +319,7 @@ public void testNoRetryOnPost() throws Exception { int port = 80; HttpMethod method = HttpMethod.POST; URI uri = new URI("http://" + host + ":" + port); - HttpClient delegate = mock(HttpClient.class); + CloseableHttpClient delegate = mock(CloseableHttpClient.class); final CloseableHttpResponse response = mock(CloseableHttpResponse.class); doThrow(new IOException("boom")).doThrow(new IOException("boom again")).doReturn(response). when(delegate).execute(any(HttpUriRequest.class)); @@ -353,8 +354,8 @@ public void testRetryOnStatusCode() throws Exception { int port = 80; HttpMethod method = HttpMethod.GET; URI uri = new URI("http://" + host + ":" + port); - HttpClient delegate = mock(HttpClient.class); - final HttpResponse response = mock(HttpResponse.class); + CloseableHttpClient delegate = mock(CloseableHttpClient.class); + final CloseableHttpResponse response = mock(CloseableHttpResponse.class); StatusLine statusLine = mock(StatusLine.class); doReturn(200).when(statusLine).getStatusCode(); doReturn(statusLine).when(response).getStatusLine(); @@ -442,13 +443,13 @@ private RequestConfig getBuiltRequestConfig(Class defaultConfigurationClass, String host = serviceName; int port = 80; URI uri = new URI("http://" + host + ":" + port); - HttpClient delegate = mock(HttpClient.class); + CloseableHttpClient delegate = mock(CloseableHttpClient.class); RibbonLoadBalancingHttpClient client = factory.getClient("service", RibbonLoadBalancingHttpClient.class); ReflectionTestUtils.setField(client, "delegate", delegate); ReflectionTestUtils.setField(client, "lb", loadBalancer); - HttpResponse httpResponse = mock(HttpResponse.class); + CloseableHttpResponse httpResponse = mock(CloseableHttpResponse.class); StatusLine statusLine = mock(StatusLine.class); doReturn(200).when(statusLine).getStatusCode(); doReturn(statusLine).when(httpResponse).getStatusLine(); diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/test/RibbonClientDefaultConfigurationTestsConfig.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/test/RibbonClientDefaultConfigurationTestsConfig.java index 10d86df193..17ac91fc47 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/test/RibbonClientDefaultConfigurationTestsConfig.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/ribbon/test/RibbonClientDefaultConfigurationTestsConfig.java @@ -17,6 +17,7 @@ package org.springframework.cloud.netflix.ribbon.test; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.cloud.commons.httpclient.HttpClientConfiguration; import org.springframework.cloud.commons.util.UtilAutoConfiguration; import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration; import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration; @@ -40,7 +41,7 @@ */ @Configuration @Import({ PropertyPlaceholderAutoConfiguration.class, ArchaiusAutoConfiguration.class, - UtilAutoConfiguration.class, RibbonAutoConfiguration.class }) + UtilAutoConfiguration.class, RibbonAutoConfiguration.class, HttpClientConfiguration.class}) @RibbonClients(defaultConfiguration = DefaultRibbonConfig.class) public class RibbonClientDefaultConfigurationTestsConfig { diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/CustomHostRoutingFilterTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/CustomHostRoutingFilterTests.java index 2b1015fd23..98ead81079 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/CustomHostRoutingFilterTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/CustomHostRoutingFilterTests.java @@ -23,6 +23,7 @@ import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.RequestConfig; +import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.impl.client.BasicCookieStore; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; @@ -38,6 +39,9 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory; +import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory; +import org.springframework.cloud.commons.httpclient.DefaultApacheHttpClientFactory; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; import org.springframework.cloud.netflix.zuul.RoutesMvcEndpoint; import org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientRouteLocator; @@ -210,16 +214,23 @@ public static void main(String[] args) { @EnableZuulProxy protected static class CustomZuulProxyConfig { + @Bean + public ApacheHttpClientFactory customHttpClientFactory() { + return new CustomApacheHttpClientFactory(); + } + @Bean public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper, - ZuulProperties zuulProperties) { - return new CustomHostRoutingFilter(helper, zuulProperties); + ZuulProperties zuulProperties, ApacheHttpClientConnectionManagerFactory connectionManagerFactory, + ApacheHttpClientFactory httpClientFactory) { + return new CustomHostRoutingFilter(helper, zuulProperties, connectionManagerFactory, httpClientFactory); } private class CustomHostRoutingFilter extends SimpleHostRoutingFilter { public CustomHostRoutingFilter(ProxyRequestHelper helper, - ZuulProperties zuulProperties) { - super(helper, zuulProperties); + ZuulProperties zuulProperties, ApacheHttpClientConnectionManagerFactory connectionManagerFactory, + ApacheHttpClientFactory httpClientFactory) { + super(helper, zuulProperties, connectionManagerFactory, httpClientFactory); } @Override @@ -227,13 +238,13 @@ public Object run() { super.addIgnoredHeaders("X-Ignored"); return super.run(); } + } + + private class CustomApacheHttpClientFactory extends DefaultApacheHttpClientFactory { @Override - protected CloseableHttpClient newClient() { - // Custom client with cookie support. - // In practice, we would want a custom cookie store using a multimap with - // a user key. - return HttpClients.custom().setConnectionManager(newConnectionManager()) + public CloseableHttpClient createClient(RequestConfig requestConfig, HttpClientConnectionManager connectionManager) { + return HttpClients.custom().setConnectionManager(connectionManager) .setDefaultCookieStore(new BasicCookieStore()) .setDefaultRequestConfig(RequestConfig.custom() .setCookieSpec(CookieSpecs.DEFAULT).build()) diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/SimpleHostRoutingFilterTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/SimpleHostRoutingFilterTests.java index f7cd9c53ad..43212d1298 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/SimpleHostRoutingFilterTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/route/SimpleHostRoutingFilterTests.java @@ -47,6 +47,10 @@ import org.springframework.boot.context.embedded.LocalServerPort; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory; +import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory; +import org.springframework.cloud.commons.httpclient.DefaultApacheHttpClientConnectionManagerFactory; +import org.springframework.cloud.commons.httpclient.DefaultApacheHttpClientFactory; import org.springframework.cloud.context.environment.EnvironmentChangeEvent; import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; @@ -113,7 +117,7 @@ public void connectionPropertiesAreApplied() { "zuul.host.maxPerRouteConnections=10", "zuul.host.timeToLive=5", "zuul.host.timeUnit=SECONDS"); setupContext(); - PoolingHttpClientConnectionManager connMgr = getFilter().newConnectionManager(); + PoolingHttpClientConnectionManager connMgr = (PoolingHttpClientConnectionManager)getFilter().getConnectionManager(); assertEquals(100, connMgr.getMaxTotal()); assertEquals(10, connMgr.getDefaultMaxPerRoute()); Object pool = getField(connMgr, "pool"); @@ -148,7 +152,7 @@ public void validationOfSslHostnamesCanBeDisabledViaProperty() { @Test public void defaultPropertiesAreApplied() { setupContext(); - PoolingHttpClientConnectionManager connMgr = getFilter().newConnectionManager(); + PoolingHttpClientConnectionManager connMgr = (PoolingHttpClientConnectionManager)getFilter().getConnectionManager(); assertEquals(200, connMgr.getMaxTotal()); assertEquals(20, connMgr.getDefaultMaxPerRoute()); @@ -222,8 +226,16 @@ ZuulProperties zuulProperties() { } @Bean - SimpleHostRoutingFilter simpleHostRoutingFilter(ZuulProperties zuulProperties) { - return new SimpleHostRoutingFilter(new ProxyRequestHelper(), zuulProperties); + ApacheHttpClientFactory clientFactory() {return new DefaultApacheHttpClientFactory(); } + + @Bean + ApacheHttpClientConnectionManagerFactory connectionManagerFactory() { return new DefaultApacheHttpClientConnectionManagerFactory(); } + + @Bean + SimpleHostRoutingFilter simpleHostRoutingFilter(ZuulProperties zuulProperties, + ApacheHttpClientConnectionManagerFactory connectionManagerFactory, + ApacheHttpClientFactory clientFactory) { + return new SimpleHostRoutingFilter(new ProxyRequestHelper(), zuulProperties, connectionManagerFactory, clientFactory); } } }