spring-bootapache-httpclient-4.xjava-17spring-boot-3apache-httpclient-5.x

I received an error org.apache.hc.core5.http.NoHttpResponseException: server failed to respond when i call API via quotaguard proxy in SpringBoot 3


We are using QuotaGuard proxy to connect with external API.
Recently we did Spring Boot version update.
Technical configuration of our application.
Before Spring Boot version update.
Java 8
Spring Boot 2.7
Htttclient 4.5
After Spring Boot version update.
Java 17
Spring Boot 3.0
Htttclient5 5.2.1
However after migration we cannot connect to the API. We get the following error message.

Nov 29 02:57:08  app/web.1 org.springframework.web.client.ResourceAccessException: I/O error on POST request for "[https://test.com/postInfo]": [test.com:443]  failed to respond
Nov 29 02:57:08  app/web.1  at org.springframework.web.client.RestTemplate.createResourceAccessException(RestTemplate.java:888)
Nov 29 02:57:08  app/web.1  at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:868)
Nov 29 02:57:08  app/web.1  at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:764)
Nov 29 02:57:08  app/web.1  at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:512)
Nov 29 02:57:08  app/web.1  at jp.co.demo.service.WinnerExportServiceImpl$3.doWithRetry(WinnerExportServiceImpl.java:381)
Nov 29 02:57:08  app/web.1  at jp.co.demo.service.WinnerExportServiceImpl$3.doWithRetry(WinnerExportServiceImpl.java:376)
Nov 29 02:57:08  app/web.1  at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:286)
Nov 29 02:57:08  app/web.1  at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:179)
Nov 29 02:57:08  app/web.1  at jp.co.demo.service.WinnerExportServiceImpl.execute(WinnerExportServiceImpl.java:376)
Nov 29 02:57:08  app/web.1  at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Nov 29 02:57:08  app/web.1  at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
Nov 29 02:57:08  app/web.1  at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
Nov 29 02:57:08  app/web.1  at java.base/java.lang.reflect.Method.invoke(Method.java:568)
Nov 29 02:57:08  app/web.1  at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
Nov 29 02:57:08  app/web.1  at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
Nov 29 02:57:08  app/web.1  at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
Nov 29 02:57:08  app/web.1  at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115)
Nov 29 02:57:08  app/web.1  at org.springframework.util.concurrent.FutureUtils.lambda$toSupplier$0(FutureUtils.java:74)
Nov 29 02:57:08  app/web.1  at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1768)
Nov 29 02:57:08  app/web.1  at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
Nov 29 02:57:08  app/web.1  at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
Nov 29 02:57:08  app/web.1  at java.base/java.lang.Thread.run(Thread.java:840)
Nov 29 02:57:08  app/web.1 Caused by: org.apache.hc.core5.http.NoHttpResponseException: [test.com:443] failed to respond
Nov 29 02:57:08  app/web.1  at org.apache.hc.core5.http.impl.io.DefaultHttpResponseParser.createConnectionClosedException(DefaultHttpResponseParser.java:87)
Nov 29 02:57:08  app/web.1  at org.apache.hc.core5.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:243)
Nov 29 02:57:08  app/web.1  at org.apache.hc.core5.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:53)
Nov 29 02:57:08  app/web.1  at org.apache.hc.core5.http.impl.io.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:298)
Nov 29 02:57:08  app/web.1  at org.apache.hc.core5.http.impl.io.HttpRequestExecutor.execute(HttpRequestExecutor.java:175)
Nov 29 02:57:08  app/web.1  at org.apache.hc.core5.http.impl.io.HttpRequestExecutor.execute(HttpRequestExecutor.java:218)
Nov 29 02:57:08  app/web.1  at org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager$InternalConnectionEndpoint.execute(PoolingHttpClientConnectionManager.java:712)
Nov 29 02:57:08  app/web.1  at org.apache.hc.client5.http.impl.classic.InternalExecRuntime.execute(InternalExecRuntime.java:216)
Nov 29 02:57:08  app/web.1  at org.apache.hc.client5.http.impl.classic.ConnectExec.createTunnelToTarget(ConnectExec.java:233)
Nov 29 02:57:08  app/web.1  at org.apache.hc.client5.http.impl.classic.ConnectExec.execute(ConnectExec.java:151)
Nov 29 02:57:08  app/web.1  at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
Nov 29 02:57:08  app/web.1  at org.apache.hc.client5.http.impl.classic.ProtocolExec.execute(ProtocolExec.java:192)
Nov 29 02:57:08  app/web.1  at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
Nov 29 02:57:08  app/web.1  at org.apache.hc.client5.http.impl.classic.HttpRequestRetryExec.execute(HttpRequestRetryExec.java:96)
Nov 29 02:57:08  app/web.1  at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
Nov 29 02:57:08  app/web.1  at org.apache.hc.client5.http.impl.classic.ContentCompressionExec.execute(ContentCompressionExec.java:152)
Nov 29 02:57:08  app/web.1  at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
Nov 29 02:57:08  app/web.1  at org.apache.hc.client5.http.impl.classic.RedirectExec.execute(RedirectExec.java:115)
Nov 29 02:57:08  app/web.1  at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
Nov 29 02:57:08  app/web.1  at org.apache.hc.client5.http.impl.classic.InternalHttpClient.doExecute(InternalHttpClient.java:170)
Nov 29 02:57:08  app/web.1  at org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:106)
Nov 29 02:57:08  app/web.1  at org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:55)
Nov 29 02:57:08  app/web.1  at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal(HttpComponentsClientHttpRequest.java:93)
Nov 29 02:57:08  app/web.1  at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
Nov 29 02:57:08  app/web.1  at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:66)
Nov 29 02:57:08  app/web.1  at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:862)
Nov 29 02:57:08  app/web.1  ... 20 more

