diff --git a/README.md b/README.md index eeeedd3..64728d9 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ kotlin { ```kotlin val opt = IO.Options() // opt.trustAllCerts = true +// opt.httpClient = yourSharedHttpClient IO.socket("http://localhost:3000", opt) { socket -> socket.on(Socket.EVENT_CONNECT) { args -> println("on connect ${args.joinToString()}") @@ -53,6 +54,10 @@ IO.socket("http://localhost:3000", opt) { socket -> } ``` +If you set `opt.httpClient`, kmp-socketio will reuse this externally managed Ktor `HttpClient` +for both polling and websocket transports. +When websocket transport is enabled, make sure your shared client installs the Ktor `WebSockets` plugin. + Most of the APIs are the same as socket.io-client-java, here are some differences: - Create socket is asynchronous, to make it's easier to guarantee thread safety. diff --git a/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/EngineSocket.kt b/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/EngineSocket.kt index 787ebcc..ee7f641 100644 --- a/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/EngineSocket.kt +++ b/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/EngineSocket.kt @@ -218,6 +218,7 @@ class EngineSocket( opts.timestampParam = options?.timestampParam ?: opt.timestampParam opts.extraHeaders = opt.extraHeaders opts.trustAllCerts = opt.trustAllCerts + opts.httpClient = options?.httpClient ?: opt.httpClient val transport = factory.create(name, opts, scope, rawMessage) emit(EVENT_TRANSPORT, transport) diff --git a/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/Transport.kt b/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/Transport.kt index c2a89e3..67cc1e6 100644 --- a/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/Transport.kt +++ b/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/Transport.kt @@ -3,6 +3,7 @@ package com.piasy.kmp.socketio.engineio import com.piasy.kmp.socketio.emitter.Emitter import com.piasy.kmp.xlog.Logging import com.piasy.kmp.socketio.parseqs.ParseQS +import io.ktor.client.HttpClient import io.ktor.util.date.* import kotlinx.coroutines.CoroutineScope import org.hildan.socketio.EngineIOPacket @@ -42,6 +43,12 @@ abstract class Transport( @JvmField var trustAllCerts: Boolean = false + + /** + * Optional externally managed ktor HttpClient to reuse. + */ + @JvmField + var httpClient: HttpClient? = null } protected var state = State.INIT diff --git a/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/transports/PollingXHR.kt b/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/transports/PollingXHR.kt index cca866d..b2aefd4 100644 --- a/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/transports/PollingXHR.kt +++ b/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/transports/PollingXHR.kt @@ -17,7 +17,10 @@ open class PollingXHR( opt: Options, scope: CoroutineScope, private val ioScope: CoroutineScope = CoroutineScope(Dispatchers.Default), - private val factory: HttpClientFactory = DefaultHttpClientFactory(trustAllCerts = opt.trustAllCerts), + private val factory: HttpClientFactory = DefaultHttpClientFactory( + externalHttpClient = opt.httpClient, + trustAllCerts = opt.trustAllCerts, + ), rawMessage: Boolean, ) : Transport(opt, scope, NAME, rawMessage) { private var polling = false diff --git a/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/transports/WebSocket.kt b/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/transports/WebSocket.kt index 340d988..2bc7061 100644 --- a/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/transports/WebSocket.kt +++ b/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/transports/WebSocket.kt @@ -23,7 +23,10 @@ open class WebSocket( opt: Options, scope: CoroutineScope, private val ioScope: CoroutineScope = CoroutineScope(Dispatchers.Default), - private val factory: HttpClientFactory = DefaultHttpClientFactory(trustAllCerts = opt.trustAllCerts), + private val factory: HttpClientFactory = DefaultHttpClientFactory( + externalHttpClient = opt.httpClient, + trustAllCerts = opt.trustAllCerts, + ), rawMessage: Boolean, ) : Transport(opt, scope, NAME, rawMessage) { private var ws: WebSocketSession? = null diff --git a/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/transports/transport.kt b/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/transports/transport.kt index 2902378..eb73922 100644 --- a/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/transports/transport.kt +++ b/kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/transports/transport.kt @@ -60,21 +60,24 @@ interface HttpClientFactory { } class DefaultHttpClientFactory( + externalHttpClient: HttpClient? = null, trustAllCerts: Boolean = false, ): HttpClientFactory { - private val wsClient = httpClient( - trustAllCerts = trustAllCerts, - ) { - install(Logging) { - logger = object : Logger { - override fun log(message: String) { - com.piasy.kmp.xlog.Logging.info("Net", message) + private val wsClient = externalHttpClient ?: run { + httpClient( + trustAllCerts = trustAllCerts, + ) { + install(Logging) { + logger = object : Logger { + override fun log(message: String) { + com.piasy.kmp.xlog.Logging.info("Net", message) + } } + level = LogLevel.ALL + } + install(WebSockets) { + pingIntervalMillis = 20_000 } - level = LogLevel.ALL - } - install(WebSockets) { - pingIntervalMillis = 20_000 } } // Linux curl engine doesn't work for simultaneous websocket and http request. diff --git a/kmp-socketio/src/jvmTest/kotlin/com/piasy/kmp/socketio/engineio/EngineSocketTest.kt b/kmp-socketio/src/jvmTest/kotlin/com/piasy/kmp/socketio/engineio/EngineSocketTest.kt index 13a1383..0cde72c 100644 --- a/kmp-socketio/src/jvmTest/kotlin/com/piasy/kmp/socketio/engineio/EngineSocketTest.kt +++ b/kmp-socketio/src/jvmTest/kotlin/com/piasy/kmp/socketio/engineio/EngineSocketTest.kt @@ -3,8 +3,10 @@ package com.piasy.kmp.socketio.engineio import com.piasy.kmp.socketio.engineio.transports.PollingXHR import com.piasy.kmp.socketio.engineio.transports.TransportFactory import com.piasy.kmp.socketio.engineio.transports.WebSocket +import io.ktor.client.HttpClient import io.mockk.every import io.mockk.mockk +import io.mockk.slot import io.mockk.spyk import io.mockk.verify import kotlinx.coroutines.* @@ -19,6 +21,7 @@ import org.hildan.socketio.SocketIOPacket import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse +import kotlin.test.assertSame class EngineSocketTest : BaseTest() { @@ -116,6 +119,25 @@ class EngineSocketTest : BaseTest() { assertEquals(listOf(EngineSocket.EVENT_TRANSPORT), sock.events) } + @Test + fun openWithExternalHttpClient() = runTest { + val opt = EngineSocket.Options() + opt.transports = listOf(PollingXHR.NAME) + val externalHttpClient = mockk(relaxed = true) + opt.httpClient = externalHttpClient + + val transport = spyk(TestTransport(Transport.Options(), this, PollingXHR.NAME)) + val factory = mockk() + val transportOpt = slot() + every { factory.create(any(), capture(transportOpt), any(), any()) } returns transport + + val socket = EngineSocket("http://localhost", opt, this, factory) + socket.open() + advanceUntilIdle() + + assertSame(externalHttpClient, transportOpt.captured.httpClient) + } + @Test fun openSuccess() = runTest { val sock = prepareSocket(listOf(WebSocket.NAME), this)