WebSocket Closes with Protocol Error 1002

12.9k Views Asked by At

I m implementing WebSocket messages command-line client. I have checked that this error corresponds to the problem with the protocol. I upgraded ws to the newest 7.4.1. At backend I use Spring Boot Websockets at version 2.3.4.RELEASE.

The 2 main causes of this are said to be packet loss or malformed messages. I have made some checks to check those but none seem valid. The messages I test are small so it shouldn't be the case with message size. The connection is fully on localhost. I test the solution with 3 users and sometimes I get this error sometimes not.

Can someone help me figure out how to get rid of this type of error?

Here is the code I use for client to send messages:

async function test(number_of_messages, break_between_messages) {
const websocket = new WebSocket(url...)

websocket.on('message', function incoming(data) {
    console.log(getMessage("Received", data))
});

websocket.on('close', function(data) {
    console.log('Disconnected!!!! ' + data.toString());
});

const opened = await connection(websocket)

//Wait 5 seconds
await sleep(5_000);

if (opened) {
    for (i = 0; i < number_of_messages; i++) {

        for (const chatId of chatIds) {
            let content = i.toString() + " from " + user;
            let msg = JSON.stringify({
                "chatId": chatId,
                "author": user,
                "content": content
            })
            websocket.send(msg)

            let message = getMessage("Sent", msg)
            console.log(message)
        }

        await sleep(break_between_messages);
    }

} else {
    console.log("ERROR on Opening Connection")
    return
}

// Wait 1 minute
await sleep(60_000);
websocket.close()

}

With backend code:

@Component
@ServerEndpoint(value = "/webSocket/{username}",
        encoders = MessageRepresentationEncoder.class, decoders = MessageRepresentationDecoder.class)
public class MessagingSocket {
    private Logger logger = LoggerFactory.getInstance();
    private Session session;
    private MessagingAPI messagingAPI = MessagingAPIFactory.createAPI();
    private UserSocketRegistry userSocketRegistry = UserSocketRegistry.createRegistry();
    private SessionUserRegistry sessionUserRegistry = SessionUserRegistry.createRegistry();

    @OnOpen
    public void onOpen(Session session, @PathParam("username") String username) {
        this.session = session;
        logger.log(LoggingType.INFO, "Started new session " + session.getId());
        logger.log(LoggingType.INFO, username + " connected");

        userSocketRegistry.addSessionForUser(this, username);
        sessionUserRegistry.addSessionForUser(session, username);
    }

    @OnMessage //Allows the client to send message to the socket.
    public void onMessage(MessageRepresentation messageRepresentation) {
        logger.log(LoggingType.INFO, "Received " + messageRepresentation.toString());
        messagingAPI.write(WriteMessage.from(UUID.fromString(messageRepresentation.chatId), messageRepresentation.author, messageRepresentation.content));
        broadcastToChat(messageRepresentation);
    }

    private void broadcastToChat(MessageRepresentation message) {
        final List<MessagingSocket> sockets = messagingAPI.getUsersConnectedToChat(UUID.fromString(message.chatId)).stream().filter(user -> userSocketRegistry.hasSocketFor(user.getName()))
                .map(user -> userSocketRegistry.getSocketFor(user.getName())).collect(Collectors.toList());

        logger.log(LoggingType.INFO, "Starting broadcast of " + message.content + " from " + message.author + " for " + String.join(",", messagingAPI.getUsersConnectedToChat(UUID.fromString(message.chatId)).stream().map(x -> x.getName()).collect(Collectors.toList())));
        for (MessagingSocket messagingSocket : sockets) {
            logger.log(LoggingType.INFO, "Broadcasting message" + message.content + " to " + messagingSocket.session.getId());
            messagingSocket.sendMessage(message);

        }
    }

    private void sendMessage(MessageRepresentation message) {
        try {
            this.session.getBasicRemote().sendObject(message);
        } catch (IOException | EncodeException e) {
            logger.log(LoggingType.ERROR, "Caught exception while sending message to Session Id: " + this.session.getId());
        }
    }

    @OnClose
    public void onClose(Session session) {
        String user = sessionUserRegistry.getUserFor(session);
        logger.log(LoggingType.INFO, "User " + user + " with session " + this.session.getId() + " disconnected ");
        sessionUserRegistry.removeSession(session);
        userSocketRegistry.removeUser(user);
    }
}

And MessageRepresentation as:

public class MessageRepresentation {
    public String chatId;
    public String author;
    public String content;

    @Override
    public String toString() {
        return "MessageRepresentation{" +
                "chatId='" + chatId + '\'' +
                ", author='" + author + '\'' +
                ", content='" + content + '\'' +
                '}';
    }
}

After further investigation I m getting the following exception:

java.lang.IllegalStateException: The remote endpoint was in state [TEXT_FULL_WRITING] which is an invalid state for called method
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase$StateMachine.checkState(WsRemoteEndpointImplBase.java:1243)
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase$StateMachine.textStart(WsRemoteEndpointImplBase.java:1205)
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase.sendString(WsRemoteEndpointImplBase.java:191)
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase.sendObject(WsRemoteEndpointImplBase.java:600)
at org.apache.tomcat.websocket.WsRemoteEndpointBasic.sendObject(WsRemoteEndpointBasic.java:74)
at presentation.frontend.websockets.server.MessagingSocket.sendMessage(MessagingSocket.java:64)
at presentation.frontend.websockets.server.MessagingSocket.broadcastToChat(MessagingSocket.java:57)
at presentation.frontend.websockets.server.MessagingSocket.onMessage(MessagingSocket.java:47)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.apache.tomcat.websocket.pojo.PojoMessageHandlerWholeBase.onMessage(PojoMessageHandlerWholeBase.java:80)
at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:402)
at org.apache.tomcat.websocket.server.WsFrameServer.sendMessageText(WsFrameServer.java:119)
at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:502)
at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:301)
at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:133)
at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:82)
at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:171)
at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:151)
at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:148)
at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:54)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:59)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:834)

java.lang.IllegalStateException: Message will not be sent because the WebSocket session has been closed

2

There are 2 best solutions below

0
On BEST ANSWER

The solution to this one involved 2 steps.

1: Find the error stacktrace

@OnError
public void onError(Session session, Throwable throwable) {
    logger.log(LoggingType.ERROR, "Error for " + session.getId() + " caused by: " + throwable.getMessage());
    throwable.printStackTrace();
}

2: Change synchronous BasicRemote to asynchronous AsyncRemote in broadcasting messages (this is important when the number of messages increased)

private void sendMessage(MessageRepresentation message) {
        this.session.getAsyncRemote().sendObject(message);
    }
3
On

I could be off the mark here but I think this is probably due to the message containing invalid UTF8 or some such. i.e. malformed.

If this sounds like it could be the cause the simple fix would be to encode the msg

        let msg = JSON.stringify({
            "chatId": chatId,
            "author": user,
            "content": content
        })

to

  let msg = unescape(encodeURIComponent(JSON.stringify({
                "chatId": chatId,
                "author": user,
                "content": content
            })));

Then decode on the other side...

JSON.parse(decodeURIComponent(escape( ... )))