30

I have been developing a nodejs server to provide server-side-events for a new website I am developing in 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.

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.

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.

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

<!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.

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

JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
Dan
  • 739
  • 1
  • 6
  • 8
  • 1
    Having used the Javascript consoles of the various browsers, it would seem that the EventSource issues a GET /sse HTTP/1.1 to my server, which is expected, but a response is not shown until the connection is closed. Is this by design? the connection state is supposed to be keep-alive. Is the EventSource() just buffering everything it's sent until the page is closed or is it waiting for some sort of marker in the data it's receiving? – Dan Oct 20 '12 at 18:34
  • 2
    In addition to the nodejs sever, I have also tried PHP as many examples seem to illustrate that. Again, unless I close the connection, this exhibits the same behavior as already mentioned. Im surprised nobody else has had problems like this? Or am I just doing something completely wrong? – Dan Oct 23 '12 at 13:34
  • 1
    Looks similar to http://stackoverflow.com/questions/6068820/node-js-problems-with-response-write or http://stackoverflow.com/questions/6258210/how-can-i-output-data-before-i-end-the-response – Victor Oct 26 '12 at 07:11
  • 1
    i wonder if its something wrong with my server or firewall configuration. i can't believe none of the demos that have been written work. someone else would have got this problem by now surely. anyone have any ideas what to look for configuration wise? everything seems to work ok in a simple telnet session – Dan Oct 29 '12 at 17:30

3 Answers3

43

cracked it! :)

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

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.

thanks for your suggestions and help everyone :)

Dan
  • 739
  • 1
  • 6
  • 8
  • Confirmed this is also a problem with "Sophos Home". Turning off Web Protection in their web dashboard fixed the problem. I've tweeted their support channel as well about it... hopefully an update can allow text/event-stream through without blocking comms. – Rob Evans Mar 02 '16 at 11:28
2

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.

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

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.

Victor
  • 3,669
  • 3
  • 37
  • 42
  • thanks for the suggestion victor; changing the line terminators makes no difference. the same issue still occurs if i stop the chunking. – Dan Oct 26 '12 at 14:24
  • This depends on OS you're running your server on. For Linux, its `\n`, for Windows - `\r\n`. For platform-independent apps, you shall use `PHP_EOL` constant. –  Feb 27 '17 at 18:13
0

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.

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

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

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

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]

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).");
 }
inDream
  • 1,277
  • 10
  • 12