SocketJS + Spring Boot > 未捕获错误:InvalidStateError:连接尚未建立

问题描述 投票:0回答:1

更新:请检查我的最新编辑,它似乎只是停留在“正在连接...”

我正在使用 Spring Boot 创建一个 SocketJS(使用 STOMP)应用程序,每当我的应用程序启动时,我都会收到此错误:

caught Error: InvalidStateError: The connection has not been established yet
    at SockJS.send (main.js:161:1)
    at Client._transmit (stomp.js:159:1)
    at Client.disconnect (stomp.js:341:1)
    at ChatRoom.tsx:37:1
    at safelyCallDestroy (react-dom.development.js:22932:1)
    at commitHookEffectListUnmount (react-dom.development.js:23100:1)
    at invokePassiveEffectUnmountInDEV (react-dom.development.js:25207:1)
    at invokeEffectsInDev (react-dom.development.js:27351:1)
    at commitDoubleInvokeEffectsInDEV (react-dom.development.js:27324:1)
    at flushPassiveEffectsImpl (react-dom.development.js:27056:1)
S

然后,在后端我收到此错误:

 WebSocketSession[2 current WS(2)-HttpStream(0)-HttpPoll(0), 2 total, 0 closed abnormally (0 connect failure, 0 send limit, 0 transport error)], stompSubProtocol[processed CONNECT(2)-CONNECTED(2)-DISCONNECT(0)], stompBrokerRelay[null], inboundChannel[pool size = 16, active threads = 0, queued tasks = 0, completed tasks = 18], outboundChannel[pool size = 2, active threads = 0, queued tasks = 0, completed tasks = 2], sockJsScheduler[pool size = 8, active threads = 1, queued tasks = 3, completed tasks = 9]
2023-10-15T15:13:35.507+13:00 ERROR 10628 --- [nio-8080-exec-9] o.s.w.s.m.StompSubProtocolHandler        : Error publishing SessionDisconnectEvent[sessionId=stpnlchb, CloseStatus[code=1000, reason=null]]

前端React代码:

import { Button, Flex, Input, Text } from "@chakra-ui/react";
import { Suspense, useEffect, useState } from "react";
import SockJS from "sockjs-client";
import { Client, Message, over } from "stompjs";

interface ChatMessage {
    content: string
    sender: string
    type: 'CHAT' | 'JOIN' | 'LEAVE'
}

interface ChatRoomProps {
    username: string
}

export default function ChatRoom(props: ChatRoomProps) {
    const [loading, setIsLoading] = useState(true)
    const [error, setError] = useState(false)
    const [message, setMessage] = useState<string | null>(null)
    const [messageStream, setMessageStream] = useState<ChatMessage[]>([])
    const [stompClient, setStompClient] = useState<Client | null>(null)

    useEffect(() => {
        const socket = new SockJS('http://localhost:8080/ws')
        const client: Client = over(socket)

        client.connect({}, () => {
            client.send('/chat.addUser', {}, JSON.stringify({ content: '', sender: props.username, type: 'JOIN' }))
            client.subscribe('/topic/public', messageReceivedCallbackHandler)

            setIsLoading(false)
        }, errorCallbackHandler)

        setStompClient(client)

        return () => {
            socket.close()
            client.disconnect(() => {})
        }
    }, [])

    function messageReceivedCallbackHandler(payload: Message): void {
        const message: ChatMessage = JSON.parse(payload.body)

        setMessageStream(prevMessageStream => [...prevMessageStream, message])
    }

    function errorCallbackHandler(): void {
        setError(true)
    }

    function messageChangeHandler(e: React.ChangeEvent<HTMLInputElement>): void {
        setMessage(e.target.value)
    }

    function sendClickHandler(): void {
        if (message && stompClient) {
            const chatMessage: ChatMessage = {
                content: message,
                sender: props.username,
                type: 'CHAT'
            }

            stompClient.send('/chat.sendMessage', {}, JSON.stringify(chatMessage))

            setMessage(null)
            
            const inputElement = document.getElementById('msg-input') as HTMLInputElement
            inputElement.value = ''
        }
    }

    return (
        <Flex
            width='750px'
            height='750px'
            flexDirection='column'
            gap='16px'
            background='white'
            alignItems='center'
            padding='42px'
            overflow='clip'
        >
            <Text fontSize='lg' fontWeight='bold'>Spring WebSocket Chat Demo - By Alibou</Text>

            {error && <Text>Error connecting to server</Text>}

            {loading ? <Text>Loading...</Text> : (
                <Flex flexDirection='column' textAlign='center' overflowY='scroll' width='100%'>
                    {messageStream.map((message, index) => (
                        <>
                            {message.type === 'CHAT' && <Text key={index}><b>{message.sender}</b>: {message.content}</Text>}
                            {message.type === 'JOIN' && <Text key={index}><b>{message.sender}</b> joined</Text>}
                            {message.type === 'LEAVE' && <Text key={index}><b>{message.sender}</b> left</Text>}
                        </>
                    ))}
                </Flex>
            )}


            <Flex marginTop='auto' width='100%' gap='16px'>
                <Input id='msg-input' placeholder='Type a message' marginTop='auto' onChange={messageChangeHandler} />
                
                <Button colorScheme='blue' onClick={sendClickHandler}>Send</Button>
            </Flex>
        </Flex>
    )
} 

