Spring Boot Web Flux with JOOQ for interfacing with DB and Kotlin coroutines to make blocking JDBC calls run asynchronously. Now with rsockets.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

96 lines
3.3 KiB

  1. package com.example.demo.controller
  2. import com.example.demo.model.Message
  3. import com.example.demo.model.User
  4. import io.vavr.collection.HashMap
  5. import io.vavr.collection.Map
  6. import kotlinx.coroutines.delay
  7. import kotlinx.coroutines.flow.Flow
  8. import kotlinx.coroutines.flow.flow
  9. import org.slf4j.LoggerFactory
  10. import org.springframework.messaging.handler.annotation.MessageExceptionHandler
  11. import org.springframework.messaging.handler.annotation.MessageMapping
  12. import org.springframework.messaging.handler.annotation.Payload
  13. import org.springframework.messaging.rsocket.RSocketRequester
  14. import org.springframework.messaging.rsocket.annotation.ConnectMapping
  15. import org.springframework.stereotype.Controller
  16. @Controller
  17. class RSocketConnectionController {
  18. private val log = LoggerFactory.getLogger(RSocketConnectionController::class.java)
  19. private var requesterMap: Map<String, RSocketRequester> = HashMap.empty()
  20. @Synchronized
  21. private fun getRequesterMap(): Map<String, RSocketRequester> {
  22. return requesterMap
  23. }
  24. @Synchronized
  25. private fun addRequester(rSocketRequester: RSocketRequester, clientId: String) {
  26. log.info("adding requester {}", clientId)
  27. requesterMap = requesterMap.put(clientId, rSocketRequester)
  28. }
  29. @Synchronized
  30. private fun removeRequester(clientId: String) {
  31. log.info("removing requester {}", clientId)
  32. requesterMap = requesterMap.remove(clientId)
  33. }
  34. @ConnectMapping("client-id")
  35. fun onConnect(rSocketRequester: RSocketRequester, @Payload user: User) {
  36. // val clientIdFixed = clientId.replace("\"", "") //check why the serializer adds " to strings
  37. // rSocketRequester.rsocket().dispose() //to reject connection
  38. val clientId = user.id.toString()
  39. rSocketRequester
  40. .rsocket()
  41. .onClose()
  42. .subscribe(null, null, {
  43. log.info("{} just disconnected", clientId)
  44. removeRequester(clientId)
  45. })
  46. addRequester(rSocketRequester, clientId)
  47. }
  48. @MessageMapping("private.news")
  49. fun privateNews(message: Message, rSocketRequesterParam: RSocketRequester) {
  50. getRequesterMap()
  51. .filterKeys { key -> key == message.toUser || key == message.fromUser }
  52. .values()
  53. .forEach { requester ->
  54. run {
  55. println("Sending message")
  56. sendMessage(requester, message)
  57. }
  58. }
  59. }
  60. @MessageExceptionHandler
  61. suspend fun handleException(ex: IllegalArgumentException): String {
  62. delay(10)
  63. return "${ex.message} handled"
  64. }
  65. @MessageMapping("echo-stream-async")
  66. suspend fun echoStreamAsync(payload: String): Flow<String> {
  67. delay(10)
  68. var i = 0
  69. return flow {
  70. while (true) {
  71. delay(10)
  72. emit("$payload ${i++}")
  73. }
  74. }
  75. }
  76. private fun sendMessage(requester: RSocketRequester, message: Message) =
  77. requester
  78. .route("user.queue.reply")
  79. // .metadata("test", MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.string))
  80. .data(message)
  81. .send()
  82. .subscribe()
  83. }