JavaScript事件源SSE在浏览器中没有触发

30

我一直在开发一个Node.js服务器,为我正在使用HTML5开发的新网站提供服务器端事件。

当我telnet到服务器时,它可以正常工作,发送所需的HTTP响应头,然后是每2到3秒生成的事件流,以证明它的工作方式。

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

但是,如果我停止我的Node.js服务器,从浏览器中断连接,它们会突然分派所有消息,所有我的事件都会显示。根据规范,所有浏览器都会尝试重新连接服务器。

我将所有东西托管在Web服务器上,没有任何本地文件在运行。

我已经阅读了我能够找到的所有在线资料,包括我购买的书籍,但没有任何迹象表明存在这样的问题。我是否遗漏了什么重要信息?

一个示例服务器实现

  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");
  }
}

一个示例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>

这些示例很基础,但与书籍和在线上看到的其他演示类似。如果我结束客户端连接或终止服务器,则 eventSource 似乎只能工作,但这将是轮询而不是 SSE, 我特别想使用 SSE。

有趣的是,在线使用示例(例如来自 html5rock)时,也似乎不能完全按预期工作。


1
使用各种浏览器的Javascript控制台后,似乎EventSource向我的服务器发出了GET /sse HTTP/1.1请求,这是预期的,但是直到连接关闭才显示响应。这是设计上的问题吗?连接状态应该是保持活动状态的。EventSource()只是缓冲它接收到的所有内容直到页面关闭,还是在等待接收到的数据中的某种标记? - Dan
2
除了使用Node.js服务器外,我还尝试了PHP,因为许多示例似乎都是这样演示的。同样地,除非我关闭连接,否则它会表现出与之前提到的相同的行为。我很惊讶没有其他人遇到过类似的问题?或者是我做错了什么? - Dan
1
看起来类似于https://dev59.com/ZVfUa4cB1Zd3GeqPHmQp或https://dev59.com/wG025IYBdhLWcg3wChKb - Victor
1
我想知道我的服务器或防火墙配置是否有问题。我不敢相信没有一个已经编写的演示程序起作用。肯定有其他人遇到过这个问题。有人有任何关于配置方面的想法吗?在简单的telnet会话中,一切似乎都运行正常。 - Dan
3个回答

43

破解成功了!:)

得益于Tom Kersten的帮助,他为我提供了测试支持。结果证明代码并不是问题所在。

请注意...如果您的客户端使用任何拦截Web请求的反病毒软件,则可能会在此处出现问题。在这种情况下,Sophos Endpoint Security提供企业级反病毒和防火墙保护,拥有一个名为Web Protection的功能。该功能中有一个选项可以扫描下载文件;看起来SSE连接被视为下载文件,因此直到关闭连接并接收到要扫描的流之后,该连接才会被释放到浏览器。禁用此选项可解决问题。我已提交了一个错误报告,但其他反病毒系统也可能执行相同的操作。

感谢大家的建议和帮助。:)


确认“Sophos Home”也存在此问题。在其Web仪表板中关闭Web保护可以解决该问题。我也在推特上联系了他们的支持渠道……希望更新可以允许文本/事件流通过而不会阻止通信。 - Rob Evans

2

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

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

尝试改变行结尾(使用 "\r\n" 而非 "\n")。

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

作者们也警告说,HTTP分块可能会对此协议的可靠性产生意外的负面影响。除非消息速率足够高而这并不重要,否则应禁用分块以提供事件流。


谢谢你的建议,维克多;改变行终止符并没有任何影响。如果我停止分块,仍然会出现相同的问题。 - Dan
这取决于您在服务器上运行的操作系统。对于Linux,它是\n,对于Windows - \r\n。对于平台无关的应用程序,您应该使用PHP_EOL常量。 - user5147563

0

我修改了你的服务器端脚本,它在Chrome上“似乎”部分工作。
但是每2个广播就会断开连接,只有1个可以显示在客户端。

Firefox可以在第一次广播时工作,并因此错误停止:

错误:当页面正在加载时,与/sse的连接被中断。

而Chrome将尝试重新连接并接收第三个广播。

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

注意:对于响应的事件侦听器(第10行),'close'和'end'具有不同的结果,
您可以尝试一下,我的结果是[close:1成功/2广播]和[end:1成功/8广播]

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).");
 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接