From 8a44d244e220de18f0add7b1771344b1b41cacd2 Mon Sep 17 00:00:00 2001 From: Oleg Mitsura Date: Mon, 13 May 2019 15:49:03 -0400 Subject: BlueprintWebClientService: added return status Issue-ID: CCSDK-1331 Change-Id: I8dab8ad4eaebf1863f11c4d38c52cf7f64b0a4d5 Signed-off-by: Oleg Mitsura (cherry picked from commit 9eb8b8a88bfac860a94b62b203b00c64330a1be7) --- .../processor/RestResourceResolutionProcessor.kt | 13 +- .../mock/MockBlueprintWebClientService.kt | 10 +- .../mock/MockRestResourceResolutionProcessor.kt | 14 +- .../rest/service/BlueprintWebClientService.kt | 234 ++++++++++----------- .../rest/service/RestClientServiceTest.kt | 22 +- 5 files changed, 141 insertions(+), 152 deletions(-) diff --git a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/RestResourceResolutionProcessor.kt b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/RestResourceResolutionProcessor.kt index 2d726d487..7eefe954d 100644 --- a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/RestResourceResolutionProcessor.kt +++ b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/RestResourceResolutionProcessor.kt @@ -27,7 +27,6 @@ import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientSer import org.onap.ccsdk.cds.controllerblueprints.core.* import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment -import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceDictionaryConstants import org.slf4j.LoggerFactory import org.springframework.beans.factory.config.ConfigurableBeanFactory import org.springframework.context.annotation.Scope @@ -86,10 +85,14 @@ open class RestResourceResolutionProcessor(private val blueprintRestLibPropertyS val restClientService = blueprintWebClientService(resourceAssignment, sourceProperties) val response = restClientService.exchangeResource(verb, urlPath, payload) - if (response.isBlank()) { - logger.warn("Failed to get $dSource result for dictionary name ($dName) using urlPath ($urlPath)") + val responseStatusCode = response.status + val responseBody = response.body + if (responseStatusCode in 200..299 && !responseBody.isBlank()) { + populateResource(resourceAssignment, sourceProperties, responseBody, path) } else { - populateResource(resourceAssignment, sourceProperties, response, path) + val errMsg = "Failed to get $dSource result for dictionary name ($dName) using urlPath ($urlPath) response_code: ($responseStatusCode)" + logger.warn(errMsg) + throw BluePrintProcessorException(errMsg) } } // Check the value has populated for mandatory case @@ -204,4 +207,4 @@ open class RestResourceResolutionProcessor(private val blueprintRestLibPropertyS raRuntimeService.getBluePrintError().addError(runtimeException.message!!) } -} \ No newline at end of file +} 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 c6ca41351..fede7be7b 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 @@ -61,17 +61,17 @@ class MockBlueprintWebClientService(private var restClientProperties: RestClient mockServer.close() } - override fun exchangeResource(method: String, path: String, payload: String): String { + override fun exchangeResource(method: String, path: String, payload: String): BlueprintWebClientService.WebClientResponse { val header = arrayOf(BasicHeader(HttpHeaders.AUTHORIZATION, headers[HttpHeaders.AUTHORIZATION])) return when (method) { "POST" -> { - post(path, payload, header) + post(path, payload, header, String::class.java) } "PUT" -> { - put(path, payload, header) + put(path, payload, header, String::class.java) } else -> { - get(path, header) + get(path, header, String::class.java) } } } @@ -121,4 +121,4 @@ class MockBlueprintWebClientService(private var restClientProperties: RestClient return Base64.getEncoder().encodeToString( credentialsString.toByteArray(Charset.defaultCharset())) } -} \ No newline at end of file +} diff --git a/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/mock/MockRestResourceResolutionProcessor.kt b/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/mock/MockRestResourceResolutionProcessor.kt index 2c481dca3..eb2a7a7ed 100644 --- a/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/mock/MockRestResourceResolutionProcessor.kt +++ b/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/mock/MockRestResourceResolutionProcessor.kt @@ -80,11 +80,15 @@ class MockRestResourceResolutionProcessor(private val blueprintRestLibPropertySe val restClientService = blueprintWebClientService(executionRequest) val response = restClientService.exchangeResource(verb, urlPath, payload) - if (response.isEmpty()) { - logger.warn("Failed to get $dSource result for dictionary name ($dName) using urlPath ($urlPath)") - } else { - populateResource(executionRequest, sourceProperties, response, path) + val responseStatusCode = response.status + val responseBody = response.body + if (responseStatusCode in 200..299 && !responseBody.isBlank()) { + populateResource(executionRequest, sourceProperties, responseBody, path) restClientService.tearDown() + } else { + val errMsg = "Failed to get $dSource result for dictionary name ($dName) using urlPath ($urlPath) response_code: ($responseStatusCode)" + logger.warn(errMsg) + throw BluePrintProcessorException(errMsg) } } } catch (e: Exception) { @@ -156,4 +160,4 @@ class MockRestResourceResolutionProcessor(private val blueprintRestLibPropertySe } } } -} \ No newline at end of file +} 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 d6d02b330..5d35186b8 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 @@ -22,11 +22,13 @@ 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 @@ -35,6 +37,7 @@ 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.HttpMethod +import java.io.IOException import java.io.InputStream import java.nio.charset.Charset @@ -46,206 +49,183 @@ interface BlueprintWebClientService { fun httpClient(): CloseableHttpClient { return HttpClients.custom() - .addInterceptorFirst(WebClientUtils.logRequest()) - .addInterceptorLast(WebClientUtils.logResponse()) - .build() + .addInterceptorFirst(WebClientUtils.logRequest()) + .addInterceptorLast(WebClientUtils.logResponse()) + .build() } - fun exchangeResource(methodType: String, path: String, request: String): - String { - return this.exchangeResource(methodType, path, request, - defaultHeaders()) + fun exchangeResource(methodType: String, path: String, request: String): WebClientResponse { + return this.exchangeResource(methodType, path, request, defaultHeaders()) } fun exchangeResource(methodType: String, path: String, request: String, - headers: Map): String { - val convertedHeaders: Array = convertToBasicHeaders( - headers) + 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) - HttpMethod.GET -> get(path, convertedHeaders) - HttpMethod.POST -> post(path, request, convertedHeaders) - HttpMethod.PUT -> put(path, request, convertedHeaders) - HttpMethod.PATCH -> patch(path, request, convertedHeaders) + 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 met" + - "hodType($methodType)") + "hodType($methodType)") } } fun convertToBasicHeaders(headers: Map): Array { - return headers.map{ BasicHeader(it.key, it.value)}.toTypedArray() + return headers.map { BasicHeader(it.key, it.value) }.toTypedArray() } - fun delete(path: String, headers: Array): String { + fun delete(path: String, headers: Array, responseType: Class): WebClientResponse { val httpDelete = HttpDelete(host(path)) httpDelete.setHeaders(headers) - httpClient().execute(httpDelete).entity.content.use { - return IOUtils.toString(it, Charset.defaultCharset()) - } + return performCallAndExtractTypedWebClientResponse(httpDelete, responseType) } - fun get(path: String, headers: Array): String { + fun get(path: String, headers: Array, responseType: Class): WebClientResponse { val httpGet = HttpGet(host(path)) httpGet.setHeaders(headers) - httpClient().execute(httpGet).entity.content.use { - return IOUtils.toString(it, Charset.defaultCharset()) - } + return performCallAndExtractTypedWebClientResponse(httpGet, responseType) } - fun post(path: String, request: String, headers: Array): - String { + fun post(path: String, request: Any, headers: Array, responseType: Class): WebClientResponse { val httpPost = HttpPost(host(path)) - val entity = StringEntity(request) + val entity = StringEntity(strRequest(request)) httpPost.entity = entity httpPost.setHeaders(headers) - httpClient().execute(httpPost).entity.content.use { - return IOUtils.toString(it, Charset.defaultCharset()) - } + return performCallAndExtractTypedWebClientResponse(httpPost, responseType) } - fun put(path: String, request: String, headers: Array): - String { + fun put(path: String, request: Any, headers: Array, responseType: Class): WebClientResponse { val httpPut = HttpPut(host(path)) - val entity = StringEntity(request) + val entity = StringEntity(strRequest(request)) httpPut.entity = entity httpPut.setHeaders(headers) - httpClient().execute(httpPut).entity.content.use { - return IOUtils.toString(it, Charset.defaultCharset()) - } + return performCallAndExtractTypedWebClientResponse(httpPut, responseType) } - fun patch(path: String, request: String, headers: Array): - String { + fun patch(path: String, request: Any, headers: Array, responseType: Class): WebClientResponse { val httpPatch = HttpPatch(host(path)) - val entity = StringEntity(request) + val entity = StringEntity(strRequest(request)) httpPatch.entity = entity httpPatch.setHeaders(headers) - httpClient().execute(httpPatch).entity.content.use { - return IOUtils.toString(it, Charset.defaultCharset()) - } + 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): String { + suspend fun getNB(path: String): WebClientResponse { return getNB(path, null, String::class.java) } - suspend fun getNB(path: String, additionalHeaders: Map?): - String { + suspend fun getNB(path: String, additionalHeaders: Array?): WebClientResponse { return getNB(path, additionalHeaders, String::class.java) } - suspend fun getNB(path: String, additionalHeaders: Map?, - responseType: Class): T = - withContext(Dispatchers.IO) { - val httpGet = HttpGet(host(path)) - httpGet.setHeaders(basicHeaders(additionalHeaders)) - httpClientNB().execute(httpGet).entity.content.use { - getResponse(it, responseType) + suspend fun getNB(path: String, additionalHeaders: Array?, responseType: Class): + WebClientResponse = + withContext(Dispatchers.IO) { + get(path, additionalHeaders!!, responseType) } - } - suspend fun postNB(path: String, request: Any): String { + suspend fun postNB(path: String, request: Any): WebClientResponse { return postNB(path, request, null, String::class.java) } - suspend fun postNB(path: String, request: Any, - additionalHeaders: Map?): String { + 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: Map?, - responseType: Class): T = - withContext(Dispatchers.IO) { - val httpPost = HttpPost(host(path)) - httpPost.entity = StringEntity(strRequest(request)) - httpPost.setHeaders(basicHeaders(additionalHeaders)) - httpClientNB().execute(httpPost).entity.content.use { - getResponse(it, responseType) - } - } - - suspend fun putNB(path: String, request: Any): String { + 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(path: String, request: Any, - additionalHeaders: Map?): String { + additionalHeaders: Array?): WebClientResponse { return putNB(path, request, additionalHeaders, String::class.java) } suspend fun putNB(path: String, request: Any, - additionalHeaders: Map?, - responseType: Class): T = - withContext(Dispatchers.IO) { - val httpPut = HttpPut(host(path)) - httpPut.entity = StringEntity(strRequest(request)) - httpPut.setHeaders(basicHeaders(additionalHeaders)) - httpClientNB().execute(httpPut).entity.content.use { - getResponse(it, responseType) + additionalHeaders: Array?, + responseType: Class): WebClientResponse = + withContext(Dispatchers.IO) { + put(path, request, additionalHeaders!!, responseType) } - } - suspend fun deleteNB(path: String): String { + suspend fun deleteNB(path: String): WebClientResponse { return deleteNB(path, null, String::class.java) } - suspend fun deleteNB(path: String, - additionalHeaders: Map?): String { + suspend fun deleteNB(path: String, additionalHeaders: Array?): + WebClientResponse { return deleteNB(path, additionalHeaders, String::class.java) } - suspend fun deleteNB(path: String, - additionalHeaders: Map?, - responseType: Class): T = - withContext(Dispatchers.IO) { - val httpDelete = HttpDelete(host(path)) - httpDelete.setHeaders(basicHeaders(additionalHeaders)) - httpClient().execute(httpDelete).entity.content.use { - getResponse(it, responseType) + 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: Map?, - responseType: Class): T = - withContext(Dispatchers.IO) { - val httpPatch = HttpPatch(host(path)) - httpPatch.entity = StringEntity(strRequest(request)) - httpPatch.setHeaders(basicHeaders(additionalHeaders)) - httpClient().execute(httpPatch).entity.content.use { - getResponse(it, 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): - String { + suspend fun exchangeNB(methodType: String, path: String, request: Any): WebClientResponse { return exchangeNB(methodType, path, request, hashMapOf(), - String::class.java) + String::class.java) } - suspend fun exchangeNB(methodType: String, path: String, request: Any, - additionalHeaders: Map?): String { - return exchangeNB(methodType, path, request, additionalHeaders, - String::class.java) + suspend fun exchangeNB(methodType: String, path: String, request: Any, additionalHeaders: Map?): + WebClientResponse { + return exchangeNB(methodType, path, request, additionalHeaders, String::class.java) } suspend fun exchangeNB(methodType: String, path: String, request: Any, additionalHeaders: Map?, - responseType: Class): T { + responseType: Class): 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, additionalHeaders, responseType) - HttpMethod.POST -> postNB(path, request, additionalHeaders, - responseType) - HttpMethod.DELETE -> deleteNB(path, additionalHeaders, responseType) - HttpMethod.PUT -> putNB(path, request, additionalHeaders, - responseType) - HttpMethod.PATCH -> patchNB(path, request, additionalHeaders, - responseType) + 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 method" + - "Type($methodType)") + "Type($methodType)") } } @@ -266,10 +246,9 @@ interface BlueprintWebClientService { } private fun basicHeaders(headers: Map?): - Array { - + Array { val basicHeaders = mutableListOf() - defaultHeaders().forEach { name, value -> + defaultHeaders().forEach { (name, value) -> basicHeaders.add(BasicHeader(name, value)) } headers?.forEach { name, value -> @@ -281,8 +260,11 @@ interface BlueprintWebClientService { // Non Blocking Rest Implementation suspend fun httpClientNB(): CloseableHttpClient { return HttpClients.custom() - .addInterceptorFirst(WebClientUtils.logRequest()) - .addInterceptorLast(WebClientUtils.logResponse()) - .build() + .addInterceptorFirst(WebClientUtils.logRequest()) + .addInterceptorLast(WebClientUtils.logResponse()) + .build() } -} \ No newline at end of file + + //TODO maybe there could be cases where we care about return headers? + data class WebClientResponse(val status: Int, val body: T) +} diff --git a/ms/blueprintsprocessor/modules/commons/rest-lib/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/RestClientServiceTest.kt b/ms/blueprintsprocessor/modules/commons/rest-lib/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/RestClientServiceTest.kt index 93eb12dd0..21d05e620 100644 --- a/ms/blueprintsprocessor/modules/commons/rest-lib/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/RestClientServiceTest.kt +++ b/ms/blueprintsprocessor/modules/commons/rest-lib/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/RestClientServiceTest.kt @@ -99,7 +99,7 @@ class RestClientServiceTest { .blueprintWebClientService("sample") val response = restClientService.exchangeResource( HttpMethod.PATCH.name, "/sample/name", "") - assertEquals("Patch request successful", response, + assertEquals("Patch request successful", response.body, "failed to get patch response") } @@ -111,7 +111,7 @@ class RestClientServiceTest { headers["X-Transaction-Id"] = "1234" val response = restClientService.exchangeResource(HttpMethod.GET.name, "/sample/name", "") - assertNotNull(response, "failed to get response") + assertNotNull(response.body, "failed to get response") } @Test @@ -130,7 +130,7 @@ class RestClientServiceTest { runBlocking { val get = async(start = CoroutineStart.LAZY) { restClientService.exchangeNB(HttpMethod.GET.name, - "/sample/basic", "")} + "/sample/basic", "").body} get.start() res = get.await() } @@ -163,32 +163,32 @@ class RestClientServiceTest { val get1 = async(start = CoroutineStart.LAZY) { restClientService.exchangeNB(HttpMethod.GET.name, "/sample/aai/v14/business/customers", "", headers, - Customer::class.java)} + Customer::class.java).body} val get2 = async(start = CoroutineStart.LAZY) { restClientService.exchangeNB(HttpMethod.GET.name, "/sample/aai/v14/business/customers", "", headers, - Customer::class.java)} + Customer::class.java).body} val post = async(start = CoroutineStart.LAZY) { restClientService.exchangeNB(HttpMethod.POST.name, "/sample/aai/v14/business/customers", post1, headers, - String::class.java)} + String::class.java).body} val put = async(start = CoroutineStart.LAZY) { restClientService.exchangeNB(HttpMethod.PUT.name, "/sample/aai/v14/business/customers", post1, headers, - String::class.java)} + String::class.java).body} val patch = async(start = CoroutineStart.LAZY) { restClientService.exchangeNB(HttpMethod.PATCH.name, "/sample/aai/v14/business/customers", post1, headers, - String::class.java)} + String::class.java).body} val delete = async(start = CoroutineStart.LAZY) { restClientService.exchangeNB(HttpMethod.DELETE.name, "/sample/aai/v14/business/customers", "", headers, - String::class.java)} + String::class.java).body} get1.start() get2.start() @@ -346,7 +346,7 @@ class HttpServer { fun servletContainer(): ServletWebServerFactory { val connector = Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL) - connector.setPort(8080) + connector.port = 8080 val tomcat = TomcatServletWebServerFactory() tomcat.addAdditionalTomcatConnectors(connector) @@ -361,4 +361,4 @@ data class Customer( val id: String, val name: String, val type: String, - val resource: String) \ No newline at end of file + val resource: String) -- cgit 1.2.3-korg