Skip to content

HTTP

Why Does HTTP Exist?

TCP gives you a reliable byte stream between two machines, but it has no idea what "a request" or "a response" even means — it's just an undifferentiated flow of bytes. HTTP is the structure layered on top, specifically for the "client asks, server responds" pattern — the protocol underneath essentially the entire web.

Raw TCP: just a stream of bytes

↓

HTTP defines STRUCTURE on top of that stream:

Request:   METHOD PATH HTTP-VERSION
           Headers
           (optional body)

Response:  HTTP-VERSION STATUS-CODE STATUS-TEXT
           Headers
           (optional body)

Without this shared structure, every client and server would need its own private agreement about how to talk. HTTP is what lets any client — a browser, curl, a mobile app — talk to any server with zero custom integration work.


Request and Response Structure

GET /products/15 HTTP/1.1
Host: example.com
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 128

{"id": 15, "name": "Wireless Mouse", "price": 25}

A request line, headers, an optional body. The response mirrors it. Same shape, covered in more depth from a design angle in the REST chapter — this is what every HTTP client and server agree on, regardless of language or framework.


Statelessness

Each request stands alone. The protocol carries no memory of previous ones. Whatever "session" a web app appears to have is built on top of HTTP, not provided by it — usually a cookie carrying a session ID the server looks up on every request, covered in the Sessions and Cookies chapters. HTTP itself remembers nothing.


HTTP/1.0 vs HTTP/1.1

Persistent Connections

HTTP/1.0:  a NEW TCP connection for every single request
            (full three-way handshake, every time)

HTTP/1.1:  persistent connections by default (Connection: keep-alive)
            — the same TCP connection is reused for multiple
            sequential requests

Reusing a connection skips paying TCP's handshake (and slow-start ramp-up) on every request — a real performance win that HTTP/1.1 made the default instead of something you had to ask for.

Pipelining — An Attempt That Mostly Failed in Practice

HTTP/1.1 also tried pipelining — sending several requests without waiting for each response, over one connection. Should've helped throughput. Barely used in practice, for the reason below.


HTTP-Level Head-of-Line Blocking

The problem HTTP/2 was built to fix, and worth keeping separate in your head from TCP-level head-of-line blocking (from the TCP and UDP chapters) — related symptom, different layer.

Client sends Request 1, Request 2, Request 3 (pipelined, same connection)

↓

Responses MUST come back in the SAME ORDER requests were sent

↓

Request 1's response is SLOW to generate (e.g. a slow database query)

↓

Response 2 and Response 3 are ready and waiting, but CANNOT be
delivered yet — HTTP/1.1 requires responses on one connection to
arrive in request order, so they're stuck queued behind Response 1

Nothing's actually wrong with the underlying TCP connection here — this isn't a lost packet. It's HTTP/1.1's own rule that responses must return in request order, and that rule alone is enough to block fast responses behind a slow one.

The Workaround Browsers Used

Browsers open MULTIPLE parallel TCP connections to the same host
(typically around 6) specifically to work around HTTP-level
head-of-line blocking — if one connection is stuck behind a slow
response, other requests can proceed on a different connection

It works, but it's wasteful — every extra connection pays its own handshake and slow-start cost, and there's a practical limit to how many are worth opening. That waste is the direct motivation for HTTP/2's multiplexing, covered in its own chapter.


Real-World Examples

Essentially the Entire Web

Browser requests, most API calls, most inter-service communication that isn't gRPC or something equally specialized — all HTTP. It's the default application-layer protocol for request/response over the internet.


Browser Connection Limits as a Real, Observable Constraint

Open your browser's dev tools during a page load and you'll see several simultaneous connections to the same domain — the parallel-connections workaround, live and visible, shaping actual browser behavior.


Common Interview Questions

What's the difference between HTTP/1.0 and HTTP/1.1?

HTTP/1.1 made persistent connections the default — reusing one TCP connection across requests instead of opening a new one every time, which HTTP/1.0 did. Skips repeatedly paying the handshake and slow-start cost.


What is HTTP-level head-of-line blocking, and how is it different from TCP-level head-of-line blocking?

HTTP-level blocking comes from HTTP/1.1's rule that responses on one connection must arrive in request order — a slow response blocks faster, already-ready ones queued behind it, purely because of that ordering rule. TCP-level blocking is a different problem, one layer down: a single lost packet blocks all subsequent data on the connection, even data that already arrived, because TCP guarantees strict in-order delivery. Same symptom, different cause, different layer.


How did browsers work around HTTP/1.1's head-of-line blocking before HTTP/2?

By opening several parallel TCP connections to the same host — usually around six — so a slow response on one connection doesn't block requests going out on another. Works, but wastes resources, since every extra connection pays its own handshake and slow-start cost. Exactly what HTTP/2's multiplexing was built to eliminate.


Why is HTTP considered stateless?

Because the protocol carries no memory between requests — each one is handled independently, with no built-in concept of a session. Anything that looks like a session (staying logged in) is built on top, usually via a cookie and server-side lookup, not something HTTP provides itself.


Common Mistakes

❌ Confusing HTTP-level head-of-line blocking with TCP-level head-of-line blocking.

Same symptom, different cause — one's a protocol-level ordering rule about responses; the other's a lost packet blocking everything behind it at the transport layer.


❌ Assuming HTTP/1.1's pipelining feature is widely used in practice.

It's rarely used, precisely because it never solved head-of-line blocking — a slow response still blocks the faster ones queued behind it.


❌ Not recognizing that opening many parallel connections is a workaround, not a fix.

Each connection carries its own handshake and slow-start cost. It papers over the symptom without touching the root cause, which is exactly why HTTP/2 exists.


Key Takeaways

  • HTTP defines the request/response structure on top of TCP's raw byte stream, letting any client and server interoperate without a private agreement.
  • HTTP/1.1 made persistent connections the default, skipping a fresh TCP handshake on every request — a real improvement over HTTP/1.0.
  • HTTP-level head-of-line blocking — a slow response blocking faster ones behind it, because of HTTP/1.1's strict ordering rule — is a distinct problem from TCP-level head-of-line blocking, at a different layer.
  • Browsers historically worked around this with multiple parallel connections per host — real, but wasteful, and exactly the gap HTTP/2 was built to close.
  • HTTP itself is stateless — any session behavior is built on top via cookies and server-side lookup, not something HTTP provides natively.