Skip to content

Commit 2047f4d

Browse files
authoredMar 11, 2025
Migrate chat sample to new transport API and kotlinx.io (#297)
Simplify the code, as we now have ktor transports fully KMP ready
1 parent cdef580 commit 2047f4d

File tree

31 files changed

+206
-520
lines changed

31 files changed

+206
-520
lines changed
 

‎samples/chat/README.md

+11-10
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@
22

33
* api - shared chat API for both client and server
44
* client - client API implementation via requesting to RSocket with Protobuf serialization.
5-
Works on JVM(TCP/WS), Native(TCP/WS), Js(WS).
5+
Works on JVM/Js/WasmJs/Native over TCP and WS.
66
Tasks for running clients:
7-
* JVM: `run`
8-
* Native: `runDebugExecutable[TARGET]` / `runReleaseExecutable[TARGET]`
9-
(where `[TARGET]` is one of `LinuxX64`, `MacosArm64` or `MacosX64`)
10-
* NodeJs: `jsNodeRun` / `jsNodeDevelopmentRun` / `jsNodeProductionRun`
11-
* Browser: `jsBrowserRun` / `jsBrowserDevelopmentRun` / `jsBrowserProductionRun`
7+
* JVM: `jvmRun`
8+
* Native: `runDebugExecutable[TARGET]` / `runReleaseExecutable[TARGET]`
9+
(where `[TARGET]` is one of `LinuxX64`, `MacosArm64`, `MacosX64` or `MingwX64`)
10+
* JS: `jsNodeRun` / `jsNodeDevelopmentRun` / `jsNodeProductionRun`
11+
* WasmJs: `wasmJsNodeRun` / `wasmJsNodeDevelopmentRun` / `wasmJsNodeProductionRun`
1212
* server - server API implementation with storage in concurrent map
1313
and exposing it through RSocket with Protobuf serialization.
1414
Can be started on JVM(TCP/WS), Native(TCP/WS), NodeJS(TCP).
1515
Tasks for running servers:
16-
* JVM: `run`
17-
* Native: `runDebugExecutable[TARGET]` / `runReleaseExecutable[TARGET]`
18-
(where `[TARGET]` is one of `LinuxX64`, `MacosArm64` or `MacosX64`)
19-
* NodeJs: `jsNodeRun` / `jsNodeDevelopmentRun` / `jsNodeProductionRun`
16+
* JVM: `jvmRun`
17+
* Native: `runDebugExecutable[TARGET]` / `runReleaseExecutable[TARGET]`
18+
(where `[TARGET]` is one of `LinuxX64`, `MacosArm64`, `MacosX64` or `MingwX64`)
19+
* JS: `jsNodeRun` / `jsNodeDevelopmentRun` / `jsNodeProductionRun`
20+
* WasmJs: `wasmJsNodeRun` / `wasmJsNodeDevelopmentRun` / `wasmJsNodeProductionRun`

‎samples/chat/api/build.gradle.kts

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2015-2024 the original author or authors.
2+
* Copyright 2015-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,6 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17+
import org.jetbrains.kotlin.gradle.*
18+
1719
plugins {
1820
kotlin("multiplatform")
1921
kotlin("plugin.serialization")
@@ -25,12 +27,16 @@ val kotlinxSerializationVersion: String by rootProject
2527
kotlin {
2628
jvm()
2729
js {
28-
browser()
30+
nodejs()
31+
}
32+
@OptIn(ExperimentalWasmDsl::class)
33+
wasmJs {
2934
nodejs()
3035
}
3136
linuxX64()
3237
macosX64()
3338
macosArm64()
39+
mingwX64()
3440

3541
sourceSets {
3642
commonMain.dependencies {

‎samples/chat/api/src/commonMain/kotlin/Serialization.kt

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2015-2022 the original author or authors.
2+
* Copyright 2015-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,10 +16,10 @@
1616

1717
package io.rsocket.kotlin.samples.chat.api
1818

19-
import io.ktor.utils.io.core.*
2019
import io.rsocket.kotlin.*
2120
import io.rsocket.kotlin.metadata.*
2221
import io.rsocket.kotlin.payload.*
22+
import kotlinx.io.*
2323
import kotlinx.serialization.*
2424
import kotlinx.serialization.protobuf.*
2525
import kotlin.jvm.*
@@ -29,7 +29,7 @@ import kotlin.jvm.*
2929
val ConfiguredProtoBuf = ProtoBuf
3030

3131
@ExperimentalSerializationApi
32-
inline fun <reified T> ProtoBuf.decodeFromPayload(payload: Payload): T = decodeFromByteArray(payload.data.readBytes())
32+
inline fun <reified T> ProtoBuf.decodeFromPayload(payload: Payload): T = decodeFromByteArray(payload.data.readByteArray())
3333

3434
@ExperimentalSerializationApi
3535
@OptIn(ExperimentalMetadataApi::class)
@@ -55,8 +55,8 @@ inline fun <reified I> ProtoBuf.decoding(payload: Payload, block: (I) -> Unit):
5555
}
5656

5757
@OptIn(ExperimentalMetadataApi::class)
58-
fun Payload(route: String, packet: ByteReadPacket = ByteReadPacket.Empty): Payload = buildPayload {
59-
data(packet)
58+
fun Payload(route: String, data: Buffer = Buffer()): Payload = buildPayload {
59+
data(data)
6060
metadata(RoutingMetadata(route))
6161
}
6262

Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2015-2022 the original author or authors.
2+
* Copyright 2015-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,29 +21,24 @@ enum class TransportType { TCP, WS }
2121
data class ServerAddress(val port: Int, val type: TransportType)
2222

2323
object Servers {
24-
object JVM {
25-
val TCP = ServerAddress(port = 8001, type = TransportType.TCP)
26-
val WS = ServerAddress(port = 8002, type = TransportType.WS)
27-
}
28-
29-
object JS {
30-
val TCP = ServerAddress(port = 7001, type = TransportType.TCP)
31-
}
32-
33-
object Native {
34-
val TCP = ServerAddress(port = 9001, type = TransportType.TCP)
35-
val WS = ServerAddress(port = 9002, type = TransportType.WS)
36-
}
24+
val JVM = listOf(
25+
ServerAddress(port = 9011, type = TransportType.TCP),
26+
ServerAddress(port = 9012, type = TransportType.WS),
27+
)
3728

38-
val WS = setOf(
39-
JVM.WS,
40-
Native.WS
29+
val JS = listOf(
30+
ServerAddress(port = 9051, type = TransportType.TCP),
31+
ServerAddress(port = 9052, type = TransportType.WS),
4132
)
42-
val TCP = setOf(
43-
JVM.TCP,
44-
JS.TCP,
45-
Native.TCP
33+
34+
val WasmJS = listOf(
35+
ServerAddress(port = 9061, type = TransportType.TCP),
36+
ServerAddress(port = 9062, type = TransportType.WS),
4637
)
4738

48-
val ALL = WS + TCP
39+
val Native = listOf(
40+
ServerAddress(port = 9041, type = TransportType.TCP),
41+
ServerAddress(port = 9042, type = TransportType.WS),
42+
)
43+
val ALL = JVM + JS + WasmJS + Native
4944
}

‎samples/chat/client/build.gradle.kts

+14-18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2015-2024 the original author or authors.
2+
* Copyright 2015-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,33 +14,37 @@
1414
* limitations under the License.
1515
*/
1616

17+
import org.jetbrains.kotlin.gradle.*
1718
import org.jetbrains.kotlin.gradle.plugin.mpp.*
1819

1920
plugins {
2021
kotlin("multiplatform")
2122
kotlin("plugin.serialization")
22-
application
2323
}
2424

2525
val rsocketVersion: String by rootProject
2626
val ktorVersion: String by rootProject
2727

28-
application {
29-
mainClass.set("io.rsocket.kotlin.samples.chat.client.AppKt")
30-
}
31-
3228
kotlin {
3329
jvm {
34-
withJava()
30+
@OptIn(ExperimentalKotlinGradlePluginApi::class)
31+
mainRun {
32+
this.mainClass.set("io.rsocket.kotlin.samples.chat.client.AppKt")
33+
}
3534
}
3635
js {
37-
browser()
36+
nodejs()
37+
binaries.executable()
38+
}
39+
@OptIn(ExperimentalWasmDsl::class)
40+
wasmJs {
3841
nodejs()
3942
binaries.executable()
4043
}
4144
linuxX64()
4245
macosX64()
4346
macosArm64()
47+
mingwX64()
4448
targets.withType<KotlinNativeTarget>().configureEach {
4549
binaries {
4650
executable {
@@ -52,18 +56,10 @@ kotlin {
5256
sourceSets {
5357
commonMain.dependencies {
5458
implementation(project(":api"))
55-
implementation("io.rsocket.kotlin:rsocket-transport-ktor-websocket-client:$rsocketVersion")
56-
}
57-
jvmMain.dependencies {
58-
implementation("io.rsocket.kotlin:rsocket-transport-ktor-tcp:$rsocketVersion")
59-
implementation("io.ktor:ktor-client-cio:$ktorVersion")
60-
}
61-
nativeMain.dependencies {
59+
6260
implementation("io.rsocket.kotlin:rsocket-transport-ktor-tcp:$rsocketVersion")
61+
implementation("io.rsocket.kotlin:rsocket-transport-ktor-websocket-client:$rsocketVersion")
6362
implementation("io.ktor:ktor-client-cio:$ktorVersion")
6463
}
65-
jsMain.dependencies {
66-
implementation("io.ktor:ktor-client-js:$ktorVersion")
67-
}
6864
}
6965
}

‎samples/chat/client/src/commonMain/kotlin/ApiClient.kt

-44
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2015-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.rsocket.kotlin.samples.chat.client
18+
19+
import io.ktor.client.engine.cio.*
20+
import io.rsocket.kotlin.*
21+
import io.rsocket.kotlin.core.*
22+
import io.rsocket.kotlin.payload.*
23+
import io.rsocket.kotlin.samples.chat.api.*
24+
import io.rsocket.kotlin.transport.ktor.tcp.*
25+
import io.rsocket.kotlin.transport.ktor.websocket.client.*
26+
import kotlinx.coroutines.*
27+
import kotlinx.serialization.*
28+
import kotlin.coroutines.*
29+
30+
@OptIn(ExperimentalSerializationApi::class)
31+
class ApiClient(rSocket: RSocket) {
32+
private val proto = ConfiguredProtoBuf
33+
val users = UserApiClient(rSocket, proto)
34+
val chats = ChatApiClient(rSocket, proto)
35+
val messages = MessageApiClient(rSocket, proto)
36+
}
37+
38+
suspend fun runClient(
39+
addresses: List<ServerAddress>,
40+
name: String,
41+
target: String,
42+
): Unit = supervisorScope {
43+
addresses.forEach { address ->
44+
launch {
45+
val client = ApiClient(coroutineContext, address, name)
46+
val message = "RSocket is awesome! (from $target)"
47+
48+
val chat = client.chats.all().firstOrNull() ?: client.chats.new("rsocket-kotlin chat")
49+
50+
val sentMessage = client.messages.send(chat.id, message)
51+
println("Send to [$address]: $sentMessage")
52+
53+
client.messages.messages(chat.id, -1).collect {
54+
println("Received from [$address]: $it")
55+
}
56+
}
57+
}
58+
}
59+
60+
private suspend fun ApiClient(
61+
context: CoroutineContext,
62+
address: ServerAddress,
63+
name: String,
64+
): ApiClient {
65+
println("Connecting client to: $address")
66+
val connector = RSocketConnector {
67+
connectionConfig {
68+
setupPayload { buildPayload { data(name) } }
69+
}
70+
}
71+
72+
val target = when (address.type) {
73+
TransportType.TCP -> KtorTcpClientTransport(context).target(host = "127.0.0.1", port = address.port)
74+
TransportType.WS -> KtorWebSocketClientTransport(context) {
75+
httpEngine(CIO)
76+
}.target(host = "127.0.0.1", port = address.port)
77+
}
78+
79+
80+
return ApiClient(connector.connect(target))
81+
}

‎samples/chat/client/src/commonMain/kotlin/usage.kt

-33
This file was deleted.

0 commit comments

Comments
 (0)