Efficient way of using apache HttpClient - performance

I am writing a multi-threaded REST client that uses different APIs for different purposes, For making these requests I am using an HttpClient with its different methods (GET,PUT,POST)
Thread 1:
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(url);
httpclient.execute(httppost);
methodThatNeedsHttpClient(httpclient);
public void methodThatNeedsHttpClient(HttpClient client) {
//perform other GET/POST/PUT requests
}
Thread 2:
DefaultHttpClient httpclient2 = new DefaultHttpClient();
HttpPost httppost = new HttpPost(url);
httpclient2.execute(httppost);
// Other methods
I read that for managing httpConnections I should using a Connection Manager. I am on version 4.5 of the client, which connection manager should I be using? How does the connection manager ensure connections dont leak and are used efficiently?
I tried the following implementation :
PoolingClientConnectionManager connectionManager = new PoolingClientConnectionManager();
connectionManager.setMaxTotal(5);
// Perform REST operations
client.getConnectionManager().shutdown();
But I am not sure how connections are managed in the pool , for a multithreaded system, does the connection manager be initialized in every thread?

In most cases, connection pool should be a shared one, accessible by all httpClient instances.
When creating httpClient,
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.setConnectionManagerShared(true)
.build();
And this will release the connection back to the pool,
EntityUtils.consume(httpResponse.getEntity());
While will close the connection,
httpResponse.close();
httpClient.close();
Since we have setConnectionManagerShared(true), httpClient.close() wouldn't shunt down the connection pool.

Related

Rest Template release Connection from Pool

I have rest template config similar to the following. I am trying to release a connection from the pool if I get a status code that does not equal 2XX (long story but need this code). Is here a way I can get the connection Manager and release a specific connection?
#Bean
public RestTemplate restTemplate() {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(100);
connectionManager.setDefaultMaxPerRoute(20);
RequestConfig requestConfig = RequestConfig
.custom()
.setConnectionRequestTimeout(5000) // timeout to get connection from pool
.setSocketTimeout(5000) // standard connection timeout
.setConnectTimeout(5000) // standard connection timeout
.build();
HttpClient httpClient = HttpClientBuilder.create()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(requestConfig).build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(requestFactory);
}
Looking for a way to accomplish something similar to the following
if(!httpStatusCode.substr(1).equals("2")) {
restTemplate.getConnectionPool().relase().thisConnection();
}
enter code here

Spring Webclient not able to create connections

I'm using Spring Webclient in a Spring Boot project to call a remote API
I've observed a strange behaviour with Webclient. Whenever the Remote API timeouts increase, Webclient goes into a state with very few active connections (less than 5, even though maxConnections is set in the config as 3200), and all the incoming connections get added to the Pending Queue, due to which after a while almost all requests are rejected with a PoolAcquirePendingLimitException exception.
The expected behaviour is that Webclient should create new connections (max upto 3200) to handle the incoming traffic
Webclient Config is as follows:
#Bean
public WebClient webClient(WebClient.Builder builder)
{
TcpClient tcpClient = TcpClient.create(getConnectionProvider())
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
.wiretap(true)
.doOnConnected(connection ->
connection.addHandlerLast(new ReadTimeoutHandler(10000, TimeUnit.MILLISECONDS)));
ClientHttpConnector connector = new ReactorClientHttpConnector(HttpClient.from(tcpClient));
return builder.uriBuilderFactory(initUriTemplateHandler())
.clientConnector(connector)
.build();
}
private ConnectionProvider getConnectionProvider()
{
return ConnectionProvider.builder("fixed")
.maxConnections(3200)
.pendingAcquireTimeout(Duration.ofMillis(10000))
.pendingAcquireMaxCount(10000)
.maxIdleTime(Duration.ofMinutes(10))
.metrics(true)
.build();
}
private DefaultUriBuilderFactory initUriTemplateHandler()
{
DefaultUriBuilderFactory uriFactory = new DefaultUriBuilderFactory();
uriFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE);
return uriFactory;
}
This is how I'm making the API calls
T response = webClient.get()
.uri(url)
.retrieve()
.bodyToMono(responseClass)
.timeout(Duration.ofMillis(requestTimeout)) // varies between 15-20ms
.block();
Below is a screenshot of the metrics[![enter image description here][2]][2]
This is a very high traffic application, and hence virtually it feels like the Pending Queue is stuck at 10000
Dependency Versions:
spring-boot-starter-webflux: 2.2.4.RELEASE
reactory-netty: 0.9.5.RELEASE

Is there a way to give dynamic timeouts to Rest Template?

