Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 56 additions & 45 deletions src/commonMain/kotlin/me/devnatan/dockerkt/io/Http.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 <T : HttpClientEngineConfig> HttpClientConfig<out T>.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<String>()}")

val errorMessage =
runCatching {
exceptionResponse.body<GenericDockerErrorResponse>()
}.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<String>()}")

// 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<GenericDockerErrorResponse>()
}.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)
Expand All @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions src/jvmMain/kotlin/me/devnatan/dockerkt/io/Http.jvm.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -34,6 +36,8 @@ private class UpgradeHeaderInterceptor : Interceptor {
}
}

internal actual val defaultHttpClientEngine: HttpClientEngineFactory<*>? get() = OkHttp

internal actual fun <T : HttpClientEngineConfig> HttpClientConfig<out T>.configureHttpClient(client: DockerClient) {
engine {
// ensure that current engine is OkHttp, cannot use CIO due to a Ktor Client bug related to data streaming
Expand Down
3 changes: 3 additions & 0 deletions src/nativeMain/kotlin/me/devnatan/dockerkt/io/Http.native.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 <T : HttpClientEngineConfig> HttpClientConfig<out T>.configureHttpClient(client: DockerClient) {
TODO("Native HTTP client is not supported for now")
}
Loading