joffrey-bion / krossbow

A Kotlin multiplatform coroutine-based STOMP client over websockets, with built-in conversions.
MIT License
208 stars 16 forks source link

How do I connect to spring boot websocket? #281

Closed mdddj closed 2 years ago

mdddj commented 2 years ago

springboot config

    override fun registerStompEndpoints(registry: StompEndpointRegistry) {
        super.registerStompEndpoints(registry)
        registry.addEndpoint("/idea-chat")
            .setAllowedOrigins("*")
            .setHandshakeHandler(principalHandshakeHandle)
            .addInterceptors(jwtVaildInterceptor)
    }

    override fun configureMessageBroker(registry: MessageBrokerRegistry) {
        registry.enableSimpleBroker("/queue", "/topic")
        registry.setUserDestinationPrefix("/user")
        super.configureMessageBroker(registry)
    }

krossbow


    private  var tokenField = JTextField()

    @OptIn(DelicateCoroutinesApi::class)
    private fun socketConnect() {
        GlobalScope.launch {
            val stompClient = StompClient(KtorWebSocketClient())
            try{
                val session = stompClient.connect("ws://192.168.199.86/idea-chat?token=${tokenField.text}")
                val subscribeText = session.subscribeText("/room/闲聊吹水")
                launch {
                    subscribeText.collect {msg ->
                        println("msg=:$msg")
                    }
                }
            }catch (e: StompConnectionException) {
                println(":$e  ")
            }
        }
    }

error log

Exception in thread "DefaultDispatcher-worker-2" org.hildan.krossbow.stomp.WebSocketConnectionException: Failed to connect at web socket level to ws://192.168.199.86/idea-chat?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2Njc1MzA2OTMsInVzZXJuYW1lIjoiYWRtaW4ifQ.Tv-6SlXjB6_GG6FhyzaEMaACtnSX6qfDLWhfgsUbiog
    at org.hildan.krossbow.stomp.StompClient.webSocketConnect(StompClient.kt:86)
    at org.hildan.krossbow.stomp.StompClient.access$webSocketConnect(StompClient.kt:23)
    at org.hildan.krossbow.stomp.StompClient$webSocketConnect$1.invokeSuspend(StompClient.kt)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)
    Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@53390416, Dispatchers.Default]
Caused by: org.hildan.krossbow.websocket.WebSocketConnectionException: Couldn't connect to web socket at ws://192.168.199.86/idea-chat?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2Njc1MzA2OTMsInVzZXJuYW1lIjoiYWRtaW4ifQ.Tv-6SlXjB6_GG6FhyzaEMaACtnSX6qfDLWhfgsUbiog
    at org.hildan.krossbow.websocket.ktor.KtorWebSocketClient.connect(KtorWebSocketClient.kt:30)
    at org.hildan.krossbow.websocket.ktor.KtorWebSocketClient$connect$1.invokeSuspend(KtorWebSocketClient.kt)
    ... 6 more
