JavaScript EventSource SSE 未在浏览器中触发

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12978466/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 17:32:16  来源:igfitidea点击:

JavaScript EventSource SSE not firing in browser

javascripthtmlnode.jsserver-sent-events

提问by Dan

I have been developing a nodejs server to provide server-side-events for a new website I am developing in HTML5.

我一直在开发一个 nodejs 服务器来为我正在用 HTML5 开发的新网站提供服务器端事件。

When I telnet to the server it works correctly, sending me the required HTTP response headers followed by a stream of events that i am presently generating every 2 or 3 seconds just to prove it works.

当我 telnet 到服务器时,它可以正常工作,向我发送所需的 HTTP 响应标头,然后是我目前每 2 或 3 秒生成一次的事件流,以证明它有效。

I have tried the latest version of FireFox, Chrome and Opera and they create the EventSource object and connect to the nodejs server OK but none of the browsers generate any of the events, including the onopen, onmessage and onerror.

我已经尝试了最新版本的 FireFox、Chrome 和 Opera,它们创建了 EventSource 对象并连接到 nodejs 服务器,但没有一个浏览器生成任何事件,包括 onopen、onmessage 和 onerror。

However, if I stop my nodejs server, terminating the connection from the browsers, they all suddenly dispatch all the messages and all my events are shown. The browsers then all try to reconnect to the server as per spec.

但是,如果我停止我的 nodejs 服务器,终止与浏览器的连接,它们会突然发送所有消息并显示我的所有事件。然后浏览器都按照规范尝试重新连接到服务器。

I am hosting everything on a webserver. nothing is running in local files.

我将所有内容都托管在网络服务器上。本地文件中没有运行任何内容。

I have read everything I can find online, including books I've purchased and nothing indicates any such problem. Is there something Im missing?

我已经阅读了我可以在网上找到的所有内容,包括我购买的书籍,但没有任何迹象表明存在任何此类问题。有什么我想念的吗?

A sample server implementation

示例服务器实现

  var http = require('http');
  var requests = [];

  var server = http.Server(function(req, res) {
    var clientIP = req.socket.remoteAddress;
    var clientPort = req.socket.remotePort;

    res.on('close', function() {
      console.log("client " + clientIP + ":" + clientPort + " died");

      for(var i=requests.length -1; i>=0; i--) {
        if ((requests[i].ip == clientIP) && (requests[i].port == clientPort)) {
          requests.splice(i, 1);
        }
      }
    });

    res.writeHead(200, {
      'Content-Type': 'text/event-stream', 
      'Access-Control-Allow-Origin': '*', 
      'Cache-Control': 'no-cache', 
      'Connection': 'keep-alive'});

    requests.push({ip:clientIP, port:clientPort, res:res});

    res.write(": connected.\n\n");
  });

  server.listen(8080);

  setInterval(function test() {
    broadcast('poll', "test message");
  }, 2000);

function broadcast(rtype, msg) {
  var lines = msg.split("\n");

  for(var i=requests.length -1; i>=0; i--) {
    requests[i].res.write("event: " + rtype + "\n");
    for(var j=0; j<lines.length; j++) {
      if (lines[j]) {
        requests[i].res.write("data: " + lines[j] + "\n");
      }
    }
    requests[i].res.write("\n");
  }
}

A sample html page

一个示例 html 页面

<!DOCTYPE html>
<html>
  <head>
    <title>SSE Test</title>
    <meta charset="utf-8" />
    <script language="JavaScript">
      function init() {
        if(typeof(EventSource)!=="undefined") {
          var log = document.getElementById('log');
          if (log) {
            log.innerHTML = "EventSource() testing begins..<br>";
          }

          var svrEvents = new EventSource('/sse');

          svrEvents.onopen = function() {
            connectionOpen(true);
          }

          svrEvents.onerror = function() {
            connectionOpen(false);
          }

          svrEvents.addEventListener('poll', displayPoll, false);             // display multi choice and send back answer

          svrEvents.onmessage = function(event) {
              var log = document.getElementById('log');
              if (log) {
                log.innerHTML += 'message: ' + event.data + "<br>";
              }
            // absorb any other messages
          }
        } else {
          var log = document.getElementById('log');
          if (log) {
            log.innerHTML = "EventSource() not supported<br>";
          }
        }
      }

      function connectionOpen(status) {
          var log = document.getElementById('log');
          if (log) {
            log.innerHTML += 'connected: ' + status + "<br>";
          }
      }

      function displayPoll(event) {
        var html = event.data;
          var log = document.getElementById('log');
          if (log) {
            log.innerHTML += 'poll: ' + html + "<br>";
          }
      }
    </script>
  </head>
  <body onLoad="init()">
    <div id="log">testing...</div>
  </body>
</html>

These examples are basic but of the same variety as every other demo i've seen in books and online. The eventSource only seems to be working if I end a client connection or terminate the server but this would be polling instead of SSE and I particularly want to use SSE.

这些示例是基本的,但与我在书籍和网上看到的所有其他演示都具有相同的多样性。eventSource 似乎只在我结束客户端连接或终止服务器时才起作用,但这将是轮询而不是 SSE,我特别想使用 SSE。

Interestingly, demos, such as thouse from html5rockalso seem to not quite work as expected when I use them online..

有趣的是,当我在线使用它们时,诸如 html5rock 中的thouse 之类的演示似乎也不太像预期的那样工作。

回答by Dan

cracked it! :)

破解了!:)

