From b4103047c7e38c1dab3cc4eb13c4ccdc81dc8ac6 Mon Sep 17 00:00:00 2001 From: Ronald Holshausen Date: Mon, 2 Dec 2024 16:05:46 +1100 Subject: [PATCH] chore: Upgrade KTor to 3.0.1 --- ...dius.pact.kotlin-common-conventions.gradle | 8 +- .../com/dius/pact/consumer/KTorMockServer.kt | 89 ++++++++++--------- .../pact/consumer/MockHttpServerSpec.groovy | 30 ++++++- pact-jvm-server/build.gradle | 2 +- .../server/KTorHttpsKeystoreMockProvider.kt | 31 +++---- .../com/dius/pact/server/KTorMockProvider.kt | 35 ++++---- .../au/com/dius/pact/server/MainServer.kt | 18 ++-- 7 files changed, 119 insertions(+), 94 deletions(-) diff --git a/buildSrc/src/main/groovy/au.com.dius.pact.kotlin-common-conventions.gradle b/buildSrc/src/main/groovy/au.com.dius.pact.kotlin-common-conventions.gradle index d336329a3..c73c966b2 100644 --- a/buildSrc/src/main/groovy/au.com.dius.pact.kotlin-common-conventions.gradle +++ b/buildSrc/src/main/groovy/au.com.dius.pact.kotlin-common-conventions.gradle @@ -42,10 +42,10 @@ dependencies { implementation 'org.apache.tika:tika-core:2.9.1' implementation 'com.google.guava:guava:31.1-jre' implementation 'org.slf4j:slf4j-api:1.7.36' - implementation 'io.ktor:ktor-http-jvm:2.3.8' - implementation 'io.ktor:ktor-server-netty:2.3.8' - implementation 'io.ktor:ktor-network-tls-certificates:2.3.8' - implementation 'io.ktor:ktor-server-call-logging:2.3.8' + implementation 'io.ktor:ktor-http-jvm:3.0.1' + implementation 'io.ktor:ktor-server-netty:3.0.1' + implementation 'io.ktor:ktor-network-tls-certificates:3.0.1' + implementation 'io.ktor:ktor-server-call-logging:3.0.1' implementation 'io.netty:netty-handler:4.1.108.Final' implementation 'org.apache.groovy:groovy:4.0.23' implementation 'org.apache.groovy:groovy-json:4.0.23' diff --git a/consumer/src/main/kotlin/au/com/dius/pact/consumer/KTorMockServer.kt b/consumer/src/main/kotlin/au/com/dius/pact/consumer/KTorMockServer.kt index 3727ceefd..d1f972de4 100644 --- a/consumer/src/main/kotlin/au/com/dius/pact/consumer/KTorMockServer.kt +++ b/consumer/src/main/kotlin/au/com/dius/pact/consumer/KTorMockServer.kt @@ -10,55 +10,43 @@ import au.com.dius.pact.core.model.Pact import au.com.dius.pact.core.model.Request import au.com.dius.pact.core.model.Response import au.com.dius.pact.core.support.Result +import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode import io.ktor.server.application.ApplicationCall import io.ktor.server.application.ApplicationCallPipeline import io.ktor.server.application.install -import io.ktor.server.engine.applicationEngineEnvironment +import io.ktor.server.application.serverConfig +import io.ktor.server.engine.EngineConnectorConfig import io.ktor.server.engine.connector import io.ktor.server.engine.embeddedServer import io.ktor.server.engine.sslConnector import io.ktor.server.netty.Netty -import io.ktor.server.netty.NettyApplicationEngine -import io.ktor.server.plugins.callloging.CallLogging +import io.ktor.server.plugins.calllogging.CallLogging import io.ktor.server.request.httpMethod import io.ktor.server.request.path import io.ktor.server.request.receiveStream import io.ktor.server.response.header import io.ktor.server.response.respond import io.ktor.server.response.respondBytes -import io.ktor.util.network.hostname -import io.ktor.util.network.port -import io.netty.channel.Channel +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import io.github.oshai.kotlinlogging.KLogging -import java.net.SocketAddress +import java.net.URI import java.util.zip.DeflaterInputStream import java.util.zip.GZIPInputStream +private val logger = KotlinLogging.logger {} + class KTorMockServer @JvmOverloads constructor( pact: BasePact, config: MockProviderConfig, private val stopTimeout: Long = 20000 ) : BaseMockServer(pact, config) { + private var localAddress: EngineConnectorConfig? = null - private val env = applicationEngineEnvironment { - if (config is MockHttpsProviderConfig) { - sslConnector(keyStore = config.keyStore!!, keyAlias = config.keyStoreAlias, - keyStorePassword = { config.keystorePassword.toCharArray() }, - privateKeyPassword = { config.privateKeyPassword.toCharArray() }) { - host = config.hostname - port = config.port - } - } else { - connector { - host = config.hostname - port = config.port - } - } - + private val serverProperties = serverConfig { module { install(CallLogging) intercept(ApplicationCallPipeline.Call) { @@ -81,8 +69,21 @@ class KTorMockServer @JvmOverloads constructor( } } } - - private var server: NettyApplicationEngine = embeddedServer(Netty, environment = env, configure = {}) + private var server = embeddedServer(Netty, serverProperties) { + if (config is MockHttpsProviderConfig) { + sslConnector(keyStore = config.keyStore!!, keyAlias = config.keyStoreAlias, + keyStorePassword = { config.keystorePassword.toCharArray() }, + privateKeyPassword = { config.privateKeyPassword.toCharArray() }) { + host = config.hostname + port = config.port + } + } else { + connector { + host = config.hostname + port = config.port + } + } + } private suspend fun pactResponseToKTorResponse(response: IResponse, call: ApplicationCall) { response.headers.forEach { entry -> @@ -121,30 +122,29 @@ class KTorMockServer @JvmOverloads constructor( } override fun getUrl(): String { - val address = socketAddress() - return if (address != null) { + val connectorConfig = server.engine.configuration.connectors.first() + return if (localAddress != null) { // Stupid GitHub Windows agents - val host = if (address.hostname.lowercase() == "miningmadness.com") { - config.hostname + val host = if (localAddress!!.host.lowercase() == "miningmadness.com") { + connectorConfig.host } else { - address.hostname + localAddress!!.host } - "${config.scheme}://$host:${address.port}" + URI(config.scheme, null, host, localAddress!!.port, null, null, null).toString() } else { - val connectorConfig = server.environment.connectors.first() - "${config.scheme}://${connectorConfig.host}:${connectorConfig.port}" + URI(config.scheme, null, connectorConfig.host, connectorConfig.port, null, null, null).toString() } } - private fun socketAddress(): SocketAddress? { - val field = server.javaClass.getDeclaredField("channels") - field.isAccessible = true - val channels = field.get(server) as List? - return channels?.first()?.localAddress() + override fun getPort(): Int { + return if (localAddress != null) { + localAddress!!.port + } else { + val connectorConfig = server.engine.configuration.connectors.first() + connectorConfig.port + } } - override fun getPort() = socketAddress()?.port ?: server.environment.connectors.first().port - override fun updatePact(pact: Pact): Pact { return if (pact.isV4Pact()) { when (val p = pact.asV4Pact()) { @@ -163,14 +163,17 @@ class KTorMockServer @JvmOverloads constructor( override fun start() { logger.debug { "Starting mock server" } + + CoroutineScope(server.application.coroutineContext).launch { + localAddress = server.engine.resolvedConnectors().first() + } + server.start() - logger.debug { "Mock server started: ${server.environment.connectors}" } + logger.debug { "Mock server started: $localAddress" } } override fun stop() { server.stop(100, stopTimeout) logger.debug { "Mock server shutdown" } } - - companion object : KLogging() } diff --git a/consumer/src/test/groovy/au/com/dius/pact/consumer/MockHttpServerSpec.groovy b/consumer/src/test/groovy/au/com/dius/pact/consumer/MockHttpServerSpec.groovy index 457d0ddda..4ced6ca65 100644 --- a/consumer/src/test/groovy/au/com/dius/pact/consumer/MockHttpServerSpec.groovy +++ b/consumer/src/test/groovy/au/com/dius/pact/consumer/MockHttpServerSpec.groovy @@ -94,6 +94,7 @@ class MockHttpServerSpec extends Specification { then: mockServer.url ==~ /http:\/\/[a-z0-9\-]+\:\d+/ + mockServer.port > 0 cleanup: mockServer.stop() @@ -113,11 +114,36 @@ class MockHttpServerSpec extends Specification { MockHttpsServer | '::1' | 1238 MockHttpsServer | 'ip6-localhost' | 0 MockHttpsServer | 'ip6-localhost' | 1239 +// KTorMockServer | '[::1]' | 0 // KTor server does not do reverse lookups of the bound host +// KTorMockServer | '[::1]' | 2234 +// KTorMockServer | '::1' | 0 +// KTorMockServer | '::1' | 2235 + KTorMockServer | 'ip6-localhost' | 0 + KTorMockServer | 'ip6-localhost' | 2236 + } + + def 'KTor IP6 test'() { + given: + def pact = new RequestResponsePact(new Provider(), new Consumer(), []) + def config = new MockProviderConfig(hostname, port) + + when: + def mockServer = mockServerClass.newInstance(pact, config) + mockServer.start() + + then: + mockServer.url ==~ /http:\/\/\[::1]:\d+/ + mockServer.port > 0 + + cleanup: + mockServer.stop() + + where: + + mockServerClass | hostname | port KTorMockServer | '[::1]' | 0 KTorMockServer | '[::1]' | 2234 KTorMockServer | '::1' | 0 KTorMockServer | '::1' | 2235 - KTorMockServer | 'ip6-localhost' | 0 - KTorMockServer | 'ip6-localhost' | 2236 } } diff --git a/pact-jvm-server/build.gradle b/pact-jvm-server/build.gradle index ec84e26d9..83dc83260 100644 --- a/pact-jvm-server/build.gradle +++ b/pact-jvm-server/build.gradle @@ -21,7 +21,7 @@ dependencies { implementation 'org.apache.commons:commons-text' implementation 'org.apache.tika:tika-core' implementation('io.netty:netty-handler') { - exclude module: 'netty-transport-native-kqueue' +// exclude module: 'netty-transport-native-kqueue' } implementation 'io.ktor:ktor-server-netty' implementation 'io.ktor:ktor-network-tls-certificates' diff --git a/pact-jvm-server/src/main/kotlin/au/com/dius/pact/server/KTorHttpsKeystoreMockProvider.kt b/pact-jvm-server/src/main/kotlin/au/com/dius/pact/server/KTorHttpsKeystoreMockProvider.kt index 0acfdc031..dbb6998f7 100644 --- a/pact-jvm-server/src/main/kotlin/au/com/dius/pact/server/KTorHttpsKeystoreMockProvider.kt +++ b/pact-jvm-server/src/main/kotlin/au/com/dius/pact/server/KTorHttpsKeystoreMockProvider.kt @@ -9,11 +9,11 @@ import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode import io.ktor.server.application.ApplicationCallPipeline import io.ktor.server.application.install -import io.ktor.server.engine.applicationEngineEnvironment +import io.ktor.server.application.serverConfig import io.ktor.server.engine.embeddedServer import io.ktor.server.engine.sslConnector import io.ktor.server.netty.Netty -import io.ktor.server.plugins.callloging.CallLogging +import io.ktor.server.plugins.calllogging.CallLogging import io.ktor.server.request.httpMethod import io.ktor.server.response.header import io.ktor.server.response.respond @@ -21,22 +21,7 @@ import io.ktor.server.response.respond private val logger = KotlinLogging.logger {} class KTorHttpsKeystoreMockProvider(override val config: MockHttpsProviderConfig): BaseKTorMockProvider(config) { - private val serverHostname = config.hostname - private val serverPort = config.port - private val keyStore = config.keyStore!! - private val keyStoreAlias = config.keyStoreAlias - private val password = config.keystorePassword - private val privateKeyPassword = config.privateKeyPassword - - private val env = applicationEngineEnvironment { - sslConnector(keyStore = keyStore, - keyAlias = keyStoreAlias, - keyStorePassword = { password.toCharArray() }, - privateKeyPassword = { privateKeyPassword.toCharArray() }) { - host = serverHostname - port = serverPort - } - + private val serverProperties = serverConfig { module { install(CallLogging) intercept(ApplicationCallPipeline.Call) { @@ -59,7 +44,13 @@ class KTorHttpsKeystoreMockProvider(override val config: MockHttpsProviderConfig } } - init { - server = embeddedServer(Netty, environment = env, configure = {}) + override var server = embeddedServer(Netty, serverProperties) { + sslConnector(keyStore = config.keyStore!!, + keyAlias = config.keyStoreAlias, + keyStorePassword = { config.keystorePassword.toCharArray() }, + privateKeyPassword = { config.privateKeyPassword.toCharArray() }) { + host = config.hostname + port = config.port + } } } diff --git a/pact-jvm-server/src/main/kotlin/au/com/dius/pact/server/KTorMockProvider.kt b/pact-jvm-server/src/main/kotlin/au/com/dius/pact/server/KTorMockProvider.kt index c853a18a8..da9ace76d 100644 --- a/pact-jvm-server/src/main/kotlin/au/com/dius/pact/server/KTorMockProvider.kt +++ b/pact-jvm-server/src/main/kotlin/au/com/dius/pact/server/KTorMockProvider.kt @@ -12,19 +12,23 @@ import io.ktor.http.HttpStatusCode import io.ktor.server.application.ApplicationCall import io.ktor.server.application.ApplicationCallPipeline import io.ktor.server.application.install -import io.ktor.server.engine.applicationEngineEnvironment +import io.ktor.server.application.serverConfig +import io.ktor.server.engine.EmbeddedServer +import io.ktor.server.engine.EngineConnectorConfig import io.ktor.server.engine.connector import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty import io.ktor.server.netty.NettyApplicationEngine -import io.ktor.server.plugins.callloging.CallLogging +import io.ktor.server.plugins.calllogging.CallLogging import io.ktor.server.request.httpMethod import io.ktor.server.request.path import io.ktor.server.request.receiveStream import io.ktor.server.response.header import io.ktor.server.response.respond import io.ktor.server.response.respondBytes +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.util.zip.DeflaterInputStream import java.util.zip.GZIPInputStream @@ -33,7 +37,8 @@ private val logger = KotlinLogging.logger {} abstract class BaseKTorMockProvider(override val config: MockProviderConfig): StatefulMockProvider() { - lateinit var server: NettyApplicationEngine + open lateinit var server: EmbeddedServer + private lateinit var localAddress: EngineConnectorConfig suspend fun toPactRequest(call: ApplicationCall): Request { val request = call.request @@ -77,8 +82,13 @@ abstract class BaseKTorMockProvider(override val config: MockProviderConfig): St override fun start() { logger.debug { "Starting mock server" } + + CoroutineScope(server.application.coroutineContext).launch { + localAddress = server.engine.resolvedConnectors().first() + } + server.start() - logger.debug { "Mock server started: ${server.environment.connectors}" } + logger.debug { "Mock server started: $localAddress" } } override fun stop() { @@ -92,15 +102,7 @@ abstract class BaseKTorMockProvider(override val config: MockProviderConfig): St } class KTorMockProvider(override val config: MockProviderConfig): BaseKTorMockProvider(config) { - private val serverHostname = config.hostname - private val serverPort = config.port - - private val env = applicationEngineEnvironment { - connector { - host = serverHostname - port = serverPort - } - + private val serverProperties = serverConfig { module { install(CallLogging) intercept(ApplicationCallPipeline.Call) { @@ -123,7 +125,10 @@ class KTorMockProvider(override val config: MockProviderConfig): BaseKTorMockPro } } - init { - server = embeddedServer(Netty, environment = env, configure = {}) + override var server = embeddedServer(Netty, serverProperties) { + connector { + host = config.hostname + port = config.port + } } } diff --git a/pact-jvm-server/src/main/kotlin/au/com/dius/pact/server/MainServer.kt b/pact-jvm-server/src/main/kotlin/au/com/dius/pact/server/MainServer.kt index eb0301a25..278240913 100644 --- a/pact-jvm-server/src/main/kotlin/au/com/dius/pact/server/MainServer.kt +++ b/pact-jvm-server/src/main/kotlin/au/com/dius/pact/server/MainServer.kt @@ -8,11 +8,11 @@ import io.ktor.http.HttpStatusCode import io.ktor.server.application.ApplicationCall import io.ktor.server.application.ApplicationCallPipeline import io.ktor.server.application.install -import io.ktor.server.engine.applicationEngineEnvironment +import io.ktor.server.application.serverConfig import io.ktor.server.engine.connector import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty -import io.ktor.server.plugins.callloging.CallLogging +import io.ktor.server.plugins.calllogging.CallLogging import io.ktor.server.request.httpMethod import io.ktor.server.request.path import io.ktor.server.request.receiveStream @@ -26,12 +26,7 @@ import java.util.zip.GZIPInputStream data class ServerStateStore(var state: ServerState = ServerState()) class MainServer(val store: ServerStateStore, val serverConfig: Config) { - private val env = applicationEngineEnvironment { - connector { - host = serverConfig.host - port = serverConfig.port - } - + private val serverProperties = serverConfig { module { install(CallLogging) intercept(ApplicationCallPipeline.Call) { @@ -43,7 +38,12 @@ class MainServer(val store: ServerStateStore, val serverConfig: Config) { } } - val server = embeddedServer(Netty, environment = env, configure = {}) + var server = embeddedServer(Netty, serverProperties) { + connector { + host = serverConfig.host + port = serverConfig.port + } + } suspend fun toPactRequest(call: ApplicationCall): Request { val request = call.request