JavaScript EventSource SSE 未在浏览器中触发

Posted

技术标签:

【中文标题】JavaScript EventSource SSE 未在浏览器中触发【英文标题】:JavaScript EventSource SSE not firing in browser 【发布时间】:2012-10-19 16:25:37 【问题描述】:

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

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

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

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

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

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

示例服务器实现

  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 的 thouse 演示似乎也无法正常工作..

【问题讨论】:

使用了各种浏览器的 Javascript 控制台后,似乎 EventSource 向我的服务器发出了 GET /sse HTTP/1.1,这是预期的,但在连接完成之前不会显示响应关闭。这是设计使然吗?连接状态应该是保持活动状态。 EventSource() 只是缓冲它发送的所有内容,直到页面关闭,还是它正在等待它接收的数据中的某种标记? 除了 nodejs 服务器,我还尝试过 php,因为许多示例似乎都说明了这一点。同样,除非我关闭连接,否则这将表现出与已经提到的相同的行为。我很惊讶没有其他人遇到过这样的问题?还是我只是做错了什么? 看起来类似于***.com/questions/6068820/… 或***.com/questions/6258210/… 我想知道是不是我的服务器或防火墙配置有问题。我不敢相信已经编写好的演示都没有工作。其他人现在肯定会遇到这个问题。任何人都知道如何寻找配置明智的?在一个简单的 telnet 会话中一切似乎都正常 【参考方案1】:

破解了! :)

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

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

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

【讨论】:

确认这也是“Sophos Home”的问题。在他们的 Web 仪表板中关闭 Web 保护解决了这个问题。我也在推特上发布了他们的支持频道……希望更新可以允许文本/事件流通过而不阻塞通信。【参考方案2】:

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

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

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

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

还提醒作者,HTTP 分块可能会出现意外情况 对该协议的可靠性产生负面影响。在可能的情况, 应该禁用分块以提供事件流,除非速率 消息数量足够多,这无关紧要。

【讨论】:

感谢胜利者的建议;更改线路终止符没有区别。如果我停止分块,同样的问题仍然存在。 这取决于您运行服务器的操作系统。对于 Linux,它的 \n,对于 Windows - \r\n。对于独立于平台的应用程序,您应使用PHP_EOL 常量。【参考方案3】:

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

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

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

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

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

注意:对于响应的事件监听器(第 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).");
 

【讨论】:

以上是关于JavaScript EventSource SSE 未在浏览器中触发的主要内容,如果未能解决你的问题,请参考以下文章

EventSource 的响应具有不是“text/event-stream”的 MIME 类型(“text/plain”)

有关自定义事件的网站知识

如何使用 Windows 性能分析器查看 EventSource 创建的 ETW 事件?

有 jQuery 事件源吗?

从(服务器发送的)EventSource 创建一个 RxJS Observable

用户注销时无法取消订阅 eventSource