From 9ab2a91f585864bf18fef788adeaf61f4f450b39 Mon Sep 17 00:00:00 2001 From: Lukasz Rajewski Date: Thu, 18 Aug 2022 21:50:31 +0200 Subject: Refactor rest clients and support timeouts - Refactored rest clients to remove redundant code - Timeouts added to the configuration of rest clients Issue-ID: CCSDK-3716 Signed-off-by: Lukasz Rajewski Change-Id: I706b8efd8447570455b8b65bd5b1a22da474f62b --- .../functions/k8s/K8sAbstractRestClientService.kt | 52 +--- .../mock/MockBlueprintWebClientService.kt | 11 +- .../rest/BluePrintRestLibData.kt | 3 + .../rest/service/BaseBlueprintWebClientService.kt | 330 +++++++++++++++++++++ .../rest/service/BasicAuthRestClientService.kt | 24 +- .../rest/service/BlueprintWebClientService.kt | 306 ++----------------- .../rest/service/SSLRestClientService.kt | 21 +- .../rest/service/TokenAuthRestClientService.kt | 12 +- 8 files changed, 396 insertions(+), 363 deletions(-) create mode 100644 ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BaseBlueprintWebClientService.kt diff --git a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/K8sAbstractRestClientService.kt b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/K8sAbstractRestClientService.kt index 14f14f61b..b9c45e423 100644 --- a/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/K8sAbstractRestClientService.kt +++ b/ms/blueprintsprocessor/functions/k8s-connection-plugin/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/k8s/K8sAbstractRestClientService.kt @@ -19,23 +19,20 @@ package org.onap.ccsdk.cds.blueprintsprocessor.functions.k8s -import org.apache.http.message.BasicHeader import org.onap.ccsdk.cds.blueprintsprocessor.rest.BasicAuthRestClientProperties -import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService -import org.springframework.http.HttpHeaders.ACCEPT -import org.springframework.http.HttpHeaders.AUTHORIZATION -import org.springframework.http.HttpHeaders.CONTENT_TYPE -import org.springframework.http.MediaType.APPLICATION_JSON_VALUE -import java.nio.charset.Charset -import java.util.Base64 +import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BasicAuthRestClientService abstract class K8sAbstractRestClientService( private val k8sConfiguration: K8sConnectionPluginConfiguration -) : BlueprintWebClientService { +) : BasicAuthRestClientService(BasicAuthRestClientProperties()) { protected val baseUrl: String = k8sConfiguration.getProperties().url private var restClientProperties: BasicAuthRestClientProperties? = null + override fun getRestClientProperties(): BasicAuthRestClientProperties { + return getBasicAuthRestClientProperties() + } + private fun getBasicAuthRestClientProperties(): BasicAuthRestClientProperties { return if (restClientProperties != null) restClientProperties!! @@ -55,45 +52,8 @@ abstract class K8sAbstractRestClientService( mapOfHeaders["Accept"] = "application/json" mapOfHeaders["Content-Type"] = "application/json" mapOfHeaders["cache-control"] = " no-cache" - mapOfHeaders["Accept"] = "application/json" return mapOfHeaders } - private fun setBasicAuth(username: String, password: String): String { - val credentialsString = "$username:$password" - return Base64.getEncoder().encodeToString(credentialsString.toByteArray(Charset.defaultCharset())) - } - - override fun defaultHeaders(): Map { - val encodedCredentials = setBasicAuth( - getBasicAuthRestClientProperties().username, - getBasicAuthRestClientProperties().password - ) - return mapOf( - CONTENT_TYPE to APPLICATION_JSON_VALUE, - ACCEPT to APPLICATION_JSON_VALUE, - AUTHORIZATION to "Basic $encodedCredentials" - ) - } - - override fun host(uri: String): String { - return getBasicAuthRestClientProperties().url + uri - } - - override fun convertToBasicHeaders(headers: Map): Array { - val customHeaders: MutableMap = headers.toMutableMap() - // inject additionalHeaders - customHeaders.putAll(verifyAdditionalHeaders(getBasicAuthRestClientProperties())) - - if (!headers.containsKey(AUTHORIZATION)) { - val encodedCredentials = setBasicAuth( - getBasicAuthRestClientProperties().username, - getBasicAuthRestClientProperties().password - ) - customHeaders[AUTHORIZATION] = "Basic $encodedCredentials" - } - return super.convertToBasicHeaders(customHeaders) - } - abstract fun apiUrl(): String } diff --git a/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/mock/MockBlueprintWebClientService.kt b/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/mock/MockBlueprintWebClientService.kt index 86a9ed056..ba273e179 100644 --- a/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/mock/MockBlueprintWebClientService.kt +++ b/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/mock/MockBlueprintWebClientService.kt @@ -21,6 +21,7 @@ import org.mockserver.model.Header import org.mockserver.model.HttpRequest.request import org.mockserver.model.HttpResponse.response import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestClientProperties +import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BaseBlueprintWebClientService import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService import org.springframework.http.HttpHeaders import org.springframework.http.MediaType @@ -28,7 +29,7 @@ import java.nio.charset.Charset import java.util.Base64 class MockBlueprintWebClientService(private var restClientProperties: RestClientProperties) : - BlueprintWebClientService { + BaseBlueprintWebClientService() { private var mockServer: ClientAndServer private var port: String = if (restClientProperties.url.split(":")[2].isEmpty()) "8080" @@ -52,6 +53,10 @@ class MockBlueprintWebClientService(private var restClientProperties: RestClient ) } + override fun getRestClientProperties(): RestClientProperties { + return restClientProperties + } + override fun defaultHeaders(): Map { val encodedCredentials = this.setBasicAuth("admin", "aaiTest") return mapOf( @@ -61,10 +66,6 @@ class MockBlueprintWebClientService(private var restClientProperties: RestClient ) } - override fun host(uri: String): String { - return restClientProperties.url + uri - } - fun tearDown() { mockServer.close() } diff --git a/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/BluePrintRestLibData.kt b/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/BluePrintRestLibData.kt index 01011cc83..b0282c40f 100644 --- a/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/BluePrintRestLibData.kt +++ b/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/BluePrintRestLibData.kt @@ -22,6 +22,9 @@ open class RestClientProperties { lateinit var type: String lateinit var url: String + var connectTimeout: Int = 0 + var socketTimeout: Int = 0 + var connectionRequestTimeout: Int = 0 var additionalHeaders: Map? = null } diff --git a/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BaseBlueprintWebClientService.kt b/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BaseBlueprintWebClientService.kt new file mode 100644 index 000000000..bc1dc4e09 --- /dev/null +++ b/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BaseBlueprintWebClientService.kt @@ -0,0 +1,330 @@ +/* + * Copyright © 2017-2019 AT&T, Bell Canada, Nordix Foundation + * Modifications Copyright © 2018-2019 IBM. + * Modifications Copyright © 2019 Huawei. + * Modifications Copyright © 2022 Deutsche Telekom AG. + * + * 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. + */ + +package org.onap.ccsdk.cds.blueprintsprocessor.rest.service + +import com.fasterxml.jackson.databind.JsonNode +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.apache.commons.io.IOUtils +import org.apache.http.client.ClientProtocolException +import org.apache.http.client.config.RequestConfig +import org.apache.http.client.methods.HttpDelete +import org.apache.http.client.methods.HttpGet +import org.apache.http.client.methods.HttpPatch +import org.apache.http.client.methods.HttpPost +import org.apache.http.client.methods.HttpPut +import org.apache.http.client.methods.HttpUriRequest +import org.apache.http.entity.StringEntity +import org.apache.http.impl.client.CloseableHttpClient +import org.apache.http.impl.client.HttpClients +import org.apache.http.message.BasicHeader +import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestClientProperties +import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestLibConstants +import org.onap.ccsdk.cds.blueprintsprocessor.rest.utils.WebClientUtils +import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException +import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpMethod +import java.io.IOException +import java.io.InputStream +import java.net.URI +import java.nio.charset.Charset + +abstract class BaseBlueprintWebClientService : BlueprintWebClientService { + + open fun host(uri: String): String { + val uri: URI = URI.create(getRestClientProperties().url + uri) + return uri.resolve(uri).toString() + } + + abstract fun getRestClientProperties(): E + + open fun getRequestConfig(): RequestConfig { + val requestConfigBuilder = RequestConfig.custom() + if (getRestClientProperties().connectionRequestTimeout > 0) + requestConfigBuilder.setConnectionRequestTimeout(getRestClientProperties().connectionRequestTimeout) + if (getRestClientProperties().connectTimeout > 0) + requestConfigBuilder.setConnectTimeout(getRestClientProperties().connectTimeout) + if (getRestClientProperties().socketTimeout > 0) + requestConfigBuilder.setSocketTimeout(getRestClientProperties().socketTimeout) + return requestConfigBuilder.build() + } + + open fun httpClient(): CloseableHttpClient { + return HttpClients.custom() + .addInterceptorFirst(WebClientUtils.logRequest()) + .addInterceptorLast(WebClientUtils.logResponse()) + .setDefaultRequestConfig(getRequestConfig()) + .build() + } + + override fun exchangeResource(methodType: String, path: String, request: String): BlueprintWebClientService.WebClientResponse { + return this.exchangeResource(methodType, path, request, defaultHeaders()) + } + + override fun exchangeResource( + methodType: String, + path: String, + request: String, + headers: Map + ): BlueprintWebClientService.WebClientResponse { + /** + * TODO: Basic headers in the implementations of this client do not get added + * in blocking version, whereas in NB version defaultHeaders get added. + * the difference is in convertToBasicHeaders vs basicHeaders + */ + val convertedHeaders: Array = convertToBasicHeaders(headers) + return when (HttpMethod.resolve(methodType)) { + HttpMethod.DELETE -> delete(path, convertedHeaders, String::class.java) + HttpMethod.GET -> get(path, convertedHeaders, String::class.java) + HttpMethod.POST -> post(path, request, convertedHeaders, String::class.java) + HttpMethod.PUT -> put(path, request, convertedHeaders, String::class.java) + HttpMethod.PATCH -> patch(path, request, convertedHeaders, String::class.java) + else -> throw BluePrintProcessorException( + "Unsupported methodType($methodType) attempted on path($path)" + ) + } + } + + // TODO: convert to multi-map + override fun convertToBasicHeaders(headers: Map): Array { + return headers.map { BasicHeader(it.key, it.value) }.toTypedArray() + } + + open fun delete(path: String, headers: Array, responseType: Class): BlueprintWebClientService.WebClientResponse { + val httpDelete = HttpDelete(host(path)) + RestLoggerService.httpInvoking(headers) + httpDelete.setHeaders(headers) + return performCallAndExtractTypedWebClientResponse(httpDelete, responseType) + } + + open fun get(path: String, headers: Array, responseType: Class): BlueprintWebClientService.WebClientResponse { + val httpGet = HttpGet(host(path)) + RestLoggerService.httpInvoking(headers) + httpGet.setHeaders(headers) + return performCallAndExtractTypedWebClientResponse(httpGet, responseType) + } + + open fun post(path: String, request: Any, headers: Array, responseType: Class): BlueprintWebClientService.WebClientResponse { + val httpPost = HttpPost(host(path)) + val entity = StringEntity(strRequest(request)) + httpPost.entity = entity + RestLoggerService.httpInvoking(headers) + httpPost.setHeaders(headers) + return performCallAndExtractTypedWebClientResponse(httpPost, responseType) + } + + open fun put(path: String, request: Any, headers: Array, responseType: Class): BlueprintWebClientService.WebClientResponse { + val httpPut = HttpPut(host(path)) + val entity = StringEntity(strRequest(request)) + httpPut.entity = entity + RestLoggerService.httpInvoking(headers) + httpPut.setHeaders(headers) + return performCallAndExtractTypedWebClientResponse(httpPut, responseType) + } + + open fun patch(path: String, request: Any, headers: Array, responseType: Class): BlueprintWebClientService.WebClientResponse { + val httpPatch = HttpPatch(host(path)) + val entity = StringEntity(strRequest(request)) + httpPatch.entity = entity + RestLoggerService.httpInvoking(headers) + httpPatch.setHeaders(headers) + return performCallAndExtractTypedWebClientResponse(httpPatch, responseType) + } + + /** + * Perform the HTTP call and return HTTP status code and body. + * @param httpUriRequest {@link HttpUriRequest} object + * @return {@link WebClientResponse} object + * http client may throw IOException and ClientProtocolException on error + */ + + @Throws(IOException::class, ClientProtocolException::class) + protected fun performCallAndExtractTypedWebClientResponse( + httpUriRequest: HttpUriRequest, + responseType: Class + ): + BlueprintWebClientService.WebClientResponse { + val httpResponse = httpClient().execute(httpUriRequest) + val statusCode = httpResponse.statusLine.statusCode + httpResponse.entity.content.use { + val body = getResponse(it, responseType) + return BlueprintWebClientService.WebClientResponse(statusCode, body) + } + } + + open suspend fun getNB(path: String): BlueprintWebClientService.WebClientResponse { + return getNB(path, null, String::class.java) + } + + open suspend fun getNB(path: String, additionalHeaders: Array?): BlueprintWebClientService.WebClientResponse { + return getNB(path, additionalHeaders, String::class.java) + } + + open suspend fun getNB(path: String, additionalHeaders: Array?, responseType: Class): + BlueprintWebClientService.WebClientResponse = withContext(Dispatchers.IO) { + get(path, additionalHeaders!!, responseType) + } + + open suspend fun postNB(path: String, request: Any): BlueprintWebClientService.WebClientResponse { + return postNB(path, request, null, String::class.java) + } + + open suspend fun postNB(path: String, request: Any, additionalHeaders: Array?): BlueprintWebClientService.WebClientResponse { + return postNB(path, request, additionalHeaders, String::class.java) + } + + open suspend fun postNB( + path: String, + request: Any, + additionalHeaders: Array?, + responseType: Class + ): BlueprintWebClientService.WebClientResponse = withContext(Dispatchers.IO) { + post(path, request, additionalHeaders!!, responseType) + } + + open suspend fun putNB(path: String, request: Any): BlueprintWebClientService.WebClientResponse { + return putNB(path, request, null, String::class.java) + } + + open suspend fun putNB( + path: String, + request: Any, + additionalHeaders: Array? + ): BlueprintWebClientService.WebClientResponse { + return putNB(path, request, additionalHeaders, String::class.java) + } + + open suspend fun putNB( + path: String, + request: Any, + additionalHeaders: Array?, + responseType: Class + ): BlueprintWebClientService.WebClientResponse = withContext(Dispatchers.IO) { + put(path, request, additionalHeaders!!, responseType) + } + + open suspend fun deleteNB(path: String): BlueprintWebClientService.WebClientResponse { + return deleteNB(path, null, String::class.java) + } + + open suspend fun deleteNB(path: String, additionalHeaders: Array?): + BlueprintWebClientService.WebClientResponse { + return deleteNB(path, additionalHeaders, String::class.java) + } + + open suspend fun deleteNB(path: String, additionalHeaders: Array?, responseType: Class): + BlueprintWebClientService.WebClientResponse = withContext(Dispatchers.IO) { + delete(path, additionalHeaders!!, responseType) + } + + open suspend fun patchNB(path: String, request: Any, additionalHeaders: Array?, responseType: Class): + BlueprintWebClientService.WebClientResponse = withContext(Dispatchers.IO) { + patch(path, request, additionalHeaders!!, responseType) + } + + override suspend fun exchangeNB(methodType: String, path: String, request: Any): BlueprintWebClientService.WebClientResponse { + return exchangeNB( + methodType, path, request, hashMapOf(), + String::class.java + ) + } + + override suspend fun exchangeNB(methodType: String, path: String, request: Any, additionalHeaders: Map?): + BlueprintWebClientService.WebClientResponse { + return exchangeNB(methodType, path, request, additionalHeaders, String::class.java) + } + + override suspend fun exchangeNB( + methodType: String, + path: String, + request: Any, + additionalHeaders: Map?, + responseType: Class + ): BlueprintWebClientService.WebClientResponse { + + // TODO: possible inconsistency + // NOTE: this basic headers function is different from non-blocking + val convertedHeaders: Array = basicHeaders(additionalHeaders!!) + return when (HttpMethod.resolve(methodType)) { + HttpMethod.GET -> getNB(path, convertedHeaders, responseType) + HttpMethod.POST -> postNB(path, request, convertedHeaders, responseType) + HttpMethod.DELETE -> deleteNB(path, convertedHeaders, responseType) + HttpMethod.PUT -> putNB(path, request, convertedHeaders, responseType) + HttpMethod.PATCH -> patchNB(path, request, convertedHeaders, responseType) + else -> throw BluePrintProcessorException("Unsupported methodType($methodType)") + } + } + + protected fun strRequest(request: Any): String { + return when (request) { + is String -> request.toString() + is JsonNode -> request.toString() + else -> JacksonUtils.getJson(request) + } + } + + protected fun getResponse(it: InputStream, responseType: Class): T { + return if (responseType == String::class.java) { + IOUtils.toString(it, Charset.defaultCharset()) as T + } else { + JacksonUtils.readValue(it, responseType)!! + } + } + + protected fun basicHeaders(headers: Map?): + Array { + val basicHeaders = mutableListOf() + defaultHeaders().forEach { (name, value) -> + basicHeaders.add(BasicHeader(name, value)) + } + headers?.forEach { name, value -> + basicHeaders.add(BasicHeader(name, value)) + } + return basicHeaders.toTypedArray() + } + + // Non Blocking Rest Implementation + suspend fun httpClientNB(): CloseableHttpClient { + return httpClient() + } + + open fun verifyAdditionalHeaders(): Map { + return verifyAdditionalHeaders(getRestClientProperties()) + } + + open fun verifyAdditionalHeaders(restClientProperties: RestClientProperties): Map { + val customHeaders: MutableMap = mutableMapOf() + // Extract additionalHeaders from the requestProperties and + // throw an error if HttpHeaders.AUTHORIZATION key (headers are case-insensitive) + restClientProperties.additionalHeaders?.let { + if (it.keys.map { k -> k.toLowerCase().trim() }.contains(HttpHeaders.AUTHORIZATION.toLowerCase())) { + val errMsg = "Error in definition of endpoint ${restClientProperties.url}." + + " User-supplied \"additionalHeaders\" cannot contain AUTHORIZATION header with" + + " auth-type \"${RestLibConstants.TYPE_BASIC_AUTH}\"" + WebClientUtils.log.error(errMsg) + throw BluePrintProcessorException(errMsg) + } else { + customHeaders.putAll(it) + } + } + return customHeaders + } +} diff --git a/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BasicAuthRestClientService.kt b/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BasicAuthRestClientService.kt index be9b849f6..943a3550d 100644 --- a/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BasicAuthRestClientService.kt +++ b/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BasicAuthRestClientService.kt @@ -20,21 +20,24 @@ import org.apache.http.message.BasicHeader import org.onap.ccsdk.cds.blueprintsprocessor.rest.BasicAuthRestClientProperties import org.springframework.http.HttpHeaders import org.springframework.http.MediaType -import java.net.URI import java.nio.charset.Charset import java.util.Base64 -class BasicAuthRestClientService( +open class BasicAuthRestClientService( private val restClientProperties: BasicAuthRestClientProperties ) : - BlueprintWebClientService { + BaseBlueprintWebClientService() { + + override fun getRestClientProperties(): BasicAuthRestClientProperties { + return restClientProperties + } override fun defaultHeaders(): Map { val encodedCredentials = setBasicAuth( - restClientProperties.username, - restClientProperties.password + getRestClientProperties().username, + getRestClientProperties().password ) return mapOf( HttpHeaders.CONTENT_TYPE to MediaType.APPLICATION_JSON_VALUE, @@ -43,21 +46,16 @@ class BasicAuthRestClientService( ) } - override fun host(uri: String): String { - val uri: URI = URI.create(restClientProperties.url + uri) - return uri.resolve(uri).toString() - } - override fun convertToBasicHeaders(headers: Map): Array { val customHeaders: MutableMap = headers.toMutableMap() // inject additionalHeaders - customHeaders.putAll(verifyAdditionalHeaders(restClientProperties)) + customHeaders.putAll(verifyAdditionalHeaders()) if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) { val encodedCredentials = setBasicAuth( - restClientProperties.username, - restClientProperties.password + getRestClientProperties().username, + getRestClientProperties().password ) customHeaders[HttpHeaders.AUTHORIZATION] = "Basic $encodedCredentials" diff --git a/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BlueprintWebClientService.kt b/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BlueprintWebClientService.kt index 945d29850..ed52e6212 100644 --- a/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BlueprintWebClientService.kt +++ b/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BlueprintWebClientService.kt @@ -2,6 +2,7 @@ * Copyright © 2017-2019 AT&T, Bell Canada, Nordix Foundation * Modifications Copyright © 2018-2019 IBM. * Modifications Copyright © 2019 Huawei. + * Modifications Copyright © 2022 Deutsche Telekom AG. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,240 +19,33 @@ package org.onap.ccsdk.cds.blueprintsprocessor.rest.service -import com.fasterxml.jackson.databind.JsonNode -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import org.apache.commons.io.IOUtils -import org.apache.http.client.ClientProtocolException -import org.apache.http.client.methods.HttpDelete -import org.apache.http.client.methods.HttpGet -import org.apache.http.client.methods.HttpPatch -import org.apache.http.client.methods.HttpPost -import org.apache.http.client.methods.HttpPut -import org.apache.http.client.methods.HttpUriRequest -import org.apache.http.entity.StringEntity -import org.apache.http.impl.client.CloseableHttpClient -import org.apache.http.impl.client.HttpClients import org.apache.http.message.BasicHeader -import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestClientProperties -import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestLibConstants -import org.onap.ccsdk.cds.blueprintsprocessor.rest.utils.WebClientUtils -import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintRetryException import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintIOUtils -import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod -import java.io.IOException -import java.io.InputStream -import java.nio.charset.Charset interface BlueprintWebClientService { - fun defaultHeaders(): Map - - fun host(uri: String): String - - fun httpClient(): CloseableHttpClient { - return HttpClients.custom() - .addInterceptorFirst(WebClientUtils.logRequest()) - .addInterceptorLast(WebClientUtils.logResponse()) - .build() - } - - /** High performance non blocking Retry function, If execution block [block] throws BluePrintRetryException - * exception then this will perform wait and retrigger accoring to times [times] with delay [delay] - */ - suspend fun retry( - times: Int = 1, - initialDelay: Long = 0, - delay: Long = 1000, - block: suspend (Int) -> T - ): T { - val exceptionBlock = { e: Exception -> - if (e !is BluePrintRetryException) { - throw e - } - } - return BluePrintIOUtils.retry(times, initialDelay, delay, block, exceptionBlock) - } - - fun exchangeResource(methodType: String, path: String, request: String): WebClientResponse { - return this.exchangeResource(methodType, path, request, defaultHeaders()) - } + fun convertToBasicHeaders( + mergedDefaultAndSuppliedHeaders: Map + ): Array fun exchangeResource( methodType: String, path: String, request: String, headers: Map - ): WebClientResponse { - /** - * TODO: Basic headers in the implementations of this client do not get added - * in blocking version, whereas in NB version defaultHeaders get added. - * the difference is in convertToBasicHeaders vs basicHeaders - */ - val convertedHeaders: Array = convertToBasicHeaders(headers) - return when (HttpMethod.resolve(methodType)) { - HttpMethod.DELETE -> delete(path, convertedHeaders, String::class.java) - HttpMethod.GET -> get(path, convertedHeaders, String::class.java) - HttpMethod.POST -> post(path, request, convertedHeaders, String::class.java) - HttpMethod.PUT -> put(path, request, convertedHeaders, String::class.java) - HttpMethod.PATCH -> patch(path, request, convertedHeaders, String::class.java) - else -> throw BluePrintProcessorException( - "Unsupported methodType($methodType) attempted on path($path)" - ) - } - } - - // TODO: convert to multi-map - fun convertToBasicHeaders(headers: Map): Array { - return headers.map { BasicHeader(it.key, it.value) }.toTypedArray() - } + ): WebClientResponse - fun delete(path: String, headers: Array, responseType: Class): WebClientResponse { - val httpDelete = HttpDelete(host(path)) - RestLoggerService.httpInvoking(headers) - httpDelete.setHeaders(headers) - return performCallAndExtractTypedWebClientResponse(httpDelete, responseType) - } - - fun get(path: String, headers: Array, responseType: Class): WebClientResponse { - val httpGet = HttpGet(host(path)) - RestLoggerService.httpInvoking(headers) - httpGet.setHeaders(headers) - return performCallAndExtractTypedWebClientResponse(httpGet, responseType) - } - - fun post(path: String, request: Any, headers: Array, responseType: Class): WebClientResponse { - val httpPost = HttpPost(host(path)) - val entity = StringEntity(strRequest(request)) - httpPost.entity = entity - RestLoggerService.httpInvoking(headers) - httpPost.setHeaders(headers) - return performCallAndExtractTypedWebClientResponse(httpPost, responseType) - } - - fun put(path: String, request: Any, headers: Array, responseType: Class): WebClientResponse { - val httpPut = HttpPut(host(path)) - val entity = StringEntity(strRequest(request)) - httpPut.entity = entity - RestLoggerService.httpInvoking(headers) - httpPut.setHeaders(headers) - return performCallAndExtractTypedWebClientResponse(httpPut, responseType) - } - - fun patch(path: String, request: Any, headers: Array, responseType: Class): WebClientResponse { - val httpPatch = HttpPatch(host(path)) - val entity = StringEntity(strRequest(request)) - httpPatch.entity = entity - RestLoggerService.httpInvoking(headers) - httpPatch.setHeaders(headers) - return performCallAndExtractTypedWebClientResponse(httpPatch, responseType) - } - - /** - * Perform the HTTP call and return HTTP status code and body. - * @param httpUriRequest {@link HttpUriRequest} object - * @return {@link WebClientResponse} object - * http client may throw IOException and ClientProtocolException on error - */ - - @Throws(IOException::class, ClientProtocolException::class) - private fun performCallAndExtractTypedWebClientResponse( - httpUriRequest: HttpUriRequest, - responseType: Class - ): - WebClientResponse { - val httpResponse = httpClient().execute(httpUriRequest) - val statusCode = httpResponse.statusLine.statusCode - httpResponse.entity.content.use { - val body = getResponse(it, responseType) - return WebClientResponse(statusCode, body) - } - } - - suspend fun getNB(path: String): WebClientResponse { - return getNB(path, null, String::class.java) - } - - suspend fun getNB(path: String, additionalHeaders: Array?): WebClientResponse { - return getNB(path, additionalHeaders, String::class.java) - } - - suspend fun getNB(path: String, additionalHeaders: Array?, responseType: Class): - WebClientResponse = withContext(Dispatchers.IO) { - get(path, additionalHeaders!!, responseType) - } - - suspend fun postNB(path: String, request: Any): WebClientResponse { - return postNB(path, request, null, String::class.java) - } - - suspend fun postNB(path: String, request: Any, additionalHeaders: Array?): WebClientResponse { - return postNB(path, request, additionalHeaders, String::class.java) - } - - suspend fun postNB( - path: String, - request: Any, - additionalHeaders: Array?, - responseType: Class - ): WebClientResponse = withContext(Dispatchers.IO) { - post(path, request, additionalHeaders!!, responseType) - } - - suspend fun putNB(path: String, request: Any): WebClientResponse { - return putNB(path, request, null, String::class.java) - } - - suspend fun putNB( + fun exchangeResource( + methodType: String, path: String, - request: Any, - additionalHeaders: Array? - ): WebClientResponse { - return putNB(path, request, additionalHeaders, String::class.java) - } + request: String + ): WebClientResponse - suspend fun putNB( - path: String, - request: Any, - additionalHeaders: Array?, - responseType: Class - ): WebClientResponse = withContext(Dispatchers.IO) { - put(path, request, additionalHeaders!!, responseType) - } - - suspend fun deleteNB(path: String): WebClientResponse { - return deleteNB(path, null, String::class.java) - } - - suspend fun deleteNB(path: String, additionalHeaders: Array?): - WebClientResponse { - return deleteNB(path, additionalHeaders, String::class.java) - } - - suspend fun deleteNB(path: String, additionalHeaders: Array?, responseType: Class): - WebClientResponse = withContext(Dispatchers.IO) { - delete(path, additionalHeaders!!, responseType) - } - - suspend fun patchNB(path: String, request: Any, additionalHeaders: Array?, responseType: Class): - WebClientResponse = withContext(Dispatchers.IO) { - patch(path, request, additionalHeaders!!, responseType) - } - - suspend fun exchangeNB(methodType: String, path: String, request: Any): WebClientResponse { - return exchangeNB( - methodType, path, request, hashMapOf(), - String::class.java - ) - } + suspend fun exchangeNB(methodType: String, path: String, request: Any): WebClientResponse suspend fun exchangeNB(methodType: String, path: String, request: Any, additionalHeaders: Map?): - WebClientResponse { - return exchangeNB(methodType, path, request, additionalHeaders, String::class.java) - } + WebClientResponse suspend fun exchangeNB( methodType: String, @@ -259,75 +53,25 @@ interface BlueprintWebClientService { request: Any, additionalHeaders: Map?, responseType: Class - ): WebClientResponse { + ): WebClientResponse - // TODO: possible inconsistency - // NOTE: this basic headers function is different from non-blocking - val convertedHeaders: Array = basicHeaders(additionalHeaders!!) - return when (HttpMethod.resolve(methodType)) { - HttpMethod.GET -> getNB(path, convertedHeaders, responseType) - HttpMethod.POST -> postNB(path, request, convertedHeaders, responseType) - HttpMethod.DELETE -> deleteNB(path, convertedHeaders, responseType) - HttpMethod.PUT -> putNB(path, request, convertedHeaders, responseType) - HttpMethod.PATCH -> patchNB(path, request, convertedHeaders, responseType) - else -> throw BluePrintProcessorException("Unsupported methodType($methodType)") - } - } - - private fun strRequest(request: Any): String { - return when (request) { - is String -> request.toString() - is JsonNode -> request.toString() - else -> JacksonUtils.getJson(request) - } - } - - private fun getResponse(it: InputStream, responseType: Class): T { - return if (responseType == String::class.java) { - IOUtils.toString(it, Charset.defaultCharset()) as T - } else { - JacksonUtils.readValue(it, responseType)!! - } - } - - private fun basicHeaders(headers: Map?): - Array { - val basicHeaders = mutableListOf() - defaultHeaders().forEach { (name, value) -> - basicHeaders.add(BasicHeader(name, value)) - } - headers?.forEach { name, value -> - basicHeaders.add(BasicHeader(name, value)) + /** High performance non blocking Retry function, If execution block [block] throws BluePrintRetryException + * exception then this will perform wait and retrigger accoring to times [times] with delay [delay] + */ + suspend fun retry( + times: Int = 1, + initialDelay: Long = 0, + delay: Long = 1000, + block: suspend (Int) -> T + ): T { + val exceptionBlock = { e: Exception -> + if (e !is BluePrintRetryException) { + throw e } - return basicHeaders.toTypedArray() } - - // Non Blocking Rest Implementation - suspend fun httpClientNB(): CloseableHttpClient { - return HttpClients.custom() - .addInterceptorFirst(WebClientUtils.logRequest()) - .addInterceptorLast(WebClientUtils.logResponse()) - .build() + return BluePrintIOUtils.retry(times, initialDelay, delay, block, exceptionBlock) } // TODO maybe there could be cases where we care about return headers? data class WebClientResponse(val status: Int, val body: T) - - fun verifyAdditionalHeaders(restClientProperties: RestClientProperties): Map { - val customHeaders: MutableMap = mutableMapOf() - // Extract additionalHeaders from the requestProperties and - // throw an error if HttpHeaders.AUTHORIZATION key (headers are case-insensitive) - restClientProperties.additionalHeaders?.let { - if (it.keys.map { k -> k.toLowerCase().trim() }.contains(HttpHeaders.AUTHORIZATION.toLowerCase())) { - val errMsg = "Error in definition of endpoint ${restClientProperties.url}." + - " User-supplied \"additionalHeaders\" cannot contain AUTHORIZATION header with" + - " auth-type \"${RestLibConstants.TYPE_BASIC_AUTH}\"" - WebClientUtils.log.error(errMsg) - throw BluePrintProcessorException(errMsg) - } else { - customHeaders.putAll(it) - } - } - return customHeaders - } } diff --git a/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/SSLRestClientService.kt b/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/SSLRestClientService.kt index a8d79b6fa..602609b6a 100644 --- a/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/SSLRestClientService.kt +++ b/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/SSLRestClientService.kt @@ -24,6 +24,7 @@ import org.apache.http.impl.client.HttpClients import org.apache.http.message.BasicHeader import org.apache.http.ssl.SSLContextBuilder import org.onap.ccsdk.cds.blueprintsprocessor.rest.BasicAuthRestClientProperties +import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestClientProperties import org.onap.ccsdk.cds.blueprintsprocessor.rest.SSLBasicAuthRestClientProperties import org.onap.ccsdk.cds.blueprintsprocessor.rest.SSLRestClientProperties import org.onap.ccsdk.cds.blueprintsprocessor.rest.SSLTokenAuthRestClientProperties @@ -35,8 +36,8 @@ import java.io.FileInputStream import java.security.KeyStore import java.security.cert.X509Certificate -class SSLRestClientService(private val restClientProperties: SSLRestClientProperties) : - BlueprintWebClientService { +open class SSLRestClientService(private val restClientProperties: SSLRestClientProperties) : + BaseBlueprintWebClientService() { var auth: BlueprintWebClientService? = null @@ -44,7 +45,11 @@ class SSLRestClientService(private val restClientProperties: SSLRestClientProper auth = getAuthService() } - private fun getAuthService(): BlueprintWebClientService? { + override fun getRestClientProperties(): SSLRestClientProperties { + return restClientProperties + } + + private fun getAuthService(): BaseBlueprintWebClientService? { // type,url and additional headers don't get carried over to TokenAuthRestClientProperties from SSLTokenAuthRestClientProperties // set them in auth obj to be consistent. TODO: refactor return when (restClientProperties) { @@ -81,10 +86,6 @@ class SSLRestClientService(private val restClientProperties: SSLRestClientProper ) } - override fun host(uri: String): String { - return restClientProperties.url + uri - } - override fun httpClient(): CloseableHttpClient { val keystoreInstance = restClientProperties.keyStoreInstance @@ -117,14 +118,10 @@ class SSLRestClientService(private val restClientProperties: SSLRestClientProper return HttpClients.custom() .addInterceptorFirst(WebClientUtils.logRequest()) .addInterceptorLast(WebClientUtils.logResponse()) + .setDefaultRequestConfig(getRequestConfig()) .setSSLSocketFactory(csf).build() } - // Non Blocking Rest Implementation - override suspend fun httpClientNB(): CloseableHttpClient { - return httpClient() - } - override fun convertToBasicHeaders(headers: Map): Array { val mergedDefaultAndSuppliedHeaders = defaultHeaders().plus(headers) // During the initialization, getAuthService() sets the auth variable. diff --git a/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/TokenAuthRestClientService.kt b/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/TokenAuthRestClientService.kt index 600eedf15..34e4a9e20 100644 --- a/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/TokenAuthRestClientService.kt +++ b/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/TokenAuthRestClientService.kt @@ -21,11 +21,15 @@ import org.onap.ccsdk.cds.blueprintsprocessor.rest.TokenAuthRestClientProperties import org.springframework.http.HttpHeaders import org.springframework.http.MediaType -class TokenAuthRestClientService( +open class TokenAuthRestClientService( private val restClientProperties: TokenAuthRestClientProperties ) : - BlueprintWebClientService { + BaseBlueprintWebClientService() { + + override fun getRestClientProperties(): TokenAuthRestClientProperties { + return restClientProperties + } override fun defaultHeaders(): Map { return mapOf( @@ -45,8 +49,4 @@ class TokenAuthRestClientService( } return super.convertToBasicHeaders(customHeaders) } - - override fun host(uri: String): String { - return restClientProperties.url + uri - } } -- cgit 1.2.3-korg