With ipv6 they can now be fully scaled easily but they are absolutely awesome, much easier to scale because you can give your client a simple list of sse services and its essentially stateless if done right.
Websockets get really complex to scale past a certain level of use.
I agree. Unfortunately you can only have 6 SSE streams per origin per browser instance, so you may be limited to 6 tabs without adding extra complexity on the client side.
SSE are really a subset of Comet-Stream (eternal HTTP response with Transfer-Encoding: chunked) only they use a header (Accept: text/event-stream) and wraps the chunks with "data:" and "\n\n".
But yes it's the superior (simplest, most robust, most performant and scalable) way to do real-time for eternity.
The browser is dead, but SSE will keep on doing work for native apps.
WebSockets lack flow control (backpressure) and multiplexing, so if you need them you either roll your own or use something similar to RSocket.
Also SSE can't send binary data directly. You have to base64 encode it or similar.
WebTransport addresses these and also solves head of line blocking. But I'm concerned that we might run into a similar problem as we had with going from Python2 to Python3 and IPv6. Too easy for people to keep using the old version, and too little (perceived) benefit to upgrading.
As long as browsers still work with TCP, some networks will continue to block UDP (and thus HTTP3/WebTransport) outright.
The article's information about WebRTC is not accurate. You can do client/server WebRTC without a "signaling server". Just make the server do the signaling. It takes a few extra round trips, but it doesn't need to be an extra server. And WebRTC data channels work quite well as a replacement for WebSockets or SSE, especially if you want to avoid head-of-line blocking. And there are many libraries that will do pretty much all of the work for you, like Pion or str0m.
I also think calling the WebTransport API complex is overblown. If you don't want the more advanced things, you can ignore them. If you want to use it like a WebSocket, just open one bidirectional stream and you're basically done. If you want to avoid head-of-line blocking, just open a stream for every message. It's a little more complex, but it's not the kind of thing you need a library for. Github Copilot will probably write the code for you. It's true there aren't as many server libraries out there yet, since WebTransport is still maturing. And we're waiting for Safari to add support.
Or, if you’re building for clients with a traditional “enterprise” and “secure” IT infrastructure: add refresh buttons and call it a day.
If there’s one thing in my experience that consistently fails in these environments and cannot be fixed due to endless red tape, it’s trying to make real-time work for these type of clients.
websockets and sse are a big headache to manage at scale, especially backend, requires special observability, if not implemented really carefully on mobile devices its a nightmare to debug on frontend side
devices switch off network or slow down etc,... for battery conservation, or when you don't explicitly do the I/O using a dedicated API for it.
new connection setup is a costly operation, the server has to store the state somewhere and when this stateful layer faces any issue, clients keep retrying and timing out. forever stuck on performing this costly operation. it's not like there is an easy way to control the throughput and slowly put the load on database
reliability wise long polling is the best one IME, if event based flow is really important, even then its better to have a 2 layer backend, where frontend does long polling on the 1st layer which then subscribes to websockets to the 2nd layer backend. much better control in terms of reliability
I cannot agree more with you. I have seen people shot themselves on foot with Websockets and SSE. Long Polling even though is expensive, is it most explainable and scalable approach in my opinion.
SSE supports long polling. You can make the server close the connection whenever you want. SSE supports automatic reconnection, and will even include the last ID seen to let the server continue seamlessly.
Not in the article by also relevant is short polling. While this does not send messages from a server to a client it can still be useful when all other options are not available (on shared hosting for example).
In my experience it even works great when the poll interval is long (for example 20 seconds) but when you also include the message list in each response. That way the client will be up to date when it interacts with the server: user presses a button -> the client sends a request to the server -> the server reponds with data and also a list of the latest messages.
To this day I still dont know why WebSockets and SSE dont support sending headers on the initial request, such as Authorization. Leaving authentication on realtime services entirely up to whoever is implementing the service.
I may be wrong here and the spec suggests a good way to do it, but i've seen so many different approaches that at this point might as well say there's none.
The EventSource API (the browser "client API" for Server-Sent Events) leaves a lot to be desired. While I am a maintainer of the most used EventSource polyfill[1], I've recently started a new project that aims to be a modern take on what an EventSource client could be: https://github.com/rexxars/eventsource-client.
Beyond handling the custom headers aspect, it also supports any request method (POST, PATCH..), allows you to include a request body, allows subscribing to any named event (the EventSource `onmessage` vs `on('named event')` is very confusing), as well as setting an initial last event ID (which can be helpful when restoring state after a reload or similar). And you can use it as an async iterator.
I love the simplicity of Server-Sent Events, but the `EventSource` API seem to me like a rushed implementation that just kinda stuck around.
Oooh boy you touched a pet peeve. I mean who needs authentication on the modern Web right? /s
The even more irritating thing is that there is nothing preventing this, and every server I've tried supports it. It's only the browser WebSocket API that was designed without this. Cookies are the only thing browsers will deign to send in the initial request.
This is probably naive, but it seems like assuming HTTP/2 or better, an EventSource combined with fetch() for sending messages should be just as good as any other protocol that uses a single TCP connection? And HTTP/3 uses UDP, so even better.
(This all assumes you only care about maintaining a connection when the tab is in the foreground.)
I’m wondering what problems people have run into when they tried this.
I always find articles like this amusing, because I designed an online auction system back in the late 90's. No XHR requests at all. Real-time updates were all handled with server-push/HTTP streaming. It wasn't easy to handle all the open connections at the time, but it could be done to an acceptable scale with the right architecture.
I kind of miss long polling. It was so stupidly simple compared to newer tech, and that's coming from someone who thinks WebRTC is the best thing since sliced bread.
SSE isn't really more complex than long polling. The only difference is the server don't close the connection immediately after sent the response. Instead, it wait for data again and send more response using the same stream.
The networking that makes Second Life go uses long polling HTTPS for an "event channel", over which the server can send event messages to the clients. Most messages go over UDP, but a few that need encryption or are large go over the HTTPS/TCP event channel.
At the client end, C++ clients use "libcurl". Its default timeout settings are not compatible with long polling. Libcurl will break connections and make another request. This can result in lost or duplicated messages.
At the server end, Apache front-ends the actual simulation servers, to filter out irrelevant connection attempts (Random HTTP attacks that try any open port, probably). Apache has its own timeouts, and will abort connections, forcing the client to retry.
There's a message serial number to try to prevent this mechanism from losing messages. The Second Life servers ignore the serial number the client sends back as a check. Some supposedly compatible servers from Open Simulator skip sequential numbers.
The end result is an HTTPS based system which can both lose and duplicate what were supposed to be reliable messages. Some of those messages, if lost, will stall out the user's activity in the game.
The people who designed this are long gone. The current staff was unaware of how bad the mess is. Outside users had to find the problem and document it. The company staff has been trying to fix this for months.
It seems to be difficult enough to fix that the current action is to defer work on the problem.
So, no, long polling is not "stupidly simple".
The right way to do this is probably to send a keep-alive message frequently enough that the TCP and HTTPS levels never time out. This keeps Apache and libcurl on their "happy paths", which work.
>I kind of miss long polling. It was so stupidly simple compared to newer tech, and that's coming from someone who thinks WebRTC is the best thing since sliced bread.
I still use it all the time. There are plenty of applications where the request overhead is reasonable in exchange for keeping everything within the context of an existing HTTP API.
Jsonrpc over websockets is underrated tech. Simple, easy to implement, maps to programming language constructs (async functions, events, errors) which means code looks natural as any library/package/module usage devs are used to, can be easily decorated with type safety, easy to debug, log, optimize etc, works on current browsers and can be used for internal service to service comms, it's fast (ie. nodejs is using simdjson [0]), can be compressed if needed (rarely the need), we built several things on top of it ie. async generators (avoids head of line blocking, maps naturally to typescript/js async generators).
It's been a while since I've used websockets, but at least the last time I did, "simple" wouldn't be the word I'd have used. All kinds of annoying issues between different browsers. SSE was generally much simpler.
(I work at Stream, we power activity feeds, chat and video calling/streaming for some very large apps)
You should in most cases just use websockets with a keep-alive ping every 30 seconds or so. It's not common anymore to block websockets on firewalls, so fallback solutions like Faye/Socket.io are typically not needed anymore.
WebTransport can have lower latency. If you're sending voice data (outside of regular webrtc), or have a realtime game its something to consider.
I'm making a WASM browser dungeon crawler game using WebTransport. It currently does not have great support -- namely Safari -- but because of other API incompatibilities I'm not planning on supporting Safari :P
WebTransport is a bit more work than other ones, like SSE, but the flexibility and performance make it work it IMO.
A decent number of corporate firewalls still don't support web sockets...
That means if you build something that requires web sockets, prepare to have a deluge of support/refund requests from the most valuable clients who think your site is broken.
I suggest just having a once-per-second polling fallback, perhaps with an info bar saying 'the network you are connected to is degrading your experience'.
Hello, I am author of https://github.com/centrifugal/centrifugo. Our users can choose from WebSocket, EventSource, WebTransport (experimental for now, but will definitely stabilize in the future). WebRTC is out of scope as the main purpose is central server based real-time json/binary messaging, and WebRTC makes things much more complex since it shines for peer-to-peer and rich media communications.
What I'd like to add is that Centrifugo also supports HTTP-streaming – not mentioned by the OP – but this is a transport which has advantages over Eventsource - like possibility to send POST body on initial request from web browser (with SSE you can not), it supports binary, and with Readable Streams browser API it's widely supported by modern browsers.
Another thing I'd like to mention about Centrifugo - it supports bidirectional WebSocket fallbacks with EventSource and HTTP-streaming, and does this without sticky sessions requirement in distributed scenario. I guess nobody else have this at this point. See https://centrifugal.dev/blog/2022/07/19/centrifugo-v4-releas.... Which solves one more practical concern. Sticky sessions is an optimization in Centrifugo case, not a requirement.
I've been reading about WebRTC, does anyone actually know if browser to browser communication actually works reliably in practice ? Specifically NAT traversal, been hesitant to research it further because of this issue, seems that most of the connection parts seem to be legacy voip related protocols.
Is there a modern open-source solution for bridging a traditional stateless web application to real-time notifications - one that's implemented all the best practices from the OP? Something like pusher.com but on self-hosted infrastructure/k8s, where messages from clients are turned into webhooks to an arbitrary server, and the server can HTTP POST to a public/private channel that clients can subscribe to if they know the channel secret.
Great comparison. Would love to see http response streaming added to the mix. I think a lot of use cases involving finite streams sent from server to client can be handled by the server streaming JSONL in the response. I tend to prefer this over SSE for finite data streams.
HTTP (POST) AJAX calls + SSE is one of the simplest ways to implement bi-directional real time functionality. IMHO this is a much more robust and nicer way than web sockets for a huge amount of applications which use web sockets today.
I did some testing with using SSE to send push notifications to my phone if someone set off a sensor, worked kinda good, but the browser had to be running in the background in order for it to work. After that i implemented a chat for a meme app that I've created to share memes with my friends, using websocket (Open Swoole) it is working nicely also. Never tested to see how many clients it can handle at once, but i guess the bottleneck would be in my server, not the software.
Open Swoole is very easy to setup and there's lots of tutorials online. Got my ass kicked a little bit trying to making my websocket secure (wss) but I'm the end it worked fine.
[+] [-] kellengreen|2 years ago|reply
[+] [-] shams93|2 years ago|reply
Websockets get really complex to scale past a certain level of use.
[+] [-] afavour|2 years ago|reply
[+] [-] ajvpot|2 years ago|reply
https://crbug.com/275955
[+] [-] paulddraper|2 years ago|reply
I wonder why they didn't just a multipart streamed response.
Supports my metadata, very commonly implemented format
[+] [-] djbusby|2 years ago|reply
[+] [-] fswd|2 years ago|reply
[+] [-] marban|2 years ago|reply
[+] [-] spintin|2 years ago|reply
But yes it's the superior (simplest, most robust, most performant and scalable) way to do real-time for eternity.
The browser is dead, but SSE will keep on doing work for native apps.
[+] [-] apitman|2 years ago|reply
WebSockets lack flow control (backpressure) and multiplexing, so if you need them you either roll your own or use something similar to RSocket.
Also SSE can't send binary data directly. You have to base64 encode it or similar.
WebTransport addresses these and also solves head of line blocking. But I'm concerned that we might run into a similar problem as we had with going from Python2 to Python3 and IPv6. Too easy for people to keep using the old version, and too little (perceived) benefit to upgrading.
As long as browsers still work with TCP, some networks will continue to block UDP (and thus HTTP3/WebTransport) outright.
[+] [-] pthatcherg|2 years ago|reply
I also think calling the WebTransport API complex is overblown. If you don't want the more advanced things, you can ignore them. If you want to use it like a WebSocket, just open one bidirectional stream and you're basically done. If you want to avoid head-of-line blocking, just open a stream for every message. It's a little more complex, but it's not the kind of thing you need a library for. Github Copilot will probably write the code for you. It's true there aren't as many server libraries out there yet, since WebTransport is still maturing. And we're waiting for Safari to add support.
[+] [-] cyanydeez|2 years ago|reply
Huh. The signalling server is implemented in websocket, typically.
It cant be implemented in webrtc unless you propose a existing decentralization of existing clients to boostrap'
[+] [-] rvanmil|2 years ago|reply
[+] [-] palmfacehn|2 years ago|reply
[+] [-] cbsmith|2 years ago|reply
[+] [-] chgs|2 years ago|reply
[+] [-] eightnoteight|2 years ago|reply
devices switch off network or slow down etc,... for battery conservation, or when you don't explicitly do the I/O using a dedicated API for it.
new connection setup is a costly operation, the server has to store the state somewhere and when this stateful layer faces any issue, clients keep retrying and timing out. forever stuck on performing this costly operation. it's not like there is an easy way to control the throughput and slowly put the load on database
reliability wise long polling is the best one IME, if event based flow is really important, even then its better to have a 2 layer backend, where frontend does long polling on the 1st layer which then subscribes to websockets to the 2nd layer backend. much better control in terms of reliability
[+] [-] debarshri|2 years ago|reply
[+] [-] pornel|2 years ago|reply
[+] [-] nchmy|2 years ago|reply
[+] [-] tdudhhu|2 years ago|reply
In my experience it even works great when the poll interval is long (for example 20 seconds) but when you also include the message list in each response. That way the client will be up to date when it interacts with the server: user presses a button -> the client sends a request to the server -> the server reponds with data and also a list of the latest messages.
[+] [-] Fabricio20|2 years ago|reply
I may be wrong here and the spec suggests a good way to do it, but i've seen so many different approaches that at this point might as well say there's none.
[+] [-] rexxars|2 years ago|reply
Beyond handling the custom headers aspect, it also supports any request method (POST, PATCH..), allows you to include a request body, allows subscribing to any named event (the EventSource `onmessage` vs `on('named event')` is very confusing), as well as setting an initial last event ID (which can be helpful when restoring state after a reload or similar). And you can use it as an async iterator.
I love the simplicity of Server-Sent Events, but the `EventSource` API seem to me like a rushed implementation that just kinda stuck around.
[1]: https://github.com/eventsource/eventsource
[+] [-] michaelt|2 years ago|reply
Doesn't the initial request get to send a full set of standard HTTP headers, cookies and all?
[+] [-] dist-epoch|2 years ago|reply
[+] [-] cbsmith|2 years ago|reply
[+] [-] omgtehlion|2 years ago|reply
[+] [-] paol|2 years ago|reply
The even more irritating thing is that there is nothing preventing this, and every server I've tried supports it. It's only the browser WebSocket API that was designed without this. Cookies are the only thing browsers will deign to send in the initial request.
[+] [-] skybrian|2 years ago|reply
(This all assumes you only care about maintaining a connection when the tab is in the foreground.)
I’m wondering what problems people have run into when they tried this.
[+] [-] cbsmith|2 years ago|reply
[+] [-] kevmo314|2 years ago|reply
[+] [-] mmis1000|2 years ago|reply
[+] [-] Animats|2 years ago|reply
The networking that makes Second Life go uses long polling HTTPS for an "event channel", over which the server can send event messages to the clients. Most messages go over UDP, but a few that need encryption or are large go over the HTTPS/TCP event channel.
At the client end, C++ clients use "libcurl". Its default timeout settings are not compatible with long polling. Libcurl will break connections and make another request. This can result in lost or duplicated messages.
At the server end, Apache front-ends the actual simulation servers, to filter out irrelevant connection attempts (Random HTTP attacks that try any open port, probably). Apache has its own timeouts, and will abort connections, forcing the client to retry.
There's a message serial number to try to prevent this mechanism from losing messages. The Second Life servers ignore the serial number the client sends back as a check. Some supposedly compatible servers from Open Simulator skip sequential numbers.
The end result is an HTTPS based system which can both lose and duplicate what were supposed to be reliable messages. Some of those messages, if lost, will stall out the user's activity in the game. The people who designed this are long gone. The current staff was unaware of how bad the mess is. Outside users had to find the problem and document it. The company staff has been trying to fix this for months. It seems to be difficult enough to fix that the current action is to defer work on the problem.
So, no, long polling is not "stupidly simple".
The right way to do this is probably to send a keep-alive message frequently enough that the TCP and HTTPS levels never time out. This keeps Apache and libcurl on their "happy paths", which work.
[+] [-] ramesh31|2 years ago|reply
I still use it all the time. There are plenty of applications where the request overhead is reasonable in exchange for keeping everything within the context of an existing HTTP API.
[+] [-] niutech|2 years ago|reply
[+] [-] mirekrusin|2 years ago|reply
[0] https://github.com/simdjson/simdjson
[+] [-] cbsmith|2 years ago|reply
[+] [-] tschellenbach|2 years ago|reply
You should in most cases just use websockets with a keep-alive ping every 30 seconds or so. It's not common anymore to block websockets on firewalls, so fallback solutions like Faye/Socket.io are typically not needed anymore.
WebTransport can have lower latency. If you're sending voice data (outside of regular webrtc), or have a realtime game its something to consider.
[+] [-] ambigious7777|2 years ago|reply
WebTransport is a bit more work than other ones, like SSE, but the flexibility and performance make it work it IMO.
[+] [-] londons_explore|2 years ago|reply
That means if you build something that requires web sockets, prepare to have a deluge of support/refund requests from the most valuable clients who think your site is broken.
I suggest just having a once-per-second polling fallback, perhaps with an info bar saying 'the network you are connected to is degrading your experience'.
[+] [-] FZambia|2 years ago|reply
What I'd like to add is that Centrifugo also supports HTTP-streaming – not mentioned by the OP – but this is a transport which has advantages over Eventsource - like possibility to send POST body on initial request from web browser (with SSE you can not), it supports binary, and with Readable Streams browser API it's widely supported by modern browsers.
Another thing I'd like to mention about Centrifugo - it supports bidirectional WebSocket fallbacks with EventSource and HTTP-streaming, and does this without sticky sessions requirement in distributed scenario. I guess nobody else have this at this point. See https://centrifugal.dev/blog/2022/07/19/centrifugo-v4-releas.... Which solves one more practical concern. Sticky sessions is an optimization in Centrifugo case, not a requirement.
If you are interested in topic, we also have a post about WebSocket scalability - https://centrifugal.dev/blog/2020/11/12/scaling-websocket - it covers some design decisions made in Centrifugo.
[+] [-] lxe|2 years ago|reply
[+] [-] ascii78|2 years ago|reply
[+] [-] Sean-Der|2 years ago|reply
You can also use it in a client/server setup. Check out 'WebRTC SFU'
I wrote a little bit about the different topologies in [0]
[0] https://webrtcforthecurious.com/docs/08-applied-webrtc/#webr...
[+] [-] btown|2 years ago|reply
I've come across https://github.com/soketi/soketi and https://centrifugal.dev/ but not sure if there are more battle-tested solutions.
[+] [-] bterlson|2 years ago|reply
[+] [-] mmis1000|2 years ago|reply
[+] [-] dustedcodes|2 years ago|reply
[+] [-] atum47|2 years ago|reply
Open Swoole is very easy to setup and there's lots of tutorials online. Got my ass kicked a little bit trying to making my websocket secure (wss) but I'm the end it worked fine.