I would advise anyone finding inspiration in this post against using a similar solution in the real world. While this may work in a local only environment, once you encounter the real world consisting of Cloudflare, DPI/MITM proxies, load balancers, zscaler, etc... it would most likely fail to work in this manner.
Standards are at least somewhat supported among the former, often not even correctly. Any solution you choose I'd suggest implementing a fall-back to the most basic of http methods (standard get/post and polling for instance). You'll find all types of problems once you start encountering middleware that just absolutely thrashes your expectations of a well functioning internet.
HTTP Streams (and Server Sent Events - which is a predefined body format that is understood among web browsers) are supported just fine by most infrastructure - probably even better than websockets.
The advantage of them is that "they are just HTTP requests", so as long as your proxy actually can forward request and response bodies in a streaming fashion it will work. There's no need to understand the contents. It will not work if the proxy is implemented by waiting for the whole HTTP response to complete before forwarding the body. But that wouldn't work very well for a whole lot of other use-cases like file transfers, where buffering the whole body is not practical.
A caveat is that most proxies won't allow for indefinite body body streaming, since they want to avoid all the TCP connections being tied up, so it's likely that the streaming would be interrupted at some time. In that case a reconnect logic would be necessary. But same applies to websockets.
He is not using SSE, he is writing to the stream of an open/incomplete http response. The caveat you name is exactly why I said this solution is one I'd avoid. You're also not taking into account several other pieces of middleware that expect a whole response before forwarding it on.
Anything attempting to proxy public Internet traffic and buffer the entire body at all times is in big trouble.
Either it has to have a very low body buffer limit and reject anything more (making it impossible to download any files through the proxy, or do anything similar) or it's trivially vulnerable to DoS where any client or server involved can crash the whole caboodle.
These are often devices or services used in the enterprise that do a lot of things that would surprise you, and just break applications willy nilly. We often have to request that exceptions be added for specific endpoints that use some of the aforementioned methods and are why I would shy away from them.
It's always fun to Response.Flush() in your server side application only to find the client receives nothing. They typically have limits to the amount they buffer, yes, usually pretty low. However, when all your trying to send is something as simple as "<script>Report.Progress(30)</script>" "<script>Report.Progress(35)</script>" that's part of some legacy code, you often see a sudden completion or jumps in the completion that are not representative of what's happened on the server side.
The best ones are the ones that try to handle mobile connections by holding open connections in weird ways, I'm talking about you NetMotion....
> In that case a reconnect logic would be necessary. But same applies to websockets.
Arguably you’re even better off in the SSE case, because it specifies a reconnect mechanism that allows clients to reconnect without receiving duplicate data. If you’re working with a client library that understands this (which includes any to-spec browser-based client), you just need to handle the reconnect header on the server and you get reconnects for “free”.
When using server sent events, the only issue I worry about is short tcp timeouts. If either of end terminates the tcp connection, you have to restart the event stream. If the timeout is low, this will basically be inefficient polling.
But with that said something that forcefully terminates your tcp connection with a really short timeout is making the internet generally unusable, so it’s something very unlikely to happen.
Interesting. Can you expand a bit on the kinds of problems that websockets would encounter with such middleware? I've seen a fair share of surprises on just TCP from DPI/MitM and high bandwidth-delay products, so I'm curious about ws analogues.
This article was not about using web sockets, it was about using a standard HTTP stream in a somewhat unorthodox way by never completing the request. Instead just beginning the response and occasionally "flush"ing data in the buffer to the client. The problem is that you'll often run into middleware that will do one of the following.
I've personally encountered WAF proxies that will wait for the request to complete before sending any data to the client, that means that the client would appear to receive no data until the request timed out or the server completed the response.
I've also encountered MITM/DPI and load-balancing proxies that will do the same, waiting until a buffer is filled and/or the response is completed.
> Internet and ChatGPT gave it a name: Server Sent Events
Interesting callout: websockets can be "two way" (server -> client, client -> server). SSE, you just get one way (server -> client) and you have to do your typical POST to achieve the other way (client -> server)
On a side note, the Remarkable Tablet is such a cool and hackable device. It surprises me that the company doesn't open source more of their client software to get help from the community, which I bet would do a large chunk of the work for them for free as it's mutually beneficial. Particularly as the market they are in is highly competitive, that could be a great way to get a great featureset, lower costs, while also providing something of immense value to the open source community. They could even offer a third-party app store of some sort and become the de-facto standard platform for e-ink devices! The rest are so locked down that this would be a huge competitive advantage.
This approach was what we had to use back in the late 90s. We called it "iframe streaming", or "forever iframe" (and years later, as an industry term emerged, "comet"). It worked surprisingly well, except in cases where a client sat behind a greedily buffering proxy. We would send JavaScript statements that invoked callbacks on the client, rather than just JSON, as this avoided the need to parse data to determine which business logic to use on the client. This has the limitation of being "non-cross domain" (i.e. the web page containing the callback functions have to be served from the same domain as the infinite document).
To get around buffering proxies, we optionally allowed our clients to use a JSONP long polling approach instead, whereby the client would dynamically generate a form inside a hidden iframe, and POST a request for JSONP data (JSON delivered wrapped in a callback), and the server would return data as soon as any was available. The client would immediately repeat the process to request more data, an infinitum.
Eventually, the emergence of the XMLHTTPRequest object in IE (and subsequently in other browsers) allowed us to implement cleaner long-poll-style methods, holding the connection open until data was available (and automatically reconnecting on error). This was later enhanced with CORS for delivery of data from arbitrary domains. As support for detecting updates to an in-progress response became available (via XMLHTTPRequest's "progress" event, which for a long time was horribly buggy in IE ) our payloads became infinite streams too (of JavaScript callback invocations). Early versions of approach also required us to reload the entire page from time to time, as IE's underlying implementations of these browser objects appeared to have memory leaks (that we did not see in Firefox, for example).
When IE8 was released, we allowed clients to optionally use its XDomainRequest object to stream a response instead.
Years later, the much cleaner Server Sent Events (SSE) and WebSocket options became possible. Intermediate proxy support was initially troublesome however, and while both of these were our preferred choices from a performance and API perspective, it took several years before we could consider removing support for our earlier approaches. Even today, there are network environments where an infinite sequence of long polls is the only reliable option...
My preference today? The JavaScript fetch API for sending commands, with a simple ack as a response, and an async flow of events over a persistent SSE connection, that feed into a simple JS message bus (implemented using the browser's native event API) for delivery to vanilla JavaScript web components. Simple, clean, and consistent.
Standards are at least somewhat supported among the former, often not even correctly. Any solution you choose I'd suggest implementing a fall-back to the most basic of http methods (standard get/post and polling for instance). You'll find all types of problems once you start encountering middleware that just absolutely thrashes your expectations of a well functioning internet.