Enhancing the REST template with HttpClient5 46/136846/9
authorwaqas.ikram <waqas.ikram@est.tech>
Thu, 14 Dec 2023 12:17:03 +0000 (12:17 +0000)
committerwaqas.ikram <waqas.ikram@est.tech>
Mon, 18 Dec 2023 12:34:36 +0000 (12:34 +0000)
for better performance and allowing users to configure timeouts as per their requirements

Issue-ID: CPS-1994
Change-Id: I9fa94fb3923a50e33b3850ec0f190a51e278698f
Signed-off-by: waqas.ikram <waqas.ikram@est.tech>
cps-ncmp-service/pom.xml
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/HttpClientConfiguration.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/HttpClientConfigurationSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy

index f448c8f..e9faf43 100644 (file)
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents.client5</groupId>
+            <artifactId>httpclient5</artifactId>
+        </dependency>
         <dependency>
             <groupId>io.cloudevents</groupId>
             <artifactId>cloudevents-json-jackson</artifactId>
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/HttpClientConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/HttpClientConfiguration.java
new file mode 100644 (file)
index 0000000..aaa4f1e
--- /dev/null
@@ -0,0 +1,57 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2023 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.api.impl.config;
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.convert.DurationUnit;
+
+@Getter
+@Setter
+@ConfigurationProperties(prefix = "httpclient5", ignoreUnknownFields = true)
+public class HttpClientConfiguration {
+
+    /**
+     * The maximum time to establish a connection.
+     */
+    @DurationUnit(ChronoUnit.SECONDS)
+    private Duration connectionTimeoutInSeconds = Duration.ofSeconds(180);
+
+    /**
+     * The maximum number of open connections per route.
+     */
+    private int maximumConnectionsPerRoute = 50;
+
+    /**
+     * The maximum total number of open connections.
+     */
+    private int maximumConnectionsTotal = maximumConnectionsPerRoute * 2;
+
+    /**
+     * The duration after which idle connections are evicted.
+     */
+    @DurationUnit(ChronoUnit.SECONDS)
+    private Duration idleConnectionEvictionThresholdInSeconds = Duration.ofSeconds(5);
+
+}
index ffecf9c..4460094 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Copyright (C) 2021-2023 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
 
 package org.onap.cps.ncmp.api.impl.config;
 
-import java.time.Duration;
 import java.util.Arrays;
 import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
+import org.apache.hc.client5.http.config.ConnectionConfig;
+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.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
+import org.apache.hc.core5.util.TimeValue;
+import org.apache.hc.core5.util.Timeout;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.boot.web.client.RestTemplateBuilder;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Scope;
 import org.springframework.http.MediaType;
+import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
 import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
 import org.springframework.stereotype.Component;
 import org.springframework.web.client.RestTemplate;
 
 @Configuration
