Skip to content

Commit

Permalink
Centralizing HTTP client creation and configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanjbaxter committed Jun 28, 2017
1 parent e2954d0 commit 2aa93ab
Show file tree
Hide file tree
Showing 19 changed files with 705 additions and 162 deletions.
Expand Up @@ -18,14 +18,24 @@


import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Timer;
import java.util.TimerTask;


import org.apache.http.client.HttpClient; 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.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 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.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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;


Expand All @@ -34,12 +44,17 @@
import feign.httpclient.ApacheHttpClient; import feign.httpclient.ApacheHttpClient;
import feign.okhttp.OkHttpClient; import feign.okhttp.OkHttpClient;


import javax.annotation.PreDestroy;
import com.netflix.client.config.CommonClientConfigKey;
import com.netflix.client.config.DefaultClientConfigImpl;

/** /**
* @author Spencer Gibb * @author Spencer Gibb
* @author Julien Roy * @author Julien Roy
*/ */
@Configuration @Configuration
@ConditionalOnClass(Feign.class) @ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({FeignHttpClientProperties.class})
public class FeignAutoConfiguration { public class FeignAutoConfiguration {


@Autowired(required = false) @Autowired(required = false)
Expand Down Expand Up @@ -86,17 +101,51 @@ public Targeter feignTargeter() {
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer") @ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true) @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
protected static class HttpClientFeignConfiguration { protected static class HttpClientFeignConfiguration {
private final Timer connectionManagerTimer = new Timer(
"FeignApacheHttpClientConfiguration.connectionManagerTimer", true);


@Autowired(required = false) @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 @Bean
@ConditionalOnMissingBean(Client.class) @ConditionalOnMissingBean(Client.class)
public Client feignClient() { public Client feignClient(HttpClient httpClient) {
if (this.httpClient != null) { return new ApacheHttpClient(httpClient);
return new ApacheHttpClient(this.httpClient); }
}
return new ApacheHttpClient(); @PreDestroy
public void destroy() throws Exception {
connectionManagerTimer.cancel();
httpClient.close();
} }
} }


Expand Down
Expand Up @@ -17,14 +17,22 @@
package org.springframework.cloud.netflix.feign.ribbon; package org.springframework.cloud.netflix.feign.ribbon;


import org.apache.http.client.HttpClient; 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.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 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.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.FeignAutoConfiguration;
import org.springframework.cloud.netflix.feign.support.FeignHttpClientProperties;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
Expand All @@ -38,6 +46,10 @@
import feign.httpclient.ApacheHttpClient; import feign.httpclient.ApacheHttpClient;
import feign.okhttp.OkHttpClient; 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 * Autoconfiguration to be activated if Feign is in use and needs to be use Ribbon as a
* load balancer. * load balancer.
Expand All @@ -47,6 +59,7 @@
@ConditionalOnClass({ ILoadBalancer.class, Feign.class }) @ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration @Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class) @AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({FeignHttpClientProperties.class})
public class FeignRibbonClientAutoConfiguration { public class FeignRibbonClientAutoConfiguration {


@Bean @Bean
Expand Down Expand Up @@ -84,22 +97,54 @@ public Request.Options feignRequestOptions() {
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true) @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
protected static class HttpClientFeignLoadBalancedConfiguration { protected static class HttpClientFeignLoadBalancedConfiguration {


private final Timer connectionManagerTimer = new Timer(
"FeignApacheHttpClientConfiguration.connectionManagerTimer", true);

private CloseableHttpClient httpClient;

@Autowired(required = false) @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 @Bean
@ConditionalOnMissingBean(Client.class) @ConditionalOnMissingBean(Client.class)
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) { SpringClientFactory clientFactory, HttpClient httpClient) {
ApacheHttpClient delegate; ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
if (this.httpClient != null) {
delegate = new ApacheHttpClient(this.httpClient);
}
else {
delegate = new ApacheHttpClient();
}
return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory); return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
} }

@PreDestroy
public void destroy() throws Exception {
connectionManagerTimer.cancel();
httpClient.close();
}
} }


@Configuration @Configuration
Expand Down
@@ -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;
}
}

0 comments on commit 2aa93ab

Please sign in to comment.