Skip to content

Commit 81b4f9e

Browse files
committed
feat: support external HttpClient in socket options (#33)
1 parent 81f3596 commit 81b4f9e

7 files changed

Lines changed: 57 additions & 13 deletions

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ kotlin {
3939
```kotlin
4040
val opt = IO.Options()
4141
// opt.trustAllCerts = true
42+
// opt.httpClient = yourSharedHttpClient
4243
IO.socket("http://localhost:3000", opt) { socket ->
4344
socket.on(Socket.EVENT_CONNECT) { args ->
4445
println("on connect ${args.joinToString()}")
@@ -53,6 +54,10 @@ IO.socket("http://localhost:3000", opt) { socket ->
5354
}
5455
```
5556

57+
If you set `opt.httpClient`, kmp-socketio will reuse this externally managed Ktor `HttpClient`
58+
for both polling and websocket transports.
59+
When websocket transport is enabled, make sure your shared client installs the Ktor `WebSockets` plugin.
60+
5661
Most of the APIs are the same as socket.io-client-java, here are some differences:
5762

5863
- Create socket is asynchronous, to make it's easier to guarantee thread safety.

kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/EngineSocket.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ class EngineSocket(
218218
opts.timestampParam = options?.timestampParam ?: opt.timestampParam
219219
opts.extraHeaders = opt.extraHeaders
220220
opts.trustAllCerts = opt.trustAllCerts
221+
opts.httpClient = options?.httpClient ?: opt.httpClient
221222

222223
val transport = factory.create(name, opts, scope, rawMessage)
223224
emit(EVENT_TRANSPORT, transport)

kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/Transport.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.piasy.kmp.socketio.engineio
33
import com.piasy.kmp.socketio.emitter.Emitter
44
import com.piasy.kmp.xlog.Logging
55
import com.piasy.kmp.socketio.parseqs.ParseQS
6+
import io.ktor.client.HttpClient
67
import io.ktor.util.date.*
78
import kotlinx.coroutines.CoroutineScope
89
import org.hildan.socketio.EngineIOPacket
@@ -42,6 +43,12 @@ abstract class Transport(
4243

4344
@JvmField
4445
var trustAllCerts: Boolean = false
46+
47+
/**
48+
* Optional externally managed ktor HttpClient to reuse.
49+
*/
50+
@JvmField
51+
var httpClient: HttpClient? = null
4552
}
4653

4754
protected var state = State.INIT

kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/transports/PollingXHR.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ open class PollingXHR(
1717
opt: Options,
1818
scope: CoroutineScope,
1919
private val ioScope: CoroutineScope = CoroutineScope(Dispatchers.Default),
20-
private val factory: HttpClientFactory = DefaultHttpClientFactory(trustAllCerts = opt.trustAllCerts),
20+
private val factory: HttpClientFactory = DefaultHttpClientFactory(
21+
externalHttpClient = opt.httpClient,
22+
trustAllCerts = opt.trustAllCerts,
23+
),
2124
rawMessage: Boolean,
2225
) : Transport(opt, scope, NAME, rawMessage) {
2326
private var polling = false

kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/transports/WebSocket.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ open class WebSocket(
2323
opt: Options,
2424
scope: CoroutineScope,
2525
private val ioScope: CoroutineScope = CoroutineScope(Dispatchers.Default),
26-
private val factory: HttpClientFactory = DefaultHttpClientFactory(trustAllCerts = opt.trustAllCerts),
26+
private val factory: HttpClientFactory = DefaultHttpClientFactory(
27+
externalHttpClient = opt.httpClient,
28+
trustAllCerts = opt.trustAllCerts,
29+
),
2730
rawMessage: Boolean,
2831
) : Transport(opt, scope, NAME, rawMessage) {
2932
private var ws: WebSocketSession? = null

kmp-socketio/src/commonMain/kotlin/com/piasy/kmp/socketio/engineio/transports/transport.kt

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,21 +60,24 @@ interface HttpClientFactory {
6060
}
6161

6262
class DefaultHttpClientFactory(
63+
externalHttpClient: HttpClient? = null,
6364
trustAllCerts: Boolean = false,
6465
): HttpClientFactory {
65-
private val wsClient = httpClient(
66-
trustAllCerts = trustAllCerts,
67-
) {
68-
install(Logging) {
69-
logger = object : Logger {
70-
override fun log(message: String) {
71-
com.piasy.kmp.xlog.Logging.info("Net", message)
66+
private val wsClient = externalHttpClient ?: run {
67+
httpClient(
68+
trustAllCerts = trustAllCerts,
69+
) {
70+
install(Logging) {
71+
logger = object : Logger {
72+
override fun log(message: String) {
73+
com.piasy.kmp.xlog.Logging.info("Net", message)
74+
}
7275
}
76+
level = LogLevel.ALL
77+
}
78+
install(WebSockets) {
79+
pingIntervalMillis = 20_000
7380
}
74-
level = LogLevel.ALL
75-
}
76-
install(WebSockets) {
77-
pingIntervalMillis = 20_000
7881
}
7982
}
8083
// Linux curl engine doesn't work for simultaneous websocket and http request.

kmp-socketio/src/jvmTest/kotlin/com/piasy/kmp/socketio/engineio/EngineSocketTest.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package com.piasy.kmp.socketio.engineio
33
import com.piasy.kmp.socketio.engineio.transports.PollingXHR
44
import com.piasy.kmp.socketio.engineio.transports.TransportFactory
55
import com.piasy.kmp.socketio.engineio.transports.WebSocket
6+
import io.ktor.client.HttpClient
67
import io.mockk.every
78
import io.mockk.mockk
9+
import io.mockk.slot
810
import io.mockk.spyk
911
import io.mockk.verify
1012
import kotlinx.coroutines.*
@@ -19,6 +21,7 @@ import org.hildan.socketio.SocketIOPacket
1921
import kotlin.test.Test
2022
import kotlin.test.assertEquals
2123
import kotlin.test.assertFalse
24+
import kotlin.test.assertSame
2225

2326
class EngineSocketTest : BaseTest() {
2427

@@ -116,6 +119,25 @@ class EngineSocketTest : BaseTest() {
116119
assertEquals(listOf(EngineSocket.EVENT_TRANSPORT), sock.events)
117120
}
118121

122+
@Test
123+
fun openWithExternalHttpClient() = runTest {
124+
val opt = EngineSocket.Options()
125+
opt.transports = listOf(PollingXHR.NAME)
126+
val externalHttpClient = mockk<HttpClient>(relaxed = true)
127+
opt.httpClient = externalHttpClient
128+
129+
val transport = spyk(TestTransport(Transport.Options(), this, PollingXHR.NAME))
130+
val factory = mockk<TransportFactory>()
131+
val transportOpt = slot<Transport.Options>()
132+
every { factory.create(any(), capture(transportOpt), any(), any()) } returns transport
133+
134+
val socket = EngineSocket("http://localhost", opt, this, factory)
135+
socket.open()
136+
advanceUntilIdle()
137+
138+
assertSame(externalHttpClient, transportOpt.captured.httpClient)
139+
}
140+
119141
@Test
120142
fun openSuccess() = runTest {
121143
val sock = prepareSocket(listOf(WebSocket.NAME), this)

0 commit comments

Comments
 (0)