I am using Spring Rest template along with apache's PoolingHttpClientConnectionManager for making API calls. The project in which I am working on requires setting custom timeout for each of the HTTP request I make via rest template. In order to achieve this, I am using CompletableFuture with a separate ExecutorService and calling get(Timeout) method.
try{
CompletableFuture<BidResponse> future = CompletableFuture.supplyAsync(() -> bidderService.getBid(), executorService);
bidResponse = future.get(bidderTimeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
bidResponse = getTimeoutBidResponse();
}
Unfortunately, the problem with this approach is that in cases of timeout, the underlying thread keeps on working until the rest template finishes its call. So I am kind of losing out a thread from the thread pool, as well as a connection from the HTTP connection pool.
Is there a way to close the HTTP connection as soon as we receive a Timeout exception, and return the HTTP connection back to the pool ?
p.s. I also tried using Spring Webclient with Mono.timeout. Turns out it actually closes the HTTP connection immediately, but does not return it back to the HTTP pool.
#Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder)
{
return restTemplateBuilder
.setConnectTimeout(...)
.setReadTimeout(...)
.build();
}

Handling multiple RestTemplate instances for multiple hosts

I am building out a web service which proxies and does some slight manipulation of HTTP requests. I'm handling requests going to multiple hosts of the same type but of which I don't know of until run time (I consume a web service that gives the host IPs). Each host that I interact with has different credentials (Basic-Auth, fetched from a non-local database, credentials change periodically). The way I handle things today is pretty naive. For every request, I am constructing a new RestTemplate like so:
public static RestOperations getRestOperations(int timeout, String username, String password)
{
RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(timeout).setConnectTimeout(timeout).setSocketTimeout(timeout).build();
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
CloseableHttpClient httpclient = HttpClients.custom()
.setDefaultCredentialsProvider(credsProvider)
.setDefaultRequestConfig(requestConfig)
.build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
httpclient);
return new RestTemplate(requestFactory);
}
So each Controller method always starts out with:
UsernamePassword userPass = credentialService.getCredentials(request.getRemoteHost())
RestOperations restOps = getRestOperations(userPass.getUser(), userPass.getPass(), TIMEOUT_IN_MILLIS);
It seems to me that since I'm constructing a new RestTemplate with each request that any previous connections that have been made between my server and the host are not being reused.
Is this the case? If so, then it seems I will need some sort of RestTemplateFactory which can cache RestTemplate instances based on the host IP address so that connections can be reused. However if I do that, then I need some mechanism that makes sure that the credentials haven't changed and to check and update credentials if they do change. Is there a better solution?

How to use Spring WebSocketClient with SSL?

I connect with my websocket client to non-SSL endpoint without any problem. But I cannot find any way how to connect to wss (SSL) endpoint. Where can I define the SSL factory etc. No object seem to have related set method.
WebSocketClient transport = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(transport);
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
String url = cfg.getWebsocketEndpoint();
StompSessionHandler handler = new MySessionHandler();
WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
stompClient.connect(url, handler);
I am using wss:// url and on the other side I have a server with self-signed certificate. However, this code does not throw any exception while connecting, but the session is not established.
EDIT: After enabling tracing for web.* I got a standard error, with
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
It occurs when connecting to server with self-signed certificate. However, for RestTemplate I already updated SSLContext with this code and REST calls are fine now, but I do not know why, StandardWebSocketClient is IGNORING the SSLContext. Why?
String keystoreType = "JKS";
InputStream keystoreLocation = new FileInputStream("src/main/resources/aaa.jks");
char [] keystorePassword = "zzz".toCharArray();
char [] keyPassword = "zzz".toCharArray();
KeyStore keystore = KeyStore.getInstance(keystoreType);
keystore.load(keystoreLocation, keystorePassword);
KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmfactory.init(keystore, keyPassword);
InputStream truststoreLocation = new FileInputStream("src/main/resources/aaa.jks");
char [] truststorePassword = "zzz".toCharArray();
String truststoreType = "JKS";
KeyStore truststore = KeyStore.getInstance(truststoreType);
truststore.load(truststoreLocation, truststorePassword);
TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmfactory.init(truststore);
KeyManager[] keymanagers = kmfactory.getKeyManagers();
TrustManager[] trustmanagers = tmfactory.getTrustManagers();
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keymanagers, trustmanagers, new SecureRandom());
SSLContext.setDefault(sslContext);
UPDATE: Unfortunately, I did not managed to do this with custom truststore. I installed the certificate with InstallCert.java.
I think that each websocket container implementation provides ways to do this.
You have to set this configuration using the StandardWebSocketClient .setUserProperties. All those properties are internally set in the ClientEndpointConfig used by the client.
Here's an example with Tomcat as a provider:
StandardWebSocketClient wsClient = //...;
SSLContext sslContext = //...;
wsClient.setUserProperties(WsWebSocketContainer.SSL_CONTEXT_PROPERTY, sslContext);
In any case you should refer to your provider reference documentation to know which configuration keys you should use.
I've faced a similar issue, where I need to use a different ssl context instead of the default one and I could not use the Tomcat provider version solution. I've met a lot of resources and examples about this solution which does not fit my case, and for sure I landed here too on this question.
When you need to use a specific/custom SSLContext to set and you don't need the 'tomcat version', you can go for the Jetty version because it allows you to set which trust store and/or key store you want to use. In this case my Spring application was on the 'client' side and not the server one, but Jetty SslContextFactory provides the same functionalities for the 'Server' case.
The short example code below is for the client side, but it shares methods and signatures with the server side (check the jetty documentation about)
final SslContextFactory.Client factory = new SslContextFactory.Client();
factory.setSslContext(sslContext); // a different loaded java SslContext instead of the default one
//Create the web socket client with jetty client factory
final JettyWebSocketClient client =
new JettyWebSocketClient(new WebSocketClient(new HttpClient(factory)));
client.start();
final WebSocketStompClient stomp = new WebSocketStompClient(client);
There're also method for setting the trust store or key store instead a fully loaded SslContext.

Resources