Gábor Paller
Gábor Paller

Reputation: 185

WebSocket server based on Spring Boot becomes unresponsive after a malformed packet

I have a trivial WebSocket application based on Spring Boot 1.5.9.RELEASE.

SocketHandler.java:

import org.springframework.stereotype.Component; 
import org.springframework.web.socket.TextMessage; 
import org.springframework.web.socket.WebSocketSession; 
import org.springframework.web.socket.handler.TextWebSocketHandler; 
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.List;
import java.io.IOException;

@Component 
public class SocketHandler extends TextWebSocketHandler { 

    List<WebSocketSession> sessions = new CopyOnWriteArrayList<WebSocketSession>();

    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message)
            throws InterruptedException, IOException {
        for(WebSocketSession webSocketSession : sessions) {
            webSocketSession.sendMessage(message);
        }
    }

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        //the messages will be broadcasted to all users.
        sessions.add(session);
    }
}

WebSocketConfig.java:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket 
public class WebSocketConfig implements WebSocketConfigurer { 
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { 
        registry.addHandler(new SocketHandler(), "/name");
    }
}

Main.java:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Main {

    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
}

I use the embedded Tomcat server.

It works great when I access the application from a simple HTML/JavaScript page. But when I try a native WebSocket implementation (cwebsocket), the whole server becomes unresponsive after the first request. The first request goes through successfully but at the end there is this exception:

018-01-18 11:32:44.666 ERROR 12567 --- [nio-8080-exec-3] w.s.h.ExceptionWebSocketHandlerDecorator : Closing session due to exception for StandardWebSocketSession[id=1, uri=/name]

java.lang.IllegalStateException: The WebSocket session [0] has been closed and no method (apart from close()) may be called on a closed session
        at org.apache.tomcat.websocket.WsSession.checkState(WsSession.java:806) ~[tomcat-embed-websocket-8.5.23.jar:8.5.23]
        at org.apache.tomcat.websocket.WsSession.getBasicRemote(WsSession.java:432) ~[tomcat-embed-websocket-8.5.23.jar:8.5.23]
        at org.springframework.web.socket.adapter.standard.StandardWebSocketSession.sendTextMessage(StandardWebSocketSession.java:203) ~[spring-websocket-4.3.13.RELEASE.jar:4.3.13.RELEASE]
        at org.springframework.web.socket.adapter.AbstractWebSocketSession.sendMessage(AbstractWebSocketSession.java:101) ~[spring-websocket-4.3.13.RELEASE.jar:4.3.13.RELEASE]
        at com.wattsense.service.prototype.websocket.SocketHandler.handleTextMessage(SocketHandler.java:20) ~[main/:na]
        at org.springframework.web.socket.handler.AbstractWebSocketHandler.handleMessage(AbstractWebSocketHandler.java:43) ~[spring-websocket-4.3.13.RELEASE.jar:4.3.13.RELEASE]
        at org.springframework.web.socket.handler.WebSocketHandlerDecorator.handleMessage(WebSocketHandlerDecorator.java:75) ~[spring-websocket-4.3.13.RELEASE.jar:4.3.13.RELEASE]
        at org.springframework.web.socket.handler.LoggingWebSocketHandlerDecorator.handleMessage(LoggingWebSocketHandlerDecorator.java:56) ~[spring-websocket-4.3.13.RELEASE.jar:4.3.13.RELEASE]
        at org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator.handleMessage(ExceptionWebSocketHandlerDecorator.java:58) ~[spring-websocket-4.3.13.RELEASE.jar:4.3.13.RELEASE]
        at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.handleTextMessage(StandardWebSocketHandlerAdapter.java:110) [spring-websocket-4.3.13.RELEASE.jar:4.3.13.RELEASE]
        at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.access$000(StandardWebSocketHandlerAdapter.java:42) [spring-websocket-4.3.13.RELEASE.jar:4.3.13.RELEASE]
        at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$3.onMessage(StandardWebSocketHandlerAdapter.java:81) [spring-websocket-4.3.13.RELEASE.jar:4.3.13.RELEASE]
        at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$3.onMessage(StandardWebSocketHandlerAdapter.java:78) [spring-websocket-4.3.13.RELEASE.jar:4.3.13.RELEASE]
        at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:395) [tomcat-embed-websocket-8.5.23.jar:8.5.23]
        at org.apache.tomcat.websocket.server.WsFrameServer.sendMessageText(WsFrameServer.java:119) [tomcat-embed-websocket-8.5.23.jar:8.5.23]
        at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:495) [tomcat-embed-websocket-8.5.23.jar:8.5.23]
        at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:294) [tomcat-embed-websocket-8.5.23.jar:8.5.23]
        at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:133) [tomcat-embed-websocket-8.5.23.jar:8.5.23]
        at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:82) [tomcat-embed-websocket-8.5.23.jar:8.5.23]
        at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:171) [tomcat-embed-websocket-8.5.23.jar:8.5.23]
        at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:151) [tomcat-embed-websocket-8.5.23.jar:8.5.23]
        at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:148) [tomcat-embed-websocket-8.5.23.jar:8.5.23]
        at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:54) [tomcat-embed-core-8.5.23.jar:8.5.23]
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:53) [tomcat-embed-core-8.5.23.jar:8.5.23]
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) [tomcat-embed-core-8.5.23.jar:8.5.23]
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459) [tomcat-embed-core-8.5.23.jar:8.5.23]
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.23.jar:8.5.23]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_131]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_131]
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.23.jar:8.5.23]
        at java.lang.Thread.run(Thread.java:748) [na:1.8.0_131]

Obviously, cwebsocket is sending a malformed packet at the end of the session.

But then nothing works, no WebSocket request is served and no further exception is thrown. The normal web server part of the embedded Tomcat works, it serves the HTML/JavaScript application but that application can't open a WebSocket connection either.

Clearly, there is a problem in cwebsocket but it is not my concern. I am more concerned by the fact that the whole server goes down because of a faulty client. Anyone has an idea, how to fix this?

I have tried TLS/SSL configuration and Spring Boot 2.0.0.M7, the symptoms are the same.

Upvotes: 4

Views: 5911

Answers (2)

Piotr Borowczynski
Piotr Borowczynski

Reputation: 195

Yeah, I faced the same problem. I ve got backend running all the time, when starting/stoping frontend/integration tests

The rootcause is that if client drops the session remains in backend in List sessions One of the defensive approach is which you proposed and other more gracefully is to

@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
    sessions.remove(session);
}

btw: I guess above code is from https://www.devglan.com/spring-boot/spring-websocket-integration-example-without-stomp there are also some comments below

Upvotes: 1

G&#225;bor Paller
G&#225;bor Paller

Reputation: 185

At last I have a solution that works. As can be seen in the stack trace above, there's an exception thrown from the WebSocketSession.sendMessage method. The original implementation did not handle this exception but let it bubble up. Because as WebSocketHandler explains: Implementations of this interface are encouraged to handle exceptions locally where it makes sense or alternatively let the exception bubble up in which case by default the exception is logged and the session closed with SERVER_ERROR(1011).

Something went very wrong with bubbling up the exception because it killed the whole websocket server.

The solution:

            try {
                webSocketSession.sendMessage(message);
            } catch( Exception ex ) {
                LOGGER.log( Level.SEVERE,"handleTextMessage",ex );
                synchronized( sessions ) {
                    sessions.remove( webSocketSession );
                }
            }

Upvotes: 3

Related Questions