diff --git a/httpClients/boot-rest-template/pom.xml b/httpClients/boot-rest-template/pom.xml
index 98b3b195d..2ee8f75f3 100644
--- a/httpClients/boot-rest-template/pom.xml
+++ b/httpClients/boot-rest-template/pom.xml
@@ -50,6 +50,11 @@
org.springframework.boot
spring-boot-starter-web
+
+ org.apache.httpcomponents.client5
+ httpclient5
+
+
org.springframework.boot
spring-boot-devtools
@@ -66,6 +71,7 @@
lombok
true
+
org.springframework.boot
spring-boot-starter-data-jpa
@@ -99,11 +105,6 @@
spring-boot-testcontainers
test
-
- org.awaitility
- awaitility
- test
-
org.testcontainers
junit-jupiter
@@ -219,7 +220,7 @@
- 1.17.0
+ 1.19.2
diff --git a/httpClients/boot-rest-template/src/main/java/com/example/rest/template/config/RestTemplateConfiguration.java b/httpClients/boot-rest-template/src/main/java/com/example/rest/template/config/RestTemplateConfiguration.java
index 989a8de94..fd1ce42fb 100644
--- a/httpClients/boot-rest-template/src/main/java/com/example/rest/template/config/RestTemplateConfiguration.java
+++ b/httpClients/boot-rest-template/src/main/java/com/example/rest/template/config/RestTemplateConfiguration.java
@@ -1,15 +1,129 @@
package com.example.rest.template.config;
+import static com.example.rest.template.utils.AppConstants.*;
+
+import java.time.Duration;
+import java.util.Iterator;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.util.TimeValue;
+import org.apache.hc.core5.util.Timeout;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.client.RestTemplate;
@Configuration(proxyBeanMethods = false)
+@EnableScheduling
+@Slf4j
public class RestTemplateConfiguration {
@Bean
- RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
- return restTemplateBuilder.build();
+ PoolingHttpClientConnectionManager poolingConnectionManager() {
+ PoolingHttpClientConnectionManager poolingConnectionManager =
+ new PoolingHttpClientConnectionManager();
+ // set a total amount of connections across all HTTP routes
+ poolingConnectionManager.setMaxTotal(MAX_TOTAL_CONNECTIONS);
+ // set a maximum amount of connections for each HTTP route in pool
+ poolingConnectionManager.setDefaultMaxPerRoute(MAX_ROUTE_CONNECTIONS);
+ // increase the amounts of connections if the host is localhost
+ HttpHost localhost = new HttpHost("http://localhost", 8080);
+ poolingConnectionManager.setMaxPerRoute(
+ new HttpRoute(localhost), MAX_LOCALHOST_CONNECTIONS);
+ return poolingConnectionManager;
+ }
+
+ @Bean
+ CloseableHttpClient httpClient(
+ PoolingHttpClientConnectionManager poolingConnectionManager,
+ ConnectionKeepAliveStrategy connectionKeepAliveStrategy) {
+ RequestConfig requestConfig =
+ RequestConfig.custom()
+ .setConnectTimeout(Timeout.ofSeconds(CONNECTION_TIMEOUT))
+ .setConnectionRequestTimeout(Timeout.ofSeconds(REQUEST_TIMEOUT))
+ .setResponseTimeout(Timeout.ofSeconds(SOCKET_TIMEOUT))
+ .build();
+
+ return HttpClients.custom()
+ .setDefaultRequestConfig(requestConfig)
+ .setConnectionManager(poolingConnectionManager)
+ .setKeepAliveStrategy(connectionKeepAliveStrategy)
+ .setConnectionManagerShared(true)
+ .build();
+ }
+
+ @Bean
+ ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
+ return (httpResponse, httpContext) -> {
+ Iterator headerIterator = httpResponse.headerIterator(HttpHeaders.KEEP_ALIVE);
+ while (headerIterator.hasNext()) {
+ Header element = headerIterator.next();
+ String param = element.getName();
+ String value = element.getValue();
+ if (value != null && param.equalsIgnoreCase("timeout")) {
+ return TimeValue.ofSeconds(Long.parseLong(value));
+ }
+ }
+
+ return TimeValue.ofSeconds(DEFAULT_KEEP_ALIVE_TIME);
+ };
+ }
+
+ @Bean
+ RestTemplate restTemplate(
+ RestTemplateBuilder restTemplateBuilder, CloseableHttpClient httpClient) {
+
+ return restTemplateBuilder
+ .setConnectTimeout(Duration.ofSeconds(60))
+ .requestFactory(() -> new HttpComponentsClientHttpRequestFactory(httpClient))
+ .interceptors(
+ ((request, body, execution) -> {
+ // log the http request
+ log.info("URI: {}", request.getURI());
+ log.info("HTTP Method: {}", request.getMethod().name());
+ log.info("HTTP Headers: {}", request.getHeaders());
+
+ return execution.execute(request, body);
+ }))
+ .build();
+ }
+
+ // close idleConnections
+ @Bean
+ Runnable idleConnectionMonitor(PoolingHttpClientConnectionManager pool) {
+ return new Runnable() {
+ @Override
+ @Scheduled(fixedDelay = 20000)
+ public void run() {
+ // only if connection pool is initialised
+ if (pool != null) {
+ log.info("cleaning connection pool");
+ pool.closeExpired();
+ pool.closeIdle(TimeValue.ofSeconds(IDLE_CONNECTION_WAIT_TIME));
+ }
+ }
+ };
+ }
+
+ // Required for Scheduler
+ @Bean
+ TaskScheduler taskScheduler() {
+ ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
+ scheduler.setThreadNamePrefix("idleMonitor");
+ scheduler.setPoolSize(5);
+ return scheduler;
}
}
diff --git a/httpClients/boot-rest-template/src/main/java/com/example/rest/template/utils/AppConstants.java b/httpClients/boot-rest-template/src/main/java/com/example/rest/template/utils/AppConstants.java
index c24d8060c..be0b1a7e7 100644
--- a/httpClients/boot-rest-template/src/main/java/com/example/rest/template/utils/AppConstants.java
+++ b/httpClients/boot-rest-template/src/main/java/com/example/rest/template/utils/AppConstants.java
@@ -1,5 +1,8 @@
package com.example.rest.template.utils;
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
public final class AppConstants {
public static final String PROFILE_PROD = "prod";
public static final String PROFILE_NOT_PROD = "!" + PROFILE_PROD;
@@ -9,4 +12,22 @@ public final class AppConstants {
public static final String DEFAULT_PAGE_SIZE = "10";
public static final String DEFAULT_SORT_BY = "id";
public static final String DEFAULT_SORT_DIRECTION = "asc";
+
+ // Connection pool
+ public static final int MAX_ROUTE_CONNECTIONS = 40;
+ public static final int MAX_TOTAL_CONNECTIONS = 40;
+ public static final int MAX_LOCALHOST_CONNECTIONS = 80;
+
+ // Keep alive
+ public static final int DEFAULT_KEEP_ALIVE_TIME = 20; // 20 sec
+
+ // Timeouts
+ public static final int CONNECTION_TIMEOUT =
+ 30; // 30 sec, the time for waiting until a connection is established
+ public static final int REQUEST_TIMEOUT =
+ 30; // 30 sec, the time for waiting for a connection from connection pool
+ public static final int SOCKET_TIMEOUT = 60; // 60 sec, the time for waiting for data
+
+ // Idle connection monitor
+ public static final int IDLE_CONNECTION_WAIT_TIME = 30; // 30 sec
}
diff --git a/httpClients/boot-rest-template/src/test/java/com/example/rest/template/TestApplication.java b/httpClients/boot-rest-template/src/test/java/com/example/rest/template/TestApplication.java
index 2a7f461e7..3b3f33c0e 100644
--- a/httpClients/boot-rest-template/src/test/java/com/example/rest/template/TestApplication.java
+++ b/httpClients/boot-rest-template/src/test/java/com/example/rest/template/TestApplication.java
@@ -11,8 +11,8 @@ public class TestApplication {
@ServiceConnection
@Bean
- PostgreSQLContainer> sqlContainer() {
- return new PostgreSQLContainer<>("postgres:16.0-alpine");
+ PostgreSQLContainer> postgreSQLContainer() {
+ return new PostgreSQLContainer<>("postgres:16.2-alpine");
}
public static void main(String[] args) {