如何确定 SSE 连接已关闭?

How to determine that an SSE connection was closed?

我们在 JavaScript 中打开了一个 SSE(服务器发送的事件)连接,它有时会因服务器重启或其他原因而关闭。在那种情况下,最好重新建立连接。怎么做?有没有办法在客户端发现连接已关闭?

这里:https://developer.mozilla.org/en-US/docs/Web/API/EventSource我只找到了关闭连接的方法,没有回调,也没有判断连接是否存活的测试方法。

感谢您的帮助。

检查 readyState 属性:

var es = new EventSource();

// Сheck that connection is not closed
es.readyState !== 2;
// or
es.readyState !== EventSource.CLOSED;

如果连接关闭(以浏览器可以实现的方式),它将自动连接。而且它往往会很快完成此操作(Chrome 中的默认值为 3 秒,Firefox 中的默认值为 5 秒)。 readyState 在执行此操作时将处于 CONNECTING (0) 状态。如果首先出现连接问题(例如,由于 CORS 问题),它只会关闭 (2)。一旦关闭,它不会重试。

我更喜欢在上面添加一个保持活动机制,因为浏览器不能总是检测到死套接字(更不用说被锁定的远程服务器进程等)。有关详细代码,请参阅带有 HTML5 SSE 的数据推送应用程序的第 5 章,但基本上它涉及让服务器每 15 秒发送一次消息,然后是一个运行 20 秒但被重置的 JavaScript 计时器每次收到消息。如果计时器过期,我们将关闭连接并重新连接。

事件源API更新

EventSource API 现在有三个事件处理程序:

这些应该足以处理您在客户端所需的一切。

像这样:

const ssEvent = new EventSource( eventUrl );

ssEvent.onopen = function (evt) {
  // handle newly opened connection
}

ssEvent.onerror = function (evt) {
  // handle dropped or failed connection
}

ssEvent.onmessage = function (evt) {
  // handle new event from server
}

参考:mozilla.org : EventSource : Event handlers

Browser support for EventSource API: onopen - caniuse.com

最好不要尝试确定连接是否已关闭。我认为没有办法做到这一点。服务器端事件在所有浏览器中的工作方式不同,但它们都会在特定情况下关闭连接。 Chrome,例如,在服务器重新启动时关闭 502 错误的连接。因此,最好像其他人建议的那样使用 keep-alive 或在每次错误时重新连接。 Keep-alive 仅在指定的时间间隔内重新连接,该时间间隔必须保持足够长的时间以避免服务器不堪重负。重新连接每个错误都有尽可能低的延迟。但是,只有采用将服务器负载保持在最低水平的方法才有可能。下面,我将演示一种以合理速率重新连接的方法。

此代码使用去抖功能以及重新连接间隔加倍。它运作良好,在 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();