Under the hood of HTTP requests in node

This is much longer than most folks likely want to wait for a secure connection to initialize, much less send and receive an HTTP request and response, so apps or libraries often set their own app-level timeout and simply close and release the socket if a connect event doesn’t emit within a desired deadline.

It’s important to realize that errors or timeouts during this phase have no impact on an HTTP transaction.

The client hasn’t even begun transmitting its HTTP request message.

The specifics of the TLS handshake evolve as attacks and vulnerabilities are discovered and mitigated.

But the gist is that the hosts at both ends of the new TCP socket connection exchange certificates and then do some computationally expensive cryptographic work in order to support encrypting/decrypting all of the upcoming in-flight data sent across the connection.

Click the lock icon in your browser address bar and you can view details for the server certificate that was used to secure the TCP socket connection underlying the HTTP request and response that served you this web page.

All of this certificate metadata and keying information was sent to your host during the TLS handshakeSometimes, HTTP requests between backend systems are done without TLS in the mix.

If all hosts involved are running within the same network trust boundary that may be OK.

When this isn’t the case, outbound HTTP requests from Nodejs or other backend systems to target endpoints should absolutely use HTTPS and certificate pinning to prevent man-in-the-middle attacks.

Finally, HTTP!At this point we finally have a TCP socket allocated by the OS, connected to a remote host’s TCP stack, with security (aka encryption/decryption of all in-flight data across the connection) in place.

Now my client can actually send an HTTP request message over the secure connection and receive a response.

The HTTP request message is often quite small compared to all the work that’s happened to set everything up.

HTTP response messages on the other hand tend to be fairly large.

The browser devtools network tab is a simple way to inspect the lower-level details of HTTP request / response messages (which are changing in some significant ways going from HTTP/1.

1 to HTTP/2).

Not much data sent for HTTP GET requests; essentially these values, and common request headersConnection PoolingAs we can see, a lot goes on to set up client-server connectivity before an HTTP request / response message exchange even takes place.

In the dark ages, the HTTP protocol was defined to close an underlying TCP connection to signal the end of an HTTP transaction.

It didn’t take long for folks to realize that was a poor decision.

So from HTTP/1.

1 forward the underlying TCP connection (along with its TLS handshake state) can be reused to perform further HTTP transactions against the same server host.

Depending on the HTTP verb, and its idempotency, multiple requests may even be pipelined rather than running in a serialized request -> response -> request -> and so on lock-step fashion.

The Nodejs library for outbound HTTP requests provides support for pooled connections, viahttp.

Agent, so close study of the API and thoughtful tuning is critical for good performance!TCP Connection Throughput; A Pooling BenefitIn addition to avoiding redo of all the setup work for a new TCP connection, connection pooling lets you better-align your workload to the realities of the TCP protocol’s flow and congestion controls.

The rate that the TCP protocol can move data across a connection is based on a variety of complex factors, but limited by bandwidth-delay productultimately.

The TCP protocol uses window sizes, flow control, and congestion control to attempt to make best use of available resources across the entire network path between the client and server (including all routers, proxies, and so on, along the way).

But the gist is that the TCP protocol passively probes to discover how much data it can transmit in the best-case, by tip-toeing forward on its amount of in-flight data, and then aggressively falling backward when loss / delay is detected.

And it starts off with a very pessimistic view, and a small window-size for in-flight data.

This algorithm is referred to as additive increase / multiplicative decrease.

Why does this matter?.It matters because the effective throughput on a brand new TCP connection is terrible.

The longer you can keep a connection around, the better the TCP protocol stacks at either end will understand true network conditions and how quickly they can shove data reliably across the connection!So by not pooling connections, you’re forcing yourself into the network slow lane…TCP IncastI mentioned above that HTTP request messages tend to be small and HTTP response messages tend to be large.

This also has interesting implications on making large numbers of outbound HTTP requests from a Nodejs server.

When the amount of data leaving a host on a large number of TCP connections is small relative to the amount of inbound response data, it’s possible to induce TCP network congestion on all of those inbound responses.

HTTP transactions tend to exhibit this asymmetry in payload size, where small outbound requests are paired with much larger inbound responses.

Watch this video for a great walk-through of this phenomenon.

The end result is that you might experience multiplicative decrease in TCP throughput for the competing responses that are trying to make their way back into your Nodejs server.

This can be a bit harder to monitor for (you’re looking for packet drops on the return path into your server across all of those TCP connections, which is what triggers the multiplicative throughput decrease).

The More You KnowHopefully you now know a bit more about what’s happening under the hood when your Nodejs app makes an outbound HTTP request, and knowing is half the battle.

.

. More details

Leave a Reply