EventSource permanent auto reconnection

2020-03-04 07:41发布

问题:

I am using JavaScript EventSource in my project front-end.

Sometimes, the connection between the browser and the server fails or the server crashes. In these cases, EventSource tries to reconnect after 3 seconds, as described in the documentation.

But it tries only once. If there is still no connection, the EventSource stops to try reconnection and the user have to refresh the browser window in order to be connected again.

How I can prevent this behavior? I need the EventSource to try reconnecting forever, not only once.

The browser is Firefox.

回答1:

I deal with this by implementing a keep-alive system; if the browser reconnects for me that is all well and good, but I assume sometimes it won't work, and also that different browsers might behave differently.

I spend a fair few pages on this in chapter five of my book (Blatant plug, find it at O'Reilly here: Data Push Applications Using HTML5 SSE), but if you want a very simple solution that does not require any back-end changes, set up a global timer that will trigger after, say, 30 seconds. If it triggers then it will kill the EventSource object and create another one. The last piece of the puzzle is in your event listener(s): each time you get data from the back-end, kill the timer and recreate it. I.e. as long as you get fresh data at least every 30 seconds, the timer will never trigger.

Here is some minimal code to show this:

var keepAliveTimer = null;

function gotActivity(){
  if(keepaliveTimer != null)clearTimeout(keepaliveTimer);
  keepaliveTimer = setTimeout(connect, 30 * 1000);
}

function connect(){
  gotActivity();
  var es = new EventSource("/somewhere/");
  es.addEventListener('message', function(e){
    gotActivity();
    },false);
}
...
connect();

Also note that I call gotActivity() just before connecting. Otherwise a connection that fails, or dies before it gets chance to deliver any data, would go unnoticed.

By the way, if you are able to change the back-end too, it is worth sending out a blank message (a "heartbeat") after 25-30 seconds of quiet. Otherwise the front-end will have to assume the back-end has died. No need to do anything, of course, if your server is sending out regular messages that are never more than 25-30 seconds apart.

If your application relies on the Event-Last-Id header, realize your keep-alive system has to simulate this; that gets a bit more involved.



回答2:

Below, I demonstrate an approach that reconnects at a reasonable rate, forever.

This code uses a debounce function along with reconnect interval doubling. During my testing, it works well. It connects at 1 second, 4, 8, 16...up to a maximum of 64 seconds at which it keeps retrying at the same rate.

function isFunction(functionToCheck) {
  return functionToCheck && {}.toString.call(functionToCheck) === '[object Function]';
}

function debounce(func, wait) {
    var timeout;
    var waitFunc;

    return function() {
        if (isFunction(wait)) {
            waitFunc = wait;
        }
        else {
            waitFunc = function() { return wait };
        }

        var context = this, args = arguments;
        var later = function() {
            timeout = null;
            func.apply(context, args);
        };
        clearTimeout(timeout);
        timeout = setTimeout(later, waitFunc());
    };
}

// reconnectFrequencySeconds doubles every retry
var reconnectFrequencySeconds = 1;
var evtSource;

var reconnectFunc = debounce(function() {
    setupEventSource();
    // Double every attempt to avoid overwhelming server
    reconnectFrequencySeconds *= 2;
    // Max out at ~1 minute as a compromise between user experience and server load
    if (reconnectFrequencySeconds >= 64) {
        reconnectFrequencySeconds = 64;
    }
}, function() { return reconnectFrequencySeconds * 1000 });

function setupEventSource() {
    evtSource = new EventSource(/* URL here */); 
    evtSource.onmessage = function(e) {
      // Handle even here
    };
    evtSource.onopen = function(e) {
      // Reset reconnect frequency upon successful connection
      reconnectFrequencySeconds = 1;
    };
    evtSource.onerror = function(e) {
      evtSource.close();
      reconnectFunc();
    };
}
setupEventSource();


回答3:

In my experience, browsers will usually reconnect if there's a network-level error but not if the server responds with an HTTP error (e.g. status 500).

Our team made a simple wrapper library to reconnect in all cases: reconnecting-eventsource. Maybe it's helpful.