diff options
author | Lukasz Rajewski <lukasz.rajewski@t-mobile.pl> | 2022-08-18 21:50:31 +0200 |
---|---|---|
committer | Lukasz Rajewski <lukasz.rajewski@t-mobile.pl> | 2022-08-24 14:48:20 +0000 |
commit | 9ab2a91f585864bf18fef788adeaf61f4f450b39 (patch) | |
tree | 295613f89d73e18a888e8ffd5c68be65911a7b59 | |
parent | e1db2469a8e42d591bd905e588975be0f2a8861e (diff) |
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 <lukasz.rajewski@t-mobile.pl>
Change-Id: I706b8efd8447570455b8b65bd5b1a22da474f62b
8 files changed, 396 insertions, 363 deletions
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<String, String> { - 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<String, String>): Array<BasicHeader> { - val customHeaders: MutableMap<String, String> = 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<RestClientProperties>() { 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<String, String> { 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<String, String>? = 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<out E : RestClientProperties> : 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<String> { + return this.exchangeResource(methodType, path, request, defaultHeaders()) + } + + override fun exchangeResource( + methodType: String, + path: String, + request: String, + headers: Map<String, String> + ): BlueprintWebClientService.WebClientResponse<String> { + /** + * 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<BasicHeader> = 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<String, String>): Array<BasicHeader> { + return headers.map { BasicHeader(it.key, it.value) }.toTypedArray() + } + + open fun <T> delete(path: String, headers: Array<BasicHeader>, responseType: Class<T>): BlueprintWebClientService.WebClientResponse<T> { + val httpDelete = HttpDelete(host(path)) + RestLoggerService.httpInvoking(headers) + httpDelete.setHeaders(headers) + return performCallAndExtractTypedWebClientResponse(httpDelete, responseType) + } + + open fun <T> get(path: String, headers: Array<BasicHeader>, responseType: Class<T>): BlueprintWebClientService.WebClientResponse<T> { + val httpGet = HttpGet(host(path)) + RestLoggerService.httpInvoking(headers) + httpGet.setHeaders(headers) + return performCallAndExtractTypedWebClientResponse(httpGet, responseType) + } + + open fun <T> post(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): BlueprintWebClientService.WebClientResponse<T> { + 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 <T> put(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): BlueprintWebClientService.WebClientResponse<T> { + 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 <T> patch(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): BlueprintWebClientService.WebClientResponse<T> { + 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 <T> performCallAndExtractTypedWebClientResponse( + httpUriRequest: HttpUriRequest, + responseType: Class<T> + ): + BlueprintWebClientService.WebClientResponse<T> { + 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<String> { + return getNB(path, null, String::class.java) + } + + open suspend fun getNB(path: String, additionalHeaders: Array<BasicHeader>?): BlueprintWebClientService.WebClientResponse<String> { + return getNB(path, additionalHeaders, String::class.java) + } + + open suspend fun <T> getNB(path: String, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>): + BlueprintWebClientService.WebClientResponse<T> = withContext(Dispatchers.IO) { + get(path, additionalHeaders!!, responseType) + } + + open suspend fun postNB(path: String, request: Any): BlueprintWebClientService.WebClientResponse<String> { + return postNB(path, request, null, String::class.java) + } + + open suspend fun postNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?): BlueprintWebClientService.WebClientResponse<String> { + return postNB(path, request, additionalHeaders, String::class.java) + } + + open suspend fun <T> postNB( + path: String, + request: Any, + additionalHeaders: Array<BasicHeader>?, + responseType: Class<T> + ): BlueprintWebClientService.WebClientResponse<T> = withContext(Dispatchers.IO) { + post(path, request, additionalHeaders!!, responseType) + } + + open suspend fun putNB(path: String, request: Any): BlueprintWebClientService.WebClientResponse<String> { + return putNB(path, request, null, String::class.java) + } + + open suspend fun putNB( + path: String, + request: Any, + additionalHeaders: Array<BasicHeader>? + ): BlueprintWebClientService.WebClientResponse<String> { + return putNB(path, request, additionalHeaders, String::class.java) + } + + open suspend fun <T> putNB( + path: String, + request: Any, + additionalHeaders: Array<BasicHeader>?, + responseType: Class<T> + ): BlueprintWebClientService.WebClientResponse<T> = withContext(Dispatchers.IO) { + put(path, request, additionalHeaders!!, responseType) + } + + open suspend fun <T> deleteNB(path: String): BlueprintWebClientService.WebClientResponse<String> { + return deleteNB(path, null, String::class.java) + } + + open suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?): + BlueprintWebClientService.WebClientResponse<String> { + return deleteNB(path, additionalHeaders, String::class.java) + } + + open suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>): + BlueprintWebClientService.WebClientResponse<T> = withContext(Dispatchers.IO) { + delete(path, additionalHeaders!!, responseType) + } + + open suspend fun <T> patchNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>): + BlueprintWebClientService.WebClientResponse<T> = withContext(Dispatchers.IO) { + patch(path, request, additionalHeaders!!, responseType) + } + + override suspend fun exchangeNB(methodType: String, path: String, request: Any): BlueprintWebClientService.WebClientResponse<String> { + return exchangeNB( + methodType, path, request, hashMapOf(), + String::class.java + ) + } + + override suspend fun exchangeNB(methodType: String, path: String, request: Any, additionalHeaders: Map<String, String>?): + BlueprintWebClientService.WebClientResponse<String> { + return exchangeNB(methodType, path, request, additionalHeaders, String::class.java) + } + + override suspend fun <T> exchangeNB( + methodType: String, + path: String, + request: Any, + additionalHeaders: Map<String, String>?, + responseType: Class<T> + ): BlueprintWebClientService.WebClientResponse<T> { + + // TODO: possible inconsistency + // NOTE: this basic headers function is different from non-blocking + val convertedHeaders: Array<BasicHeader> = 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 <T> getResponse(it: InputStream, responseType: Class<T>): T { + return if (responseType == String::class.java) { + IOUtils.toString(it, Charset.defaultCharset()) as T + } else { + JacksonUtils.readValue(it, responseType)!! + } + } + + protected fun basicHeaders(headers: Map<String, String>?): + Array<BasicHeader> { + val basicHeaders = mutableListOf<BasicHeader>() + 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<String, String> { + return verifyAdditionalHeaders(getRestClientProperties()) + } + + open fun verifyAdditionalHeaders(restClientProperties: RestClientProperties): Map<String, String> { + val customHeaders: MutableMap<String, String> = 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<BasicAuthRestClientProperties>() { + + override fun getRestClientProperties(): BasicAuthRestClientProperties { + return restClientProperties + } override fun defaultHeaders(): Map<String, String> { 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<String, String>): Array<BasicHeader> { val customHeaders: MutableMap<String, String> = 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<String, String> - - 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 <T> 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<String> { - return this.exchangeResource(methodType, path, request, defaultHeaders()) - } + fun convertToBasicHeaders( + mergedDefaultAndSuppliedHeaders: Map<String, String> + ): Array<BasicHeader> fun exchangeResource( methodType: String, path: String, request: String, headers: Map<String, String> - ): WebClientResponse<String> { - /** - * 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<BasicHeader> = 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<String, String>): Array<BasicHeader> { - return headers.map { BasicHeader(it.key, it.value) }.toTypedArray() - } + ): WebClientResponse<String> - fun <T> delete(path: String, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> { - val httpDelete = HttpDelete(host(path)) - RestLoggerService.httpInvoking(headers) - httpDelete.setHeaders(headers) - return performCallAndExtractTypedWebClientResponse(httpDelete, responseType) - } - - fun <T> get(path: String, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> { - val httpGet = HttpGet(host(path)) - RestLoggerService.httpInvoking(headers) - httpGet.setHeaders(headers) - return performCallAndExtractTypedWebClientResponse(httpGet, responseType) - } - - fun <T> post(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> { - val httpPost = HttpPost(host(path)) - val entity = StringEntity(strRequest(request)) - httpPost.entity = entity - RestLoggerService.httpInvoking(headers) - httpPost.setHeaders(headers) - return performCallAndExtractTypedWebClientResponse(httpPost, responseType) - } - - fun <T> put(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> { - val httpPut = HttpPut(host(path)) - val entity = StringEntity(strRequest(request)) - httpPut.entity = entity - RestLoggerService.httpInvoking(headers) - httpPut.setHeaders(headers) - return performCallAndExtractTypedWebClientResponse(httpPut, responseType) - } - - fun <T> patch(path: String, request: Any, headers: Array<BasicHeader>, responseType: Class<T>): WebClientResponse<T> { - 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 <T> performCallAndExtractTypedWebClientResponse( - httpUriRequest: HttpUriRequest, - responseType: Class<T> - ): - WebClientResponse<T> { - 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<String> { - return getNB(path, null, String::class.java) - } - - suspend fun getNB(path: String, additionalHeaders: Array<BasicHeader>?): WebClientResponse<String> { - return getNB(path, additionalHeaders, String::class.java) - } - - suspend fun <T> getNB(path: String, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>): - WebClientResponse<T> = withContext(Dispatchers.IO) { - get(path, additionalHeaders!!, responseType) - } - - suspend fun postNB(path: String, request: Any): WebClientResponse<String> { - return postNB(path, request, null, String::class.java) - } - - suspend fun postNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?): WebClientResponse<String> { - return postNB(path, request, additionalHeaders, String::class.java) - } - - suspend fun <T> postNB( - path: String, - request: Any, - additionalHeaders: Array<BasicHeader>?, - responseType: Class<T> - ): WebClientResponse<T> = withContext(Dispatchers.IO) { - post(path, request, additionalHeaders!!, responseType) - } - - suspend fun putNB(path: String, request: Any): WebClientResponse<String> { - return putNB(path, request, null, String::class.java) - } - - suspend fun putNB( + fun exchangeResource( + methodType: String, path: String, - request: Any, - additionalHeaders: Array<BasicHeader>? - ): WebClientResponse<String> { - return putNB(path, request, additionalHeaders, String::class.java) - } + request: String + ): WebClientResponse<String> - suspend fun <T> putNB( - path: String, - request: Any, - additionalHeaders: Array<BasicHeader>?, - responseType: Class<T> - ): WebClientResponse<T> = withContext(Dispatchers.IO) { - put(path, request, additionalHeaders!!, responseType) - } - - suspend fun <T> deleteNB(path: String): WebClientResponse<String> { - return deleteNB(path, null, String::class.java) - } - - suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?): - WebClientResponse<String> { - return deleteNB(path, additionalHeaders, String::class.java) - } - - suspend fun <T> deleteNB(path: String, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>): - WebClientResponse<T> = withContext(Dispatchers.IO) { - delete(path, additionalHeaders!!, responseType) - } - - suspend fun <T> patchNB(path: String, request: Any, additionalHeaders: Array<BasicHeader>?, responseType: Class<T>): - WebClientResponse<T> = withContext(Dispatchers.IO) { - patch(path, request, additionalHeaders!!, responseType) - } - - suspend fun exchangeNB(methodType: String, path: String, request: Any): WebClientResponse<String> { - return exchangeNB( - methodType, path, request, hashMapOf(), - String::class.java - ) - } + suspend fun exchangeNB(methodType: String, path: String, request: Any): WebClientResponse<String> suspend fun exchangeNB(methodType: String, path: String, request: Any, additionalHeaders: Map<String, String>?): - WebClientResponse<String> { - return exchangeNB(methodType, path, request, additionalHeaders, String::class.java) - } + WebClientResponse<String> suspend fun <T> exchangeNB( methodType: String, @@ -259,75 +53,25 @@ interface BlueprintWebClientService { request: Any, additionalHeaders: Map<String, String>?, responseType: Class<T> - ): WebClientResponse<T> { + ): WebClientResponse<T> - // TODO: possible inconsistency - // NOTE: this basic headers function is different from non-blocking - val convertedHeaders: Array<BasicHeader> = 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 <T> getResponse(it: InputStream, responseType: Class<T>): T { - return if (responseType == String::class.java) { - IOUtils.toString(it, Charset.defaultCharset()) as T - } else { - JacksonUtils.readValue(it, responseType)!! - } - } - - private fun basicHeaders(headers: Map<String, String>?): - Array<BasicHeader> { - val basicHeaders = mutableListOf<BasicHeader>() - 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 <T> 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<T>(val status: Int, val body: T) - - fun verifyAdditionalHeaders(restClientProperties: RestClientProperties): Map<String, String> { - val customHeaders: MutableMap<String, String> = 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<SSLRestClientProperties>() { 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<RestClientProperties>? { // 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<String, String>): Array<BasicHeader> { 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<TokenAuthRestClientProperties>() { + + override fun getRestClientProperties(): TokenAuthRestClientProperties { + return restClientProperties + } override fun defaultHeaders(): Map<String, String> { return mapOf( @@ -45,8 +49,4 @@ class TokenAuthRestClientService( } return super.convertToBasicHeaders(customHeaders) } - - override fun host(uri: String): String { - return restClientProperties.url + uri - } } |