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 | 16 ++++++++++-- .../pl/treksoft/kvision/remote/KVServiceManager.kt | 29 ++++++++++++++++------ 2 files changed, 35 insertions(+), 10 deletions(-) (limited to 'kvision-modules/kvision-server-ktor/src/main/kotlin/pl') diff --git a/kvision-modules/kvision-server-ktor/src/main/kotlin/pl/treksoft/kvision/remote/KVModules.kt b/kvision-modules/kvision-server-ktor/src/main/kotlin/pl/treksoft/kvision/remote/KVModules.kt index a5e22b2d..34fff02d 100644 --- a/kvision-modules/kvision-server-ktor/src/main/kotlin/pl/treksoft/kvision/remote/KVModules.kt +++ b/kvision-modules/kvision-server-ktor/src/main/kotlin/pl/treksoft/kvision/remote/KVModules.kt @@ -45,6 +45,9 @@ import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.channels.SendChannel import kotlin.coroutines.CoroutineContext +/** + * Initialization function for Ktor server. + */ fun Application.kvisionInit(vararg modules: Module) { install(ContentNegotiation) { jackson() @@ -69,18 +72,21 @@ val injectorKey = AttributeKey("injector") val ApplicationCall.injector: Injector get() = attributes[injectorKey] -class CallModule(private val call: ApplicationCall) : AbstractModule() { +internal class CallModule(private val call: ApplicationCall) : AbstractModule() { override fun configure() { bind(ApplicationCall::class.java).toInstance(call) } } -class MainModule(private val application: Application) : AbstractModule() { +internal class MainModule(private val application: Application) : AbstractModule() { override fun configure() { bind(Application::class.java).toInstance(application) } } +/** + * @suppress internal class + */ class WsSessionModule(private val webSocketSession: WebSocketServerSession) : AbstractModule() { override fun configure() { @@ -88,12 +94,18 @@ class WsSessionModule(private val webSocketSession: WebSocketServerSession) : } } +/** + * @suppress internal class + */ class DummyWsSessionModule : AbstractModule() { override fun configure() { bind(WebSocketServerSession::class.java).toInstance(DummyWebSocketServerSession()) } } +/** + * @suppress internal class + */ @Suppress("UNUSED_PARAMETER") class DummyWebSocketServerSession : WebSocketServerSession { override val call: ApplicationCall diff --git a/kvision-modules/kvision-server-ktor/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt b/kvision-modules/kvision-server-ktor/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt index aca5d2f0..11771cfa 100644 --- a/kvision-modules/kvision-server-ktor/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt +++ b/kvision-modules/kvision-server-ktor/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt @@ -36,6 +36,7 @@ import io.ktor.routing.get import io.ktor.routing.options import io.ktor.routing.post import io.ktor.routing.put +import io.ktor.util.KtorExperimentalAPI import io.ktor.util.pipeline.PipelineContext import io.ktor.websocket.WebSocketServerSession import io.ktor.websocket.webSocket @@ -45,7 +46,6 @@ import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.channels.filterNotNull import kotlinx.coroutines.channels.map -import kotlinx.coroutines.channels.mapNotNull import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import org.slf4j.Logger @@ -55,6 +55,7 @@ import kotlin.reflect.KClass /** * Multiplatform service manager for Ktor. */ +@KtorExperimentalAPI @UseExperimental(ExperimentalCoroutinesApi::class) @Suppress("LargeClass") actual open class KVServiceManager actual constructor(val serviceClass: KClass) { @@ -374,15 +375,17 @@ actual open class KVServiceManager actual constructor(val serviceClass: route: String? ) { val routeDef = "route${this::class.simpleName}${counter++}" - webSocketRequests["/kv/$routeDef"] = { + webSocketRequests["/kvws/$routeDef"] = { val wsInjector = call.injector.createChildInjector(WsSessionModule(this)) val service = wsInjector.getInstance(serviceClass.java) - val requestChannel = incoming.mapNotNull { it as? Frame.Text }.map { - val jsonRpcRequest = getParameter(it.readText()) - if (jsonRpcRequest.params.size == 1) { - getParameter(jsonRpcRequest.params[0]) - } else { - null + val requestChannel = incoming.map { + (it as? Frame.Text)?.readText()?.let { text -> + val jsonRpcRequest = getParameter(text) + if (jsonRpcRequest.params.size == 1) { + getParameter(jsonRpcRequest.params[0]) + } else { + null + } } }.filterNotNull() val responseChannel = Channel() @@ -452,6 +455,9 @@ actual open class KVServiceManager actual constructor(val serviceClass: } } + /** + * @suppress Internal method + */ fun addRoute( method: HttpMethod, path: String, @@ -466,6 +472,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) { @@ -477,6 +486,10 @@ actual open class KVServiceManager actual constructor(val serviceClass: } } +/** + * A function to generate routes based on definitions from the service manager. + */ +@KtorExperimentalAPI fun Route.applyRoutes(serviceManager: KVServiceManager) { serviceManager.getRequests.forEach { (path, handler) -> get(path, handler) -- cgit