top | item 30403438

Server-sent events, WebSockets, and HTTP

299 points| grappler | 4 years ago |mnot.net

94 comments

order
[+] eldelshell|4 years ago|reply
My experience with SSE so far (take this as recommendations if you wish)

You need to implement a server-side heartbeat feature.

You need to handle the close event from EventSource and be able to reconnect.

Tabs can be problematic. When you subscribe, you use a URL with a nominal ID to identify the client. For example, on a chat app, you would use /api/sse/userA/subscribe

Problem is, if userA starts opening tabs, each tab creates a new subscription for userA so you need to randomize each connection (userA-UUID).

If you don't use a nominal id, the server won't know to which subscriber to send the data and you don't want to broadcast all your chats.

I've used the Broadcast channel API in conjunction with SSE to have only one tab handle the SSE connection, and broadcast incoming SSEs to the other tabs which also reduces the number of connections to the server to one.

On the server it's also a PITA because not all instances/pods have the subscribers list. The way I've found to solve this is with clustering the instances with Hazelcast or Redis or a MQ.

But once you figure out all this, SSE works quite well.

[+] qwtel|4 years ago|reply
Sounds like a service worker, for which there's only one active at a time for all tabs (and can communicate with them) could help with your client side issues.
[+] samwillis|4 years ago|reply
This chimes a lot with my experience. Although I think SSEs are brilliant if the stack I’m using supports Websockets I would probably default to them even for a simple event stream now.

To add to your list of problems, I have had memory leaks with SSE responses stuck open on the server even when the client disconnects. Resorted to killing the response on the server every couple of minutes and relying on the client reconnecting.

[+] sbergjohansen|4 years ago|reply
Also, when using an nginx reverse proxy, including 'X-Accel-Buffering: no' in the HTTP header of the server response may be required to keep events from being buffered.
[+] remram|4 years ago|reply
I thought it reconnected automatically, and sent the last received event ID? Why do you need heartbeat?

Why don't you want to send the event to all the tabs? Why do you want to differentiate the tabs (by UUID)?

[+] taf2|4 years ago|reply
SharedWorker is a good solution for handling the multiple tab issue
[+] dpweb|4 years ago|reply
These are all potential issues with plain sse, but are pretty easily solved in a few lines of code.

Granted the benefits of sse: no additional port to open, no dependencies - just slip your logic into your routing at /events. Of course this may not be important for some. Websockets are a good choice as well. I tend to id clients on ip address not connections however, depends on the specific app.

