Server-sent events
developer.mozilla.org
developer.mozilla.org
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
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.
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.
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.
> It's common that someone starts typing but immediately change their mind and clears the input after only a couple of characters.
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.
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.
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.
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.
> 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.
[1] https://developer.mozilla.org/en-US/docs/Web/API/EventSource
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).
You can use EventSource in a singleton worker and use postMessage to forward messages to all open browser tabs for the same origin.
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.
I was looking at : https://stackoverflow.com/questions/61041389/managing-server...
Shared worker would actually allow for SSE in a service worker ! That's nice.
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.
If you have an answer to that answer I'd be very much interested
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.
[1] https://developer.mozilla.org/en-US/docs/Web/API/BroadcastCh...
[2] https://github.com/tracim/tracim/blob/develop/frontend_lib/s...
EDIT: Someone posted this very relevant and interesting post : https://greenvitriol.com/posts/browser-leader
Or move away from the browser. Browsers are toxic now.
Not an unreasonable requirement for a serious modern web app.
And why would latency be lower with HTTP requests + SSE than with websockets?
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.
[1] https://stackoverflow.com/questions/18584525/server-sent-eve...
endpoint-00.example.com endpoint-01.example.com endpoint-02.example.com
etc. It isn't specific to SSE. It's any HTTP request. SSE is just the most obvious because the connections stay open for long periods.
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.
Websockets to my knowledge have no such limit because they are not HTTP requests.
Source: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent...
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.
Are you thinking of RFC 8441? https://www.rfc-editor.org/rfc/rfc8441
If so, then it's already implemented by Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1434137
...and by Chrome/Chromium since 91: https://chromestatus.com/feature/6251293127475200
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!
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.
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.
https://github.com/tracim/tracim/blob/develop/frontend_lib/s...
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.
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.
HTTP/2 streams are just as easy to implement, and work both ways and support binary data.
console.log or wireshark
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
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
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.
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.
It's quite simple to start with on the Go backend side of things and the frontend things aren't too complicated either.
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.
Turns out in their FAQ, they have this:
> 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.
Didn't knew OpenAI uses the same mechanism.
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.
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.
REST+SSE ftw, I guess!
So also avoid HTTPS.
It's trending because it's the final real-time solution for eternity.
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:
> Axiom 0a: Universality 2. Any resource of significance should be given a URI. https://www.w3.org/DesignIssues/Axioms.html#uri
Or alternatively,
> Cool URLs don't change (implicitly, cool things have URLs, see above). https://www.w3.org/Provider/Style/URI
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?
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.
Real life stats: "759 out of 205.875 customers refunded because their antivirus blocks the pull HTTP stream", so 99.7%!
They even go through the great firewall without bureaucracy.
Be careful what you release because it might not be fixable after release.
But fundamentally nothing that Comet, steaming XHR, etc, WebSockets, can't do.
Why do you believe that?