I know alot of websites and engineers use Websocket for push only data. SSE is tailored made for real time one sided updates!
In fact, I think it made a better chat protocol, when we built a chat system at a previous job, similar to Slack. We ended up leveraging SSE to push updates to channels and traditional HTTP requests to send data to the server, resulting in lower latency and less overhead on the server side. We did this because we found the following to be true:
- At any given time, there are far more people reading chat messages than writing them
- What matters is that updates after being sent to everyone else is that their pretty close to instantaneous but you had a 500ms window to allow for the server to receive a new message and then propagate it out. Therefore, for the sender, you update the UI optimistically so they aren't waiting for a response, and everyone else will get it in ~500ms and its imperceptible
- one downside: its harder (we never figured it out before I left) to do indicators for when someone is typing. That really does seem to require two way real time connections
The big problem i've run into with SSE is the connection limit [1]:
> Warning: When not used over HTTP/2, SSE suffers from a limitation to the maximum number of open connections, which can be specially painful when opening various tabs as the limit is per browser and set to a very low number (6). The issue has been marked as "Won't fix" in Chrome and Firefox. This limit is per browser + domain, so that means that you can open 6 SSE connections across all of the tabs to www.example1.com and another 6 SSE connections to www.example2.com. (from Stackoverflow). When using HTTP/2, the maximum number of simultaneous HTTP streams is negotiated between the server and the client (defaults to 100).
The natural way to use SSE is to create an EventSource on each page. If you do this, and your user opens six tabs, they can now no longer make HTTP requests to your site. Not just SSE requests, any HTTP requests at all!
It's also quite natural, i think, to want to use multiple separate streams. It's very convenient to write separate endpoints for various kinds of data that a page might want, for example one for streaming numerical data, one for control messages, and one for descriptive events; or one for each panel on a dashboard. Here, you can hit the limit on a single page.
I wrote a thin layer over my HTTP server's APIs which lets me write a single handler which can send events via either SSE or a websocket. Then, when i build a page, i use websockets, but if i want to debug, i can still use curl to make a normal HTTP request. Also, other apps can use SSE to pull data, which only requires an HTTP client, not a websocket client.
> The natural way to use SSE is to create an EventSource on each page. If you do this, and your user opens six tabs, they can now no longer make HTTP requests to your site. Not just SSE requests, any HTTP requests at all!
You can use EventSource in a singleton worker and use postMessage to forward messages to all open browser tabs for the same origin.
SharedWorker (the thing needed for a "singleton worker") only just made it into Safari the end of last year...
plus you now have to handle multiplexing and demultiplexing for different tabs on both server and client.
By the time that arrived and HTTP/2 had wide enough adoption, everyone's already used to building atop Websocket...
Also the EventSource in the browser is heavily gimped. GET only, No custom headers, and last I checked you can forget about configuring any CORS stuff for credential passing.
You can use BroadcastChannel [1] to communicate between tabs. It works well and does not require service workers. It's used in Tracim [2], for some reason we could not move to HTTP/2 at the time I was working on this project so we indeed learned about this connection limit soon enough…
Even on HTTP/2, I believe it's best avoid sending 6 times the same message to the user just because they opened 6 tabs. It's less wasteful both for the server and the client.
Thanks very much for the examples ! I get your point but it does seem quite more involved to do all that management and broadcasting by hand rather than having a shared worker with a EventSource singleton for all, don't you think ? : https://stackoverflow.com/a/61636606/10469162
EventSource is bad. We were using it to connect to GPT 4 via our server acting as a middleware (we hosted the code for returning the LLM response on our server since our company's OpenAI API key is supposed to be private).
However, previous developers had written a basic middleware to check if a req.body exists and if it contains a userId (since our server was handling multipler other things apart from this one component of the business logic).
However, any such manipulation of the request body isn't possible with EventSource (and I felt that using a workaround such as passing the userId in the querystring could cause some security issue), so I ended up using the fetch() method to connect to our server for interacting with GPT 4.
> It's also quite natural, i think, to want to use multiple separate streams.
i dont think i'd call it natural per se - i'd say it's just "easy" to do so, and especially if each of these separate streams are managed by different teams, and it makes business sense to separate the responsibility.
There should be a framework, perhaps, to allow "multiplexing" of multiple streams of events into one actual tcp connection. Of course, this requires some work, but i dont think it's actually that much extra work. It just needed to be planned out as a need, as it might be a tad hard to tack it on after the fact (tho that's not impossible either).
One major issue I encountered with SSE is dealing with reconnection. WebSocket makes it very easy to detect differences between a loss of network connectivity, a server dying, and a client exiting properly (eg: closing the tab), on either side of the socket.
Maybe there are facilities to do so with SSE that I don't know of, but my experience in reliable server-to-client-only comms with it has been a bit rough.
This also matters for load balancers. With SSE or chunked encoding long polling, it's possible to lose the client connection without the load balancer closing the connection to the server, at least for a while.
WebSockets make it easy to do periodic heartbeats from the server, which puts an upper bound on how long a client connection can be in an uncertain state.
This also enables client presence data, even if the client only ever receives messages.
You pretty much just have to listen for and handle premature closing of the underlying TCP connection. It's the only form of dead peer detection you'll get if someone just abrubtly closes their browser tab.
Not TCP specifically, but ASP.NET Core binds your controller/action methods' CancellationToken parameter (if present) to HttpContext.RequestAborted - so assuming one uses it correctly (e.g. tying it to things like SQL TRANSACTION/ROLLBACK) then that helps prevent inadvertent changes if the user clicked their browser's Stop button after a POST, or gracefully handle long file upload failures, and so on.
I don't think I understand. Both WebSockets and server-sent events are just a long lived TCP socket, right? Why would SSE be lower overhead? Why wouldn't it be more overhead to go through the whole HTTP request handling machinery for EVRY chat message everyone sends? You already have an underlying 2-way connection in the TCP socket, why not use it?
And why would latency be lower with HTTP requests + SSE than with websockets?
It's not. SSE has higher overhead and there are browser limits as to how many concurrent channels a single client can consume. Also, SSE encourages you to shift complexity to the back end where it would be more appropriate on the front end.
For example, the client knows which channels it needs, so why not let the client decide which channels to subscribe to over a single WebSocket connection? With SSE, you end up having to do this channel management and keep track of which user is associated with what SSE connections on the back end since the user cannot just open any number of new channels on-demand (due to limits in number of concurrent SSE connections; it's better to multiplex over a single SSE connection). If your system runs on multiple processes or hosts, figuring out which process/host a particular user is connected to is a nightmare and forces you to create clients on the back end to aggregate messages for specific users and then publish to the correct SSE connection which uses extra memory and exposes your server to potential DoS vulnerabilities. It creates the need for a bunch of complex user-connection-matching and cleanup logic on the back end and it complicates access control.
I have no idea why this "let's micromanage data streams for each user individually on the back end" approach became so popular while the alternative approach of end-to-end pub/sub is rarely used when it results in a much simpler, more secure system.
With end-to-end pub/sub, you can multiplex multiple channels over a single bidirectional connection in a way which conveniently and cleanly allows you to couple multiple subscriptions over that single WebSocket connection... No complex cleanup on the back end on disconnection, no risk of stale channels on the back end, no need to poll for user activity to check for subscription liveness. As soon as the WebSocket is disconnected, the client can be fully unsubscribed from all their channels on the server-side.
In NestJs it is quite easy to manage in the backend, there's a SSE decorator for you SSE service and then you just return an observable on a subject. (See RxJs)
Sending message events sends to all observers on the subject and when the `observed` property` becomes false, you just close the connection.
You can skip the "typing ended". You're gonna need a shortish timeout anyway, may as well just let the "typing is happening" messages all terminate by timeout.
What do you have in mind? I can't think of a case where you'd want "X is typing" to persist more than a second or maybe two past the last-received message that affirmed X is, in fact, still typing. I can think of cases in which a short timeout might break down in bad ways (making the notice really jittery on high-latency connections, say) but I think those are probably just situations in which any "... is typing" message is simply gonna be bad in one way or another.
You don't want to clear it after only a second. It should persist much longer since people pause and think between words.
It's common that someone starts typing but immediately change their mind and clears the input after only a couple of characters. So if the timeout is longer you need an "ended" event.
But messages themselves are already sent out-of-band as a POST request, if I understand correctly. Don't see why that couldn't be extended to include typing indicators.
I used both WebSockets and SSE. In one project, I even support WebSockets and a fallback working like what you describe.
I'm sold on using SSE for pushed updates. But how is regular requests + SSE easier on the server compared to WebSockets? It seems like an open connection to maintain in both cases plus additional http request for the SSE+requests case.
You need to multi-thread your server on shared memory to build many-to-many solutions. The simplest way to get that working is Java + NIO with concurrent package, I have done it for you: http://github.com/tinspin/rupy
My web server outperforms everything on the planet for multiplayer.
Websockets are notoriously hard to parallelize if you want shared memory which you need for many-to-many, mostly because JavaScript and C++ have very poor atomic concurrency, or atleast I never found any Websocket server that could do it.
> You need to multi-thread your server on shared memory to build many-to-many solutions
Not necessarily, it depends on the needs, but in any case I would not expect SSE and WebSockets to be different in this regard.
I'm not sure what's specifically hard to parallelize with WebSockets. Shared memory may be hard to handle but that does not seem specific to WebSockets which are just regular two way communication channels that happen to start with an HTTP request from the client.
Websockets use one TCP socket for starters with SE you use separate sockets from ingress and egress which is much simpler to scale from an infrastructural point of view.
I clearly said many-to-many, everything else is trivial to scale because only many-to-many requires strictly shared memory atomic parallelization.
You could write a better server in Java than javascript/c(++) but I haven't found any, and to be honest the extra complication of the binary protocol doesn't even make me curious enough to waste 5 seconds to google it.
SSE over HTTP/1.1 is the final solution. Everything else is a waste of time for eternity.
Server Sent Events infamously have low global connection limit per origin[1], so if you have multiple tabs open for a single site you'll run into it pretty quickly. If it weren't for that, they would be great.
I've never worked with Web Workers, but the doc page mentions them at the very top. Isn't that the intended workaround? Have a background thread that receives the events & coordinates, rather than having each page deal with them?
I wanted so use these in a project recently, only to find out that they aren't supported on Chrome for Android. This makes them non-usable for me, since there is no drop-in polyfill available yet.
Sophisticated services that offer SSE will have many different endpoint domains to get around browser limits on simultaneous connections to the same service.
That's what I'm saying: it's not that good for non-sophisticated services, you need workarounds to make this "great technology" work. Might as well switch to websockets and not worry about this.
Right, but with websockets you wouldn't have to think about this at all.
You'd also need to ensure http2 for local development, proxies in between, etc. You _can_ do all this, but you can also avoid putting any thought into this matter.
I believe you have it essentially backward. Websockets consume a connection no matter what, as the protocol does not work with HTTP2 (there was an attempt, but I believe it wasn't adopted and abandoned).
The SSE requests do work with HTTP2, as do some other old methods like long polling. All the tabs will generally share one connection to that domain.
> Websockets consume a connection no matter what, as the protocol does not work with HTTP2 (there was an attempt, but I believe it wasn't adopted and abandoned).
My favorite use of this that I've spotted in the wild is https://nightride.fm (not affiliated, just a fan!). It's a Synthwave music site + community.
In particular, I noticed that EventSource goodies are used to broadcast currently playing songs on each station, as well as to provide anon read access to their IRC server via a simple vanilla javascript webapp.
WAY cleaner and easier than web sockets for this use case, and CDN friendly to boot!
Note that this really isn’t anything except the agreement to send messages separated by 2 new lines. The SSE object in browsers is old and quirky; for example, it only supports GET requests, which means you’ll hit path length limits if you use it for something like LLM completion with large prompts.
Luckily since there’s nothing special about the browser support, you can very easily replace it with custom implementations. https://www.npmjs.com/package/@microsoft/fetch-event-source for example offers mostly the same API, but does offer POST requests and a bunch of other goodies.
There is this agreement, and also the fact that browsers can forget previous messages, unlike any xhr-based custom implementation since the EventSource object does not provide methods to access previous contents. I don't know how well this package handles this.
I agree with the note about the auto retry mechanism of EventSource. It does no good, is unavoidable and a big annoyance. The work we had to do to work around it in Tracim (previous job) [1] was pure madness. I see this part has not been touched since I left by the way.
This seems like a great solution for streaming responses scoped to a single request. Neat!
I've always used SSEs to open a more persistent comms channel to a browser which creates way more state and complexity both on the client and the server.
I was looking through the implementation of chat.openai.com recently and was pleasantly surprised to see that it was 100% Server Sent Events, no Websockets.
SSE is a great tool to have in your belt. I'd say it can replace the majority of use cases for Websockets out there today (basically pushing small amounts of text/JSON data to the client) with a tiny fraction of the complexity. Opening and maintaining a bidirectional TCP stream is otherwise a huge pain in the ass.
I fear years back I made a Monopoly Deal game with a client-server arch.
Server was in Go with SSE, and client was in JS/Svelte with SVG and a lot of CSS.
It work so so well ! The clientside cmds (like "place this card there") was vanilla ajax send to Go Server, but the "state" was always send via SSE to connected clients.
I was surprised to see how far Joe Average (Yea I use bootstrap for most things) can push CSS for nice looking card game.
PS: Oops I mean "WildDeal" not "Monopoly Deal", no need to get sued
I took it down, was like 95% done but ran out of motivation to make it bullet-proof and catch all the corner cases. Also needed to add something like WebRTC, we found out playing with my friends we always were in a Zoom/Jitsi/WhatsApp call with each other, half the fun is the "vocal banter" :D
Wait Wait do you work for "Hasbro" :O ?
PS - Fun Nerd Fact: Hasbro owners of many many board games including monopoly also owns the trademark for "Ouija Board" (yea the spirits phone-home one) :D
Love to see stuff like this on the HN front page, when I know tons of web devs that have never even heard of SSE.
Tangentially, I’m having a massive problem with SSE datastreams erroring out about two minutes in for “ERR: Network chunk encoding” reasons, anyone else seen this before?
In theory, a TCP session can remain alive forever. In practice, various things got in the way, and you need traffic to keep it alive (keep-alive, ping, heartbeat, it gets given various names). I believe once a minute should be enough. I’ve encountered "ping" and "" used as SSE event names for this purpose.
I'm a little confused by how this is used. The trivial example provided on developer.mozilla.org is a PHP script that's fully self-contained. As far as I could tell, a self-contained PHP script doesn't provide much value. I would have expected something to happen on the server, like a database record getting updated, which triggers the PHP to send an SSE message to the browser, but how do you trigger that particular PHP instance to send a message? Does the PHP instance just poll the database on a periodic basis? That doesn't seem much better than having the browser poll the web server on a periodic basis...
> That doesn't seem much better than having the browser poll the web server on a periodic basis...
It could indeed be much better for performance. It means the polling happens over a (presumably) low-latency, high-throughput local datacenter connection, and not over the end-user's internet connection over which you have no control.
But more importantly, SSE only defines the interface between the web browser and web server. Rather than polling a DB, you could just as well hook the web server up to a more sophisticated pubsub system (although PHP might not be the best choice for that). In that regard, SSE is no different from websockets; the main difference is that it's a simpler protocol to implement, and only works one-way.
You could have some kind of event system running that pushes the event down to php. For instance, redis has a PUBSUB system, and postgresql has NOTIFY.
A bit easy to miss, but includes some actual code samples and a really nice walkthrough. Highly encouraged even if you already "know" SSE and want to solidify your knowledge, I like how it doesn't hide any of the details.
Curious why is this trending? Btw sse are pretty great, only thing I don't get is why they don't allow for binary payloads. That'd have been the icing on the cake.
> only thing I don't get is why they don't allow for binary payloads
I've always appreciated that it's just a simple line-oriented UTF-8 text protocol. Binary transfers are handled more efficiently with something else. Or just base64 the payload if it's not that big of a deal.
This got into the zeitgeist somehow. I just implemented my first SSE app with FastAPI last week, then I saw a youtube video and this post!
I was implementing something with websockets and asked chatgpt to tell me about modern alternatives. In my years of full-stack… I hadn’t heard of SSE, or at least never got why I should care.
Damn, I see the limitations of REST, but I was building state machines and custom protocols with websockets. It’s nice to prototype in REST again.
> Our API is designed to be a backend that incorporates Claude into any application you’ve developed. Our application sends text to our API, then receives a response via *server-sent events*, a streaming protocol for the web. We have API documentation with drop-in example code in Python and Typescript to get you started.
Was curious what it was and thought it was probably worth sharing.
I did a web app project with SSEs a while back. They worked well in the browser, but when it came time to build a mobile app (React Native), I had trouble finding a good library to use. I'm sure I could have rolled my own, but being in a time crunch I ended up using Firebase Cloud Messaging. Hopefully there's better support these days.
I wish there were a way to push not just anonymous data/events but http resources, to similar effect.
SSE & websockets & http streams can send anonymous data, but I'd like to be able to send an actual thing. As per the most core architecture of the web:
Instead of pushing to a chat app an anonymous blob of json for a chat message to a room, the server could assert a /room/42/msg/c0f3 resource, could identify universally what it is it's sending. The advantage would be so high. It'd become a standard way to assert a resource, to make known a fact, that would be viable across systems. Further http requests to the resource would just work. Folks could link to the resource. How cool it would be if we could send not just data but resources?
We have come glancingly close to getting such a thing so many times. The HyBi mailing list that begat websockets had a number of alternate more resourceful ideas floating around such as a BEEP protocol that allowed patterns beyond request/response of resources. The browser actually implements an internal protocol that uses HTTP2/push to send resourceful messages, web push protocol/RFC8030, https://datatracker.ietf.org/doc/html/rfc8030.
But the underlying http2/push was de-implemented for webserving in general, and even when it was available, it lacked the oft requested ability to get notice of new pushed resources. (https://github.com/whatwg/fetch/issues/65 was an old request. https://github.com/whatwg/fetch/issues/607 had some steam in making it happen.) Tragic backwards moves in my view, and never acknowledged by blink-dev when protests arose.
The best we have today is to stream json-ls events, which have an @id property identifying them. But developers would have to snoop these events, and store them in a service worker, to make them actually accessible as http resources.
I continue to hold hope eventually we'll get better at using urls to send data, to assert new things happening... But it's been nearly 30 years of me hoping, and with some fleeting exceptions the browser teams have seemed disinterested in making urls cool, in spite of a number of requests. We've been left at un-web side-channels like SSE & Websockets for a long long time now. Wouldn't it be nice to see some real growth for http capabilities that we really can use, that are more than abstract underlying transport tweaks like h2 and h3?
They're well supported on modern browsers, both mobile and desktop. If you need to support IE then you can use a polyfill. It's essentially just a long-running HTTP request. The main issue I've run into before is if your server has a response timeout configured then you'll need to disable or work around it, otherwise it will disrupt the event stream whenever the timeout is hit, since the event stream is just an HTTP response.
I use this at work (Enterprise SAAS web software) and we had to implement a backup "polling every 5 seconds" for clients that would not support it. It's quite rare though. We assume that some caching proxy might be waiting for the GET request to be fully done to cache it and then give it to the user or something like this. The SSE would then never get updates, while every normal HTTP request would work
I've read that this does indeed happen with some HTTP proxies. In some environments that includes HTTPS because some environments intercept HTTPS traffic too. It's enough for me to consider SSE too unreliable to use if my application needs to be reliable with events, and it's the first thing I thought when I saw the SSE spec the first time.
Back beore SSE, I recall seeing some server-to-client event protocol specification inserted extra padding bytes after events in the middle of an HTTP response stream, just to convince some HTTP proxies to forward the event in an incomplete HTTP response, but I don't recall which protocol now. It was never entirely clear how much padding would be enough, or if any amount would be.
When cometd and the Bayeux publish-subscribe protocol was designed, this is why long-polling was always used, instead of streaming partial responses inside a single HTTP response. With correctly designed overlapping long-polling requests, it's possible to get event latency very similar to SSE, without this issue of lost events due to proxies. So that's a good, reliable choice, if you don't use WebSockets. In fact it's more reliable than WebSockets (which can also get stuck at some proxies), so if you value very reliable event delivery long-polling or just periodic polling are the way to do it, but with much higher overhead.
In theory SSE provides the option for mobile device power saving described at https://html.spec.whatwg.org/multipage/server-sent-events.ht... Using more active protocols almost certainly defeats this. In theory a mobile network and network stack (modified inside the mobile browser where cleartext is available) could implement that power saving strategy for mostly-idle WebSockets too, which is another argument for WebSockets.
In fact, I think it made a better chat protocol, when we built a chat system at a previous job, similar to Slack. We ended up leveraging SSE to push updates to channels and traditional HTTP requests to send data to the server, resulting in lower latency and less overhead on the server side. We did this because we found the following to be true:
- At any given time, there are far more people reading chat messages than writing them
- What matters is that updates after being sent to everyone else is that their pretty close to instantaneous but you had a 500ms window to allow for the server to receive a new message and then propagate it out. Therefore, for the sender, you update the UI optimistically so they aren't waiting for a response, and everyone else will get it in ~500ms and its imperceptible
- one downside: its harder (we never figured it out before I left) to do indicators for when someone is typing. That really does seem to require two way real time connections