+@EnableConfigurationProperties(HttpClientConfiguration.class)
 @RequiredArgsConstructor(access = AccessLevel.PROTECTED)
 public class NcmpConfiguration {
 
-    private static final Duration CONNECTION_TIMEOUT_MILLISECONDS = Duration.ofMillis(180000);
-    private static final Duration READ_TIMEOUT_MILLISECONDS = Duration.ofMillis(180000);
-
     @Getter
     @Component
     public static class DmiProperties {
@@ -60,13 +67,38 @@ public class NcmpConfiguration {
      * Rest template bean.
      *
      * @param restTemplateBuilder the rest template builder
+     * @param httpClientConfiguration the http client configuration
      * @return rest template instance
      */
     @Bean
     @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
-    public static RestTemplate restTemplate(final RestTemplateBuilder restTemplateBuilder) {
-        final RestTemplate restTemplate = restTemplateBuilder.setConnectTimeout(CONNECTION_TIMEOUT_MILLISECONDS)
-                .setReadTimeout(READ_TIMEOUT_MILLISECONDS).build();
+    public static RestTemplate restTemplate(final RestTemplateBuilder restTemplateBuilder, 
+                                            final HttpClientConfiguration httpClientConfiguration) {
+        
+        final ConnectionConfig connectionConfig = ConnectionConfig.copy(ConnectionConfig.DEFAULT)
+                .setConnectTimeout(Timeout.of(httpClientConfiguration.getConnectionTimeoutInSeconds()))
+                .build();
+        
+        final PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
+                .setDefaultConnectionConfig(connectionConfig)
+                .setMaxConnTotal(httpClientConfiguration.getMaximumConnectionsTotal())
+                .setMaxConnPerRoute(httpClientConfiguration.getMaximumConnectionsPerRoute())
+                .build();
+        
+        final CloseableHttpClient httpClient = HttpClients.custom()
+                .setConnectionManager(connectionManager)
+                .evictExpiredConnections()
+                .evictIdleConnections(
+                        TimeValue.of(httpClientConfiguration.getIdleConnectionEvictionThresholdInSeconds()))
+                .build();
+        
+        final ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
+        
+        final RestTemplate restTemplate = restTemplateBuilder
+                .requestFactory(() -> requestFactory)
+                .setConnectTimeout(httpClientConfiguration.getConnectionTimeoutInSeconds())
+                .build();
+        
         setRestTemplateMessageConverters(restTemplate);
         return restTemplate;
     }
index 51b00d1..013341f 100644 (file)
@@ -212,7 +212,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
             1 * mockLcmEventsCmHandleStateHandler.initiateStateAdvised(_) >> {
                 args ->
                     {
-                        def cmHandleStatePerCmHandle = (args[0] as Map)
+                        def cmHandleStatePerCmHandle = (args[0] as List)
                         cmHandleStatePerCmHandle.each {
                             assert (it.id == 'cmhandle' && it.dmiServiceName == 'my-server')
                         }
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/HttpClientConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/HttpClientConfigurationSpec.groovy
new file mode 100644 (file)
index 0000000..941c8b8
--- /dev/null
@@ -0,0 +1,48 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2023 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.cps.ncmp.api.impl.config
+
+import java.time.Duration
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.context.properties.EnableConfigurationProperties
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.context.ContextConfiguration
+import org.springframework.test.context.TestPropertySource
+import org.springframework.test.context.support.AnnotationConfigContextLoader
+import spock.lang.Specification
+
+@SpringBootTest
+@ContextConfiguration(classes = [HttpClientConfiguration])
+@EnableConfigurationProperties(HttpClientConfiguration.class)
+@TestPropertySource(properties = ["httpclient5.connectionTimeoutInSeconds=1", "httpclient5.maximumConnectionsTotal=200"])
+class HttpClientConfigurationSpec extends Specification {
+
+    @Autowired
+    private HttpClientConfiguration httpClientConfiguration
+
+    def 'Test HttpClientConfiguration properties with custom and default values'() {
+        expect: 'custom property values'
+            httpClientConfiguration.getConnectionTimeoutInSeconds() == Duration.ofSeconds(1)
+            httpClientConfiguration.getMaximumConnectionsTotal() == 200
+        and: 'default property values'
+            httpClientConfiguration.getMaximumConnectionsPerRoute() == 50
+            httpClientConfiguration.getIdleConnectionEvictionThresholdInSeconds() == Duration.ofSeconds(5)
+    }
+}
index e1aba79..a4df9b3 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2021 Nordix Foundation
+ *  Copyright (C) 2021-2023 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
  */
 package org.onap.cps.ncmp.api.impl.config
 
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.test.context.SpringBootTest
 import org.springframework.boot.web.client.RestTemplateBuilder
 import org.springframework.http.MediaType
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory
 import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
 import org.springframework.test.context.ContextConfiguration
 import org.springframework.web.client.RestTemplate
 import spock.lang.Specification
 
 @SpringBootTest
-@ContextConfiguration(classes = [NcmpConfiguration.DmiProperties])
+@ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, HttpClientConfiguration])
 class NcmpConfigurationSpec extends Specification{
 
     @Autowired
     NcmpConfiguration.DmiProperties dmiProperties
-
+    
+    @Autowired
+    HttpClientConfiguration httpClientConfiguration
+    
     def mockRestTemplateBuilder = new RestTemplateBuilder()
 
     def 'NcmpConfiguration Construction.'() {
@@ -48,11 +53,14 @@ class NcmpConfigurationSpec extends Specification{
             dmiProperties.authPassword == 'some-password'
     }
 
-    def 'Rest Template creation.'() {
+    def 'Rest Template creation with CloseableHttpClient and MappingJackson2HttpMessageConverter.'() {
         when: 'a rest template is created'
-            def result = NcmpConfiguration.restTemplate(mockRestTemplateBuilder)
+            def result = NcmpConfiguration.restTemplate(mockRestTemplateBuilder, httpClientConfiguration)
         then: 'the rest template is returned'
             assert result instanceof RestTemplate
+        and: 'the rest template is created with httpclient5'
+            assert result.getRequestFactory() instanceof HttpComponentsClientHttpRequestFactory
+            assert ((HttpComponentsClientHttpRequestFactory) result.getRequestFactory()).getHttpClient() instanceof CloseableHttpClient;
         and: 'a jackson media converter has been added'
             def lastMessageConverter = result.getMessageConverters().get(result.getMessageConverters().size()-1)
             lastMessageConverter instanceof MappingJackson2HttpMessageConverter