Caused by: java.lang.IllegalArgumentException: Engine doesn't support WebSocketCapability
    at io.ktor.client.engine.HttpClientEngine$DefaultImpls.checkExtensions(HttpClientEngine.kt:105)
    at io.ktor.client.engine.HttpClientEngine$DefaultImpls.access$checkExtensions(HttpClientEngine.kt:24)
    at io.ktor.client.engine.HttpClientEngine$install$1.invokeSuspend(HttpClientEngine.kt:68)
    at io.ktor.client.engine.HttpClientEngine$install$1.invoke(HttpClientEngine.kt)
    at io.ktor.client.engine.HttpClientEngine$install$1.invoke(HttpClientEngine.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:123)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:81)
    at io.ktor.util.pipeline.SuspendFunctionGun.execute$ktor_utils(SuspendFunctionGun.kt:101)
    at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77)
    at io.ktor.client.plugins.HttpSend$DefaultSender.execute(HttpSend.kt:138)
    at io.ktor.client.plugins.HttpRedirect$Plugin$install$1.invokeSuspend(HttpRedirect.kt:61)
    at io.ktor.client.plugins.HttpRedirect$Plugin$install$1.invoke(HttpRedirect.kt)
    at io.ktor.client.plugins.HttpRedirect$Plugin$install$1.invoke(HttpRedirect.kt)
    at io.ktor.client.plugins.HttpSend$InterceptedSender.execute(HttpSend.kt:116)
    at io.ktor.client.plugins.HttpCallValidator$Companion$install$3.invokeSuspend(HttpCallValidator.kt:147)
    at io.ktor.client.plugins.HttpCallValidator$Companion$install$3.invoke(HttpCallValidator.kt)
    at io.ktor.client.plugins.HttpCallValidator$Companion$install$3.invoke(HttpCallValidator.kt)
    at io.ktor.client.plugins.HttpSend$InterceptedSender.execute(HttpSend.kt:116)
    at io.ktor.client.plugins.HttpSend$Plugin$install$1.invokeSuspend(HttpSend.kt:104)
    at io.ktor.client.plugins.HttpSend$Plugin$install$1.invoke(HttpSend.kt)
    at io.ktor.client.plugins.HttpSend$Plugin$install$1.invoke(HttpSend.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:123)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:81)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceedWith(SuspendFunctionGun.kt:91)
    at io.ktor.client.plugins.websocket.WebSockets$Plugin$install$1.invokeSuspend(WebSockets.kt:161)
    at io.ktor.client.plugins.websocket.WebSockets$Plugin$install$1.invoke(WebSockets.kt)
    at io.ktor.client.plugins.websocket.WebSockets$Plugin$install$1.invoke(WebSockets.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:123)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:81)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceedWith(SuspendFunctionGun.kt:91)
    at io.ktor.client.plugins.HttpCallValidator$Companion$install$1.invokeSuspend(HttpCallValidator.kt:126)
    at io.ktor.client.plugins.HttpCallValidator$Companion$install$1.invoke(HttpCallValidator.kt)
    at io.ktor.client.plugins.HttpCallValidator$Companion$install$1.invoke(HttpCallValidator.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:123)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:81)
    at io.ktor.client.plugins.HttpRequestLifecycle$Plugin$install$1.invokeSuspend(HttpRequestLifecycle.kt:35)
    at io.ktor.client.plugins.HttpRequestLifecycle$Plugin$install$1.invoke(HttpRequestLifecycle.kt)
    at io.ktor.client.plugins.HttpRequestLifecycle$Plugin$install$1.invoke(HttpRequestLifecycle.kt)
    at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:123)
    at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:81)
    at io.ktor.util.pipeline.SuspendFunctionGun.execute$ktor_utils(SuspendFunctionGun.kt:101)
    at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77)
    at io.ktor.client.HttpClient.execute$ktor_client_core(HttpClient.kt:191)
    at io.ktor.client.statement.HttpStatement.executeUnsafe(HttpStatement.kt:108)
    at io.ktor.client.plugins.websocket.BuildersKt$webSocketSession$2.invokeSuspend(builders.kt:239)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    at kotlinx.coroutines.internal.LimitedDispatcher.run(LimitedDispatcher.kt:42)
    at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:95)
    ... 4 more

I'm a novice. It's boring. I can't connect it with anything

my code repository https://github.com/mdddj/json_to_dart_pro/blob/ab5e947ea6612358ac84b2a15449f8fe4cbb1ccd/src/main/kotlin/windown/ChatWindow.kt#L49

joffrey-bion commented 2 years ago

Hi, thank you for your question.

If you check the stacktrace of the error, you can see multiple Caused by lines. The last one that appears is the root cause of the error. In this case, it reads Engine doesn't support WebSocketCapability. This means the Ktor engine you're using doesn't support web sockets. Which Ktor engine do you use?

You can check which engines support web sockets here: https://ktor.io/docs/http-client-engines.html#limitations

mdddj commented 2 years ago

@joffrey-bion Hi, thank you for your reply. I checked the document, but an exception occurred

Modified code

val okHttpClient = OkHttpClient.Builder()
                .callTimeout(Duration.ofMinutes(1))
                .pingInterval(Duration.ofSeconds(10))
                .build()
            val wsClient = OkHttpWebSocketClient(okHttpClient)
            val stompClient = StompClient(wsClient)
            try{
                val session = stompClient.connect("ws://192.168.199.86/idea-chat?token=${tokenField.text}")
                val subscribeText = session.subscribeText("/room/闲聊吹水")
                launch {
                    subscribeText.collect {msg -> println("收到消息:$msg") }
                }
            }catch (e: StompConnectionException) {
                println("Error:$e  ")
            }

error message

Error:org.hildan.krossbow.stomp.StompConnectionException: Failed to connect at STOMP protocol level to host '192.168.199.86'  
mdddj commented 2 years ago

My local service has been started and can receive connections, but the client cannot connect

image image
mdddj commented 2 years ago
Error:org.hildan.krossbow.stomp.StompConnectionException: Failed to connect at STOMP protocol level to host '192.168.199.86'
    at org.hildan.krossbow.stomp.StompWsExtensionsKt.stomp(StompWsExtensions.kt:36)
    at org.hildan.krossbow.stomp.StompWsExtensionsKt$stomp$1.invokeSuspend(StompWsExtensions.kt)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.internal.ScopeCoroutine.afterResume(Scopes.kt:33)
    at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:102)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
    at kotlinx.coroutines.internal.ScopeCoroutine.afterResume(Scopes.kt:33)
    at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:102)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)
