HTTP Feeds
http-feeds.org
http-feeds.org
https://developer.mozilla.org/en-US/docs/Web/API/Server-sent... https://html.spec.whatwg.org/multipage/server-sent-events.ht...
To your point, https://caniuse.com/eventsource
Also there are polyfills for very old browsers like IE.
Starting with out of the box swagger support: https://github.com/OAI/OpenAPI-Specification/issues/396
Shame they are not interested in adding it.
Sadly that makes it a bit of a pain in the ass to manage for sites served as (/ compatible with) http 1.1.
The feedback is great, and valid. I wish it could have been expressed less dismissively though.
I was curious enough to browse through the comments of @lexicality to conclude that he is European, so there is a chance that you were considered capable of receiving a direct feedback.
That is, by giving direct feedback, @lexicality showed you respect.
Also, I live in northern Europe. Generally feedback is direct, but respectful. Calling something silly is usually (but not always) out of bounds. European-style feedback might be: "I'm surprised you did not use X because it perfectly matches your use case."
Or "Great feedback but I wish it could have been expressed less dismissively"
Recently at my place of work we were required to take some diversity training. In this training we learned about high context and low context regional and familial cultures. High context individuals convey and respond to indirect details and nuance, while low context cultures are more direct with what they receive and convey.
Learning this helped me a lot and I notice these differences often, as I too come from a different cultural background than my team mates.
A) People have vastly different (even abusive) forms of communications. What works for one group does not work for another. "Sensitivity training" does not account for this, in fact it causes issues.
B) EQ and CQ are made up terms, but more importantly they aren't realistic. IQ, also is somewhat made up, its used as a sort of "general intelligence" marker and it is that in the context of broad studies, not so much on the individual level. IQ has been long used to browbeat certain ethnic groups, these new "made up" terms are similar, in that people are using them to browbeat various groups, in attempt to put themselves on a higher "moral ground". Its disgusting, and insensitive nonsense. They don't exist, and we don't have the bandwidth to all just "conform". And no, thanks I don't take kindly to being brainwashed.
This also ties in indirectly to punctuality of cultures I think. Lower context cultures tend to be punctual.
And I've felt the Eastern cultures are becoming lower context due to doing business with the West. This may or may not be a good thing.
PS: I'm from a high context culture, but I've lived in America for a while before returning back to my home country. Ever since, I've felt like something was off.
Why take offense when none may have been intended?
Moreover I don't see that "being European" has anything to do with anything, and classifying someone as either being "capable of" or not "capable of" receiving direct feedback, far from respectful, is an instance of insulting with plausible deniability, which is perhaps the most practiced art form of internet comment sections.
Perhaps your view of communication with Europeans is influenced by other factors that's led you to this conclusion? (or to put it respectfully, silly)
First, it's addressing the idea rather than the person.
Secondly silly isn't used to convey an idea being bad, stupid or otherwise without merit.
The way I read this is that "I think this should address why it's not built on SSE or perhaps should be redefined to be built on SSE", silly just does that with much less words.
But somehow others read it as like you said "being bad, stupid or otherwise without merit."
How much of the supply of great, honest, and valid feedback would you sacrifice in order to make the supply that remains use "less dismissive"[0] language like "a bit silly"?
[0] Less dismissive in quotes because I do not find that language dismissive in the slightest
Let's assume that "silly" was meant kindly and not dismissively!
> Warning: When not used over HTTP/2, SSE suffers from a limitation to the maximum number of open connections, which can be especially painful when opening multiple tabs, as the limit is per browser and is 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, which 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 (per Stackoverflow). When using HTTP/2, the maximum number of simultaneous HTTP streams is negotiated between the server and the client (defaults to 100).
This is the first time I’ve heard of per-domain connection limits. Seems… not great? Doesn’t this turn into a client side DoS? User opens 6+1 tabs and now the browser has exhausted all HTTP connections to your domain serving SSE connections?
I’ve used long polling before, I don’t understand how I’ve never observed the connection limit of 6…
telnet news.ycombinator.com 80
GET / HTTP/1.1
Host: news.ycombinator.com
\n
\n
Adding a header could be as simple as: HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Multiple-Subscriptions: OKThis is especially a problem for servers implemented with one thread or process per connection, rather than an async event queue like nodejs, because each thread takes up a few megabytes of memory, and if a browser opens 1000 connections, that's 2 gigs of memory right there.
But if a server is implemented properly to support long-lived connections (by using node, or greenlets, or other async system) then it should be able to opt out of that browser protection. A standardized header would allow a server to opt out of it.
Practically speaking this limitation has been obsolete for 5 years or so. With HTTP/1.1 there used to be ways to achieve more parallel downloads through domain sharding [1] on the server side and browser tweaks [2] on the client side.
[1]: https://developer.mozilla.org/en-US/docs/Glossary/Domain_sha...
[2]: http://kb.mozillazine.org/Network.http.max-persistent-connec...
[1]: https://developer.mozilla.org/en-US/docs/Glossary/Domain_sha...
For this spec I aimed to keep it as simple as possible. And plain polling-based JSON Endpoints are the most simple and robust endpoints IMHO.
If you need, you could implement an SSE representation on the server endpoint by prober content negotiation.
The main reason, why I dropped SSE it the lack of proper back pressure, i.e. what happens when a consumes slower than the server produces messages. Plus, it is quite hard to debug SSE connections, e. g. no support by Postman and other dev tools. And long lasting HTTP connections are still a problem in todays infrastructure. E. g. there is currently no support for SSE endpoints in Digital Ocean App Platform, and I am not sure about them in Google Cloud Run.
Overall, plain GET endpoints felt much simpler.
I think totally right that back-pressure and plain GETs are an important use-case to support, and am really happy to see a beautiful spec written up to articulate concretely how to support them.
It is also great to be able to switch amongst these methods of subscription, for instance, if your server can keep a persistent connection open, it's nice to be able to get realtime updates over a single channel, but to still be able to fall back to polling or long-polling if you can't. And if you switch between a polling and a subscription, it's nice if you don't have to change the entire protocol — but can just change the subscription method.
There's a growing effort to integrate all our various subscription-over-http protocols into an IETF HTTP standard, that we can use for all these use-cases: https://lists.w3.org/Archives/Public/ietf-http-wg/2022JanMar...
Maybe you'd be interested in incorporating your experience, use-cases, and design decisions into that effort? We have been talking about starting at this November's IETF. [1]
For instance, you can do polling over the Braid protocol [2] with a sequence of GETs, where you specify the version you are coming from in the Parents: header:
GET /foo
Parents: "1"
GET /foo
Parents: "2"
GET /foo
Parents: "3"
Each response would include the next version.And you can get back-pressure over Braid by disconnecting a subscription when your client gets overwhelmed, and then reconnecting again later on with a new Parents: header:
GET /foo
Subscribe: true
Parents: "3"
..25 of updates flow back to the client..
**client disconnects**
Now the client can reconnect whenever it's ready for new data: GET /foo
Subscribe: true
Parents: "28"
Or if it wants to re-fetch an old version, it can simply ask for it via the version ID it got: GET /foo
Version: "14"
And if the source for these updates is a git repository, we could use a SHA hash for the version instead of an integer: GET /foo
Version: "eac8eb8cb2f21c5e79c305c738aa8a8171391b36"
Parents: "8f8bfc8ea356d929135d5c3f8cb891031d1539bd"
There's a magical universality to the basic concepts at play here![1] https://www.ietf.org/how/meetings/115/
[2] https://datatracker.ietf.org/doc/html/draft-toomim-httpbis-b...
> The main reason, why I dropped SSE it the lack of proper back pressure, i.e. what happens when a consumes slower than the server produces messages.
Could you point me to where the spec handles this please? As far as I can tell it has the same problem of the server needing to buffer events until the client next connects
Some differences between our approaches:
- I think its a good idea to support arbitrary patch formats via a content-type style field, just like we have different formats for images. This lets you use the same protocol for things like collaborative editors.
- For some data sets (like CRDTs), you want each change to be able to refer to multiple "parents".
- Your protocol is very JSON-y and not very HTTP-y. It looks like you're basically using JSON to express HTTP. Why not just use HTTP? One big downside of the JSON approach is that it makes it awkward to transmit binary patches. (Eg, 'patching' an image)
Feel free to reach out if you're up for a chat! Looks like we're working on the same problem.
[1] https://github.com/braid-org/braid-spec/blob/master/draft-to...
Having worked in this problem space a bit recently, I find this part a bit too optimistic:
> The event.id is used as lastEventId to scroll through further events. This means that events need to be strongly ordered to retrieve subsequent events.
The example relies on time-ordered UUIDv6 and mentions time sync as a gotcha. This should work well if you only have a single writer.
Even with perfectly synced clocks, anything that lets you do _concurrent_ writes can still commit out of order, though.
Consider two transactions in a single-node-and-trivially-clock-synced Postgres, for example. If the first transaction that gets the lower timestamp commits after a second transaction that gets a higher timestamp, the second and higher timestamp might've been retrieved by a consumer already (it committed, so it's visible after all), and now you've missed writes. This is also (at least for Postgres, but I guess also in general) true for sequences.
The approach I'm currently pursuing involves having an opaque cursor that encodes enough of the MVCC information (i.e. Postgres' txid_current and xip_list) to be able to catch those situations. For a client, the cursor is opaque and they can't see the internals. For the server side, it's quite implementation specific, however. It still has the nice property that clients keep track on where they are, without the server keeping track of where the clients are, which is desirable if the downstream client can roll back e.g. due to recovery/restore from backup)
A base64-encoded (possibly encrypted) cursor can wrap whatever implementation specifics are needed and hide them from the client. That implementation could of course be a simple event id if the writing side is strictly serial.
Though to address the GP a bit, the problem of concurrent writers without a sequencer (like a database) is less common than you might think. It definitely still comes up and there are things like CRDTs to help you address these cases (which do generally rely on either logical clocks or hybrid logical clocks). However most cases of event streaming to the browser you have each write round-tripping through the DB anyway and you use a feed like this to push a CDC stream or similar down into the browser to get "instant" feedback of a change that occurred after initial load.
My point was that even if you have a single non-scaleout database with a single time source, a sequence or a timestamp or a combination of both isn't as reliable a sequencer as you might think, unless you have at most one writer.
Thus, I think a "standard" should encourage a cursor concept that can use something that may reliably provide _all_ changes. If you have a single writer, you have a pretty easy job implementing that, as a plain sequence would work. (A timestamp could still break on clock adjustments, though)
This pertains to the "data replication" part of the listed goals, where getting everything is more important than in e.g. a social media news feed style thing where chronological order may be tenable - or less consequential if an item is missed.
I think the Braid standard might support what you want: https://datatracker.ietf.org/doc/html/draft-toomim-httpbis-b...
We don't call it a "cursor", but the idea is to encode whatever clock or versioning information you want as an arbitrary string, with the only general requirement that the string is unique to that version.
You can use this with Lamport clocks, Vector Clocks, Version Vectors, Wall Clocks, (Agentid+Sequence) pairs, SHA hashes, or even blockchain transactions.
* What are some scenarios where you would need a feed?
* What's being done now to solve the problem and how is this different?
Many thanks
So whatever events you want to expose publicly can be done over plain HTTP.
Having said that, IMO using SSE or websockets would be a better fit than raw HTTP.
Likewise, the stores might need to know what is in the warehouses, or even across town; someone walks in store to order something, but its not in that store. But they know, in real time, that its in the warehouse for delivery next day or that the store across the city has it.
What is done now is a more centralised approach; all sites would have a connection to a single DB in head office that stores everything. This makes things more distributed and, in theory, removes a single point of failure.
I should clarify, I am not working on any of this, this is just how I think it would work... if anyone wants to step in and tell me if I'm wrong, right, or just plan stupid, please shout.
[1] https://www.w3.org/2017/08/realtime-paged-data-exchange/
However what I used there was simple http stream/json stream like this:
- No start of [] but JSON newline entries a new line is an new entry
- Using Anything as an id (we've been using redis XSTREAMS as lightweight kafka concepts, just 64bit integers)
- have an type as an event, and versioning is just done by upgrading the type, ugly, but easy.
- We'er considering using SSE at this moment
Compaction is not something that I would do in the protocol I think I would just expose another version of it on a different url I think or put it in a different spec.
Yes, the network topology and protocol are certainly less complex, but there are now additional strong requirements how an endpoint has to store and manage existing events. (See the bits about strict time ordering, compaction, aggregation, etc).
A lot of this is effectively what a message broker is doing in a "traditional" system to guarantee consistency. Those tasks aren't gone, they are just pushed to the endpoints now.
For those not aware of what message brokers are, there are many to choose from such as: Mosquito, RabbitMQ, ActiveMQ, Solace... If delivery over HTTP is a requirement, many of these brokers support delivery over websockets or (in the case of Solace) also support long polling.
- Security: In B2B scenarios or public APIs would you open your broker to the WWW? HTTP has a solid infrastructure, including firewalls, ddos defence, API gateways, certificate management, ... - Organisational dependencies: Some team needs to maintain the broker (team 1, team 2, or a third platform team). You have a dependency to this team, if you need a new topic, user, ... Who is on call when something goes wrong? - Technology ingestion: A message broker ingests technology into the system. You need compatible client libraries, handle version upgrades, resilience concepts, learn troubleshooting...
That's a valid point. I think it's a pity we don't have an equivalent standard for asynchronous messaging with the same support as HTTP. However, there are lots of options for presenting an asynchronous public API that would use your message broker behind the scenes, without fully exposing it: Websockets, SSE, web hooks, etc...
> Organisational dependencies: Some team needs to maintain the broker (team 1, team 2, or a third platform team). You have a dependency to this team, if you need a new topic, user,
True, but don't you have that anyway? How is this different from requesting a new database, service route, service definition, etc?
> Technology ingestion: A message broker ingests technology into the system. You need compatible client libraries, handle version upgrades, resilience concepts, learn troubleshooting...
How simple or complex this is depends on the concrete broker at hand. There are some protocols, e.g. STOMP that are simple enough that you could write your own client. And as I wrote in the parent: HTTP feeds are a technology as well. You'll have to think about troubleshooting and resiliency there as well.
> Platform independent and easy to understand.
The platform independence is implicit in the "HTTP" part of the name.They need to clearly document what is the behaviour for this case.
I wasn't aware of this but it fits the use case perfectly, I will update the post.
I've also seen this described as hints: https://blog.andyet.com/2014/09/23/send-hints-not-data/
This is much easier to implement, both in server and client side, and it greatly removed the amount of data transferred. With fixed pages you would return content of the latest page for every poll request until it is "full".