From 9374d0df9c462493e9cb91c846e4f820b3325f7b Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Wed, 10 Apr 2019 18:24:14 +0200 Subject: Websockets support for Spring Boot and Jooby. --- .../kotlin/pl/treksoft/kvision/remote/KVModules.kt | 3 + .../pl/treksoft/kvision/remote/KVServiceManager.kt | 79 +++++++++++++++++++++- 2 files changed, 79 insertions(+), 3 deletions(-) (limited to 'kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/remote') diff --git a/kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/remote/KVModules.kt b/kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/remote/KVModules.kt index 34b2877f..b3da785d 100644 --- a/kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/remote/KVModules.kt +++ b/kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/remote/KVModules.kt @@ -24,6 +24,9 @@ package pl.treksoft.kvision.remote import org.jooby.Kooby import org.jooby.json.Jackson +/** + * Initialization function for Jooby server. + */ fun Kooby.kvisionInit() { assets("/", "/assets/index.html") assets("/**", "/assets/{0}").onMissing(0) diff --git a/kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt b/kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt index cfd9d467..ca08b080 100644 --- a/kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt +++ b/kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt @@ -24,9 +24,15 @@ package pl.treksoft.kvision.remote import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.google.inject.Injector import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.channels.SendChannel +import kotlinx.coroutines.channels.filterNotNull +import kotlinx.coroutines.channels.map +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import org.jooby.Kooby import org.jooby.Request @@ -39,6 +45,7 @@ import kotlin.reflect.KClass * Multiplatform service manager for Jooby. */ @Suppress("LargeClass") +@UseExperimental(ExperimentalCoroutinesApi::class) actual open class KVServiceManager actual constructor(val serviceClass: KClass) { companion object { @@ -313,7 +320,7 @@ actual open class KVServiceManager actual constructor(val serviceClass: } /** - * Binds a given web socket connetion with a function of the receiver. + * Binds a given web socket connection with a function of the receiver. * @param function a function of the receiver * @param route a route */ @@ -321,10 +328,67 @@ actual open class KVServiceManager actual constructor(val serviceClass: noinline function: suspend T.(ReceiveChannel, SendChannel) -> Unit, route: String? ) { - TODO("Not implemented in Jooby module") + val routeDef = "route${this::class.simpleName}${counter++}" + routes.add { + ws("/kvws/$routeDef") { ws -> + val injector = ws.require(Injector::class.java) + val service = injector.getInstance(serviceClass.java) + val incoming = Channel() + val outgoing = Channel() + GlobalScope.launch { + coroutineScope { + launch(Dispatchers.IO) { + for (text in outgoing) { + ws.send(text) + } + ws.close() + } + launch { + val requestChannel = incoming.map { + val jsonRpcRequest = getParameter(it) + if (jsonRpcRequest.params.size == 1) { + getParameter(jsonRpcRequest.params[0]) + } else { + null + } + }.filterNotNull() + val responseChannel = Channel() + coroutineScope { + launch(Dispatchers.IO) { + for (p in responseChannel) { + val text = mapper.writeValueAsString( + JsonRpcResponse( + id = 0, + result = mapper.writeValueAsString(p) + ) + ) + outgoing.send(text) + } + } + launch { + function.invoke(service, requestChannel, responseChannel) + if (!responseChannel.isClosedForReceive) responseChannel.close() + } + } + if (!outgoing.isClosedForReceive) outgoing.close() + } + } + } + ws.onClose { + GlobalScope.launch { + outgoing.close() + incoming.close() + } + } + ws.onMessage { msg -> + GlobalScope.launch { + incoming.send(msg.value()) + } + } + } + } } - /** * Binds a given function of the receiver as a select options source * @param function a function of the receiver @@ -363,6 +427,9 @@ actual open class KVServiceManager actual constructor(val serviceClass: } } + /** + * @suppress Internal method + */ fun call( method: HttpMethod, path: String, @@ -379,6 +446,9 @@ actual open class KVServiceManager actual constructor(val serviceClass: } } + /** + * @suppress Internal method + */ protected inline fun getParameter(str: String?): T { return str?.let { if (T::class == String::class) { @@ -391,6 +461,9 @@ actual open class KVServiceManager actual constructor(val serviceClass: } +/** + * A function to generate routes based on definitions from the service manager. + */ fun Kooby.applyRoutes(serviceManager: KVServiceManager) { serviceManager.routes.forEach { it.invoke(this@applyRoutes) -- cgit