diff --git a/src/commonMain/kotlin/me/devnatan/dockerkt/io/Http.kt b/src/commonMain/kotlin/me/devnatan/dockerkt/io/Http.kt index 2eef12af..e1e0e646 100644 --- a/src/commonMain/kotlin/me/devnatan/dockerkt/io/Http.kt +++ b/src/commonMain/kotlin/me/devnatan/dockerkt/io/Http.kt @@ -4,6 +4,7 @@ import io.ktor.client.HttpClient import io.ktor.client.HttpClientConfig import io.ktor.client.call.body import io.ktor.client.engine.HttpClientEngineConfig +import io.ktor.client.engine.HttpClientEngineFactory import io.ktor.client.plugins.HttpResponseValidator import io.ktor.client.plugins.ResponseException import io.ktor.client.plugins.UserAgent @@ -27,62 +28,71 @@ import me.devnatan.dockerkt.DockerClient import me.devnatan.dockerkt.DockerResponseException import me.devnatan.dockerkt.GenericDockerErrorResponse +internal expect val defaultHttpClientEngine: HttpClientEngineFactory<*>? internal expect fun HttpClientConfig.configureHttpClient(client: DockerClient) internal fun createHttpClient(client: DockerClient): HttpClient { check(client.config.socketPath.isNotBlank()) { "Socket path cannot be blank" } - return HttpClient { - expectSuccess = true - - install(ContentNegotiation) { - json( - Json { - ignoreUnknownKeys = true - }, - ) - } - if (client.config.debugHttpCalls) { - install(Logging) { - logger = Logger.SIMPLE - level = LogLevel.ALL - } - } + val clientEngine = defaultHttpClientEngine + return if (clientEngine != null) { + HttpClient(clientEngine) { configure(client) } + } else { + HttpClient { configure(client) } + } +} + +private fun HttpClientConfig<*>.configure(client: DockerClient) { + expectSuccess = true + + install(ContentNegotiation) { + json( + Json { + ignoreUnknownKeys = true + }, + ) + } - install(UserAgent) { agent = "docker-kotlin" } - configureHttpClient(client) - - HttpResponseValidator { - handleResponseExceptionWithRequest { exception, _ -> - val responseException = exception as? ResponseException ?: return@handleResponseExceptionWithRequest - val exceptionResponse = responseException.response - println("exceptionResponse = ${exceptionResponse.body()}") - - val errorMessage = - runCatching { - exceptionResponse.body() - }.getOrNull()?.message - throw DockerResponseException( - cause = exception, - message = errorMessage, - statusCode = exceptionResponse.status, - ) - } + if (client.config.debugHttpCalls) { + install(Logging) { + logger = Logger.SIMPLE + level = LogLevel.ALL } + } + + install(UserAgent) { agent = "docker-kotlin" } + configureHttpClient(client) - defaultRequest { - contentType(ContentType.Application.Json) + HttpResponseValidator { + handleResponseExceptionWithRequest { exception, _ -> + val responseException = exception as? ResponseException ?: return@handleResponseExceptionWithRequest + val exceptionResponse = responseException.response + println("exceptionResponse = ${exceptionResponse.body()}") - // workaround for URL prepending - // https://github.com/ktorio/ktor/issues/537#issuecomment-603272476 - url.takeFrom( - URLBuilder(createUrlBuilder(client.config.socketPath)).apply { - encodedPath = "/v${client.config.apiVersion}/" - encodedPath += url.encodedPath - }, + val errorMessage = + runCatching { + exceptionResponse.body() + }.getOrNull()?.message + throw DockerResponseException( + cause = exception, + message = errorMessage, + statusCode = exceptionResponse.status, ) } } + + defaultRequest { + contentType(ContentType.Application.Json) + + // workaround for URL prepending + // https://github.com/ktorio/ktor/issues/537#issuecomment-603272476 + url.takeFrom( + URLBuilder(createUrlBuilder(client.config.socketPath)).apply { + encodedPath = "/v${client.config.apiVersion}/" + encodedPath += url.encodedPath + }, + ) + } } @OptIn(ExperimentalStdlibApi::class) @@ -91,7 +101,8 @@ private fun createUrlBuilder(socketPath: String): URLBuilder = URLBuilder( protocol = URLProtocol.HTTP, port = DockerSocketPort, - host = socketPath.substringAfter(UnixSocketPrefix).encodeToByteArray().toHexString() + EncodedHostnameSuffix, + host = socketPath.substringAfter(UnixSocketPrefix).encodeToByteArray() + .toHexString() + EncodedHostnameSuffix, ) } else { val url = Url(socketPath) diff --git a/src/jvmMain/kotlin/me/devnatan/dockerkt/io/Http.jvm.kt b/src/jvmMain/kotlin/me/devnatan/dockerkt/io/Http.jvm.kt index 25933535..aec77f68 100644 --- a/src/jvmMain/kotlin/me/devnatan/dockerkt/io/Http.jvm.kt +++ b/src/jvmMain/kotlin/me/devnatan/dockerkt/io/Http.jvm.kt @@ -2,6 +2,8 @@ package me.devnatan.dockerkt.io import io.ktor.client.HttpClientConfig import io.ktor.client.engine.HttpClientEngineConfig +import io.ktor.client.engine.HttpClientEngineFactory +import io.ktor.client.engine.okhttp.OkHttp import io.ktor.client.engine.okhttp.OkHttpConfig import me.devnatan.dockerkt.DockerClient import okhttp3.Interceptor @@ -34,6 +36,8 @@ private class UpgradeHeaderInterceptor : Interceptor { } } +internal actual val defaultHttpClientEngine: HttpClientEngineFactory<*>? get() = OkHttp + internal actual fun HttpClientConfig.configureHttpClient(client: DockerClient) { engine { // ensure that current engine is OkHttp, cannot use CIO due to a Ktor Client bug related to data streaming diff --git a/src/nativeMain/kotlin/me/devnatan/dockerkt/io/Http.native.kt b/src/nativeMain/kotlin/me/devnatan/dockerkt/io/Http.native.kt index fd2ccdb9..7a9385e7 100644 --- a/src/nativeMain/kotlin/me/devnatan/dockerkt/io/Http.native.kt +++ b/src/nativeMain/kotlin/me/devnatan/dockerkt/io/Http.native.kt @@ -2,8 +2,11 @@ package me.devnatan.dockerkt.io import io.ktor.client.HttpClientConfig import io.ktor.client.engine.HttpClientEngineConfig +import io.ktor.client.engine.HttpClientEngineFactory import me.devnatan.dockerkt.DockerClient +internal actual val defaultHttpClientEngine: HttpClientEngineFactory<*>? get() = null + internal actual fun HttpClientConfig.configureHttpClient(client: DockerClient) { TODO("Native HTTP client is not supported for now") }