Detect browser tab close when using Server-Sent Events? #SSE #EventSource

I’m developing group chat for my site. I’m doing this in-house so users aren’t forced to have a separate account just for chat and/or I don’t have to deal with integrating with an outsourced chat app. I know Node.js would be ideal for this, but hey this is a Lucee forum so here we go! And I’ve decided to use SSE instead of WebSockets for a variety of reasons.

It will be very simple feature-wise, but I’m expecting 300-400 concurrent users. I’ve learned how to increase Apache2 connection limits, but I still want to prevent wasted connections as much as possible.

Questions:

  • Keeping in mind how the EventSource object in JavaScript automatically reconnects as part of the SSE spec, is there an optimum time limit for keeping each connection open? I picked 15 minutes, but should it be less so each thread ends quicker after users are gone? I’m trying to balance that vs frequency of JavaScript reconnects.

  • Is there any way for the server to detect when a user has closed their browser tab or window or in general whether the connection has closed? The answer to this question could be useful for much more than just SSE, i.e. any long-running script with a flush loop that should be aborted when the user doesn’t stick around for the output.

<cfscript>
TimeoutSeconds = 900; // 15 minutes
LoopCount = 0;
cfcontent(type="text/event-stream");
cfheader(name="Cache-Control", value="no-cache");
cfsetting(requestTimeOut="#TimeoutSeconds#");

function SendData(data, id) {
	echo("data:" & SerializeJSON(data) & Chr(10) & "id:" & id & Chr(10) & Chr(10));
	cfflush();
}

if (!StructKeyExists(getHttpRequestData().headers, 'last-event-id')) lastid = 0;
else lastid = Val(getHttpRequestData().headers['last-event-id']);

while(LoopCount < TimeoutSeconds) {

	// loop new chat messages
		// SendData("<p>#qry.name#<br>#qry.msg#</p>", qry.id);
	// end new messages loop

	LoopCount++;

	cfthread(action="sleep", duration="1000"); // one second pause
}
</cfscript>

keeping long connection opens like this via an event gateway is exactly what the Websocket gateway :: Lucee Documentation handles efficiently in Lucee.

Doing this via lucee normal requests, that stay open for a long time isn’t going to be efficient

@isapir it wouldn’t be hard to use the websocket extension with SSE?

Here is another article with detailed comparisons and a variety of issues to consider, in addition to the numerous benefits summarized in the first article in my OP (and there are others), which led me to my analysis that SSE is best suited for my needs.

However, @Zackster, I am interested to know how is SSE not efficient in Lucee? If you mean performance-wise, which resources are wasted? And why/how would WebSockets be used with SSE when only one of those solutions is sufficient for my use case?

With either of those proven technology options the connections stay open for a long time. If the concern with SSE is that it runs over https protocol, well that is also an advantage in some ways e.g. no proxy/firewall worries. Plus, “Secure WebSocket over TLS is strongly recommended for use in production environments”, thus it should be going through https also.

Also I’m no longer concerned about the possibility of SSE connections staying open too long after browser tabs are closed. I figured out how to answer my own question by cflogging once per second while the script is running. I found that there are no more log entries a second or two after I either explicitly close the EventSource object via JavaScript or just close the browser tab. I don’t know if EventSource notifies the server when it’s closing or Lucee (or perhaps Apache) detects when the connection has closed, but either way it’s very good news!

Each Lucee request has it’s own overhead/memory footprint (i.e. pageContext, session etc), keeping 400 Lucee threads open simply uses a lot more resources, Lucee can handle this, but it’s not designed to.

Using an event gateway, each connection no longer requires a full lucee request context, there is just a single pageContext running for the event gateway.cfc with servicing a pool of connections (without a pageContext)

1 Like

Server Sent Events can be more efficient than WebSockets for a Chat application, as opposed to a Game application for example where real time is really required.

But either way @Zackster is right about keeping references to the clients in some global scope and not per Request. An Event Gateway seems like a good approach.

@Zackster and @isapir thank you both for guiding me!

SSE can be more efficient in what ways?

If I go with WebSockets is there a browser API either of you recommend for handling issues that I’ve read are not handled out of the box such as proxy/firewall blocking, load balancing, and automatically resuming after disconnects? Fallback to SSE instead of long polling would ideal if anyone has implemented that.

I see that Socket.IO is recommended, but my understanding of that API is that it only communicates with a Node.js server running Socket.IO and the client side cannot communicate directly with a pure WebSockets server. There are also many other issues with Socket.IO detailed in this article.

Also with Apache/Lucee will I be able to use wss:// protocol?

@kenricashe I haven’t played much with SSE but it seems to take less resources than WebSockets.

You can see some videos that I made several years ago about WebSockets (some are a little outdated so examples would require some tweaks to work) at isapir - YouTube

I looked at your code on GitHub and I don’t see solutions for the issues of proxy/firewall blocking, etc. From your video titles I assume those are also specific to setting up WebSockets for Lucee.

However, my WebSockets questions are moot after reading this 2012 article by Remy Sharp. Particularly from the comments by Alice Wonder and Kll, I’ve come to realize SSE is actually perfect for my specific group chat needs. The vast majority of my users don’t even participate in the chat and just view what others are posting, i.e. unidirectional. And even those who are posting are only doing so occasionally, certainly not 60 posts per second or anywhere near a frequency which would require WebSockets.

Since Lucee isn’t ideal for SSE, I’ve decided to bite the bullet and finally develop my first Node.js app with NGINX as the web server. I will continue to use Lucee for processing message posts via AJAX. And both Node and Lucee will access the chat data via MySQL.

@isapir and @Zackster thank you both again for helping me understand what I need to do!

HI @kenricashe

I just posted an example of using SSE with Lucee. I dont see why it wouldnt be ideal for lucee. Keep the requests fairly short and they will restart (like 5 seconds?)

I had no problem getting SSE to work with Lucee, but I did have a major problem with scalability.

With Lucee/Apache I could only achieve about 250 concurrent connections, whereas Node/NGINX achieves more than 7,300 connections with only a single $5/mth DigitalOcean droplet, which by the way I’ve had in production for a few months already. :slight_smile:

Lucee is not ideal for SSE because of the reasons already mentioned above. Unless you found solutions to the inherent architecture limitations? They certainly don’t seem solvable at the code level.

I’m not sure what you mean re: short requests, because each request in SSE is kept open until the client disconnects. Did you mean short reconnect intervals? That doesn’t solve the problem of getting constantly bumped or having concurrent connections just hanging and no one receiving data, and also the inefficient use of server resources. Shorter intervals also result in the server getting clogged just that much quicker.

But for use cases of guaranteed low concurrency, Lucee is adequate for the task.