Skip to content

Commit 86e27e3

Browse files
authored
Readme and examples: interaction, mpp-chat, nodejs-transport (#100)
1 parent 9820250 commit 86e27e3

File tree

37 files changed

+1784
-4
lines changed

37 files changed

+1784
-4
lines changed

README.md

Lines changed: 247 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,248 @@
11
# rsocket-kotlin
2-
RSocket Kotlin multi-platform implementation
2+
RSocket Kotlin multi-platform implementation based on [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines).
3+
4+
RSocket is a binary protocol for use on byte stream transports such as TCP, WebSockets and Aeron.
5+
6+
It enables the following symmetric interaction models via async message passing over a single connection:
7+
8+
- request/response (stream of 1)
9+
- request/stream (finite stream of many)
10+
- fire-and-forget (no response)
11+
- event subscription (infinite stream of many)
12+
13+
Learn more at http://rsocket.io
14+
15+
## Supported platforms and transports :
16+
Transports are implemented based on [ktor](https://github.com/ktorio/ktor) to ensure Kotlin multiplatform.
17+
So it depends on `ktor` engines for available transports and platforms (JVM, JS, Native):
18+
* JVM - TCP and WebSocket for both client and server
19+
* JS - WebSocket client only
20+
* [SOON] Native - TCP for both client and server
21+
22+
## Interactions
23+
24+
RSocket interface contains 5 methods:
25+
* Fire and Forget:
26+
27+
`suspend fun fireAndForget(payload: Payload)`
28+
* Request-Response:
29+
30+
`suspend requestResponse(payload: Payload): Payload`
31+
* Request-Stream:
32+
33+
`fun requestStream(payload: Payload): Flow<Payload>`
34+
* Request-Channel:
35+
36+
`fun requestChannel(payloads: Flow<Payload>): Flow<Payload>`
37+
* Metadata-Push:
38+
39+
`suspend fun metadataPush(metadata: ByteReadPacket)`
40+
41+
## Using in your projects
42+
The `master` branch is now dedicated to development of multiplatform rsocket-kotlin.
43+
For now only snapshots are available via [oss.jfrog.org](oss.jfrog.org) (OJO).
44+
45+
Make sure, that you use Kotlin 1.4.
46+
47+
### Gradle:
48+
49+
50+
```groovy
51+
repositories {
52+
maven { url 'https://oss.jfrog.org/oss-snapshot-local' }
53+
}
54+
dependencies {
55+
implementation 'io.rsocket.kotlin:rsocket-core:0.10.0-SNAPSHOT'
56+
implementation 'io.rsocket.kotlin:rsocket-transport-ktor:0.10.0-SNAPSHOT'
57+
58+
// client feature for ktor
59+
// implementation 'io.rsocket.kotlin:rsocket-transport-ktor-client:0.10.0-SNAPSHOT'
60+
61+
// server feature for ktor
62+
// implementation 'io.rsocket.kotlin:rsocket-transport-ktor-server:0.10.0-SNAPSHOT'
63+
64+
// one of ktor engines to work with websockets
65+
// client engines
66+
// implementation 'io.ktor:ktor-client-js:1.4.0' //js
67+
// implementation 'io.ktor:ktor-client-cio:1.4.0' //jvm
68+
// implementation 'io.ktor:ktor-client-okhttp:1.4.0' //jvm
69+
70+
// server engines (jvm only)
71+
// implementation 'io.ktor:ktor-server-cio:1.4.0'
72+
// implementation 'io.ktor:ktor-server-netty:1.4.0'
73+
// implementation 'io.ktor:ktor-server-jetty:1.4.0'
74+
// implementation 'io.ktor:ktor-server-tomcat:1.4.0'
75+
}
76+
```
77+
78+
### Gradle Kotlin DSL:
79+
80+
```kotlin
81+
repositories {
82+
maven("https://oss.jfrog.org/oss-snapshot-local")
83+
}
84+
dependencies {
85+
implementation("io.rsocket.kotlin:rsocket-core:0.10.0-SNAPSHOT")
86+
implementation("io.rsocket.kotlin:rsocket-transport-ktor:0.10.0-SNAPSHOT")
87+
88+
// client feature for ktor
89+
// implementation("io.rsocket.kotlin:rsocket-transport-ktor-client:0.10.0-SNAPSHOT")
90+
91+
// server feature for ktor
92+
// implementation("io.rsocket.kotlin:rsocket-transport-ktor-server:0.10.0-SNAPSHOT")
93+
94+
// one of ktor engines to work with websockets
95+
// client engines
96+
// implementation("io.ktor:ktor-client-js:1.4.0") //js
97+
// implementation("io.ktor:ktor-client-cio:1.4.0") //jvm
98+
// implementation("io.ktor:ktor-client-okhttp:1.4.0") //jvm
99+
100+
// server engines (jvm only)
101+
// implementation("io.ktor:ktor-server-cio:1.4.0")
102+
// implementation("io.ktor:ktor-server-netty:1.4.0")
103+
// implementation("io.ktor:ktor-server-jetty:1.4.0")
104+
// implementation("io.ktor:ktor-server-tomcat:1.4.0")
105+
}
106+
```
107+
108+
## Usage
109+
110+
### Client WebSocket with CIO ktor engine
111+
112+
```kotlin
113+
//create ktor client
114+
val client = HttpClient(CIO) {
115+
install(WebSockets)
116+
install(RSocketClientSupport) {
117+
//configure rSocket client (all values have defaults)
118+
119+
keepAlive = KeepAlive(
120+
interval = 30.seconds,
121+
maxLifetime = 2.minutes
122+
)
123+
124+
//payload for setup frame
125+
setupPayload = Payload(...)
126+
127+
//mime types
128+
payloadMimeType = PayloadMimeType(
129+
data = "application/json",
130+
metadata = "application/json"
131+
)
132+
133+
//optional acceptor for server requests
134+
acceptor = {
135+
RSocketRequestHandler {
136+
requestResponse = { it } //echo request payload
137+
}
138+
}
139+
}
140+
}
141+
142+
//connect to some url
143+
val rSocket: RSocket = client.rScoket("wss://rsocket-demo.herokuapp.com/rsocket")
144+
145+
//request stream
146+
val stream: Flow<Payload> = rSocket.requestStream(Payload.Empty)
147+
148+
//take 5 values and print response
149+
stream.take(5).collect { payload: Payload ->
150+
println(payload.data.readText())
151+
}
152+
```
153+
154+
### Server WebSocket with CIO ktor engine
155+
156+
```kotlin
157+
//create ktor server
158+
embeddedServer(CIO) {
159+
install(RSocketServerSupport) {
160+
//configure rSocket server (all values have defaults)
161+
162+
//install interceptors
163+
plugin = Plugin(
164+
connection = listOf(::SomeConnectionInterceptor)
165+
)
166+
}
167+
//configure routing
168+
routing {
169+
//configure route `url:port/rsocket`
170+
rSocket("rsocket") {
171+
RSocketRequestHandler {
172+
//handler for request/response
173+
requestResponse = { request: Payload ->
174+
//... some work here
175+
delay(500) // work emulation
176+
Payload("data", "metadata")
177+
}
178+
//handler for request/stream
179+
requestStream = { request: Payload ->
180+
flow {
181+
repeat(1000) { i ->
182+
emit(Payload("data: $i"))
183+
}
184+
}
185+
}
186+
}
187+
}
188+
}
189+
}.start(true)
190+
```
191+
192+
### More examples:
193+
194+
- [interactions](examples/interactions) - contains usages of some supported functions
195+
- [multiplatform-chat](examples/multiplatform-chat) - chat implementation with JVM server and JS/JVM client with shared classes
196+
and serializing data using [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization)
197+
- [nodejs-tcp-transport](examples/nodejs-tcp-transport) - implementation of TCP transport for nodejs
198+
199+
## Reactive Streams Semantics
200+
201+
From [RSocket protocol](https://github.com/rsocket/rsocket/blob/master/Protocol.md#reactive-streams-semantics):
202+
203+
Reactive Streams semantics are used for flow control of Streams, Subscriptions, and Channels.
204+
This is a credit-based model where the Requester grants the Responder credit for the number of PAYLOADs it can send.
205+
It is sometimes referred to as "request-n" or "request(n)".
206+
207+
`kotlinx.coroutines` doesn't truly support `request(n)` semantic, but it has `Flow.buffer(n)` operator
208+
which can be used to achieve something similar:
209+
210+
Example:
211+
212+
```kotlin
213+
//assume we have client
214+
val client: RSocket = TODO()
215+
216+
//and stream
217+
val stream: Flow<Payload> = client.requestStream(Payload("data"))
218+
219+
//now we can use buffer to tell underlying transport to request values in chunks
220+
val bufferedStream: Flow<Payload> = stream.buffer(10) //here buffer is 10, if `buffer` operator is not used buffer is by default 64
221+
222+
//now you can collect as any other `Flow`
223+
//just after collection first request for 10 elements will be sent
224+
//after 10 elements collected, 10 more elements will be requested, and so on
225+
bufferedStream.collect { payload: Payload ->
226+
println(payload.data.readText())
227+
}
228+
```
229+
230+
## Bugs and Feedback
231+
232+
For bugs, questions and discussions please use the [Github Issues](https://github.com/rsocket/rsocket-kotlin/issues).
233+
234+
## LICENSE
235+
236+
Copyright 2015-2020 the original author or authors.
237+
238+
Licensed under the Apache License, Version 2.0 (the "License");
239+
you may not use this file except in compliance with the License.
240+
You may obtain a copy of the License at
241+
242+
http://www.apache.org/licenses/LICENSE-2.0
243+
244+
Unless required by applicable law or agreed to in writing, software
245+
distributed under the License is distributed on an "AS IS" BASIS,
246+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
247+
See the License for the specific language governing permissions and
248+
limitations under the License.

build.gradle.kts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,20 @@ subprojects {
106106
compilations.findByName("test")?.dependencies {
107107
when (platformType) {
108108
KotlinPlatformType.jvm -> implementation(kotlin("test-junit"))
109-
KotlinPlatformType.js -> implementation(kotlin("test-js"))
109+
KotlinPlatformType.js -> implementation(kotlin("test-js"))
110110
else -> Unit
111111
}
112112
}
113113
}
114+
115+
//fix atomicfu for examples and playground
116+
if ("examples" in project.path || project.name == "playground") {
117+
val commonMain by sourceSets.getting {
118+
dependencies {
119+
implementation("org.jetbrains.kotlinx:atomicfu:0.14.4")
120+
}
121+
}
122+
}
114123
}
115124
}
116125
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2015-2020 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+
plugins {
18+
kotlin("multiplatform")
19+
}
20+
21+
kotlin {
22+
jvm()
23+
24+
sourceSets {
25+
val commonMain by getting {
26+
dependencies {
27+
implementation(project(":rsocket-core"))
28+
implementation(project(":rsocket-transport-local"))
29+
}
30+
}
31+
}
32+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2015-2020 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+
import io.rsocket.kotlin.*
18+
import io.rsocket.kotlin.connection.*
19+
import io.rsocket.kotlin.payload.*
20+
import kotlinx.coroutines.*
21+
import kotlinx.coroutines.flow.*
22+
23+
24+
fun main(): Unit = runBlocking {
25+
val (clientConnection, serverConnection) = SimpleLocalConnection()
26+
27+
launch {
28+
serverConnection.startServer {
29+
RSocketRequestHandler {
30+
requestChannel = { request ->
31+
request.buffer(3).take(3).flatMapConcat { payload ->
32+
val data = payload.data.readText()
33+
flow {
34+
repeat(3) {
35+
emit(Payload("$data(copy $it)"))
36+
}
37+
}
38+
}
39+
}
40+
}
41+
}
42+
}
43+
44+
val rSocket = clientConnection.connectClient()
45+
46+
val request = flow {
47+
emit(Payload("Hello"))
48+
println("Client: Hello")
49+
emit(Payload("World"))
50+
println("Client: World")
51+
emit(Payload("Yes"))
52+
println("Client: Yes")
53+
emit(Payload("No"))
54+
println("Client: No") //no print
55+
}
56+
57+
val response = rSocket.requestChannel(request)
58+
response.collect {
59+
val data = it.data.readText()
60+
println("Client receives: $data")
61+
}
62+
}

0 commit comments

Comments
 (0)