0

I have a Spring Boot application that is creating a request to an external system. The external system is responding after some time, 3-4 minutes. I would like to keep the connection open until i receive an response from the remote API. I tried using webflux, i tried setup the connection timeout for my application in application.yml file. I could make the application to wait for a response more than 2 minutes. Some code that i have tried

@Configuration
public class RestConfigurations {
@Bean(name = "restTemplate")
public RestTemplate getRestTemplate(RestTemplateBuilder restTemplateBuilder) {
    RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());

    // Error handler
    restTemplate.setErrorHandler(new CustomRestTemplateErrorHandler());

    // Media converters
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    converter.setSupportedMediaTypes(Arrays.asList(
            MediaType.APPLICATION_JSON,
            MediaType.TEXT_PLAIN,
            MediaType.TEXT_HTML));
    restTemplate.getMessageConverters().add(converter);

    return restTemplate;
}

private ClientHttpRequestFactory getClientHttpRequestFactory() {
    int connectionTimeout = 15*60000; // milliseconds
    int socketTimeout = 15*60000; // milliseconds
    RequestConfig config = RequestConfig.custom()
            .setConnectTimeout(connectionTimeout)
            .setConnectionRequestTimeout(connectionTimeout)
            .setSocketTimeout(socketTimeout)
            .build();
    CloseableHttpClient client = HttpClientBuilder
            .create()
            .setDefaultRequestConfig(config)
            .setKeepAliveStrategy(connectionKeepAliveStrategy())
            .build();
    return new HttpComponentsClientHttpRequestFactory(client);
}

public HttpComponentsClientHttpRequestFactory getHttpClientFactory() {
    HttpComponentsClientHttpRequestFactory httpClientFactory = new 
   HttpComponentsClientHttpRequestFactory(
            HttpClients.createDefault()
    );
    httpClientFactory.setConnectTimeout(15 * 600000);
    httpClientFactory.setReadTimeout(15 * 600000);
    httpClientFactory.setConnectionRequestTimeout(15 * 600000);
    return httpClientFactory;
}

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);
}


private 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) * 999999999;
                } catch (NumberFormatException exception) {
                    exception.printStackTrace();
                }
            }
        }

        return 15 * 60000;
    } ;
}

@Bean(name = "webClient")
public WebClient webClient() {
    TcpClient tcpClient = TcpClient
            .create()
            .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 60000 * 15)//15 minutes
            .doOnConnected(connection -> {
                connection.addHandlerLast(new ReadTimeoutHandler(15, TimeUnit.MINUTES));
                connection.addHandlerLast(new WriteTimeoutHandler(15, TimeUnit.MINUTES));
            });

    return WebClient.builder()
            .clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient).wiretap(true)))
            .build();
}

None of this configs worked. I tried using okhttpclient like this.

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.MINUTES);
    builder.readTimeout(20, TimeUnit.MINUTES);
    builder.writeTimeout(20, TimeUnit.MINUTES);
    builder.retryOnConnectionFailure(false);
    return builder;
   }
 }

@Bean
@Qualifier("OKSpringCommonsRestTemplate")
public ClientHttpRequestFactory createOKCommonsRequestFactory() {
    OkHttpClientFactoryImpl httpClientFactory = new OkHttpClientFactoryImpl();
    OkHttpClient client = httpClientFactory.createBuilder(false).build();
    return new OkHttp3ClientHttpRequestFactory(client);
}

It did not work. I don't know what could cause the connection to close other than the things i've setup.

After receiving an answer, i tried :

HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .timeout(Duration.ofMinutes(10))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(json))
            .build();

    HttpClient client= HttpClient.newHttpClient();
    try {
        client.send(request, HttpResponse.BodyHandlers.ofString());
    } catch (IOException | InterruptedException e) {
        e.printStackTrace();
    }

The result is the same. After 2 minutes i get an error saying java.io.IOException: HTTP/1.1 header parser received no bytes

user978123
  • 343
  • 4
  • 12

2 Answers2

0

I found the problem. It was in fact because of the server on the other end of the call. Express server is closing the connection, by default, after 2 minutes.

user978123
  • 343
  • 4
  • 12
0

Increase Client Idle Timeout at server/LB to a higher number.

Manu7
  • 1
  • 1