Java 后端:

package com.example.socket.controller;

import com.example.socket.controller.model.ChatMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.web.bind.annotation.RestController;

import java.util.Objects;

@RestController
@Slf4j
public class ChatController {

    @MessageMapping("/chat.sendMessage")
    @SendTo("/topic/public")
    public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {
        return chatMessage;
    }

    @MessageMapping("/chat.addUser")
    @SendTo("/topic/public")
    public ChatMessage addUser(
        @Payload ChatMessage chatMessage,
        SimpMessageHeaderAccessor headerAccessor
    ) {
        Objects.requireNonNull(headerAccessor.getSessionAttributes()).put("username", chatMessage.getSender());

        return chatMessage;
    }
}
package com.example.socket.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws").setAllowedOriginPatterns("*").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.setApplicationDestinationPrefixes("/app");
        registry.enableSimpleBroker("/topic");
    }
}
package com.example.socket.config;

import com.example.socket.controller.model.ChatMessage;
import com.example.socket.controller.model.MessageType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.SessionDisconnectEvent;

import java.util.Objects;

@Component
@Slf4j
public class WebSocketEventListener {

    private final SimpMessageSendingOperations messageTemplate;

    @Autowired
    public WebSocketEventListener(SimpMessageSendingOperations messageTemplate) {
        this.messageTemplate = messageTemplate;
    }

    @EventListener
    public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
        StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage());
        String username = Objects.requireNonNull(headerAccessor.getSessionAttributes()).get("username").toString();

        log.info(STR."User disconnected: \{username}");

        ChatMessage chatMessage = new ChatMessage();
        chatMessage.setType(MessageType.LEAVE);
        chatMessage.setSender(username);

        messageTemplate.convertAndSend("/topic/public", chatMessage);
    }
}

我不知道为什么会发生这种情况。我尝试解决这个问题有一段时间了,但我不知道问题的根源是什么。我怀疑问题出在前端的某个地方。

更新:

将代码重构为如下所示:

useEffect(() => {
        const socket = new SockJS('http://localhost:8080/ws')

        socket.onopen = () => {
            const client = over(socket)
            setStompClient(client)

            client.connect({}, () => {
                client.send('/chat.addUser', {}, JSON.stringify({ content: '', sender: props.username, type: 'JOIN' }))
                client.subscribe('/topic/public', messageReceivedCallbackHandler)

                setIsLoading(false)
            }, errorCallbackHandler)
        }

        return () => {
            socket.close()
            stompClient?.disconnect(() => {})
        }
    }, [])

它似乎只是停留在“正在连接...”,这很奇怪。

堆栈跟踪:

2023-10-19T17:23:06.840+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.authenticator.AuthenticatorBase    : Security checking request GET /ws/463/evl1y24y/websocket
2023-10-19T17:23:06.840+13:00 DEBUG 26253 --- [nio-8080-exec-3] org.apache.catalina.realm.RealmBase      :   No applicable constraints defined
2023-10-19T17:23:06.840+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.authenticator.AuthenticatorBase    : Not subject to any constraint
2023-10-19T17:23:06.841+13:00 DEBUG 26253 --- [nio-8080-exec-3] org.apache.tomcat.util.http.Parameters   : Set encoding to UTF-8
2023-10-19T17:23:06.841+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet        : GET "/ws/463/evl1y24y/websocket", parameters={}
2023-10-19T17:23:06.841+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.s.w.s.s.s.WebSocketHandlerMapping      : Mapped to org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler@6b350309
2023-10-19T17:23:06.842+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.s.w.s.s.t.h.DefaultSockJsService       : Processing transport request: GET http://localhost:8080/ws/463/evl1y24y/websocket
2023-10-19T17:23:06.938+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase       :     findClass(org.apache.tomcat.util.collections.LocalStrings)
2023-10-19T17:23:06.939+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase       :     --> Returning ClassNotFoundException
2023-10-19T17:23:06.939+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase       : getResource(org/apache/tomcat/util/collections/LocalStrings.properties)
2023-10-19T17:23:06.939+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase       :   Delegating to parent classloader jdk.internal.loader.ClassLoaders$AppClassLoader@4e0e2f2a
2023-10-19T17:23:06.939+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase       :   --> Resource not found, returning null
2023-10-19T17:23:06.939+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase       :     findClass(org.apache.tomcat.util.collections.LocalStrings_en)
2023-10-19T17:23:06.939+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase       :     --> Returning ClassNotFoundException
2023-10-19T17:23:06.939+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase       : getResource(org/apache/tomcat/util/collections/LocalStrings_en.properties)
2023-10-19T17:23:06.940+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase       :   Delegating to parent classloader jdk.internal.loader.ClassLoaders$AppClassLoader@4e0e2f2a
2023-10-19T17:23:06.940+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase       :   --> Resource not found, returning null
2023-10-19T17:23:06.940+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase       :     findClass(org.apache.tomcat.util.collections.LocalStrings_en_NZ)
2023-10-19T17:23:06.940+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase       :     --> Returning ClassNotFoundException
2023-10-19T17:23:06.940+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase       : getResource(org/apache/tomcat/util/collections/LocalStrings_en_NZ.properties)
2023-10-19T17:23:06.940+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase       :   Delegating to parent classloader jdk.internal.loader.ClassLoaders$AppClassLoader@4e0e2f2a
2023-10-19T17:23:06.941+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.loader.WebappClassLoaderBase       :   --> Resource not found, returning null
2023-10-19T17:23:06.962+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet        : Completed 101 SWITCHING_PROTOCOLS
2023-10-19T17:23:06.964+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.apache.coyote.http11.Http11Processor   : Socket: [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@718be7b6:org.apache.tomcat.util.net.NioChannel@7f4c17f7:java.nio.channels.SocketChannel[connected local=/[0:0:0:0:0:0:0:1]:8080 remote=/[0:0:0:0:0:0:0:1]:55430]], Status in: [OPEN_READ], State out: [UPGRADING]
2023-10-19T17:23:07.001+13:00 DEBUG 26253 --- [nio-8080-exec-3] org.apache.tomcat.websocket.WsSession    : Created WebSocket session [0]
2023-10-19T17:23:07.006+13:00 DEBUG 26253 --- [nio-8080-exec-3] s.w.s.h.LoggingWebSocketHandlerDecorator : New WebSocketServerSockJsSession[id=evl1y24y]
2023-10-19T17:23:07.027+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.t.websocket.server.WsFrameServer     : wsFrameServer.onDataAvailable
2023-10-19T17:23:07.028+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.tomcat.util.net.SocketWrapperBase    : Socket: [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@718be7b6:org.apache.tomcat.util.net.NioChannel@7f4c17f7:java.nio.channels.SocketChannel[connected local=/[0:0:0:0:0:0:0:1]:8080 remote=/[0:0:0:0:0:0:0:1]:55430]], Read from buffer: [0]
2023-10-19T17:23:07.028+13:00 DEBUG 26253 --- [nio-8080-exec-3] org.apache.tomcat.util.net.NioEndpoint   : Socket: [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@718be7b6:org.apache.tomcat.util.net.NioChannel@7f4c17f7:java.nio.channels.SocketChannel[connected local=/[0:0:0:0:0:0:0:1]:8080 remote=/[0:0:0:0:0:0:0:1]:55430]], Read direct from socket: [0]
2023-10-19T17:23:07.028+13:00 DEBUG 26253 --- [nio-8080-exec-3] o.a.c.h.u.UpgradeProcessorInternal       : Socket: [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@718be7b6:org.apache.tomcat.util.net.NioChannel@7f4c17f7:java.nio.channels.SocketChannel[connected local=/[0:0:0:0:0:0:0:1]:8080 remote=/[0:0:0:0:0:0:0:1]:55430]], Status in: [OPEN_READ], State out: [UPGRADED]
2023-10-19T17:23:07.029+13:00 DEBUG 26253 --- [nio-8080-exec-3] org.apache.tomcat.util.net.NioEndpoint   : Registered read interest for [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@718be7b6:org.apache.tomcat.util.net.NioChannel@7f4c17f7:java.nio.channels.SocketChannel[connected local=/[0:0:0:0:0:0:0:1]:8080 remote=/[0:0:0:0:0:0:0:1]:55430]]
2023-10-19T17:23:44.841+13:00 DEBUG 26253 --- [alina-utility-1] o.apache.catalina.session.ManagerBase    : Start expire sessions StandardManager at 1697689424841 sessioncount 0
2023-10-19T17:23:44.842+13:00 DEBUG 26253 --- [alina-utility-1] o.apache.catalina.session.ManagerBase    : End expire sessions StandardManager processingTime 1 expired sessions: 0
java spring spring-boot sockets stomp
1个回答
0
投票

您正在配置

registry.setApplicationDestinationPrefixes("/app");
这意味着主题必须包含前缀
/app
。 而不是

client.send('/chat.addUser', {}, JSON.stringify({ content: '', sender: props.username, type: 'JOIN' }))
stompClient.send('/chat.sendMessage', {}, JSON.stringify(chatMessage))

试试这个

client.send('/app/chat.addUser', {}, JSON.stringify({ content: '', sender: props.username, type: 'JOIN' }))
stompClient.send('/app/chat.sendMessage', {}, JSON.stringify(chatMessage))
© www.soinside.com 2019 - 2024. All rights reserved.