Caused by: java.lang.IllegalStateException: Expected CONNECTED frame in response to CONNECT, got Connect(headers=StompConnectHeaders(rawHeaders=SimpleStompHeaders(headers={host=192.168.199.86, accept-version=1.2, heart-beat=0,0})))
    at org.hildan.krossbow.stomp.StompWsExtensionsKt.awaitConnectedFrame(StompWsExtensions.kt:58)
    at org.hildan.krossbow.stomp.StompWsExtensionsKt.access$awaitConnectedFrame(StompWsExtensions.kt:1)
    at org.hildan.krossbow.stomp.StompWsExtensionsKt$awaitConnectedFrame$1.invokeSuspend(StompWsExtensions.kt)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    ... 4 more
joffrey-bion commented 2 years ago

This is strange. According to the error, the server replied CONNECT instead of CONNECTED.

Do you have other code to share from the server side? Could it be that some websocket handler code is echoing the frames back to the client?

mdddj commented 2 years ago

Yes, I think there is a problem on the server. I am looking for a solution

    @Resource
    private lateinit var connectErrorHandle: ConnectErrorHandle

    override fun registerStompEndpoints(registry: StompEndpointRegistry) {
        super.registerStompEndpoints(registry)
        registry.addEndpoint("/idea-chat")
            .setAllowedOrigins("*")
            .setHandshakeHandler(principalHandshakeHandle)
            .addInterceptors(jwtVaildInterceptor)

        registry.setErrorHandler(connectErrorHandle)
    }
@Component
class ConnectErrorHandle : StompSubProtocolErrorHandler() {

    private  var log = LoggerFactory.getLogger(ConnectErrorHandle::class.java)

    override fun handleClientMessageProcessingError(
        clientMessage: Message<ByteArray>?,
        ex: Throwable
    ): Message<ByteArray>? {

//        clientMessage?.let {
//            return ErrorResultMessage(clientMessage,ex)
//        }

        log.error("error: ${ex}")

        return super.handleClientMessageProcessingError(clientMessage, ex)
    }
}

The error code

2022-11-04T17:11:23.073+08:00 ERROR 92662 --- [p-nio-80-exec-1] s.i.t.i.w.socket.v1.ConnectErrorHandle   : error: org.springframework.messaging.MessageDeliveryException: Failed to send message to ExecutorSubscribableChannel[clientInboundChannel], failedMessage=GenericMessage [payload=byte[0], headers={simpMessageType=CONNECT, stompCommand=CONNECT, nativeHeaders={host=[192.168.199.86], accept-version=[1.2], heart-beat=[0,0]}, simpSessionAttributes={user=shop.itbug.ticket.idea.web.socket.v1.JwtVaildInterceptor$$Lambda$1957/0x0000000801812ff0@ec4ad95}, simpHeartbeat=[J@291659c7, simpUser=shop.itbug.ticket.idea.web.socket.v1.JwtVaildInterceptor$$Lambda$1957/0x0000000801812ff0@ec4ad95, simpSessionId=be89781b-4b81-034a-e701-e4038d4e32b3}]
2022-11-04T17:11:23.081+08:00  WARN 92662 --- [p-nio-80-exec-1] w.s.h.ExceptionWebSocketHandlerDecorator : Unhandled exception after connection closed for ExceptionWebSocketHandlerDecorator [delegate=LoggingWebSocketHandlerDecorator [delegate= [delegate=SubProtocolWebSocketHandler[StompSubProtocolHandler[v10.stomp, v11.stomp, v12.stomp]]]]]
joffrey-bion commented 2 years ago

log.error("error: ${ex}") note that you can use the overload of .error() that takes an exception as argument, so you can see the full stacktrace in the logs: log.error("websocket connection error", ex).

At the moment it's hard to tell what the root cause of the error is without the full stacktrace

mdddj commented 2 years ago

Thanks, I got the following error, but I still failed after disabling csrf

Caused by: org.springframework.security.web.csrf.MissingCsrfTokenException: Could not verify the provided CSRF token because no token was found to compare.
        at org.springframework.security.messaging.web.csrf.CsrfChannelInterceptor.preSend(CsrfChannelInterceptor.java:53) ~[spring-security-messaging-6.0.0-M7.jar:6.0.0-M7]
        at org.springframework.messaging.support.AbstractMessageChannel$ChannelInterceptorChain.applyPreSend(AbstractMessageChannel.java:181) ~[spring-messaging-6.0.0-M6.jar:6.0.0-M6]
        at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:135) ~[spring-messaging-6.0.0-M6.jar:6.0.0-M6]
        ... 28 common frames omitted
mdddj commented 2 years ago

Thank you for your help. I found a solution to the problem. Deleting the @ EnableWebSocketSecurity annotation in Springboot3.0 will solve the problem

https://docs.spring.io/spring-security/reference/6.0/servlet/integrations/websocket.html

image

joffrey-bion commented 2 years ago

Glad that you found the problem!