1

I have a problem where a application keeps blocking indefinitely on a post call made with a RestTemplate from Spring Boot.

ResponseEntity<String> response = restTemplate.postForEntity(destination.getUri(), request, String.class);

We use the default standard JDK implementation and create it like this:

    this.restTemplate = restTemplateBuilder
                       .setConnectTimeout(5000)
                       .setReadTimeout(5000)
                       .build();

Which sets the connection and read timeout to 5 seconds. But it seems this is not an absolute value, as soon as our application receives some bytes this read timeout resets and this causes our application to wait indefinitely.

I rather have an absolute read timeout where if you don't get the end response in less than 5 seconds the template throws an TimeoutException.

I couldn't find something like this in the options for the default client?

---EDIT---

I tried out @Peekay answer but it doesn't seem to work:

    CloseableHttpClient httpClient = HttpClientBuilder.create()
           .setConnectionTimeToLive(1, TimeUnit.SECONDS)
           .setConnectionManager(new PoolingHttpClientConnectionManager())
           .build();

   HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
   clientHttpRequestFactory.setHttpClient(httpClient);
   return new RestTemplate(clientHttpRequestFactory);

I have also tried different implementations of the client's RestTemplate e.g. HttpComponentsClientHttp, Netty4Client and OkHttp3Client created them like so:

    Netty4ClientHttpRequestFactory factory = new Netty4ClientHttpRequestFactory();
    factory.setConnectTimeout(timeout);
    factory.setReadTimeout(readTimeout);
    return new RestTemplate(factory);

And tested them on a response that took longer than 5 seconds to respond. All of them except for Netty, which returned a ReadTimeoutException, had returned a 200 success. Unfortunately I cannot switch to that client, it seems you need to implement it yourself if you want to keep using the default client.

bramdc
  • 580
  • 1
  • 5
  • 21

4 Answers4

2

You are right, you cannot setup absolute value and you have to interrupt the tread itself.

AlexGera
  • 756
  • 9
  • 19
1

You can use alternate http clients with RestTemplate, such as the Apache HttpClient which gives you more control over how the connections are setup, pooled, and maintained:

  • From its HttpClientBuilder you can set a Connection Time-to-Live which is the max TTL for the connection
  • You can define a RequestConfig specifying a connect timeout (max time to wait for a connection to be established) and a separate socket timeout (max time a read() will wait for data).

For more details see: setConnectTimeout vs. setConnectionTimeToLive vs. setSocketTimeout()

peekay
  • 1,885
  • 13
  • 11
1

The way we fixed this was by wrapping the RestTemplate REST call in a CompletableFuture and use the timeout functionality from that wrapper to kill the thread if it takes too long.

Here is an example:

CompletableFuture<T> requestWrapper = CompletableFuture.supplyAsync(() -> {
  return restTemplate.postForEntity(/* Whatever arguments you need to pass */);
});

try {
  return requestWrapper.get(5000, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
  requestWrapper.cancel(true);
  throw new TimeoutException("Endpoint took too long to respond, TimeoutException is triggered");
} catch (ExecutionException e) {
  throw e.getCause();
}
Titulum
  • 9,928
  • 11
  • 41
  • 79
1
-Dsun.net.client.defaultConnectTimeout=<TimeoutInMiliSec>
-Dsun.net.client.defaultReadTimeout=<TimeoutInMiliSec>

https://howtodoinjava.com/spring-boot2/resttemplate/resttemplate-timeout-example/

zhuguowei
  • 8,401
  • 16
  • 70
  • 106