diff --git a/kvision-modules/kvision-common-remote/src/jsMain/kotlin/io/kvision/remote/CallAgent.kt b/kvision-modules/kvision-common-remote/src/jsMain/kotlin/io/kvision/remote/CallAgent.kt index 52783bb93f3..e81a9687d2a 100644 --- a/kvision-modules/kvision-common-remote/src/jsMain/kotlin/io/kvision/remote/CallAgent.kt +++ b/kvision-modules/kvision-common-remote/src/jsMain/kotlin/io/kvision/remote/CallAgent.kt @@ -87,7 +87,11 @@ open class CallAgent { val jsonRpcRequest = JsonRpcRequest(counter++, url, data) val urlAddr = urlPrefix + url.drop(1) val fetchUrl = if (method == HttpMethod.GET) { - urlAddr + "?" + URLSearchParams(obj { id = jsonRpcRequest.id }).toString() + val paramsObject = obj { id = jsonRpcRequest.id } + data.forEachIndexed { index, s -> + if (s != null) paramsObject["p$index"] = encodeURIComponent(s) + } + urlAddr + "?" + URLSearchParams(paramsObject).toString() } else { requestInit.body = RemoteSerialization.plain.encodeToString(jsonRpcRequest) urlAddr @@ -182,9 +186,18 @@ open class CallAgent { if (response.headers.get("Content-Type") == "application/json") { response.json().then { cont.resume(it) } } else { - cont.cancel(ContentTypeException("Invalid response content type: ${response.headers.get("Content-Type")}")) + cont.cancel( + ContentTypeException( + "Invalid response content type: ${ + response.headers.get( + "Content-Type" + ) + }" + ) + ) } } + ResponseBodyType.TEXT -> response.text().then { cont.resume(it) } ResponseBodyType.READABLE_STREAM -> cont.resume(response.body) } diff --git a/kvision-modules/kvision-common-remote/src/jsMain/kotlin/io/kvision/remote/KVServiceManagerJs.kt b/kvision-modules/kvision-common-remote/src/jsMain/kotlin/io/kvision/remote/KVServiceManagerJs.kt index 106ae7a0c23..d349228d19f 100644 --- a/kvision-modules/kvision-common-remote/src/jsMain/kotlin/io/kvision/remote/KVServiceManagerJs.kt +++ b/kvision-modules/kvision-common-remote/src/jsMain/kotlin/io/kvision/remote/KVServiceManagerJs.kt @@ -36,7 +36,7 @@ open class KVServiceManagerJs : KVServiceMgr { noinline function: suspend T.(PAR) -> RET, method: HttpMethod, route: String? ) { - bindFunctionWithParamsInternal(method, route, function) + bindFunctionInternal(route, function, method) } /** @@ -49,7 +49,7 @@ open class KVServiceManagerJs : KVServiceMgr { noinline function: suspend T.(PAR1, PAR2) -> RET, method: HttpMethod, route: String? ) { - bindFunctionWithParamsInternal(method, route, function) + bindFunctionInternal(route, function, method) } /** @@ -62,7 +62,7 @@ open class KVServiceManagerJs : KVServiceMgr { noinline function: suspend T.(PAR1, PAR2, PAR3) -> RET, method: HttpMethod, route: String? ) { - bindFunctionWithParamsInternal(method, route, function) + bindFunctionInternal(route, function, method) } /** @@ -75,7 +75,7 @@ open class KVServiceManagerJs : KVServiceMgr { noinline function: suspend T.(PAR1, PAR2, PAR3, PAR4) -> RET, method: HttpMethod, route: String? ) { - bindFunctionWithParamsInternal(method, route, function) + bindFunctionInternal(route, function, method) } /** @@ -89,7 +89,7 @@ open class KVServiceManagerJs : KVServiceMgr { noinline function: suspend T.(PAR1, PAR2, PAR3, PAR4, PAR5) -> RET, method: HttpMethod, route: String? ) { - bindFunctionWithParamsInternal(method, route, function) + bindFunctionInternal(route, function, method) } /** @@ -103,7 +103,7 @@ open class KVServiceManagerJs : KVServiceMgr { noinline function: suspend T.(PAR1, PAR2, PAR3, PAR4, PAR5, PAR6) -> RET, method: HttpMethod, route: String? ) { - bindFunctionWithParamsInternal(method, route, function) + bindFunctionInternal(route, function, method) } /** @@ -141,13 +141,6 @@ open class KVServiceManagerJs : KVServiceMgr { bindFunctionInternal(route, function, HttpMethod.GET, "/kvsse/") } - @PublishedApi - internal fun bindFunctionWithParamsInternal(method: HttpMethod, route: String?, function: Function<*>) { - if (method == HttpMethod.GET) - throw UnsupportedOperationException("GET method is only supported for methods without parameters") - bindFunctionInternal(route, function, method) - } - @PublishedApi internal fun bindFunctionInternal( route: String?, diff --git a/kvision-modules/kvision-common-remote/src/jsMain/kotlin/io/kvision/remote/Utils.kt b/kvision-modules/kvision-common-remote/src/jsMain/kotlin/io/kvision/remote/Utils.kt index d4618954996..5612c8f6ce4 100644 --- a/kvision-modules/kvision-common-remote/src/jsMain/kotlin/io/kvision/remote/Utils.kt +++ b/kvision-modules/kvision-common-remote/src/jsMain/kotlin/io/kvision/remote/Utils.kt @@ -28,6 +28,11 @@ import kotlinx.browser.window */ external class Object +/** + * JavaScript encodeURIComponent function + */ +external fun encodeURIComponent(uri: String): String + /** * Helper function for creating JavaScript objects. */ diff --git a/kvision-modules/kvision-common-remote/src/jvmMain/kotlin/io/kvision/remote/KVServiceBinder.kt b/kvision-modules/kvision-common-remote/src/jvmMain/kotlin/io/kvision/remote/KVServiceBinder.kt index f9b700b68b7..5e2cfe5afb0 100644 --- a/kvision-modules/kvision-common-remote/src/jvmMain/kotlin/io/kvision/remote/KVServiceBinder.kt +++ b/kvision-modules/kvision-common-remote/src/jvmMain/kotlin/io/kvision/remote/KVServiceBinder.kt @@ -52,6 +52,7 @@ abstract class KVServiceBinder( abstract fun createRequestHandler( method: HttpMethod, function: suspend T.(params: List) -> RET, + numberOfParams: Int, serializerFactory: () -> KSerializer ): RH @@ -74,12 +75,13 @@ abstract class KVServiceBinder( internal inline fun bind( method: HttpMethod, route: String?, + numberOfParams: Int, noinline function: suspend T.(params: List) -> RET ) { routeMapRegistry.addRoute( method, "/kv/${route ?: generateRouteName()}", - createRequestHandler(method, function) { deSerializer.serializersModule.serializer() } + createRequestHandler(method, function, numberOfParams) { deSerializer.serializersModule.serializer() } ) } @@ -90,7 +92,7 @@ abstract class KVServiceBinder( * @param route a route */ inline fun bind(noinline function: suspend T.() -> RET, method: HttpMethod, route: String?) { - bind(method, route) { + bind(method, route, 0) { requireParameterCountEqualTo(it, 0) function.invoke(this) } @@ -107,8 +109,7 @@ abstract class KVServiceBinder( method: HttpMethod, route: String? ) { - expectMethodSupportsParameters(method) - bind(method, route) { + bind(method, route, 1) { requireParameterCountEqualTo(it, 1) function.invoke(this, deserialize(it[0])) } @@ -125,8 +126,7 @@ abstract class KVServiceBinder( method: HttpMethod, route: String? ) { - expectMethodSupportsParameters(method) - bind(method, route) { + bind(method, route, 2) { requireParameterCountEqualTo(it, 2) function.invoke(this, deserialize(it[0]), deserialize(it[1])) } @@ -143,8 +143,7 @@ abstract class KVServiceBinder( method: HttpMethod, route: String? ) { - expectMethodSupportsParameters(method) - bind(method, route) { + bind(method, route, 3) { requireParameterCountEqualTo(it, 3) function.invoke(this, deserialize(it[0]), deserialize(it[1]), deserialize(it[2])) } @@ -161,8 +160,7 @@ abstract class KVServiceBinder( method: HttpMethod, route: String? ) { - expectMethodSupportsParameters(method) - bind(method, route) { + bind(method, route, 4) { requireParameterCountEqualTo(it, 4) function.invoke(this, deserialize(it[0]), deserialize(it[1]), deserialize(it[2]), deserialize(it[3])) } @@ -179,8 +177,7 @@ abstract class KVServiceBinder( method: HttpMethod, route: String? ) { - expectMethodSupportsParameters(method) - bind(method, route) { + bind(method, route, 5) { requireParameterCountEqualTo(it, 5) function.invoke( this, @@ -204,8 +201,7 @@ abstract class KVServiceBinder( method: HttpMethod, route: String? ) { - expectMethodSupportsParameters(method) - bind(method, route) { + bind(method, route, 6) { requireParameterCountEqualTo(it, 6) function.invoke( this, @@ -285,10 +281,3 @@ abstract class KVServiceBinder( return deSerializer.deserialize(txt) } } - - -@PublishedApi -internal fun expectMethodSupportsParameters(method: HttpMethod) { - if (method == HttpMethod.GET) - throw UnsupportedOperationException("GET method is only supported for methods without parameters") -} diff --git a/kvision-modules/kvision-common-remote/src/jvmTest/kotlin/io/kvision/remote/KVServiceBinderTest.kt b/kvision-modules/kvision-common-remote/src/jvmTest/kotlin/io/kvision/remote/KVServiceBinderTest.kt index ed2de72148b..7026c6a766e 100644 --- a/kvision-modules/kvision-common-remote/src/jvmTest/kotlin/io/kvision/remote/KVServiceBinderTest.kt +++ b/kvision-modules/kvision-common-remote/src/jvmTest/kotlin/io/kvision/remote/KVServiceBinderTest.kt @@ -141,23 +141,6 @@ class KVServiceBinderTest { return Array(BINDING_INITIALIZERS.size) { args(it, BINDING_INITIALIZERS[it]) } } - @Test - fun bind_canUseGetMethod_ifNoArgs() { - // execution & evaluation - testBind(BINDING_INITIALIZERS[0], method = HttpMethod.GET, result = true) - } - - @Test( - dataProvider = "provide_bindingInitializersWithArgs", - expectedExceptions = [UnsupportedOperationException::class] - ) - fun bind_failsForGetMethod_ifHasArgs(bindingInitializer: BindingInitializer) { - // execution - testBind(bindingInitializer, method = HttpMethod.GET, result = true) - - // evaluation handled by expectedExceptions - } - @DataProvider fun provide_bindingInitializersWithArgs(): Array = BINDING_INITIALIZERS.copyOfRange(1, BINDING_INITIALIZERS.size) @@ -218,6 +201,7 @@ private class KVServiceBinderImpl : KVServiceBinder createRequestHandler( method: HttpMethod, function: suspend Any.(params: List) -> RET, + numberOfParams: Int, serializerFactory: () -> KSerializer ): RouteHandler = { runBlocking { function.invoke(HANDLER_THIS, it) } } diff --git a/kvision-modules/kvision-server-javalin/src/jvmMain/kotlin/io/kvision/remote/KVServiceManager.kt b/kvision-modules/kvision-server-javalin/src/jvmMain/kotlin/io/kvision/remote/KVServiceManager.kt index 60fdcaf5da0..3af1de1da30 100644 --- a/kvision-modules/kvision-server-javalin/src/jvmMain/kotlin/io/kvision/remote/KVServiceManager.kt +++ b/kvision-modules/kvision-server-javalin/src/jvmMain/kotlin/io/kvision/remote/KVServiceManager.kt @@ -45,6 +45,8 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.modules.SerializersModule import org.slf4j.Logger import org.slf4j.LoggerFactory +import java.net.URLDecoder +import java.nio.charset.StandardCharsets import kotlin.reflect.KClass typealias RequestHandler = (Context) -> Unit @@ -69,12 +71,18 @@ actual open class KVServiceManager actual constructor(private val s override fun createRequestHandler( method: HttpMethod, function: suspend T.(params: List) -> RET, + numberOfParams: Int, serializerFactory: () -> KSerializer ): RequestHandler { val serializer by lazy { serializerFactory() } return { ctx -> val jsonRpcRequest = if (method == HttpMethod.GET) { - JsonRpcRequest(ctx.queryParamAsClass("id").get(), "", listOf()) + val parameters = (0..("id").get(), "", parameters) } else { ctx.bodyAsClass() } diff --git a/kvision-modules/kvision-server-jooby/src/jvmMain/kotlin/io/kvision/remote/KVServiceManager.kt b/kvision-modules/kvision-server-jooby/src/jvmMain/kotlin/io/kvision/remote/KVServiceManager.kt index ebd7732b7de..153f1a48add 100644 --- a/kvision-modules/kvision-server-jooby/src/jvmMain/kotlin/io/kvision/remote/KVServiceManager.kt +++ b/kvision-modules/kvision-server-jooby/src/jvmMain/kotlin/io/kvision/remote/KVServiceManager.kt @@ -44,6 +44,8 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.modules.SerializersModule import org.slf4j.Logger import org.slf4j.LoggerFactory +import java.net.URLDecoder +import java.nio.charset.StandardCharsets import kotlin.reflect.KClass typealias RequestHandler = suspend HandlerContext.() -> Any @@ -66,12 +68,18 @@ actual open class KVServiceManager actual constructor(private val s override fun createRequestHandler( method: HttpMethod, function: suspend T.(params: List) -> RET, + numberOfParams: Int, serializerFactory: () -> KSerializer ): RequestHandler { val serializer by lazy { serializerFactory() } return { val jsonRpcRequest = if (method == HttpMethod.GET) { - JsonRpcRequest(ctx.query("id").intValue(), "", listOf()) + val parameters = (0...(Unit) -> Unit @@ -61,6 +63,7 @@ actual open class KVServiceManager actual constructor(private val s override fun createRequestHandler( method: HttpMethod, function: suspend T.(params: List) -> RET, + numberOfParams: Int, serializerFactory: () -> KSerializer ): RequestHandler { val serializer by lazy { serializerFactory() } @@ -69,7 +72,12 @@ actual open class KVServiceManager actual constructor(private val s val service = call.getKoin().get(serviceClass) KoinModule.threadLocalApplicationCall.remove() val jsonRpcRequest = if (method == HttpMethod.GET) { - JsonRpcRequest(call.request.queryParameters["id"]?.toInt() ?: 0, "", listOf()) + val parameters = (0...(Unit) -> Unit @@ -60,13 +62,19 @@ actual open class KVServiceManager actual constructor(private val s override fun createRequestHandler( method: HttpMethod, function: suspend T.(params: List) -> RET, + numberOfParams: Int, serializerFactory: () -> KSerializer ): RequestHandler { val serializer by lazy { serializerFactory() } return { val service = call.injector.createChildInjector(DummyWsSessionModule()).getInstance(serviceClass.java) val jsonRpcRequest = if (method == HttpMethod.GET) { - JsonRpcRequest(call.request.queryParameters["id"]?.toInt() ?: 0, "", listOf()) + val parameters = (0.. Unit typealias SseHandler = - (HttpRequest<*>, ThreadLocal>, ApplicationContext) -> Publisher> + (HttpRequest<*>, ThreadLocal>, ApplicationContext) -> Publisher> /** * Multiplatform service manager for Micronaut. @@ -67,6 +69,7 @@ actual open class KVServiceManager actual constructor(private val s override fun createRequestHandler( method: HttpMethod, function: suspend T.(params: List) -> RET, + numberOfParams: Int, serializerFactory: () -> KSerializer ): RequestHandler { val serializer by lazy { serializerFactory() } @@ -75,7 +78,12 @@ actual open class KVServiceManager actual constructor(private val s val service = ctx.getBean(serviceClass.java) tlReq.remove() val jsonRpcRequest = if (method == HttpMethod.GET) { - JsonRpcRequest(req.parameters["id"]?.toInt() ?: 0, "", listOf()) + val parameters = (0.., ApplicationContext) -> ServerResponse @@ -69,6 +72,7 @@ actual open class KVServiceManager actual constructor(private val s override fun createRequestHandler( method: HttpMethod, function: suspend T.(params: List) -> RET, + numberOfParams: Int, serializerFactory: () -> KSerializer ): RequestHandler { val serializer by lazy { serializerFactory() } @@ -77,7 +81,12 @@ actual open class KVServiceManager actual constructor(private val s val service = ctx.getBean(serviceClass.java) tlReq.remove() val jsonRpcRequest = if (method == HttpMethod.GET) { - JsonRpcRequest(req.queryParam("id").map { it.toInt() }.orElse(0), "", listOf()) + val parameters = (0.. actual constructor(private val s val channel = Channel() val events = flux { for (item in channel) { - send(ServerSentEvent.builder() - .event("message") - .data(item) - .build()) + send( + ServerSentEvent.builder() + .event("message") + .data(item) + .build() + ) } }.doOnCancel { channel.close() diff --git a/kvision-modules/kvision-server-vertx/src/jvmMain/kotlin/io/kvision/remote/KVServiceManager.kt b/kvision-modules/kvision-server-vertx/src/jvmMain/kotlin/io/kvision/remote/KVServiceManager.kt index ee138d50b4c..be5eb253253 100644 --- a/kvision-modules/kvision-server-vertx/src/jvmMain/kotlin/io/kvision/remote/KVServiceManager.kt +++ b/kvision-modules/kvision-server-vertx/src/jvmMain/kotlin/io/kvision/remote/KVServiceManager.kt @@ -42,6 +42,8 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.modules.SerializersModule import org.slf4j.Logger import org.slf4j.LoggerFactory +import java.net.URLDecoder +import java.nio.charset.StandardCharsets import kotlin.reflect.KClass @@ -65,12 +67,18 @@ actual open class KVServiceManager actual constructor(private val s override fun createRequestHandler( method: HttpMethod, function: suspend T.(params: List) -> RET, + numberOfParams: Int, serializerFactory: () -> KSerializer ): RequestHandler { val serializer by lazy { serializerFactory() } return { ctx -> val jsonRpcRequest = if (method == HttpMethod.GET) { - JsonRpcRequest(ctx.request().getParam("id").toInt(), "", listOf()) + val parameters = (0..