我正在开发一个利用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();
答案 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。