Предполагается ли, что EventSource (SSE) пытается повторно подключиться бесконечно?
Я работаю над проектом, использующим 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 для пользователей, в основном на основе того факта, что у вас не может быть нескольких вкладок с потоком событий из того же URL-адреса, открытого в Chrome, но этот новый вывод, вероятно, будет более проблематичным. Хотя, если Firefox ведет себя по спецификации, я мог бы как-то обойти его.
Edit:
Сейчас я собираюсь настроить таргетинг на 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();
Ответы
Ответ 1
Я читаю стандарт так же, как и вы, но даже если нет, есть ошибки браузера, ошибки в сети, серверы, которые умирают, но не открывают сокет и т.д. Поэтому я обычно добавляю 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 секунд.
Ответ 2
То, что я заметил (по крайней мере, в Chrome), заключается в том, что при закрытии вашего SSE-соединения с помощью функции close()
он не будет пытаться снова подключиться.
var sse = new EventSource("...");
sse.onerror = function() {
sse.close();
};
Ответ 3
События на стороне сервера работают по-разному во всех браузерах, но все они закрывают соединение при определенных обстоятельствах. Например, Chrome закрывает соединение при 502 ошибках во время перезапуска сервера. Таким образом, лучше использовать keep-alive, как другие предлагают или переподключать при каждой ошибке. Поддержание активности возобновляется только через указанный интервал, который должен храниться достаточно долго, чтобы избежать перегрузки сервера. Повторное подключение при каждой ошибке имеет минимально возможную задержку. Однако это возможно только в том случае, если вы используете подход, который сводит нагрузку на сервер к минимуму. Ниже я демонстрирую подход, который воссоединяется с разумной скоростью.
Этот код использует функцию debounce вместе с удвоением интервала повторного подключения. Он работает хорошо, соединяясь через 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();