Thanks to some help from Tom Kerstenwho helped me with testing. Turns out the code isnt the problem.

感谢Tom Kersten 的一些帮助,他帮助我进行了测试。原来代码不是问题。

Be warned.. if your client uses any kind of anti-virus software which intercepts web requests, it may cause problems here. In this case, Sophos Endpoint Security, which provides enterprise grade anti-virus and firewall protection has a feature called web protection. Within this features is an option to scan downloads; it seems that the SSE connection is treated as a download and thus not released to the browser until the connection is closed and the stream received to scan. Disabling this option cures the problem. I have submitted a bug report but other anti-virus systems may do the same.

请注意.. 如果您的客户端使用任何拦截 Web 请求的防病毒软件,则可能会在此处引起问题。在这种情况下,提供企业级防病毒和防火墙保护的 Sophos Endpoint Security 具有称为 Web 保护的功能。在此功能中,可以选择扫描下载;似乎 SSE 连接被视为下载,因此在连接关闭并接收到要扫描的流之前不会释放到浏览器。禁用此选项可以解决问题。我已提交错误报告,但其他防病毒系统可能会这样做。

thanks for your suggestions and help everyone :)

感谢您的建议并帮助大家:)

回答by Victor

http://www.w3.org/TR/eventsource/#parsing-an-event-stream

http://www.w3.org/TR/eventsource/#parsing-an-event-stream

Since connections established to remote servers for such resources are expected to be long-lived, UAs should ensure that appropriate buffering is used. In particular, while line buffering with lines are defined to end with a single U+000A LINE FEED (LF) character is safe, block buffering or line buffering with different expected line endings can cause delays in event dispatch.

由于为此类资源建立到远程服务器的连接预计是长期存在的,因此 UA 应确保使用适当的缓冲。特别是,虽然行缓冲定义为以单个 U+000A 换行 (LF) 字符结尾是安全的,但块缓冲或具有不同预期行结尾的行缓冲可能会导致事件调度延迟。

Try to play with line endings ("\r\n"instead of "\n").

尝试使用行尾("\r\n"而不是"\n")。

http://www.w3.org/TR/eventsource/#notes

http://www.w3.org/TR/eventsource/#notes

Authors are also cautioned that HTTP chunking can have unexpected negative effects on the reliability of this protocol. Where possible, chunking should be disabled for serving event streams unless the rate of messages is high enough for this not to matter.

作者还被警告说,HTTP 分块可能对该协议的可靠性产生意想不到的负面影响。在可能的情况下,应禁用分块以提供事件流,除非消息速率足够高以至于无关紧要。

回答by inDream

I modified your server-side script, which 'seems' partly works for Chrome.
But the connection break for every 2 broadcast & only 1 can be shown on client.

我修改了您的服务器端脚本,它“似乎”部分适用于 Chrome。
但是每 2 个广播的连接中断,客户端上只能显示 1 个。

Firefox works for 1st broadcast and stop by this error:

Firefox 适用于第一次广播并因以下错误而停止:

Error: The connection to /sse was interrupted while the page was loading.

错误:加载页面时与 /sse 的连接中断。

And Chrome will try to reconnect and received 3rd broadcast.

Chrome 会尝试重新连接并接收到第 3 次广播。

I think it's related to firewall setting too but can't explain why sometime will works.

我认为这也与防火墙设置有关,但无法解释为什么有时会起作用。

Note: For event listener of response (line 10), 'close' & 'end' have different result,
You can try it and my result is [close: 1 success/2 broadcast] & [end: 1 success/8 broadcast]

注意:对于响应的事件监听器(第 10 行),'close' 和 'end' 有不同的结果,
你可以试试,我的结果是 [close: 1 success/2 broadcast] & [end: 1 success/8 broadcast]

var http = require('http'), fs = require('fs'), requests = [];

var server = http.Server(function(req, res) {
  var clientIP = req.socket.remoteAddress;
  var clientPort = req.socket.remotePort;
  if (req.url == '/sse') {
    var allClient="";for(var i=0;i<requests.length;i++){allClient+=requests[i].ip+":"+requests[i].port+";";}
    if(allClient.indexOf(clientIP+":"+clientPort)<0){
      requests.push({ip:clientIP, port:clientPort, res:res});
      res.on('close', function() {
        console.log("client " + clientIP + ":" + clientPort + " died");
        for(var i=requests.length -1; i>=0; i--) {
          if ((requests[i].ip == clientIP) && (requests[i].port == clientPort)) {
            requests.splice(i, 1);
          }
        }
      });
    }
  }else{
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write(fs.readFileSync('./test.html'));
    res.end();
  }
});
server.listen(80);

setInterval(function test() {
  broadcast('poll', "test message");
}, 500);

var broadcastCount=0;
 function broadcast(rtype, msg) {
    if(!requests.length)return;
    broadcastCount++;
    var lines = msg.split("\n");
    for(var i = requests.length - 1; i >= 0; i--) {
        requests[i].res.writeHead(200, {
            'Content-Type': 'text/event-stream',
            'Cache-Control': 'no-cache',
            'Connection': 'keep-alive'
        });
        requests[i].res.write("event: " + rtype + "\n");
        for(var j = 0; j < lines.length; j++) {
            if(lines[j]) {
                requests[i].res.write("data: " + lines[j] + "\n");
            }
        }
        requests[i].res.write("data: Count\: " + broadcastCount + "\n");
        requests[i].res.write("\n");
    }
    console.log("Broadcasted " + broadcastCount + " times to " + requests.length + " user(s).");
 }