javascriptangularjswebsocketspring-websocketstompjs

stompjs xhr_streaming timeout


I'm using SockJS + stomp client in angular(1.5.x) to establish websocket with a spring (mvc) server. All works fine except this: if I kill the server, it takes up to two minutes for the stomp client to detect connection error on the browser. Is there a way to manage a much shorter (or immediate) timeout or throw an event as soon as the server died or is disconnected?

function socketService($rootScope, $q, $log, $timeout, URL) {
    var listener = $q.defer(),
        socket = {
        client: null,
        stomp: null
    };

    var reconnect = function() {
        $log.info('Reconnecting');
        $timeout(function() {
            initialize();
        }, 2000);
    };

    var getMessage = function(data) {
        var message = JSON.parse(data), out = {};
        out.message = message;
        if (message.metadata) {
            out.time = new Date(message.metadata.timestamp);
        }
        $log.info(out);
        return out;
    };

    var startListener = function() {
        $log.info('Connected');
        socket.stomp.subscribe(URL.PROCESS_UPDATES, function(data) {
            listener.notify(getMessage(data.body));
        });

        socket.stomp.subscribe(URL.CONTAINER_UPDATES, function(data) {
            listener.notify(getMessage(data.body));
        });
        $rootScope.$broadcast('web_socket_event', 'CONNECTED');
    };

    var errorCallback = function (error) {
        // Browser gets here 2 minutes after the server is killed. Seems like might be affected by the the xhr_streaming timeout 
        $rootScope.$broadcast('web_socket_event', 'DISCONNECTED');
        reconnect();
    };

    return {
        initialize: initialize,
        receive: receive
    };
    function initialize() {
        var header = {
          'accept-version': 1.1
        };
        $log.info('Connecting');
        // custom header to specify version.
        socket.client = new SockJS(header, URL.ROOT + URL.UPDATES);

        socket.client.debug = function(){};
        socket.stomp.heartbeat.outgoing = 0;
        socket.stomp.heartbeat.incoming = 2000;
        socket.stomp = Stomp.over(socket.client);
        socket.stomp.connect({}, startListener, errorCallback);
        socket.stomp.onerror = errorCallback;
        socket.stomp.onclose = reconnect;
    };

    function receive() {
        return listener.promise;
    };
}


**// browser console:**
Opening Web Socket...
  stomp.js:145 Web Socket Opened...
  stomp.js:145 >>> CONNECT
  accept-version:1.1,1.0
  heart-beat:0,2000

  stomp.js:145 <<< CONNECTED
  version:1.1
  heart-beat:2000,0

  stomp.js:145 connected to server undefined
  stomp.js:145 check PONG every 2000ms

Solution

  • I'm not an expert in WS but based on our conversation through the question comments, and my understanding of WS it's clear that your server is negotiating a connection with NO heart-beats at all: heart-beat 0,0. The first 0 is the max time (in millis) that the client should expect no packets from the server at all (when this timeout elapses with no communication at all from either side the server should send a heartbeat frame), the 2nd 0 is the equivalent but from looked from the server perspective.

    You should set up your server to send a heartbeat periodically and also to expect a heartbeat from the client. This way you allow your server and client to have a better management on the WS connection resources and also you ensure that the connection doesn't get disconnected by the 'network' when applying stalled connection detection policies or by any other mechanism.

    I don't know how you have set up your WS server but the below sample applies to a simple WS server in spring boot:

    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.messaging.simp.config.MessageBrokerRegistry;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
    import org.springframework.stereotype.Component;
    import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
    import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
    
    @Component
    public class WebSocketConfigurer extends AbstractWebSocketMessageBrokerConfigurer {
    
        @Override
        public void configureMessageBroker(MessageBrokerRegistry config) {
            long heartbeatServer = 10000; // 10 seconds
            long heartbeatClient = 10000; // 10 seconds
    
            ThreadPoolTaskScheduler ts = new ThreadPoolTaskScheduler();
            ts.setPoolSize(2);
            ts.setThreadNamePrefix("wss-heartbeat-thread-");
            ts.initialize();
    
            config.enableSimpleBroker("/topic")
                    .setHeartbeatValue(new long[]{heartbeatServer, heartbeatClient})
                    .setTaskScheduler(ts);
        }
    
        @Override
        public void registerStompEndpoints(StompEndpointRegistry registry) {
            registry.addEndpoint("/my/ws/endpoint")
                    .setAllowedOrigins("*")
                    .withSockJS();
        }
    }