EventSource(SSE)是否应该尝试无限期重新连接?

时间:2014-07-03 22:11:20

标签: javascript google-chrome firefox server-sent-events

我正在开发一个利用Server-Sent-Events的项目,并且遇到了一些有趣的事情:Chrome和Firefox之间的连接丢失处理方式不同。

在Chrome 35或Opera 22上,如果您丢失了与服务器的连接,它将每隔几秒尝试无限期重新连接,直到成功为止。另一方面,在Firefox 30上,它只会尝试一次,然后您必须刷新页面或处理引发的错误事件并手动重新连接。

我更喜欢Chrome或Opera的方式,但是阅读http://www.w3.org/TR/2012/WD-eventsource-20120426/#processing-model,似乎一旦EventSource尝试重新连接并因网络错误或其他原因而失败,它就不应该重试连接。但不确定我是否正确理解规范。

我开始要求用户使用Firefox,主要是因为您无法在Chrome上打开来自同一网址的事件流的多个标签,但这一新发现可能更多的是一个问题。虽然,如果Firefox的行为符合规范,那么我不妨以某种方式解决它。

修改

我现在要继续定位Firefox。这就是我处理重新连接的方式:

var es = null;
function initES() {
    if (es == null || es.readyState == 2) { // this is probably not necessary.
        es = new EventSource('/push');
        es.onerror = function(e) {
            if (es.readyState == 2) {
                setTimeout(initES, 5000);
            }
        };
        //all event listeners should go here.
    }
}
initES();

7 个答案:

答案 0 :(得分:3)

我以与你相同的方式阅读标准,但即使没有,也要考虑浏览器错误,网络错误,服务器死机但保持套接字打开等等。因此,我通常在顶部添加一个keep-alive SSE提供的重新连接。

在客户端,我使用几个全局变量和辅助函数来完成它:

var keepaliveSecs = 20;
var keepaliveTimer = null;

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

然后我在gotActivity()的顶部拨打connect(),然后每次收到消息。 (connect()基本上只是调用new EventSource()

在服务器端,它可以在正常数据流之上每隔15秒吐出一个时间戳(或其他东西),或者使用定时器本身并在正常数据流时吐出时间戳(或其他东西)安静了15秒。

答案 1 :(得分:3)

我重写了@Wade的解决方案,并进行了一些测试,得出的结论是该功能保持不变,但代码更少,可读性更好(imo)。 不过,我仍然是一个初学者,我希望收到您的反馈。我不确定是否遗漏了一些重要内容。

我不了解的一件事是,如果每次尝试重新连接时将timeout变量设置回null,为什么要清除超时。所以我只是完全省略了它。而且我还省略了wait参数是否为函数的检查。我只是认为是这样,所以它使代码更简洁。

var reconnectFrequencySeconds = 1;
var evtSource;

// Putting these functions in extra variables is just for the sake of readability
var waitFunc = function() { return reconnectFrequencySeconds * 1000 };
var tryToSetupFunc = function() {
    setupEventSource();
    reconnectFrequencySeconds *= 2;
    if (reconnectFrequencySeconds >= 64) {
        reconnectFrequencySeconds = 64;
    }
};

var reconnectFunc = function() { setTimeout(tryToSetupFunc, waitFunc()) };

function setupEventSource() {
    evtSource = new EventSource("url"); 
    evtSource.onmessage = function(e) {
      console.log(e);
    };
    evtSource.onopen = function(e) {
      reconnectFrequencySeconds = 1;
    };
    evtSource.onerror = function(e) {
      evtSource.close();
      reconnectFunc();
    };
}

setupEventSource();

答案 2 :(得分:2)

我注意到的(至少在Chrome中)是当您使用layoutMargin功能关闭SSE连接时,它不会再尝试重新连接。

extension UITableViewCell {
    override public var layoutMargins: UIEdgeInsets {
        get { return UIEdgeInsetsZero }
        set { }
    }
}

答案 3 :(得分:2)

服务器端事件在所有浏览器中的工作方式都不同,但是在某些情况下它们都会关闭连接。例如,Chrome重新启动服务器时,由于502错误而关闭了连接。因此,最好使用保持活动状态(如其他人建议的那样)或针对每个错误重新连接。保持活动状态仅在指定的时间间隔内重新连接,该间隔必须保持足够长的时间,以免使服务器不堪重负。每次发生错误时重新连接都将延迟降到最低。但是,只有采取使服务器负载最小的方法才有可能。下面,我演示一种以合理的速率重新连接的方法。

此代码使用防抖功能以及重新连接间隔加倍。它工作良好,连接时间为1秒,4、8、16 ...,最长为64秒,在此时间内,它以相同的速率不断重试。我希望这对某些人有帮助。

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();

答案 4 :(得分:1)

这是人们可能喜欢的另一种变化

let events = null;

function connect() {
    events = new EventSource("/some/url");
    events.onerror = function() {
        events.close();
    }
}
connect();

let reconnecting = false;
setInterval(() => {
    if (events.readyState == EventSource.CLOSED) {
        reconnecting = true;
        console.log("reconnecting...");
        connect();
    } else if (reconnecting) {
        reconnecting = false
        console.log("reconnected!");
    }
}, 3000);

答案 5 :(得分:0)

正如已经提到的,不同的浏览器根据返回码执行不同的操作。我要做的就是不管连接如何就关闭连接,然后检查服务器运行状况以确保再次连接正常。我认为,如果我们实际上不知道服务器/代理服务器是否回来了,尝试重新打开流是愚蠢的。

在FF和Chrome中进行了测试:

let sseClient

function sseInit() {
  console.log('SSE init')
  sseClient = new EventSource('/server/events')
  sseClient.onopen = function () { console.log('SSE open ') }
  sseClient.onmessage = onMessageHandler
  sseClient.onerror = function(event) {
    if (event.target.readyState === EventSource.CLOSED) {
      console.log('SSE closed ' + '(' + event.target.readyState + ')')
    } else if (event.target.readyState === EventSource.CONNECTING) {
      console.log('SSE reconnecting ' + '(' + event.target.readyState + ')')
      sseClient.close()
    }
  }
}

sseInit()

setInterval(function() {
  let sseOK
  if (sseClient === null) {
    sseOK = false
  } else {
    sseOK = (sseClient.readyState === EventSource.OPEN)
  }
  if (!sseOK) {
    // only try reconnect if server health is OK
    axios.get('/server/health')
      .then(r => {
        sseInit()
        store.commit('setServerOK_true')
      })
      .catch(e => {
        store.commit('setServerOK_false')
        sseClient = null
      })
  }
}, 5000)

请注意,我将Vue与ECMAScript结合使用并跟踪商店中的状态,因此有些事情可能没有直接意义。

答案 6 :(得分:0)

在我当前的 Node.js 应用程序开发中,我注意到当我的应用程序重新启动时 Chrome 会自动重新连接,但 Firefox 没有。

ReconnectingEventSource 是一个 EventSource 包装器,是我找到的最简单的解决方案。

使用或不使用您选择的 polyfill。