javaspringtomcatspring-websockettyrus

Spring Websocket Server Resets Connection When Sending Large Amounts of Data


I'm having issues where while trying to send data from a Spring Websocket Server (hosted on Tomcat) it seems to reset the connection in the middle of trying to send the message. The error seems happen more when sending large amounts of binary data (5526584 bytes in the example here), but I've seen it happen on the text channel and with less data as well. However sending more then 5 or so MB seems to crash it relatively consistently.

Edit - The below the end edit block is the server side code and error. I now believe the error is being caused on the server side. I put in some timestamps and it appears the client is receiving an onClose error before the server throws its error. The close reason is Buffer Overflow. Here is the code where I setup the websocket container and start the session:

WebSocketContainer c = ContainerProvider.getWebSocketContainer();


    c.setDefaultMaxTextMessageBufferSize(128000000); // Should we enable these on the client?
    c.setDefaultMaxBinaryMessageBufferSize(128000000); // Should we enable these on the client?
    c.setDefaultMaxSessionIdleTimeout(500000);
    c.setAsyncSendTimeout(50000);
    session = c.connectToServer(this, config, new URI("wss://" + SERVER + ":" + PORT + WEBSOCKETENDPOINT));

I later print out the sessions MaxBuffer sizes and they are both set to 128000000 as expected.

End Edit

Here is the stack trace:

java.io.IOException: An established connection was aborted by the software in your host machine
at sun.nio.ch.SocketDispatcher.write0(Native Method)
at sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:51)
at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:93)
at sun.nio.ch.IOUtil.write(IOUtil.java:65)
at sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:471)
at org.apache.tomcat.util.net.SecureNioChannel.flush(SecureNioChannel.java:143)
at org.apache.tomcat.util.net.SecureNioChannel.close(SecureNioChannel.java:494)
at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.close(NioEndpoint.java:1201)
at org.apache.tomcat.websocket.server.WsRemoteEndpointImplServer.doClose(WsRemoteEndpointImplServer.java:167)
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase.close(WsRemoteEndpointImplBase.java:710)
at org.apache.tomcat.websocket.WsSession.sendCloseMessage(WsSession.java:599)
at org.apache.tomcat.websocket.WsSession.doClose(WsSession.java:480)
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase.sendMessageBlock(WsRemoteEndpointImplBase.java:313)
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase.sendMessageBlock(WsRemoteEndpointImplBase.java:258)
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase.sendPartialBytes(WsRemoteEndpointImplBase.java:161)
at org.apache.tomcat.websocket.WsRemoteEndpointBasic.sendBinary(WsRemoteEndpointBasic.java:56)
at org.springframework.web.socket.adapter.standard.StandardWebSocketSession.sendBinaryMessage(StandardWebSocketSession.java:202)
at org.springframework.web.socket.adapter.AbstractWebSocketSession.sendMessage(AbstractWebSocketSession.java:107)
at org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator.tryFlushMessageBuffer(ConcurrentWebSocketSessionDecorator.java:132)
at org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator.sendMessage(ConcurrentWebSocketSessionDecorator.java:104)
at myHandlerpart2.handleBinaryMessage(myHandlerpart2.java:252)
at myHandler.handleMessageFromClient(myHandler.java:200)
at org.springframework.web.socket.messaging.SubProtocolWebSocketHandler.handleMessage(SubProtocolWebSocketHandler.java:307)
at org.springframework.web.socket.handler.WebSocketHandlerDecorator.handleMessage(WebSocketHandlerDecorator.java:75)
at org.springframework.web.socket.handler.LoggingWebSocketHandlerDecorator.handleMessage(LoggingWebSocketHandlerDecorator.java:56)
at org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator.handleMessage(ExceptionWebSocketHandlerDecorator.java:58)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.handleBinaryMessage(StandardWebSocketHandlerAdapter.java:120)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.access$100(StandardWebSocketHandlerAdapter.java:42)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$4.onMessage(StandardWebSocketHandlerAdapter.java:87)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$4.onMessage(StandardWebSocketHandlerAdapter.java:84)
at org.apache.tomcat.websocket.WsFrameBase.sendMessageBinary(WsFrameBase.java:576)
at org.apache.tomcat.websocket.server.WsFrameServer.sendMessageBinary(WsFrameServer.java:122)
at org.apache.tomcat.websocket.WsFrameBase.processDataBinary(WsFrameBase.java:535)
at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:294)
at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:127)
at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:73)
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:53)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1455)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)

In my websocket configurer I've setup pretty loose buffers and timeouts

public ServletServerContainerFactoryBean createWebSocketContainer() {

ServletServerContainerFactoryBean container = new 
ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(128000000); //128 MB
container.setMaxBinaryMessageBufferSize(128000000); //128 MB
container.setMaxSessionIdleTimeout(500000);
container.setAsyncSendTimeout(50000);

return container;
}

I also set up the limits for the SubProtoclHandler

@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {


    SubProtocolWebSocketHandler test = new SubProtocolWebSocketHandler(new DirectChannel(), clientInboundChannel());
    test.setSendBufferSizeLimit(128000000);
    test.setSendTimeLimit(50000);
    for(SubProtocolHandler handler : handlers)
    {
        test.addProtocolHandler(handler);
    }


    MyWebSocketHandshakeHandler handshaker = new MyWebSocketHandshakeHandler();


    handshaker.setSupportedProtocols(test.getSubProtocols().toArray(new String[0]));
    registry.addHandler(test, websocketEndpoint).setHandshakeHandler(handshaker)
            .addInterceptors(new HttpSessionHandshakeInterceptor()).setAllowedOrigins("*");

}

Finally the actual send that is causing the error looks like this

byte[] tbuf = null;
        try {
            tbuf = jsonMapper.writeValueAsBytes(demoLists.get(stage));
            ByteBuffer buf = ByteBuffer.allocate(tbuf.length);
            buf.put(tbuf);
            buf.flip();
            WebSocketMessage<ByteBuffer> ret = new BinaryMessage(buf);
            System.out.println("Binary limit: " + session.getBinaryMessageSizeLimit());
            System.out.println("Sending bytes: " + ret.getPayloadLength());
            if(session.isOpen()) {
                session.sendMessage(ret);

            } else {
                System.out.println("Session is closed already.");
            }

There are a lot of moving pieces in the code, so let me know if you need any other information.


Solution

  • After doing more research, I found this question Incoming buffer size cannot be set to Tyrus client which lead me to https://tyrus-project.github.io/documentation/1.12/user-guide.html#d0e1197 . Apparently setting the Default buffer size on the client was not working properly. After adding what the guide recommended everything works appropriately.

    ClientManager client = ClientManager.createClient(c); 
        client.getProperties().put("org.glassfish.tyrus.incomingBufferSize", 128000000); 
        session = client.connectToServer(this, config, new URI("wss://" + SERVER + ":" + PORT + WEBSOCKETENDPOINT));