diff options
| author | Robert Jaros <rjaros@finn.pl> | 2019-10-12 18:22:43 +0200 |
|---|---|---|
| committer | Robert Jaros <rjaros@finn.pl> | 2019-10-12 18:22:43 +0200 |
| commit | bcf7504392baccf1568e740c1d453eac7080fb5b (patch) | |
| tree | 519489751032814bfc0d5426c48de257b788329f /kvision-modules/kvision-server-spring-boot/src | |
| parent | 28b6e9d646123d774660ebdf0124eeb8a22fe087 (diff) | |
| download | kvision-bcf7504392baccf1568e740c1d453eac7080fb5b.tar.gz kvision-bcf7504392baccf1568e740c1d453eac7080fb5b.tar.bz2 kvision-bcf7504392baccf1568e740c1d453eac7080fb5b.zip | |
Redesign spring-boot server module to use Spring WebFlux instead of Spring MVC.
Diffstat (limited to 'kvision-modules/kvision-server-spring-boot/src')
9 files changed, 569 insertions, 464 deletions
diff --git a/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/Annotations.kt b/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/Annotations.kt new file mode 100644 index 00000000..7655ae70 --- /dev/null +++ b/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/Annotations.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package pl.treksoft.kvision.remote + +import org.springframework.data.annotation.Id +import org.springframework.data.annotation.Transient +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table + +actual typealias Id = Id + +actual typealias Transient = Transient + +actual typealias Table = Table + +actual typealias Column = Column diff --git a/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVController.kt b/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVController.kt deleted file mode 100644 index 23e00284..00000000 --- a/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVController.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2017-present Robert Jaros - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package pl.treksoft.kvision.remote - -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.context.ApplicationContext -import org.springframework.stereotype.Controller -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestMethod -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse - -/** - * Controller for handling automatic routes. - */ -@Controller -open class KVController { - - @Autowired - lateinit var services: List<KVServiceManager<*>> - - @Autowired - lateinit var applicationContext: ApplicationContext - - @RequestMapping( - "/kv/**", - method = [RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE, RequestMethod.OPTIONS] - ) - open fun kVMapping(req: HttpServletRequest, res: HttpServletResponse) { - val routeUrl = req.requestURI - val handler = services.mapNotNull { - when (req.method) { - "GET" -> it.getRequests[routeUrl] - "POST" -> it.postRequests[routeUrl] - "PUT" -> it.putRequests[routeUrl] - "DELETE" -> it.deleteRequests[routeUrl] - "OPTIONS" -> it.optionsRequests[routeUrl] - else -> null - } - }.firstOrNull() - if (handler != null) { - handler.invoke(req, res, applicationContext) - } else { - res.status = HttpServletResponse.SC_NOT_FOUND - } - } -} diff --git a/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVRouterConfiguration.kt b/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVRouterConfiguration.kt new file mode 100644 index 00000000..215848a7 --- /dev/null +++ b/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVRouterConfiguration.kt @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package pl.treksoft.kvision.remote + +import org.springframework.context.ApplicationContext +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.support.GenericApplicationContext +import org.springframework.stereotype.Component +import org.springframework.web.reactive.function.server.RouterFunction +import org.springframework.web.reactive.function.server.ServerRequest +import org.springframework.web.reactive.function.server.ServerResponse +import org.springframework.web.reactive.function.server.buildAndAwait +import org.springframework.web.reactive.function.server.coRouter +import org.springframework.web.reactive.function.server.router +import java.net.URI + +@Configuration +open class KVRouterConfiguration { + @Bean + open fun kvRoutes(kvHandler: KVHandler) = coRouter { + GET("/kv/**", kvHandler::handle) + POST("/kv/**", kvHandler::handle) + PUT("/kv/**", kvHandler::handle) + DELETE("/kv/**", kvHandler::handle) + OPTIONS("/kv/**", kvHandler::handle) + } + + @Bean + open fun indexRouter(): RouterFunction<ServerResponse> { + val redirectToIndex = + ServerResponse + .temporaryRedirect(URI("/index.html")) + .build() + + return router { + GET("/") { + redirectToIndex + } + } + } +} + +@Component +open class KVHandler(var services: List<KVServiceManager<*>>, var applicationContext: ApplicationContext) { + + open suspend fun handle(request: ServerRequest): ServerResponse { + val routeUrl = request.path() + val handler = services.mapNotNull { + when (request.method()?.name) { + "GET" -> it.getRequests[routeUrl] + "POST" -> it.postRequests[routeUrl] + "PUT" -> it.putRequests[routeUrl] + "DELETE" -> it.deleteRequests[routeUrl] + "OPTIONS" -> it.optionsRequests[routeUrl] + else -> null + } + }.firstOrNull() + return if (handler != null) { + handler(request, applicationContext as GenericApplicationContext) + } else { + ServerResponse.notFound().buildAndAwait() + } + } +} diff --git a/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt b/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt index 185356da..14360abc 100644 --- a/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt +++ b/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt @@ -21,24 +21,35 @@ */ package pl.treksoft.kvision.remote +import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import kotlinx.coroutines.CoroutineStart 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.coroutineScope import kotlinx.coroutines.launch +import kotlinx.coroutines.reactive.awaitSingle import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.context.ApplicationContext -import org.springframework.web.context.support.GenericWebApplicationContext -import org.springframework.web.socket.WebSocketSession -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse +import org.springframework.security.core.Authentication +import org.springframework.web.reactive.function.server.ServerRequest +import org.springframework.web.reactive.function.server.ServerResponse +import org.springframework.web.reactive.function.server.awaitBody +import org.springframework.web.reactive.function.server.bodyValueAndAwait +import org.springframework.web.reactive.function.server.json +import org.springframework.web.reactive.socket.WebSocketSession +import pl.treksoft.kvision.types.* +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.OffsetDateTime +import java.time.OffsetTime import kotlin.reflect.KClass + /** * Multiplatform service manager for Spring Boot. */ @@ -50,25 +61,61 @@ actual open class KVServiceManager<T : Any> actual constructor(val serviceClass: val LOG: Logger = LoggerFactory.getLogger(KVServiceManager::class.java.name) } - val getRequests: MutableMap<String, (HttpServletRequest, HttpServletResponse, ApplicationContext) -> Unit> = + val getRequests: MutableMap<String, suspend (ServerRequest, ApplicationContext) -> ServerResponse> = mutableMapOf() - val postRequests: MutableMap<String, (HttpServletRequest, HttpServletResponse, ApplicationContext) -> Unit> = + val postRequests: MutableMap<String, suspend (ServerRequest, ApplicationContext) -> ServerResponse> = mutableMapOf() - val putRequests: MutableMap<String, (HttpServletRequest, HttpServletResponse, ApplicationContext) -> Unit> = + val putRequests: MutableMap<String, suspend (ServerRequest, ApplicationContext) -> ServerResponse> = mutableMapOf() - val deleteRequests: MutableMap<String, (HttpServletRequest, HttpServletResponse, ApplicationContext) -> Unit> = + val deleteRequests: MutableMap<String, suspend (ServerRequest, ApplicationContext) -> ServerResponse> = mutableMapOf() - val optionsRequests: MutableMap<String, (HttpServletRequest, HttpServletResponse, ApplicationContext) -> Unit> = + val optionsRequests: MutableMap<String, suspend (ServerRequest, ApplicationContext) -> ServerResponse> = mutableMapOf() val webSocketsRequests: MutableMap<String, suspend ( - WebSocketSession, GenericWebApplicationContext, ReceiveChannel<String>, SendChannel<String> + WebSocketSession, ApplicationContext, ReceiveChannel<String>, SendChannel<String> ) -> Unit> = mutableMapOf() - val mapper = jacksonObjectMapper() + val mapper = jacksonObjectMapper().apply { + val module = SimpleModule() + module.addSerializer(LocalDateTime::class.java, LocalDateTimeSerializer()) + module.addSerializer(LocalDate::class.java, LocalDateSerializer()) + module.addSerializer(LocalTime::class.java, LocalTimeSerializer()) + module.addSerializer(OffsetDateTime::class.java, OffsetDateTimeSerializer()) + module.addSerializer(OffsetTime::class.java, OffsetTimeSerializer()) + module.addDeserializer(LocalDateTime::class.java, LocalDateTimeDeserializer()) + module.addDeserializer(LocalDate::class.java, LocalDateDeserializer()) + module.addDeserializer(LocalTime::class.java, LocalTimeDeserializer()) + module.addDeserializer(OffsetDateTime::class.java, OffsetDateTimeDeserializer()) + module.addDeserializer(OffsetTime::class.java, OffsetTimeDeserializer()) + this.registerModule(module) + } var counter: Int = 0 /** + * @suppress internal function + */ + suspend fun initializeService(service: T, req: ServerRequest) { + if (service is WithRequest) { + service.serverRequest = req + } + if (service is WithWebSession) { + val session = req.session().awaitSingle() + service.webSession = session + } + if (service is WithPrincipal) { + val principal = req.principal().awaitSingle() + service.principal = principal + } + if (service is WithProfile) { + val profile = req.principal().ofType(Authentication::class.java).map { + it.principal as Profile + }.awaitSingle() + service.profile = profile + } + } + + /** * Binds a given route with a function of the receiver. * @param function a function of the receiver * @param method a HTTP method @@ -80,35 +127,34 @@ actual open class KVServiceManager<T : Any> actual constructor(val serviceClass: method: HttpMethod, route: String? ) { val routeDef = route ?: "route${this::class.simpleName}${counter++}" - addRoute(method, "/kv/$routeDef") { req, res, ctx -> + addRoute(method, "/kv/$routeDef") { req, ctx -> val service = ctx.getBean(serviceClass.java) + initializeService(service, req) val jsonRpcRequest = if (method == HttpMethod.GET) { - JsonRpcRequest(req.getParameter("id")?.toInt() ?: 0, "", listOf()) + JsonRpcRequest(req.queryParam("id").orElse(null)?.toInt() ?: 0, "", listOf()) } else { - mapper.readValue(req.inputStream, JsonRpcRequest::class.java) + req.awaitBody() } - GlobalScope.launch(start = CoroutineStart.UNDISPATCHED) { - try { - val result = function.invoke(service) - res.writeJSON( - mapper.writeValueAsString( - JsonRpcResponse( - id = jsonRpcRequest.id, - result = mapper.writeValueAsString(result) - ) + try { + val result = function.invoke(service) + ServerResponse.ok().json().bodyValueAndAwait( + mapper.writeValueAsString( + JsonRpcResponse( + id = jsonRpcRequest.id, + result = mapper.writeValueAsString(result) ) ) - } catch (e: Exception) { - LOG.error(e.message, e) - res.writeJSON( - mapper.writeValueAsString( - JsonRpcResponse( - id = jsonRpcRequest.id, - error = e.message ?: "Error" - ) + ) + } catch (e: Exception) { + LOG.error(e.message, e) + ServerResponse.ok().json().bodyValueAndAwait( + mapper.writeValueAsString( + JsonRpcResponse( + id = jsonRpcRequest.id, + error = e.message ?: "Error" ) ) - } + ) } } } @@ -127,36 +173,35 @@ actual open class KVServiceManager<T : Any> actual constructor(val serviceClass: if (method == HttpMethod.GET) throw UnsupportedOperationException("GET method is only supported for methods without parameters") val routeDef = route ?: "route${this::class.simpleName}${counter++}" - addRoute(method, "/kv/$routeDef") { req, res, ctx -> + addRoute(method, "/kv/$routeDef") { req, ctx -> val service = ctx.getBean(serviceClass.java) - val jsonRpcRequest = mapper.readValue(req.inputStream, JsonRpcRequest::class.java) + initializeService(service, req) + val jsonRpcRequest = req.awaitBody<JsonRpcRequest>() if (jsonRpcRequest.params.size == 1) { val param = getParameter<PAR>(jsonRpcRequest.params[0]) - GlobalScope.launch(start = CoroutineStart.UNDISPATCHED) { - try { - val result = function.invoke(service, param) - res.writeJSON( - mapper.writeValueAsString( - JsonRpcResponse( - id = jsonRpcRequest.id, - result = mapper.writeValueAsString(result) - ) + try { + val result = function.invoke(service, param) + ServerResponse.ok().json().bodyValueAndAwait( + mapper.writeValueAsString( + JsonRpcResponse( + id = jsonRpcRequest.id, + result = mapper.writeValueAsString(result) ) ) - } catch (e: Exception) { - LOG.error(e.message, e) - res.writeJSON( - mapper.writeValueAsString( - JsonRpcResponse( - id = jsonRpcRequest.id, - error = e.message ?: "Error" - ) + ) + } catch (e: Exception) { + LOG.error(e.message, e) + ServerResponse.ok().json().bodyValueAndAwait( + mapper.writeValueAsString( + JsonRpcResponse( + id = jsonRpcRequest.id, + error = e.message ?: "Error" ) ) - } + ) } } else { - res.writeJSON( + ServerResponse.ok().json().bodyValueAndAwait( mapper.writeValueAsString( JsonRpcResponse( id = jsonRpcRequest.id, @@ -182,37 +227,36 @@ actual open class KVServiceManager<T : Any> actual constructor(val serviceClass: if (method == HttpMethod.GET) throw UnsupportedOperationException("GET method is only supported for methods without parameters") val routeDef = route ?: "route${this::class.simpleName}${counter++}" - addRoute(method, "/kv/$routeDef") { req, res, ctx -> + addRoute(method, "/kv/$routeDef") { req, ctx -> val service = ctx.getBean(serviceClass.java) - val jsonRpcRequest = mapper.readValue(req.inputStream, JsonRpcRequest::class.java) + initializeService(service, req) + val jsonRpcRequest = req.awaitBody<JsonRpcRequest>() if (jsonRpcRequest.params.size == 2) { val param1 = getParameter<PAR1>(jsonRpcRequest.params[0]) val param2 = getParameter<PAR2>(jsonRpcRequest.params[1]) - GlobalScope.launch(start = CoroutineStart.UNDISPATCHED) { - try { - val result = function.invoke(service, param1, param2) - res.writeJSON( - mapper.writeValueAsString( - JsonRpcResponse( - id = jsonRpcRequest.id, - result = mapper.writeValueAsString(result) - ) + try { + val result = function.invoke(service, param1, param2) + ServerResponse.ok().json().bodyValueAndAwait( + mapper.writeValueAsString( + JsonRpcResponse( + id = jsonRpcRequest.id, + result = mapper.writeValueAsString(result) ) ) - } catch (e: Exception) { - LOG.error(e.message, e) - res.writeJSON( - mapper.writeValueAsString( - JsonRpcResponse( - id = jsonRpcRequest.id, - error = e.message ?: "Error" - ) + ) + } catch (e: Exception) { + LOG.error(e.message, e) + ServerResponse.ok().json().bodyValueAndAwait( + mapper.writeValueAsString( + JsonRpcResponse( + id = jsonRpcRequest.id, + error = e.message ?: "Error" ) ) - } + ) } } else { - res.writeJSON( + ServerResponse.ok().json().bodyValueAndAwait( mapper.writeValueAsString( JsonRpcResponse( id = jsonRpcRequest.id, @@ -238,39 +282,38 @@ actual open class KVServiceManager<T : Any> actual constructor(val serviceClass: if (method == HttpMethod.GET) throw UnsupportedOperationException("GET method is only supported for methods without parameters") val routeDef = route ?: "route${this::class.simpleName}${counter++}" - addRoute(method, "/kv/$routeDef") { req, res, ctx -> + addRoute(method, "/kv/$routeDef") { req, ctx -> val service = ctx.getBean(serviceClass.java) - val jsonRpcRequest = mapper.readValue(req.inputStream, JsonRpcRequest::class.java) + initializeService(service, req) + val jsonRpcRequest = req.awaitBody<JsonRpcRequest>() @Suppress("MagicNumber") if (jsonRpcRequest.params.size == 3) { val param1 = getParameter<PAR1>(jsonRpcRequest.params[0]) val param2 = getParameter<PAR2>(jsonRpcRequest.params[1]) val param3 = getParameter<PAR3>(jsonRpcRequest.params[2]) - GlobalScope.launch(start = CoroutineStart.UNDISPATCHED) { - try { - val result = function.invoke(service, param1, param2, param3) - res.writeJSON( - mapper.writeValueAsString( - JsonRpcResponse( - id = jsonRpcRequest.id, - result = mapper.writeValueAsString(result) - ) + try { + val result = function.invoke(service, param1, param2, param3) + ServerResponse.ok().json().bodyValueAndAwait( + mapper.writeValueAsString( + JsonRpcResponse( + id = jsonRpcRequest.id, + result = mapper.writeValueAsString(result) ) ) - } catch (e: Exception) { - LOG.error(e.message, e) - res.writeJSON( - mapper.writeValueAsString( - JsonRpcResponse( - id = jsonRpcRequest.id, - error = e.message ?: "Error" - ) + ) + } catch (e: Exception) { + LOG.error(e.message, e) + ServerResponse.ok().json().bodyValueAndAwait( + mapper.writeValueAsString( + JsonRpcResponse( + id = jsonRpcRequest.id, + error = e.message ?: "Error" ) ) - } + ) } } else { - res.writeJSON( + ServerResponse.ok().json().bodyValueAndAwait( mapper.writeValueAsString( JsonRpcResponse( id = jsonRpcRequest.id, @@ -296,40 +339,39 @@ actual open class KVServiceManager<T : Any> actual constructor(val serviceClass: if (method == HttpMethod.GET) throw UnsupportedOperationException("GET method is only supported for methods without parameters") val routeDef = route ?: "route${this::class.simpleName}${counter++}" - addRoute(method, "/kv/$routeDef") { req, res, ctx -> + addRoute(method, "/kv/$routeDef") { req, ctx -> val service = ctx.getBean(serviceClass.java) - val jsonRpcRequest = mapper.readValue(req.inputStream, JsonRpcRequest::class.java) + initializeService(service, req) + val jsonRpcRequest = req.awaitBody<JsonRpcRequest>() @Suppress("MagicNumber") if (jsonRpcRequest.params.size == 4) { val param1 = getParameter<PAR1>(jsonRpcRequest.params[0]) val param2 = getParameter<PAR2>(jsonRpcRequest.params[1]) val param3 = getParameter<PAR3>(jsonRpcRequest.params[2]) val param4 = getParameter<PAR4>(jsonRpcRequest.params[3]) - GlobalScope.launch(start = CoroutineStart.UNDISPATCHED) { - try { - val result = function.invoke(service, param1, param2, param3, param4) - res.writeJSON( - mapper.writeValueAsString( - JsonRpcResponse( - id = jsonRpcRequest.id, - result = mapper.writeValueAsString(result) - ) + try { + val result = function.invoke(service, param1, param2, param3, param4) + ServerResponse.ok().json().bodyValueAndAwait( + mapper.writeValueAsString( + JsonRpcResponse( + id = jsonRpcRequest.id, + result = mapper.writeValueAsString(result) ) ) - } catch (e: Exception) { - LOG.error(e.message, e) - res.writeJSON( - mapper.writeValueAsString( - JsonRpcResponse( - id = jsonRpcRequest.id, - error = e.message ?: "Error" - ) + ) + } catch (e: Exception) { + LOG.error(e.message, e) + ServerResponse.ok().json().bodyValueAndAwait( + mapper.writeValueAsString( + JsonRpcResponse( + id = jsonRpcRequest.id, + error = e.message ?: "Error" ) ) - } + ) } } else { - res.writeJSON( + ServerResponse.ok().json().bodyValueAndAwait( mapper.writeValueAsString( JsonRpcResponse( id = jsonRpcRequest.id, @@ -356,9 +398,10 @@ actual open class KVServiceManager<T : Any> actual constructor(val serviceClass: if (method == HttpMethod.GET) throw UnsupportedOperationException("GET method is only supported for methods without parameters") val routeDef = route ?: "route${this::class.simpleName}${counter++}" - addRoute(method, "/kv/$routeDef") { req, res, ctx -> + addRoute(method, "/kv/$routeDef") { req, ctx -> val service = ctx.getBean(serviceClass.java) - val jsonRpcRequest = mapper.readValue(req.inputStream, JsonRpcRequest::class.java) + initializeService(service, req) + val jsonRpcRequest = req.awaitBody<JsonRpcRequest>() @Suppress("MagicNumber") if (jsonRpcRequest.params.size == 5) { val param1 = getParameter<PAR1>(jsonRpcRequest.params[0]) @@ -366,31 +409,29 @@ actual open class KVServiceManager<T : Any> actual constructor(val serviceClass: val param3 = getParameter<PAR3>(jsonRpcRequest.params[2]) val param4 = getParameter<PAR4>(jsonRpcRequest.params[3]) val param5 = getParameter<PAR5>(jsonRpcRequest.params[4]) - GlobalScope.launch(start = CoroutineStart.UNDISPATCHED) { - try { - val result = function.invoke(service, param1, param2, param3, param4, param5) - res.writeJSON( - mapper.writeValueAsString( - JsonRpcResponse( - id = jsonRpcRequest.id, - result = mapper.writeValueAsString(result) - ) + try { + val result = function.invoke(service, param1, param2, param3, param4, param5) + ServerResponse.ok().json().bodyValueAndAwait( + mapper.writeValueAsString( + JsonRpcResponse( + id = jsonRpcRequest.id, + result = mapper.writeValueAsString(result) ) ) - } catch (e: Exception) { - LOG.error(e.message, e) - res.writeJSON( - mapper.writeValueAsString( - JsonRpcResponse( - id = jsonRpcRequest.id, - error = e.message ?: "Error" - ) + ) + } catch (e: Exception) { + LOG.error(e.message, e) + ServerResponse.ok().json().bodyValueAndAwait( + mapper.writeValueAsString( + JsonRpcResponse( + id = jsonRpcRequest.id, + error = e.message ?: "Error" ) ) - } + ) } } else { - res.writeJSON( + ServerResponse.ok().json().bodyValueAndAwait( mapper.writeValueAsString( JsonRpcResponse( id = jsonRpcRequest.id, @@ -413,9 +454,19 @@ actual open class KVServiceManager<T : Any> actual constructor(val serviceClass: ) { val routeDef = "route${this::class.simpleName}${counter++}" webSocketsRequests[routeDef] = { webSocketSession, ctx, incoming, outgoing -> - val service = synchronized(this) { - WebSocketSessionHolder.webSocketSession = webSocketSession - ctx.getBean(serviceClass.java) + val service = ctx.getBean(serviceClass.java) + if (service is WithWebSocketSession) { + service.webSocketSession = webSocketSession + } + if (service is WithPrincipal) { + val principal = webSocketSession.handshakeInfo.principal.awaitSingle() + service.principal = principal + } + if (service is WithProfile) { + val profile = webSocketSession.handshakeInfo.principal.ofType(Authentication::class.java).map { + it.principal as Profile + }.awaitSingle() + service.profile = profile } val requestChannel = Channel<PAR1>() val responseChannel = Channel<PAR2>() @@ -440,6 +491,7 @@ actual open class KVServiceManager<T : Any> actual constructor(val serviceClass: ) outgoing.send(text) } + if (!incoming.isClosedForReceive) incoming.cancel() } launch(start = CoroutineStart.UNDISPATCHED) { function.invoke(service, requestChannel, responseChannel) @@ -458,15 +510,16 @@ actual open class KVServiceManager<T : Any> actual constructor(val serviceClass: function: T.(String?, String?) -> List<RemoteOption> ) { val routeDef = "route${this::class.simpleName}${counter++}" - addRoute(HttpMethod.POST, "/kv/$routeDef") { req, res, ctx -> + addRoute(HttpMethod.POST, "/kv/$routeDef") { req, ctx -> val service = ctx.getBean(serviceClass.java) - val jsonRpcRequest = mapper.readValue(req.inputStream, JsonRpcRequest::class.java) + initializeService(service, req) + val jsonRpcRequest = req.awaitBody<JsonRpcRequest>() if (jsonRpcRequest.params.size == 2) { val param1 = getParameter<String?>(jsonRpcRequest.params[0]) val param2 = getParameter<String?>(jsonRpcRequest.params[1]) try { val result = function.invoke(service, param1, param2) - res.writeJSON( + ServerResponse.ok().json().bodyValueAndAwait( mapper.writeValueAsString( JsonRpcResponse( id = jsonRpcRequest.id, @@ -476,7 +529,7 @@ actual open class KVServic |
