Some HTTP limitations
Learning objectives
- You rehearse key features of the HTTP protocol including statelessness and how connections work in HTTP/1.0, HTTP/1.1, and HTTP/2.0.
- You know that browsers limit the number of HTTP connections per host.
As we've discussed in the Web Software Development course and in the brief Web Software Development rehearsal, HTTP is the communication protocol for the majority of web applications. The protocol comes with a handful of features which could be seen as weaknesses, but which could as well be seen as strengths. As an example, HTTP is stateless, which means that each request-response pair is independent from the previous request and response, and thus features such as authentication and authorization need to be implemented on top of the protocol. Similarly, when older versions of HTTP is used, a new TCP connection needs to be established for each request-response pair (HTTP/1.0) and in the case of TCP connections being left open (HTTP/1.1), request made over a connection must be made in subsequent fashion (i.e., HTTP/1.1 does not feature multiplexing).
While statelessness could be seen as a weakness from the perspective of an application that requires stateful connections, it brings a wide variety of other benefits. As each request-response pair is independent, subsequent requests do not need to be directed to the same server, which makes it easier to scale the application. As a consequence to the relative ease of adding more servers, statelessness makes it also easier to build reliable applications that continue functioning even if individual servers crash. Furthermore, as the request-response pairs are independent, some responses may also be cached, which increases performance.
In our applications so far, we've increased the number of servers by adding more deployments to our Docker Compose configuration, using a load balancer that is built in to Docker and hiding the complexity of the application behind an NGINX server. While this works, it brings a few limitations to our application. Namely, all traffic goes through the NGINX server, and from the perspective of a browser, the application is running in a single host.
There are some practical considerations related to this in the (now obsolete) RFC2616 that specifies HTTP/1.1. In particular, the chapter 8 on connections states the following:
Clients that use persistent connections SHOULD limit the number of simultaneous connections that they maintain to a given server. A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy. A proxy SHOULD use up to 2 * N connections to another server or proxy, where N is the number of simultaneously active users. These guidelines are intended to improve HTTP response times and avoid congestion.
In effect, the recommendation highlights that browsers should limit the number of open connections per host or otherwise servers could be overwhelmed by the number of connections. The RFC2616 was obsoleted by RFCs 7230-7235 on the HTTP protocol, which in turn were obsoleted by the RFC9110 on HTTP Semantics. Although the specific wording has been removed in the newer RFC, browsers still limit the amount of concurrent connections per host. Presently, the limit ranges between 6 to 12 persistent connections.
As some web applications may need even 300 requests to load, limiting the number of connections may decrease the performance of the application. For example, if the 300 requests would be made to the same host with HTTP/1.0, every request would require setting up a TCP connection, making the HTTP request, receiving the HTTP response, and closing the TCP connection. If the browser would follow the old recommendation on limiting the connections to two, and if each request would take 100 milliseconds from the start to finish, simply the requests would take somewhere around 15 seconds (300 * 100 / 2 = 15000). The issue, stemming from head-of-line blocking, would be exacerbated in the case of network issues.
With HTTP/2 that allows multiplexing, i.e. sending multiple requests at the same time over a single connection, a high number of requests will not create a similar performance hit as it did with HTTP/1.0 or HTTP/1.1.