During the 1990s, the web grew into the dominant way people exchange information, and browsers kept adding new features as more and more people got comfortable browsing. By the 2000s, the first attempts at realtime web apps started showing up, apps trying to feel responsive and interactive instead of the usual static webpage. But realtime was hard to pull off then, and slower than anything you would have recognize today, because it was being built by hacking HTTP-based technologies that were never designed for that kind of work in the first place.
The Origins of HTTP
To understand why hacking HTTP-based technologies was even necessary, it helps to go back to where HTTP came from, In 1989, Tim Berners-Lee was working at CERN and grew frustrated with how hard it was to pull information off computers running different software from each other. Out of that frustration came a project called WorldWideWeb: a web of hypertext documents that browsers could view over the internet using a client-server setup. That idea spread fast, and the first websites for everyday use appeared around 1993 to 1994.
To build the web, Berners-Lee combined hypertext with the internet and produced three foundational pieces:
- HTML: a markup language used to structure and present content on the web.
- URI: a unique address assigned to every resource on the web.
- HTTP: a way to request and receive resources on the web.
That very first version of HTTP, later called HTTP/0.9, was about as minimal as a protocol can get. A request was one line, starting with the only method that existed then, GET, followed by a path. The response was raw hypertext, with no headers, status codes, or versioning, and the connection closed once the response was delivered.
As interest in the web grew, browsers and servers started adding new features to the HTTP protocol, but a new problem began, new features added to the HTTP protocol was not coordinated, so there were different implementations of the HTTP/0.9, these implementations started drifting apart which led to the compatibility issues between different softwares that need to communicate with each other.
The Rise of JavaScript
While HTTP was maturing, something else was happening, "The Browser Wars - browsers like Internet Explorer and Netscape Navigator working hard to out-feature each other", it was in that competitive climate, in 1995, that Netscape hired Brendan Eich to add scripting to Netscape Navigator, and JavaScript was born. It started out doing almost nothing interesting, mostly basic form validation, but it was the first form of dynamism in a web that had until then been completely static. From there it kept getting standardized and adopted everywhere, eventually becoming one of the most important technologies of the web today.
Early Attempts at Realtime
With the existence of HTTP, HTML and now JavaScript, real web applications started to emerge, and the first serious attempts and efforts to make the web feel realtime came along in the late 90s. Two design models dominated that effort: AJAX and Comet.
AJAX (Asynchronous JavaScript and XML)
Ajax is a way of exchanging data with a server in the background and updating parts of a page without forcing a full reload. It was not a single technology on its own, it uses many existing technologies to achieve its realtime feel. It uses:
- HTML and CSS for the UI
- Document Object Model (DOM) for dynamic updates
- XML/JSON for the data format itself
- and most importantly the XMLHttpRequest object which allows JavaScript to make HTTP requests behind the scenes.
Ajax changed something structurally about how the classic web model worked. In the web model before AJAX, almost every user action fired off an HTTP request, and the server always sends back an entirely new HTML page in response. Ajax introduced something new between the server and the client - the AJAX engine. The client, instead of loading a fresh page each time it gets new contents, it loads the AJAX engine once and the engine takes over the job of polling the server in the background and asynchronously updating parts of the UI that needs updating.
Comet
The comet model which came a bit later than AJAX, is a web application design model that allows the server to continuously push updates to the browser, Similar to Ajax, it asynchronously communicates with the browser, unlike Ajax, comet opens a long lived http connection that allows the server to continuously push updates to the client (browser), whenever they are available, without the client explicitly requesting them. Comet itself was also not a single technology, it was an umbrella covering a couple of approaches that are used to achieve the realtime feel. Comet uses two approaches:
- Long Polling: A smarter version of normal polling, where instead of the server replying immediately with "nothing yet" when there is no new data available, it holds the connection open for as long as required, until either new data actually shows up or a timeout hits and then the client immediately fires off another request to the server to keep the cycle going.
- HTTP Streaming (HTTP server push): A data transfer technique that allows a web server to continuously send data to a client over a single HTTP connection that remains open indefinitely. Whenever there's an update available, the server sends a response, and only closes the connection when explicitly told to do so. It is similar to Long Polling, just that it goes further still by keeping a single connection indefinitely.
The Cost of Hacking HTTP
By this point AJAX and Comet had already opened the door to dynamic, realtime-feeling web apps, and in a smaller way, both are still used today. But they were never solved the problem cleanly, because underneath all of it was still HTTP, and HTTP was built from the ground up to serve hypermedia resources in a request-response fashion: the client asks for something, the server responds, and that exchange is the complete transaction. It was never designed to handle high-frequency or ongoing client-server communication, or with the ability to react instantly to changes on either side. So, hacking HTTP-based technologies to emulate realtime web brought about different kinds of problems, and some of them are discussed below.
Limited Scalability
Take HTTP polling for example, which is sending requests to the server at fixed intervals to check whether new data is available. Polling very frequently means you get always get new data, but that comes with a problem, because high polling frequencies result in increased network traffic and server demands, which obviously does not scale well, especially as the number of concurrent users increases. Polling less frequently can reduce the network traffic and server demands, but that means handing the client information that has already gone stale by the time it arrives, which defeats the purpose of chasing realtime behavior in the first place.
A better option would be Long Polling, but it also comes at its own cost because keeping thousands of "held-open" requests alive simultaneously demands a lot of server resources, since each one is still, at bottom, a connection the server has to maintain and eventually close and reopen.
Unreliable Message Ordering and Delivery Guarantee
A client can have multiple requests in flight to the server at the same time, and because network conditions are unpredictable, there is no guarantee that requests will arrive at the server, or response will get to the client in the specific order they were sent. On top of that, a server might send a response that never reaches the client, whether due to a network hiccup or a browser issue, and unless the application has built some kind of receipt confirmation on top of HTTP itself, a message can simply vanish without either side realizing it.
No Bidirectional Streaming
Another limitation of HTTP is that it does not have the concept of bidirectional streaming. It is a request/response protocol at its core, which means it was never built to let client and server hold an always-on, two-way conversation across a single connection. You can fake this by opening two separate HTTP connections, one for each direction, but now the server has to maintain double the connections to serve what is functionally one client, and that overhead compounds badly as your user count grows.
Higher Latency
Setting up a new HTTP connection requires a couple of handshakes involving several round-trips between the server and the client, before any useful data is sent, couple with the fact that HTTP requests are sequential by nature: the next request can only go out after the response to the current request is back. Network delays might also be a thing, depending on the network conditions at that moment. String enough of these together and the cumulative latency becomes a real problem for anything trying to feel instantaneous.
A better option to avoid higher latency would be HTTP streaming techniques, since they avoid opening new connections repeatedly. But, they are still bound by the overhead of HTTP headers riding along with every message, resulting in increased message size and causing unnecessary delays. In many cases these headers end up outweighing the actual payload being delivered, which is a strange kind of inefficiency when the whole goal is speed.
So every one of these techniques was, at its core, a workaround, and workarounds built on a mismatched foundation tend to produce their own new problems. That was the thread that leads into everything WebSocket was eventually built to fix.
In the next series we will go fully into WebSocket
Top comments (0)