3

I have the following Node application

var express = require("express"),
    app = express();


app.get("/api/time", function(req, res) {
    sendSSE(req, res);
});

function sendSSE(req, res) {
    res.set({
        "Content-Type": "text/event-stream",
        "Cache-Control": "no-cache",
        "Connection": "keep-alive",
        "Access-Control-Allow-Origin": "*"
    });

    var id = (new Date()).toLocaleTimeString();

    setInterval(function() {
        constructSSE(res, id, (new Date()).toLocaleTimeString());
    }, 5000);

    constructSSE(res, id, (new Date()).toLocaleTimeString());
};


function constructSSE(res, id, data) {
    res.write("id: " + id + "\n");
    res.write("data: " + data + "\n\n");
}

var server = app.listen(8081, function() {

});

I am using it to use Server Side Events with my client app. When I browse to http://localhost:8081/api/time it starts returning straight away. If I open the URI in another browser window then it will take several seconds before it responds, however then it works fine.

So my question is setInterval blocking, or is there some other explanation for the poor performance? Based on this answer it is not supposed to be, but I would not expect that constructSSE would take 5 seconds. However, I am seeing an issue.

Thanks.

Update As suggested that it might be something to do with express, I removed it and just used the http module.

var http = require("http");

http.createServer(function(req, res){
    if (req.url == "/api/time") {
        sendSSE(req, res);
    }
}).listen(8081);

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

    var id = (new Date()).toLocaleTimeString();

    setInterval(function() {
        constructSSE(res, id, (new Date()).toLocaleTimeString());
    }, 5000);

    constructSSE(res, id, (new Date()).toLocaleTimeString());
};


function constructSSE(res, id, data) {
    res.write("id: " + id + "\n");
    res.write("data: " + data + "\n\n");
};

It has the exact same behaviour. So it looks like some limitation of Node, or a mistake by me.

Community
  • 1
  • 1
baynezy
  • 6,493
  • 10
  • 48
  • 73
  • What is the difference between "*I browse to [the URI]*" and "*I open the URI in another browser window*"? – Bergi Jun 24 '15 at 16:41
  • 3
    No, `setInterval` is indeed not blocking. Maybe you experience some buffering behaviour of `res` and need to flush the output? – Bergi Jun 24 '15 at 16:43
  • Bergi. What I mean is I make a second request while the original connection is still open. – baynezy Jun 24 '15 at 16:43
  • 3
    @RodrigoMedeiros this is not a duplicate. The question you reference makes requests to another resource too fast while this is about serving a request that does not finish. While the reason for the question might be related the solution is not. You cannot reduce the number of request in this question because there are no request at all. I increase the interval and the problem persist. – devconcept Jun 25 '15 at 14:31
  • 2
    What do you see in the "network" tab of your browser development tool ? Is there any data coming ? – Telokis Jun 25 '15 at 16:05
  • @devconcept You're right. I didn't replicate the scenario here, which lead me to generalize the problem. Thanks for pointing it out. Flag removed. – Rodrigo Medeiros Jun 25 '15 at 16:40
  • Try another browser altogether, not a new window of the same browser. Browsers try to limit the number of connections to the same webserver, or wait for a previous connection to close before opening new ones. Since you're dealing with long-lived connections, that may be causing the delay. – robertklep Jun 25 '15 at 20:13
  • Based on your answer @devconcept, I tried to put the same logic of `get('/api/time')` in the other route, `get('/')`, and it doesn't block. So it might be something related to how `express` handle its routes? – Rodrigo Medeiros Jun 26 '15 at 13:10
  • @RodrigoMedeiros Not really. It has no relation with express because I also tested with node alone and also with a node module without express and the behaviour is the same. I think the most revealing clue is that the browser does not fill the EventStream tab as you would expect. It doesn't block if you call it with the javascript EventSource API otherwise it will behave like it's blocking (I use those words on purpose because I still don't think it's a node.js issue). – devconcept Jun 26 '15 at 13:51
  • @devconcept I see what you mean now. If I replicate the scenario with `curl`, it works fine too. – Rodrigo Medeiros Jun 26 '15 at 14:01

1 Answers1

2

I think the reason of the poor performance is not related to node or setInterval itself but the way you read your event data. I do a little search and implement 3 or 4 different examples found on the web of Server Sent Events on node and they all suffer from the same problem.

Thinking that node.js was no good for the task didn't fit on my head.

I tried

  1. Using process.nextTick
  2. Increasing the interval
  3. Using setTimeout instead of setInterval
  4. With express.js or just node.js
  5. With some node modules like connect-sse and sse-stream

I was about to start testing using webworker-threads or the cluster module or another server platform trying to pin down the problem and then I had the idea to write a little page to grab the events using the EventSource API. When i did this everything worked just fine, moreover in the previous tests using Chrome i see that in the Network Tab the SSE request contain an additional tab called EventStream as was espected but the content was empty even when data was arriving regularly.

This lead me to believe that maybe it was the browser not behaving correctly interpreting the request in the wrong way due it was requested using the address bar and not the EventSource API. The reasons for this I don't know but I made a little example and there is absolutely no poor performance.

I changed your code like this.

  1. Added another route to the root of website for testing

    var express = require("express"),
    app = express();
    
    // Send an html file for testing
    app.get("/", function (req, res, next) {
        res.setHeader('content-type', 'text/html')
        fs.readFile('./index.html', function(err, data) {
            res.send(data);
        });
    });
    
    app.get("/api/time", function(req, res) {
        sendSSE(req, res);
    });
    
  2. Created an index.html file

    <head>
        <title>Server-Sent Events Demo</title>
        <meta charset="UTF-8" />
        <script>
            document.addEventListener("DOMContentLoaded", function () {
                var es = new EventSource('/api/time'),
                    closed = false,
                    ul = document.querySelector('ul');
    
                es.onmessage = function (ev) {
                    var li;
                    if (closed) return;
    
                    li = document.createElement("li");
                    li.innerHTML = "message: " + ev.data;
                    ul.appendChild(li);
                };
    
                es.addEventListener('end', function () {
                    es.close()
                    closed = true
                }, true);
    
                es.onerror = function (e) {
                    closed = true
                };
            }, false);
        </script>
    </head>
    <body>
        <ul>
        </ul>
    </body> 
    

{Edit}

I also want to point out, thanks to @Rodrigo Medeiros, that making a request to /api/time with curl shows no poor performance which reinforce the idea that is is a browser related issue.

devconcept
  • 3,665
  • 1
  • 26
  • 40
  • thanks so much for your help. That makes sense and hitting it on curl has none of the issues. – baynezy Jun 26 '15 at 14:58
  • 1
    @baynezy You are welcome. I was trying to configure chrome to use http/1.0 to check if this has to do with http/1.1 persistent connections as stated in RFC 2616 section 8 to provide a more accurate answer but I was unable to do so. Couldn't find a chrome setting for that :( – devconcept Jun 26 '15 at 15:12