|
1 | 1 | # 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. |
0 commit comments