2

I'm trying to send a SSE text/event-stream response from an express.js end point. My route handler looks like:

function openSSE(req, res) {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream; charset=UTF-8',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
    'Transfer-Encoding': 'chunked'
  });

  // support the polyfill
  if (req.headers['x-requested-with'] == 'XMLHttpRequest') {
    res.xhr = null;
  }

  res.write(':' + Array(2049).join('\t') + '\n'); //2kb padding for IE
  res.write('id: '+ lastID +'\n');
  res.write('retry: 2000\n');
  res.write('data: cool connection\n\n');

  console.log("connection added");
  connections.push(res);
}

Later I then call:

function sendSSE(res, message){
    res.write(message);
    if (res.hasOwnProperty('xhr')) {
        clearTimeout(res.xhr);
        res.xhr = setTimeout(function () {
          res.end();
          removeConnection(res);
        }, 250);
    }
}

My browser makes the and holds the request: enter image description here

None of the response gets pushed to the browser. None of my events are fired. If I kill the express.js server. The response is suddenly drained and every event hits the browser at once. enter image description here

If I update my code to add res.end() after the res.write(message) line It flushes the stream correctly however it then fallsback to event polling and dosen't stream the response. enter image description here

I've tried adding padding to the head of the response like res.write(':' + Array(2049).join('\t') + '\n'); as I've seen from other SO post that can trigger a browser to drain the response.

I suspect this is an issue with express.js because I had been previously using this code with nodes native http server and it was working correctly. So I'm wondering if there is some way to bypass express's wrapping of the response object.

kevzettler
  • 4,783
  • 15
  • 58
  • 103
  • 1
    I believe it's a Chrome issue. I literally did this 2 days ago. Changed my `Content-Type` to `text/json` and it magically started working. – Randy Apr 22 '15 at 20:15
  • When I get home I'll try `text/event-stream` on the code that I know works. – Randy Apr 22 '15 at 20:21
  • I tried in firefox and was seeing same behavior. I"ll try text/json and if that helps. – kevzettler Apr 22 '15 at 20:42
  • @Randy changed my Content-Type ( for the response ) to `text/json`. didn't do anything – kevzettler Apr 22 '15 at 20:56
  • same problem, did you solve this issues? how did you do that? thx. – vikingmute Dec 04 '15 at 09:18
  • Yes. For anyone else seeing this problem. For me it was an issue with my nginx config. There we're upstream rules not being applied to my downstream route. – kevzettler Dec 06 '15 at 08:31
  • @kevzettler hey, I am using a simple express server, and found the same problem. when I do curl /endpoint in command line, it works fine, but when I open a SSE in my client using new EventSource(), has the exactly same issue as you describe, any idea about that? Thx – vikingmute Dec 08 '15 at 08:52
  • @vikingmute all I can say if is if you're behind a proxy or firewall like nginx make sure everythings forwarded correctly – kevzettler Dec 08 '15 at 23:24
  • @kevzettler i'm behind nginx too. How did you configure the 'upstream/downstream forwarding' correctly in nginx? – kyw Oct 22 '21 at 01:49

2 Answers2

2

This is the code I have working in my project.

Server side:

router.get('/listen', function (req, res) {
    res.header('transfer-encoding', 'chunked');
    res.set('Content-Type', 'text/json');

    var callback = function (data) {
        console.log('data');
        res.write(JSON.stringify(data));
    };

    //Event listener which calls calback.
    dbdriver.listener.on(name, callback);

    res.socket.on('end', function () {
        //Removes the listener on socket end
        dbdriver.listener.removeListener(name, callback);
    });
});

Client side:

xhr = new XMLHttpRequest();
xhr.open("GET", '/listen', true);
xhr.onprogress = function () {
    //responseText contains ALL the data received
    console.log("PROGRESS:", xhr.responseText)
};
xhr.send();
Randy
  • 4,351
  • 2
  • 25
  • 46
  • may you look here pls http://stackoverflow.com/questions/32506980/sockjs-eventsource-response-has-a-mime-type-text-html-that-is-not-text-eve – VB_ Sep 10 '15 at 16:35
2

I was struggling with this one too, so after some browsing and reading I solved this issue by setting an extra header to the response object:

res.writeHead(200, {
  "Content-Type": "text/event-stream",
  "Cache-Control": "no-cache",
  "Content-Encoding": "none"
});

Long story short, when the EventSource is negotiating with the server, it is sending an Accept-Encoding: gzip, deflate, br header which is making express to respond with an Content-Encoding: gzip header. So there are two solutions for this issue, the first is to add a Content-Encoding: none header to the response and the second is to (gzip) compress your response.

Zoti
  • 822
  • 11
  • 31