From 95c1cdec5b0e65a0f617d5fa88d1d49a7abcfe95 Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Thu, 26 Apr 2018 15:41:32 +0200 Subject: Automatic JSON-RPC conectivity. CallAgent component for remote JSON-RPC and AJAX. --- .../pl/treksoft/kvision/remote/ServiceManager.kt | 270 ++++++++++++++------- 1 file changed, 185 insertions(+), 85 deletions(-) (limited to 'kvision-server/src/main/kotlin/pl') diff --git a/kvision-server/src/main/kotlin/pl/treksoft/kvision/remote/ServiceManager.kt b/kvision-server/src/main/kotlin/pl/treksoft/kvision/remote/ServiceManager.kt index 45a995ac..7b339621 100644 --- a/kvision-server/src/main/kotlin/pl/treksoft/kvision/remote/ServiceManager.kt +++ b/kvision-server/src/main/kotlin/pl/treksoft/kvision/remote/ServiceManager.kt @@ -24,7 +24,9 @@ package pl.treksoft.kvision.remote import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import kotlinx.coroutines.experimental.Deferred import kotlinx.coroutines.experimental.runBlocking +import org.jooby.Response import org.jooby.Status +import org.slf4j.LoggerFactory /** * Multiplatform service manager. @@ -32,6 +34,10 @@ import org.jooby.Status @Suppress("EXPERIMENTAL_FEATURE_WARNING") actual open class ServiceManager actual constructor(val service: T?) { + companion object { + val LOG = LoggerFactory.getLogger(ServiceManager::class.java.name) + } + protected val routes: MutableList Unit> = mutableListOf() val mapper = jacksonObjectMapper() @@ -39,24 +45,33 @@ actual open class ServiceManager actual constructor(val service: T?) { * Binds a given route with a function of the receiver. * @param route a route * @param function a function of the receiver + * @param method a HTTP method + * @param prefix an URL address prefix */ protected actual inline fun bind( route: String, - noinline function: T.(Request?) -> Deferred + noinline function: T.(Request?) -> Deferred, method: RpcHttpMethod, prefix: String ) { routes.add({ - post("$SERVICE_PREFIX/$route") { req, res -> + call(method, "$prefix$route") { req, res -> if (service != null) { + val jsonRpcRequest = req.body(JsonRpcRequest::class.java) try { - res.send(runBlocking { function.invoke(service, req).await() }) + val result = runBlocking { function.invoke(service, req).await() } + res.send( + JsonRpcResponse( + id = jsonRpcRequest.id, + result = mapper.writeValueAsString(result) + ) + ) } catch (e: Exception) { - e.printStackTrace() - res.status(500).send(e.message ?: "Error") + LOG.error(e.message, e) + res.send(JsonRpcResponse(id = jsonRpcRequest.id, error = e.message ?: "Error")) } } else { - res.status(Status.BAD_REQUEST) + res.status(Status.SERVER_ERROR) } - } + }.invoke(this) }) } @@ -64,29 +79,38 @@ actual open class ServiceManager actual constructor(val service: T?) { * Binds a given route with a function of the receiver. * @param route a route * @param function a function of the receiver + * @param method a HTTP method + * @param prefix an URL address prefix */ protected actual inline fun bind( route: String, - noinline function: T.(PAR, Request?) -> Deferred + noinline function: T.(PAR, Request?) -> Deferred, method: RpcHttpMethod, prefix: String ) { routes.add({ - post("$SERVICE_PREFIX/$route") { req, res -> - val param = try { - req.body(PAR::class.java) - } catch (e: Exception) { - null as PAR - } + call(method, "$prefix$route") { req, res -> if (service != null) { - try { - res.send(runBlocking { function.invoke(service, param, req).await() }) - } catch (e: Exception) { - e.printStackTrace() - res.status(500).send(e.message ?: "Error") + val jsonRpcRequest = req.body(JsonRpcRequest::class.java) + if (jsonRpcRequest.params.size == 1) { + val param = getParameter(jsonRpcRequest.params[0]) + try { + val result = runBlocking { function.invoke(service, param, req).await() } + res.send( + JsonRpcResponse( + id = jsonRpcRequest.id, + result = mapper.writeValueAsString(result) + ) + ) + } catch (e: Exception) { + LOG.error(e.message, e) + res.send(JsonRpcResponse(id = jsonRpcRequest.id, error = e.message ?: "Error")) + } + } else { + res.send(JsonRpcResponse(id = jsonRpcRequest.id, error = "Invalid parameters")) } } else { - res.status(Status.BAD_REQUEST) + res.status(Status.SERVER_ERROR) } - } + }.invoke(this) }) } @@ -94,28 +118,39 @@ actual open class ServiceManager actual constructor(val service: T?) { * Binds a given route with a function of the receiver. * @param route a route * @param function a function of the receiver + * @param method a HTTP method + * @param prefix an URL address prefix */ protected actual inline fun bind( route: String, - noinline function: T.(PAR1, PAR2, Request?) -> Deferred + noinline function: T.(PAR1, PAR2, Request?) -> Deferred, method: RpcHttpMethod, prefix: String ) { routes.add({ - post("$SERVICE_PREFIX/$route") { req, res -> - val str = req.body(String::class.java) - val tree = mapper.readTree(str) - if (tree.size() == 2 && service != null) { - val p1 = mapper.treeToValue(tree[0], PAR1::class.java) - val p2 = mapper.treeToValue(tree[1], PAR2::class.java) - try { - res.send(runBlocking { function.invoke(service, p1, p2, req).await() }) - } catch (e: Exception) { - e.printStackTrace() - res.status(500).send(e.message ?: "Error") + call(method, "$prefix$route") { req, res -> + if (service != null) { + val jsonRpcRequest = req.body(JsonRpcRequest::class.java) + if (jsonRpcRequest.params.size == 2) { + val param1 = getParameter(jsonRpcRequest.params[0]) + val param2 = getParameter(jsonRpcRequest.params[1]) + try { + val result = runBlocking { function.invoke(service, param1, param2, req).await() } + res.send( + JsonRpcResponse( + id = jsonRpcRequest.id, + result = mapper.writeValueAsString(result) + ) + ) + } catch (e: Exception) { + LOG.error(e.message, e) + res.send(JsonRpcResponse(id = jsonRpcRequest.id, error = e.message ?: "Error")) + } + } else { + res.send(JsonRpcResponse(id = jsonRpcRequest.id, error = "Invalid parameters")) } } else { - res.status(Status.BAD_REQUEST) + res.status(Status.SERVER_ERROR) } - } + }.invoke(this) }) } @@ -123,29 +158,40 @@ actual open class ServiceManager actual constructor(val service: T?) { * Binds a given route with a function of the receiver. * @param route a route * @param function a function of the receiver + * @param method a HTTP method + * @param prefix an URL address prefix */ protected actual inline fun bind( route: String, - noinline function: T.(PAR1, PAR2, PAR3, Request?) -> Deferred + noinline function: T.(PAR1, PAR2, PAR3, Request?) -> Deferred, method: RpcHttpMethod, prefix: String ) { routes.add({ - post("$SERVICE_PREFIX/$route") { req, res -> - val str = req.body(String::class.java) - val tree = mapper.readTree(str) - if (tree.size() == 3 && service != null) { - val p1 = mapper.treeToValue(tree[0], PAR1::class.java) - val p2 = mapper.treeToValue(tree[1], PAR2::class.java) - val p3 = mapper.treeToValue(tree[2], PAR3::class.java) - try { - res.send(runBlocking { function.invoke(service, p1, p2, p3, req).await() }) - } catch (e: Exception) { - e.printStackTrace() - res.status(500).send(e.message ?: "Error") + call(method, "$prefix$route") { req, res -> + if (service != null) { + val jsonRpcRequest = req.body(JsonRpcRequest::class.java) + if (jsonRpcRequest.params.size == 3) { + val param1 = getParameter(jsonRpcRequest.params[0]) + val param2 = getParameter(jsonRpcRequest.params[1]) + val param3 = getParameter(jsonRpcRequest.params[2]) + try { + val result = runBlocking { function.invoke(service, param1, param2, param3, req).await() } + res.send( + JsonRpcResponse( + id = jsonRpcRequest.id, + result = mapper.writeValueAsString(result) + ) + ) + } catch (e: Exception) { + LOG.error(e.message, e) + res.send(JsonRpcResponse(id = jsonRpcRequest.id, error = e.message ?: "Error")) + } + } else { + res.send(JsonRpcResponse(id = jsonRpcRequest.id, error = "Invalid parameters")) } } else { - res.status(Status.BAD_REQUEST) + res.status(Status.SERVER_ERROR) } - } + }.invoke(this) }) } @@ -153,30 +199,42 @@ actual open class ServiceManager actual constructor(val service: T?) { * Binds a given route with a function of the receiver. * @param route a route * @param function a function of the receiver + * @param method a HTTP method + * @param prefix an URL address prefix */ protected actual inline fun bind( route: String, - noinline function: T.(PAR1, PAR2, PAR3, PAR4, Request?) -> Deferred + noinline function: T.(PAR1, PAR2, PAR3, PAR4, Request?) -> Deferred, method: RpcHttpMethod, prefix: String ) { routes.add({ - post("$SERVICE_PREFIX/$route") { req, res -> - val str = req.body(String::class.java) - val tree = mapper.readTree(str) - if (tree.size() == 4 && service != null) { - val p1 = mapper.treeToValue(tree[0], PAR1::class.java) - val p2 = mapper.treeToValue(tree[1], PAR2::class.java) - val p3 = mapper.treeToValue(tree[2], PAR3::class.java) - val p4 = mapper.treeToValue(tree[3], PAR4::class.java) - try { - res.send(runBlocking { function.invoke(service, p1, p2, p3, p4, req).await() }) - } catch (e: Exception) { - e.printStackTrace() - res.status(500).send(e.message ?: "Error") + call(method, "$prefix$route") { req, res -> + if (service != null) { + val jsonRpcRequest = req.body(JsonRpcRequest::class.java) + if (jsonRpcRequest.params.size == 4) { + val param1 = getParameter(jsonRpcRequest.params[0]) + val param2 = getParameter(jsonRpcRequest.params[1]) + val param3 = getParameter(jsonRpcRequest.params[2]) + val param4 = getParameter(jsonRpcRequest.params[3]) + try { + val result = + runBlocking { function.invoke(service, param1, param2, param3, param4, req).await() } + res.send( + JsonRpcResponse( + id = jsonRpcRequest.id, + result = mapper.writeValueAsString(result) + ) + ) + } catch (e: Exception) { + LOG.error(e.message, e) + res.send(JsonRpcResponse(id = jsonRpcRequest.id, error = e.message ?: "Error")) + } + } else { + res.send(JsonRpcResponse(id = jsonRpcRequest.id, error = "Invalid parameters")) } } else { - res.status(Status.BAD_REQUEST) + res.status(Status.SERVER_ERROR) } - } + }.invoke(this) }) } @@ -184,34 +242,76 @@ actual open class ServiceManager actual constructor(val service: T?) { * Binds a given route with a function of the receiver. * @param route a route * @param function a function of the receiver + * @param method a HTTP method + * @param prefix an URL address prefix */ - protected actual inline fun bind( + protected actual inline fun bind( route: String, - noinline function: T.(PAR1, PAR2, PAR3, PAR4, PAR5, Request?) -> Deferred + noinline function: T.(PAR1, PAR2, PAR3, PAR4, PAR5, Request?) -> Deferred, + method: RpcHttpMethod, + prefix: String ) { routes.add({ - post("$SERVICE_PREFIX/$route") { req, res -> - val str = req.body(String::class.java) - val tree = mapper.readTree(str) - if (tree.size() == 5 && service != null) { - val p1 = mapper.treeToValue(tree[0], PAR1::class.java) - val p2 = mapper.treeToValue(tree[1], PAR2::class.java) - val p3 = mapper.treeToValue(tree[2], PAR3::class.java) - val p4 = mapper.treeToValue(tree[3], PAR4::class.java) - val p5 = mapper.treeToValue(tree[4], PAR5::class.java) - try { - res.send(runBlocking { function.invoke(service, p1, p2, p3, p4, p5, req).await() }) - } catch (e: Exception) { - e.printStackTrace() - res.status(500).send(e.message ?: "Error") + call(method, "$prefix$route") { req, res -> + if (service != null) { + val jsonRpcRequest = req.body(JsonRpcRequest::class.java) + if (jsonRpcRequest.params.size == 5) { + val param1 = getParameter(jsonRpcRequest.params[0]) + val param2 = getParameter(jsonRpcRequest.params[1]) + val param3 = getParameter(jsonRpcRequest.params[2]) + val param4 = getParameter(jsonRpcRequest.params[3]) + val param5 = getParameter(jsonRpcRequest.params[4]) + try { + val result = + runBlocking { + function.invoke(service, param1, param2, param3, param4, param5, req).await() + } + res.send( + JsonRpcResponse( + id = jsonRpcRequest.id, + result = mapper.writeValueAsString(result) + ) + ) + } catch (e: Exception) { + LOG.error(e.message, e) + res.send(JsonRpcResponse(id = jsonRpcRequest.id, error = e.message ?: "Error")) + } + } else { + res.send(JsonRpcResponse(id = jsonRpcRequest.id, error = "Invalid parameters")) } } else { - res.status(Status.BAD_REQUEST) + res.status(Status.SERVER_ERROR) } - } + }.invoke(this) }) } + fun call( + method: RpcHttpMethod, + path: String, + handler: (Request, Response) -> Unit + ): JoobyServer.() -> Unit { + return { + when (method) { + RpcHttpMethod.POST -> post(path, handler) + RpcHttpMethod.PUT -> put(path, handler) + RpcHttpMethod.DELETE -> delete(path, handler) + RpcHttpMethod.OPTIONS -> options(path, handler) + } + } + } + + protected inline fun getParameter(str: String?): T { + return str?.let { + if (T::class == String::class) { + str as T + } else { + mapper.readValue(str, T::class.java) + } + } ?: null as T + } + /** * Applies all defined routes to the given server. * @param k a Jooby server @@ -226,5 +326,5 @@ actual open class ServiceManager actual constructor(val service: T?) { * Returns the list of defined bindings. * Not used on the jvm platform. */ - actual fun getCalls(): Map = mapOf() + actual fun getCalls(): Map> = mapOf() } -- cgit