[+] mmzeeman|4 years ago|reply
The best way to do pub/sub on the web with a standard protocol is MQTT (https://mqtt.org). It supports websockets, it scales, supports authentication, can handle unreliable networks.

We use it exclusively for the soon to be released 1.0 version of Zotonic. See: https://test.zotonic.com (running on a 4.99 euro Hetzner vps).

We developed an independent support javascript library called Cotonic (https://cotonic.org) to handle pub/sub via MQTT. This library can also connect to other compliant MQTT brokers. Because MQTT is fairly simple protocol, it is fairly easy to integrate in existing frameworks. Here is an example chat application (https://cotonic.org/examples/chat) which uses the open Eclipse broker.

[+] nickjj|4 years ago|reply
I don't know why but when I read this from the article:

> Because WebSockets is effectively a blank canvas, there are a lot of choices to be made when designing a protocol on top of it, and no one way of doing it has yet gained momentum.

It immediately reminded me of Flash.

Flash was basically a blank canvas where you can do just about anything. Turns out that's not a very good model for a number of reasons -- one of which being it turns everything you do into a snowflake and you need to invent your own patterns and abstractions instead of leaning on an extremely well thought out but more limited approach that's been vetted for many years before Flash was a thing.

I don't think WebSockets are really bad but it makes me internally wince whenever I see tech stacks trying to push using them for everything, even for transitioning between pages with no broadcast mechanism. Now I see part of the allure though. If you're a language or framework maker you get to make decisions at the protocol level instead of sticking to decades of standards which is the awesomeness of HTTP.

I'm all for sprinkling in tiny bits of WebSockets when it makes sense, ie. doing actual pub/sub things like showing notifications or showing messages like "4 new people commented on your post, click here to show them". In the same way that it felt ok to use a little bit of Flash back in the day for specific functionality instead of throwing out everything and trying to make your entire site in Flash.

[+] jcelerier|4 years ago|reply
> Flash was basically a blank canvas where you can do just about anything. Turns out that's not a very good model for a number of reasons -- one of which being it turns everything you do into a snowflake and you need to invent your own patterns and abstractions instead of leaning on an extremely well thought out but more limited approach that's been vetted for many years before Flash was a thing.

yet we're in 2022 and the only things that are remotely competitive with what Flash allowed 15 years ago, are some games put in WASM blobs (which will "work" until $BROWSER deprecates some API in two years).

[+] dpeck|4 years ago|reply
| Flash was basically a blank canvas where you can do just about anything. Turns out that's not a very good model for a number of reasons

I don’t think of this as a bad thing. There was exactly a lot of what you say happening around that time, but the time when Flash was biggest was also a time of massive creativeness on the web. I think we’re missing some of that now, and I’m more than ok with our collective “pendulum” swinging back towards the blank canvas than the overfit tech world we have today.

[+] PragmaticPulp|4 years ago|reply
WebSockets are actually fantastic for providing low overhead and high flexibility. It’s really not difficult to use any number of modern frameworks to do things like encode and decode from wire formats or even multiplex across the channel.

WebSockets shouldn’t be approached as a “from scratch” communication method unless you have very specific needs or your application is dead simple. Everyone else should take advantage of the numberous libraries available to help with the basics.

> I don't think WebSockets are really bad but it makes me internally wince whenever I see tech stacks trying to push using them for everything, even for transitioning between pages with no broadcast mechanism.

I’m not up to date with the less popular web frameworks. Which frameworks do that?

[+] josephg|4 years ago|reply
In case people don't know, Mark Nottingham (the author) is the chair of the HTTP working group at the IETF. He isn't just some guy with opinions on the internet. (Sorry mnot!)

I've never found pub/sub quite the right abstraction, because almost every implementation I've seen has race conditions or issues on reconnect. Usually its possible to lose messages during reconnection, and there's often other issues too. I usually want an event queue abstraction, not pub/sub.

I met mnot a few years ago when we (the braid group) took a stab at writing a spec for HTTP based streaming updates[1]. Our proposal is to do state syncronization around a shared objects (the URL). Each event on the channel is (explicitly or implicitly) an update for some resource. So:

- The document (resource) has a version header (ETag?).

- Each event sent on the stream is a patch which changes the version from X to Y. Patches can either be incremental ("insert A at position 5") or if its small, just contain a new copy of the document.

- You can reconnect at any time, and specify a known version of the document. The server can bring you up to date by sending the patches you missed. If the server doesn't store historical changes, it should be able to just send you a fresh copy of the document. After that, the subscription should drip feed events as they come in.

One nice thing about this is that CDNs can do highly efficient fan-out of changes. A cache could also use braid to subscribe to resources which change a lot on the origin server, in order to keep the cached values hot.

[1] https://datatracker.ietf.org/doc/html/draft-toomim-httpbis-b...

We've done some revisions since then but have been working on getting more running code before pushing forward more with the approach. Most recent draft & issues: https://github.com/braid-org/braid-spec/blob/master/draft-to...

[+] mathgladiator|4 years ago|reply
pub/sub is a horrible abstraction. I've written about it here: http://www.adama-lang.org/blog/pubsub-sucks

My experience is building an exceptionally large pub/sub service, and pub/sub starts nice until you really care about reliability. I made the mistake of patenting a protocol to sit on top of WebSocket/SSE/MQTT as the ecosystem was a giant mess: https://uspto.report/patent/grant/11,228,626

What I learned was that by having E2E anti-entropy protocol is that it's hard for pub/sub abstractions to not lose stuff or lie. That protocol emerged precisely because years of investment in pub/sub was a leaky bucket of issues.

Braid looks interesting. I'm using JSON marge as my algebraic operator since I have an ordered stream of updates for my system. http://www.adama-lang.org/blog/json-on-the-brain

[+] thibauts|4 years ago|reply
Here is an event stream abstraction that has very strong semantics (exactly once in most cases) with simple usage examples, native HTTP and websockets APIs, strong atomicity and durability guarantees [1].

What it doesn’t have is clustering and a father that’s != null at marketing =]

[1] https://github.com/thibauts/styx

[+] mmzeeman|4 years ago|reply
Very interesting. I've implemented something similar. It evolved out of the co-browsing solution I developed for the company I work for.

The solution uses mqtt. Clients subscribe to a topic on the server, and the server publishes patches to update the view. Patches can be incremental (patch against the last frame), cumulative (patch agains the last keyframe) or a new keyframe. It allows for server side rendered views. Multiple clients can subscribe to the same view and keep in sync. See: https://github.com/mmzeeman/zotonic_mod_teleview

[+] samwillis|4 years ago|reply
Did you consider using CRDTs for your document sync? It sounds like what you were implementing was somewhere between OTs and CRDTs and trying to create a standard event protocol for them?

CRDTs remove the need keeping track of all (or any just recent) revisions on the server as you would with OTs, your server can be stateless and just act as a message broker between clients.

Edit:

Should have followed your links, yes that’s exactly what you are doing.

https://braid.org/

[+] jamil7|4 years ago|reply
Thanks a lot for sharing this, it’s very relevant to my work at the moment.
[+] the_gipsy|4 years ago|reply
Can Web Push Notifications not also be considered as an alternative?

They are quite different from SSE and WebSockets in some aspects. Using Web Workers for some core feature shouldn't be considered lightly. But the end result is pub/sub, so it's worth to check out.

https://developer.mozilla.org/en-US/docs/Web/API/Push_API/Be...

[+] karl42|4 years ago|reply
Technically it would fit nicely. Unfortunately, it requires the user to explicitly give permissions to show notifications to the website, even if you don't show any notifications. This is a blocker for its usage in many cases.
[+] olesku|4 years ago|reply
I've been working on a pub/sub server that supports both Websockets and SSE as a hobby project for a couple of years. It have been successfully implemented and is used in production on some high traffic sites with 300k+ simultaneous connections. If someone are interested the projects webpage can be found here: https://github.com/olesku/eventhub
[+] tjpnz|4 years ago|reply
When would you use WebSockets versus SSE? The introductory example most tutorials on WS give is for chat, yet it looks like you could implement the same functionality using a combination of REST and SSE. That would allow you to stay in HTTP land which seems desirable based on all of the issues I've read about people having here.
[+] samwillis|4 years ago|reply
SSE are single direction (server to client), you could use a http get/post for message from the client but then if you have multiple servers it could land of another server. Websockes allow you to have the server end processed in one place.

The big advantage of SSE when doing a simple event stream from the server is that they can be implemented in stacks that don’t support Websockes, such as many of the older Python frameworks (i.e. Django) built on top is WSGI. To use Websockes with these frameworks they either need upgrading to ASGI (or equivalent) or you have to run an additional server process for Websockes.

I do a lot of Django, SSE are super easy with it, just a StreamingResponce implementing the SSE protocol. I have then tended to use Gevent with Gunicorn to handle the long streaming responses rather than the default threaded workers.

There are few caveats around implementing a heartbeat event and ensuring they are closed properly when the client disconnects. This can be super annoying, I tent to kill the connection every couple of minutes as you don’t always know when the client has disconnected. Also buffering in reverse proxy’s need to be worked around.

To be honest though as Django gains better support for Websockes I would probably use them over SSE even for a single direction event stream, less edge cases to think about.

[+] yencabulator|4 years ago|reply
One thing that comes to bite you surprisingly quickly is that with SSE+POST, you lose ordering in the C->S direction. Consider a chat client that POSTs every line you type; two POSTs might get reordered in-flight while two WebSocket messages won't.
[+] austincheney|4 years ago|reply
Websockets are bidirectional, which means a slimmer alternative to XHR from the browser. Think of websockets as a TCP pipe without additional overhead. The limitation of websockets is that it is a single socket and data cannot be interlaced, which means it must be queued per direction.
[+] tomberek|4 years ago|reply
SSE has been extremely useful for my purposes. And the issues with scaling it are similar in nature to scaling and getting a similar feature set out of any solution. But in terms of speed to PoC/MVP nothing has been as easy as SSE. The final reasoning is that it can easily be inspected, debugged, curl'd, and so forth.

Not sure how often this is done, but I've been sending query parameters along with the request and using it to stream results back to the client for low-latency initial responses to long-running API calls.

[+] jkarneges|4 years ago|reply
> The question, then, is how we enable intermediation for pub/sub

My company (Fanout) is attempting to solve this via the Generic Realtime Intermediary Protocol: https://pushpin.org/docs/protocols/grip/

The idea is to enable origin servers to delegate connection management to a proxy tier, without introducing a new client-facing protocol.

[+] stavros|4 years ago|reply
I'm just now planning to make a web app with a Django backend that will need simple notifications on the frontend, and I'd like to keep the frontend as light as possible (no React, for example).

What would you recommend? Websockets? MQTT?

[+] musingsole|4 years ago|reply
WebSockets

Frameworks like JustPy have used it to couple the frontend to the backend and have a Python server doing all the logic work (i.e. frontend just passes everything over WebSockets to the server and asked what to do). It's an awesome demonstration of what WebSockets can do.

MQTT has no place on the web. It barely has a place in IoT and should lose all rights to that claim yesterday.

[+] politician|4 years ago|reply
Mark mentioned the idea of using edge compute to process protocol traffic. Do any CDNs support edge compute with WebSockets?
[+] jokoon|4 years ago|reply
It's easy to point out that it's not trivial to make async stuff with web technologies.

To be completely honest, when you look at the big picture, HTML was designed to be a static document format.

I really wish some new format or standard would be built from the ground up. HTML JS was never a good format, it just got popular to fight microsoft.

[+] rektide|4 years ago|reply
No mention of what browsers do for notifications, Web Push Protocl[1]. Which makes sense only in the context of the one of the ancient grand-daddy issues of Fetch being completely ignored by the powers that be[2], & the browser & the browser alone (not the page) having the capability to hear & observe HTTP2+ PUSH requests coming in. This github issue to let a fetch request hear PUSH responses come back at it has been mostly ignored, for 3/4 a decade.

Meanwhile Chrome is saying people don't use Push. Yeah, well, because ye jerknuts have diluted & made unusable the best part of it: the ability to be responsive to resources coming at us. What a sad truckload of tragedy this undelivered capability has been. To invent a new HTTP, HTTP2, with new capabilities, then spend most of a decade ignoring & denying developers access to the best parts of what you just invented. Irony & tragedy. Now they're taking away PUSH[3], having never made it usable at all. Classic frigging Google, what frigging fickle monsters, incapable of even the most basic follow-through on what they start.

[1] https://datatracker.ietf.org/doc/html/rfc8030

[2] https://github.com/whatwg/fetch/issues/51

[3] https://groups.google.com/a/chromium.org/g/blink-dev/c/K3rYL...

[+] aww_dang|4 years ago|reply
Jetty/Cometd abstracts away the problems mentioned in the comments here. I enjoyed it for personal projects without the enterprisey overhead. It does integrate with that stuff if you're into that.

Just my personal preference. Not claiming anything beyond that.

[+] dpweb|4 years ago|reply
Been on SSE since about 10 years ago, and seems a nice time to modernize and move to something that supports peer to peer (browser) and not just client server. Anyone using anything like that now?
[+] dSebastien|4 years ago|reply
I'd love to see support for pub/sub as part of HTTP.
[+] cryptica|4 years ago|reply
I don't understand this mindset. HTTP was a protocol designed for transferring text files. It literally means 'HyperText Transfer Protocol'. Its use case has been getting broader and broader over time. Why does everyone want it to be a silver bullet? There are no silver bullets. Jack of all trades, master of none. WebSockets is great because it's a separate protocol and can be dealt independently by browser and server vendors as security and functional requirement change.

HTTP was never designed for pub/sub... How does the additional layer of complexity provided by HTTP for file transfers (e.g. request headers with each request, response headers, cookies sent in each request, mime types, etc...) benefit us for the pub/sub use case? It just adds overhead and makes it harder for vendors to fully implement HTTP. What used to be a simple protocol is becoming prohibitively complicated.

[+] ShoveItHN|4 years ago|reply
Interesting. I'm writing an application that will control devices over HTTP with a REST-style API (defined in OpenAPI). But we also want those devices to be able to alert the controlling application to events without being polled. This won't be a large datastream, but rather the occasional update of progress or notification of a state change.

I was considering Websockets or MQTT (or both), so server-sent events sound like a direct and possibly superior competitor. But from a design standpoint, what do you do? Just do a GET on some general "status" endpoint to open this SSE stream and then have the server send everything down this pipe indefinitely?

[+] samwillis|4 years ago|reply
Yes, it’s just a GET that stays open, super simple. There are various client libs that support SSE outside of browsers, worth having a look if there is one for the language you are using.