webwise
webwise

Reputation: 627

Terminating Server Sent Events in node

I'm trying to use SSE with node + express: I intercept requests using an express route, then I initiate a SSE session by directly writing headers:

res.writeHead(200, {
    "content-type": "text/event-stream",
    "cache-control": "no-cache"
});

I proceed with writing intermittent payloads using "res.write()"s.

This works well with Chrome's EventSource, up until the time when I call ".close()" to end the session. Then, the connection keeps hanging: Chrome doesn't reuse the connection to initiate additional EventSource requests (or any other requests), and node never triggers a "close" event on the IncomingMessage instance.

My question is: How do I handle "eventSource.close()" properly using node's http API?


It's worth noting that:

Upvotes: 1

Views: 3159

Answers (1)

poida
poida

Reputation: 3599

When the browser closes the event source it does let the server side know. On the server side, the response socket object (res.socket) will generate an end event followed by a close event. You can listen to this event and respond appropriately.

E.g.

res.socket.on('end', e => {
  console.log('event source closed');
  sseResponses = sseResponses.filter(x => x != res);
  res.end();
});

If your server is trying to write to a socket closed on the browser, it should not raise an error, but will return false from res.write.

If both your server side code and client side code are hanging after you close the event source, you may have bugs on both sides.


More complete prototype, with your writeHead code from above.

var app = new (require('express'));
var responses = [];

app.get("/", (req, res) => {
    res.status(200).send(`
<html>
    <script>
        var eventsource = null;

        function connect() {
            if (!eventsource) {
                eventsource = new EventSource("/sse");
                eventsource.onmessage = function(e) {
                    var logArea = window.document.getElementById('log');
                    logArea.value += e.data;
                };
            }
        }

        function disconnect() {
            if (eventsource) {
                var myeventsource = eventsource;
                eventsource = null;
                myeventsource.close();
            }
        }
    </script>
    <div>
        <span>
            <a href="javascript: connect()">Connect</a>
            <a href="javascript: disconnect()">Disconnect</a>
        <span>
    </div>
    <textarea id="log" style="width: 500px; height: 500px"></textarea>
</html>`);
});

app.get("/sse", (req, res) => {
    res.writeHead(200, {
        "content-type": "text/event-stream",
        "cache-control": "no-cache"
    });

    res.socket.on('end', e => {
        responses = responses.filter(x => x != res);
        res.end();
    });

    responses.push(res);
});

app.listen(8080);

setInterval(() => {
    responses.forEach(res => {
        res.write('data: .\n\n');
    });
}, 100);

Upvotes: 3

Related Questions