Skip to content

Instantly share code, notes, and snippets.

@jkuipers
Last active November 27, 2023 18:44
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save jkuipers/cd462d163c2c1c81f34092de12f7bab2 to your computer and use it in GitHub Desktop.
Save jkuipers/cd462d163c2c1c81f34092de12f7bab2 to your computer and use it in GitHub Desktop.
Spring Boot auto-configuration example for an Apache Components HTTP client and its usage in all RestTemplates created by the RestTemplateBuilder, plus trace logging support
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.InterceptingClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.client.RestTemplate;
import java.lang.reflect.Field;
/**
* Ensures that every {@link org.springframework.web.client.RestTemplate} built with a
* {@link org.springframework.boot.web.client.RestTemplateBuilder} uses an Apache Components
* HTTP client using a pooling connection pool with the configured number of maximum connections
* (default is way too small) and time-out settings.
*/
@Configuration
@EnableConfigurationProperties(HttpClientProperties.class)
public class HttpClientAutoConfiguration {
@Autowired HttpClientProperties client;
@Bean
@ConditionalOnMissingBean
CloseableHttpClient httpClient(HttpClientConnectionManager connectionManager) {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(client.getConnectionTimeoutInMillis())
.setSocketTimeout(client.getReadTimeoutInMillis())
.setCookieSpec(CookieSpecs.STANDARD)
.build();
return HttpClients.custom()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(requestConfig)
.build();
}
@Bean(destroyMethod = "") // will be closed by httpClient
@ConditionalOnMissingBean
HttpClientConnectionManager httpClientConnectionManager() {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
// since most services only connect to a single backend
// we use the same value for max per-route and total
connectionManager.setDefaultMaxPerRoute(client.getMaxConnections());
connectionManager.setMaxTotal(client.getMaxConnections());
return connectionManager;
}
@Bean
RestTemplateCustomizer httpClientCustomizer(HttpClient httpClient) {
return restTemplate -> {
ClientHttpRequestFactory requestFactory = restTemplate.getRequestFactory();
// not-so-proudly copied from the TestRestTemplate code:
if (requestFactory instanceof InterceptingClientHttpRequestFactory) {
Field requestFactoryField = ReflectionUtils.findField(RestTemplate.class, "requestFactory");
ReflectionUtils.makeAccessible(requestFactoryField);
requestFactory = (ClientHttpRequestFactory) ReflectionUtils.getField(requestFactoryField, restTemplate);
}
if (requestFactory instanceof HttpComponentsClientHttpRequestFactory) {
HttpComponentsClientHttpRequestFactory clientRequestFactory =
(HttpComponentsClientHttpRequestFactory) requestFactory;
clientRequestFactory.setHttpClient(httpClient);
if (client.isLoggingEnabled()) {
restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(requestFactory));
restTemplate.getInterceptors().add(new LoggingClientHttpRequestInterceptor());
}
}
};
}
@Bean
RestTemplateCustomizer unwantedConvertersRemovingCustomizer() {
return restTemplate -> {
boolean foundFirstJsonConverter = false;
for (var iter = restTemplate.getMessageConverters().listIterator(); iter.hasNext(); ) {
HttpMessageConverter<?> converter = iter.next();
if (converter instanceof MappingJackson2HttpMessageConverter) {
if (foundFirstJsonConverter) {
iter.remove();
} else {
foundFirstJsonConverter = true;
}
}
}
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment