Description
Problem
When ClientHttpRequestFactory
isn't explicitly set, RestTemplateBuilder
invokes detectRequestFactory()
internally for every new RestTemplate. So every RestTemplate gets it's own 'prototype' ClientHttpRequestFactory
.
On the other hand, when ClientHttpRequestFactory
is explicitly set in RestTemplateBuilder, all subsequent build RestTemplates get the same 'singleton' ClientHttpRequestFactory
.
During ResttemplateBuilder.build()
method, the current ClientHttpRequestFactory
becomes customized with timout values (connection-timeout and read-timeout). This leads to problems when multiple RestTemplates with different timeout values are created.
We have a Spring-Boot application that provides a RestTemplateBuilder
bean which is already provided with a HttpComponentsHttpRequestFactory
. Two service beans uses the RestTemplateBuilder
to create a custom RestTemplate with different connection-timouts:
public class TestResttemplateBuilderTimeouts {
private static Logger logger = LoggerFactory.getLogger(TestResttemplateBuilderTimeouts.class);
@Test
public void test1(){
// globally prepared ResttemplateBuilder:
HttpComponentsClientHttpRequestFactory f = new HttpComponentsClientHttpRequestFactory();
RestTemplateBuilder builder = new RestTemplateBuilder().requestFactory(f);
// in service bean 1:
RestTemplate rt1 = builder.setConnectTimeout(10000).build();
// in service bean 2
RestTemplate rt2 = builder.setConnectTimeout(1000).build();
// service bean 1 uses RestTemplate expecting connectionTime 10 Secs.
// but Connection-Timeout is only 1 Sec.
try {
logger.debug("start ...");
String s = rt1.getForObject("http://www.google.com:81", String.class);
}finally {
logger.debug("stop ...");
}
}
}
Workaround
Our current Workaround: we do not set HttpComponentsHttpRequestFactory
explicitly but let RestTemplateBuilder
detect this factory. For customizing (i.e. setting max-connections und max-connections-per-route) we use the systemproperties approach of the httpcomponents httpclient.
Possible Solution (breaking the api in spring-web)
As far as I can see, all supported http implementations (httpcommons, netty, okhttp, okhttp3, SimpleClientHttpRequestFactory) allow a per request setting of connection- and read-timeouts. So a solution would be not to inject these timeouts into the ClientHttpRequestFactory
but hold it as 'RequestSettings' in the RestTemplateBuilder
and give them to every new created RestTemplate.
The RestTemplate in turn has to provide these settings as additional method-parameter to ClientHttpRequestFactory.createRequest()
. This will however break the api of ClientHttpRequestFactory...
public interface ClientHttpRequestFactory {
/**
* Create a new {@link ClientHttpRequest} for the specified URI and HTTP method.
* <p>The returned request can be written to, and then executed by calling
* {@link ClientHttpRequest#execute()}.
* @param uri the URI to create a request for
* @param httpMethod the HTTP method to execute
* @param requestSettings Request-Settings, containing for instance connection- and read-timeouts
* @return the created request
* @throws IOException in case of I/O errors
*/
ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, RequestSettings requestSettings) throws IOException;