Here are the sample code we are using,

URL proxy;
proxy = new URL(proxyUrl);
String userInfo = proxy.getUserInfo();
String user = userInfo.substring(0, userInfo.indexOf(':'));
String password = userInfo.substring(userInfo.indexOf(':') + 1);

final CredentialsProvider credsProvider = new BasicCredentialsProvider();
Credentials credentials = new UsernamePasswordCredentials(user, password.toCharArray());
((BasicCredentialsProvider) credsProvider).setCredentials(new AuthScope(proxy.getHost(), proxy.getPort()), credentials);

HttpHost p = new HttpHost(proxy.getHost(), proxy.getPort());
HttpClient httpClient = HttpClients.custom().setProxy(p).setDefaultCredentialsProvider(credsProvider).build();
final HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setHttpClient(httpClient);

RestTemplate restTemplate = new RestTemplate(factory);
ResponseEntity<ResponseData> response = restTemplate.postForEntity(APIURL, req, ResponseData.class);
ResponseData responseBody = response.getBody();

However if we request API using quotaguard proxy via CURL on the command line, it passes successfully.
Also if we request API using Postman, it passes successfully.
Did anyone faced problem to connect API using proxyserver recently after updating SpringBoot 3.0 and Java 17 version?
Can anyone give any guideline how to approach or fix this problem? We have tried several method and example code none of them worked.


Solution

  • So far i found two solutions. Both are working for my scenario.

    Solution 1: Telling the system to not keep alive the connection by setting setConnectionReuseStrategy.

    final CloseableHttpClient httpClient = Httpclients.custom()
            .setProxy(p)
            .setDefaultCredentialsProvider(credsProvider)
            .setConnectionReuseStrategy(new ConnectionReuseStrategy(){
                @Override
                public boolean keepAlive(HttpRequest request, HttpResponse response, HttpContext context){
                    return false;
                }
            }).build();
        
    

    Solution 2: Implementing CustomRetryStrategy which implements RetryStrategy. Then Set CustomRetryStrategy in setRetryStrategy.

    final CloseableHttpClient httpClient = Httpclients.custom()
            .setProxy(p)
            .setDefaultCredentialsProvider(credsProvider)
            .setRetryStrategy(new CustomRetryStrategy())
            }).build();     
            
    private static class CustomRetryStrategy implements RetryStrategy {
        @Override
        public boolean retryRequest(HttpRequest request, IOException exception, int execCount, HttpContext context) {
            //Set execCount according to business requirement.
            return execCount <= 3 ;
        }
    
        @Override
        public boolean retryRequest(HttpResponse response, int execCount, HttpContext context) {
            //Set execCount according to business requirement.
            return execCount <= 3 ;
        }
    
    
        @Override
        public TimeValue getRetryInterval(HttpResponse response, int execCount, RetryExec exec) {
            return TimeValue.ofSeconds(3);
        }
    }