Changing HttpClient in Spring RestTemplate

If you’re a Spring boot user, you might have definitely used RestTemplate. If you read the official documentation carefully, you might read that RestTemplate will be deprecated in the future and we must use WebClient which offers Synchronous, Asynchronous and Streaming scenarios such as Server-Sent Events, WebSockets, etc. Majority of the applications in production uses RestTemplates and will be practically a long way before it is completely replaced with Reactive WebFlux. It is important to know how we can customize the RestTemplate changing different Http clients.

The default HttpClient used in the RestTemplate is provided by the JDK. It is developed on top of the HttpURLConnection. There is a new module added in Java 9 in incubation status and standardized in Java 11 called java.net.http.HttpClient. We can use this to make a client connection as well without needing third-party libraries. It is still unclear whether this will be used in Spring clients. Let’s get back to the business. In Spring, the default HTTP client can be changed to Apache’s HttpClient or Square’s OkHttpClient. We can configure the RestTemplate to use the HttpClient of our choice. We can do this either directly or by using Spring Cloud Commons org.springframework.cloud.commons.httpclient which provides ApacheHttpClientFactory and OkHttpClientFactory. Both are solid Http Client implementations used by many projects/libraries with a great community.

In order to configure the RestTemplate, we need to set up user-provided request factory instead of the default. The request factory can be created using the ClientHttpRequestFactory in which we can use the HTTP Client of our choice Apache or OkHttp. Let’s take a look at the Apache Http Client first.

Apache HttpClient can be created using two ways. One is using a HttpClient directly another way using the ApacheHttpClientFactory.

  @Bean
  @Qualifier("apacheRestTemplate")
  public ClientHttpRequestFactory createRequestFactory(){
    PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
    connectionManager.setMaxTotal(400);
    connectionManager.setDefaultMaxPerRoute(200);
    RequestConfig requestConfig = RequestConfig
        .custom()
        .setConnectionRequestTimeout(5000)
        .setSocketTimeout(10000)
        .build();
    CloseableHttpClient httpClient = HttpClients
        .custom()
        .setConnectionManager(connectionManager)
        .setKeepAliveStrategy(connectionKeepAliveStrategy)
        .setDefaultRequestConfig(requestConfig)
        .build();
    return new HttpComponentsClientHttpRequestFactory(httpClient);
  }
  @Bean
  @Qualifier("apacheSpringCommonsRestTemplate")
  public ClientHttpRequestFactory createCommonsRequestFactory() {
    ApacheHttpClientFactoryImpl httpClientFactory = new ApacheHttpClientFactoryImpl();
    HttpClient client = httpClientFactory.createBuilder().build();
    return new HttpComponentsClientHttpRequestFactory(client);
  }

It is straightforward code and you get more control of the strategies that you want to use. For example, the connection keep-alive strategies, idle connection monitor, timeouts and you can even allocate max connections per route, etc. Usually, the Keep-Alive is provided in the nonstandard Http Header like this Keep-Alive: timeout=5, max=1000. (P.S. In HTTP/2 the Keep-Alive headers are ignored. Reference: https://http2.github.io/http2-spec/#rfc.section.8.1.2.2)

@Configuration
public class KeepAliveConfig {
  @Bean ConnectionKeepAliveStrategy connectionKeepAliveStrategy(){
    return (response, context) -> {
      HeaderElementIterator it = new BasicHeaderElementIterator(
          response.headerIterator(HTTP.CONN_KEEP_ALIVE));
      while (it.hasNext()){
        HeaderElement he = it.nextElement();
        String param = he.getName();
        String value = he.getValue();
        if (value != null && param.equalsIgnoreCase("timeout")) {
          try {
            return Long.parseLong(value) * 1000;
          } catch(NumberFormatException exception) {
            exception.printStackTrace();
          }
        }
      }
      // If there is no Keep-Alive header. Keep the connection for 30 seconds
      return 30*1000;
    };
  }
}

In the Spring Cloud Commons, the ApacheHttpClientFactory provides DSL to create a HttpClient using the Builder. We are reusing the same the ConnectionKeepAliveStrategy here as well for the demonstration purposes.

public class ApacheHttpClientFactoryImpl implements ApacheHttpClientFactory {
  @Autowired
  private ConnectionKeepAliveStrategy connectionKeepAliveStrategy;
  @Override public HttpClientBuilder createBuilder() {
    RequestConfig requestConfig = RequestConfig
        .custom()
        .setConnectionRequestTimeout(20000)
        .setSocketTimeout(30000)
        .build();
    return HttpClientBuilder
        .create()
        .setMaxConnTotal(400)
        .setMaxConnPerRoute(200)
        .setKeepAliveStrategy(connectionKeepAliveStrategy)
        .evictIdleConnections(30, TimeUnit.SECONDS)
        .setDefaultRequestConfig(requestConfig);
  }
}

We have the Apache HTTP Client ready now, what about the OkHttpClient? Spring Cloud Commons has an interface OkHttpClientFactory and provide an implementation if we do not want to use the DefaultOkHttpClientFactory provided.

public class OkHttpClientFactoryImpl implements OkHttpClientFactory {
  @Override public OkHttpClient.Builder createBuilder(boolean disableSslValidation) {
    OkHttpClient.Builder builder = new OkHttpClient.Builder();
    ConnectionPool okHttpConnectionPool = new ConnectionPool(50, 30, TimeUnit.SECONDS);
    builder.connectionPool(okHttpConnectionPool);
    builder.connectTimeout(20, TimeUnit.SECONDS);
    builder.retryOnConnectionFailure(false);
    return builder;
  }
}

Let us see how we can supply these HTTP client factories to the RestTemplate. Here comes another nice feature of that you may already know, Qualifiers. We will be using different qualifiers to create different RestTemplate beans and use it.

@Bean
  @Qualifier("apacheRestTemplate")
  public RestTemplate createCustomRestTemplate(){
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setRequestFactory(customRequestFactory);
    return restTemplate;
  }

@Bean
  @Qualifier("apacheSpringCommonsRestTemplate")
  public RestTemplate createApacheCustomRestTemplate() {
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setRequestFactory(apacheHttpRequestFactory);
    return restTemplate;
  }

@Bean
  @Qualifier("OKSpringCommonsRestTemplate")
  public RestTemplate createOKCustomRestTemplate() {
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setRequestFactory(okHttpRequestFactory);
    return restTemplate;
  }

There are a couple of other things to keep in mind. For example, we can change the max threads and the max minimum spare threads in used by Tomcat

server.tomcat.max-threads=400
server.tomcat.min-spare-threads=50

Minimum spare threads will help make sure the number of the minimum threads available when the connector is started and during the idle time.

As always the code is available in github with a Bonus benchmark results.

Leave a Reply

Your email address will not be published. Required fields are marked *