web-dev-qa-db-ja.com

EventSource(SSE)は無期限に再接続しようとするはずですか?

私はServer-Sent-Eventsを利用するプロジェクトに取り組んでいて、何か興味深いことに遭遇しました:接続の切断はChromeとFirefoxの間で異なって処理されます。

Chrome 35またはOpera 22の場合、サーバーへの接続が失われると、成功するまで数秒おきに無期限に再接続を試みます。Firefox30一方、1回だけ試行するため、ページを更新するか、発生したエラーイベントを処理して手動で再接続する必要があります。

私はChromeまたはOperaがそれを行いますが、読んでいます http://www.w3.org/TR/2012/WD -eventsource-20120426 /#processing-model 、EventSourceが再接続を試み、ネットワークエラーなどが原因で失敗した場合、接続を再試行すべきではないようです。仕様を理解しているかどうかは不明です正しく、しかし。

私は、Firefoxをユーザーに要求することにしました。ほとんどの場合、Chromeで同じURLからのイベントストリームを開いて複数のタブを開くことはできないという事実に基づいていますが、この新しい発見はおそらくさらに問題になるでしょう。ただし、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();
21
rhyek

私はあなたと同じ方法で標準を読みましたが、そうでない場合でも、考慮すべきブラウザのバグ、ネットワークエラー、ソケットが開いたままのサーバーなどがあります。したがって、通常はキープアライブをreの上に追加します-connect 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秒。

8
Darren Cook

サーバーサイドイベントはすべてのブラウザで異なる動作をしますが、特定の状況ではすべて接続を閉じます。たとえば、Chromeはサーバーの再起動中に502エラーで接続を閉じます。したがって、すべてのエラーで他の人が提案または再接続するときにキープアライブを使用するのが最善です。キープアライブは、サーバーが過負荷にならないように十分な時間を維持する必要がある指定された間隔でのみ再接続します。エラーが発生するたびに再接続すると、遅延が最小限に抑えられます。ただし、サーバーの負荷を最小限に抑える方法を採用した場合にのみ可能です。以下に、合理的な割合で再接続するアプローチを示します。

このコードは、再接続間隔を2倍にするデバウンス機能を使用しています。それはうまく機能し、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();
6
Wade

私が気付いたのは(Chromeで))SSE close()関数を使用して接続を閉じると、 t再接続を試みます。

var sse = new EventSource("...");
sse.onerror = function() {
    sse.close();
};
4
Martin