Skip to content
Menu

Networking11 min read

HTTP (1.1 / 2 / 3)

The application protocol of the web — from 1.1 to 3

HTTP/1.1 — The Foundation

HTTP (Hypertext Transfer Protocol) is a request-response protocol operating at Layer 7. The client sends a request, the server returns a response. Every interaction on the web — loading a page, submitting a form, calling an API — is an HTTP exchange. HTTP/1.1 (RFC 2616, later refined in RFCs 7230-7235) has been the workhorse since 1997.

Request Format

An HTTP/1.1 request is a text-based message with a well-defined structure:

Request Line METHOD SP PATH SP HTTP/VERSION CRLF
Headers Key: Value CRLF (repeated)
Blank Line CRLF (separates headers from body)
Body (optional) Payload — JSON, form data, binary, etc.
bash
GET /api/v1/pods HTTP/1.1
Host: kubernetes.default.svc
Authorization: Bearer eyJhbGciOi...
Accept: application/json
Connection: keep-alive

Response Format

Status Line HTTP/VERSION SP STATUS-CODE SP REASON CRLF
Headers Key: Value CRLF (repeated)
Blank Line CRLF
Body HTML, JSON, binary, etc.
bash
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 1842
Cache-Control: no-cache
ETag: "a1b2c3d4"

{"kind":"PodList","apiVersion":"v1","items":[...]}

Request / Response Flow

Client
Server
GET /index.html HTTP/1.1
HTTP/1.1 200 OK + body
GET /style.css HTTP/1.1
HTTP/1.1 200 OK + body

Note

Note: In HTTP/1.1 without pipelining, each request must wait for the previous response before being sent. This is inherently sequential on a single connection.

HTTP Methods

Method Semantics Idempotent Has Body Typical Use
GET Retrieve resource Yes No Fetch data, load pages
POST Create / submit No Yes Create resource, form submit
PUT Replace entire resource Yes Yes Full update (replace)
PATCH Partial update No* Yes Update specific fields
DELETE Remove resource Yes Optional Delete resource
HEAD GET without body Yes No Check if resource exists, get headers
OPTIONS Describe capabilities Yes No CORS preflight, API discovery

Note

*PATCH idempotency: PATCH is not guaranteed idempotent because the operation depends on the patch format. A JSON Merge Patch ({"name":"new"}) is idempotent, but a JSON Patch ([{"op":"add","path":"/tags/-","value":"x"}]) that appends to an array is not.

Key Headers

Content Negotiation & Identity

  • HostRequired in HTTP/1.1. Identifies the target virtual host. One IP can serve many domains (name-based vhosts in nginx/Apache, Ingress in K8s).
  • Content-Type — MIME type of the body: application/json, text/html, multipart/form-data
  • Accept — What the client can handle: application/json, text/html;q=0.9 (quality factor for preference)
  • Authorization — Credentials: Bearer <token>, Basic <base64>

Caching

  • Cache-Control — Directives for caches:
    • max-age=3600 — fresh for 3600s
    • no-cache — must revalidate before use (can still cache)
    • no-store — do not cache at all
    • public / private — CDN vs browser-only
  • ETag — Opaque identifier for a specific version of a resource (like a content hash)
  • If-None-Match — Client sends previous ETag; server returns 304 Not Modified if unchanged

Connection Management

  • Connection: keep-aliveDefault in HTTP/1.1. Reuse the TCP connection for multiple requests instead of opening a new one per request (which was the HTTP/1.0 default). Send Connection: close to indicate the connection will be closed after this response.

Conditional Request Flow (ETag)

Client
Server
GET /resource
200 OK + ETag: "abc123"
GET /resource + If-None-Match: "abc123"
304 Not Modified (no body)

Tip

Ops insight: ETags save bandwidth and reduce origin load. In K8s, the API server uses resourceVersion as an ETag-like mechanism for watch and list operations — same principle.

Status Codes

Range Category Key Codes
1xx Informational 100 Continue — server ready for body; 101 Switching Protocols — WebSocket upgrade
2xx Success 200 OK; 201 Created (POST success); 204 No Content (DELETE success, no body)
3xx Redirection 301 Moved Permanently (update bookmarks); 302 Found (temporary redirect); 304 Not Modified (cache hit); 307 Temporary Redirect (preserve method); 308 Permanent Redirect (preserve method)
4xx Client Error 400 Bad Request; 401 Unauthorized (not authenticated); 403 Forbidden (authenticated but not authorized); 404 Not Found; 405 Method Not Allowed; 409 Conflict; 429 Too Many Requests (rate limited)
5xx Server Error 500 Internal Server Error; 502 Bad Gateway (upstream failed); 503 Service Unavailable (overloaded/maintenance); 504 Gateway Timeout (upstream timeout)

Warning

Ops war story: 502 vs 503 vs 504 — know the difference cold. 502 means your reverse proxy (nginx, envoy, ALB) got a bad response from upstream. 503 means the service itself is refusing connections. 504 means the upstream didn't respond in time. These point to very different root causes.

Keep-Alive (Persistent Connections)

HTTP/1.0 (No Keep-Alive)
  • New TCP connection per request
  • 3-way handshake overhead every time
  • TCP slow start penalty on every connection
  • Massive latency cost
bash
Request 1: TCP open → GET → response → TCP close
Request 2: TCP open → GET → response → TCP close
Request 3: TCP open → GET → response → TCP close
HTTP/1.1 (Keep-Alive Default)
  • Reuse TCP connection for multiple requests
  • One handshake, many requests
  • TCP congestion window grows over time
  • Connection stays warm
bash
TCP open → GET → response
         → GET → response
         → GET → response
         → ... → TCP close

Pipelining — A Good Idea That Failed

HTTP/1.1 pipelining allows the client to send multiple requests on a single connection without waiting for each response. In theory, this eliminates the round-trip latency between sequential requests.

Client
Server
GET /a
GET /b
GET /c
Response /a (slow...)
Response /b (blocked!)
Response /c (blocked!)

Warning

Head-of-Line (HOL) Blocking: The server MUST respond in the order requests were received. If /a takes 5 seconds, /b and /c sit waiting even if they're ready — hence "head of line" blocking. Most browsers and proxies never enabled pipelining because of this, plus broken intermediaries (proxies that corrupted pipelined responses). The real-world workaround was opening 6-8 parallel TCP connections per domain.

HTTP/2 — Multiplexing Over a Single Connection

HTTP/2 (RFC 7540, 2015) was designed specifically to solve HTTP/1.1's performance problems: HOL blocking, redundant headers, and the hack of opening multiple connections. It keeps HTTP semantics (methods, status codes, headers) identical but completely replaces the wire format.

Binary Framing Layer

HTTP/1.1 — Text Protocol
bash
GET /api/pods HTTP/1.1\r\n
Host: k8s.io\r\n
Accept: application/json\r\n
\r\n
  • Human-readable text
  • Parsed by scanning for delimiters (\r\n)
  • Headers are verbose, repeated every request
HTTP/2 — Binary Frames
Length 24 bits
Type 8 bits
Flags 8 bits
Stream ID 31 bits
Frame Payload Variable length
  • Machine-optimized binary format
  • Fixed-length frame headers — fast parsing
  • Every frame tagged with a stream ID

Multiplexed Streams

The core innovation: multiple streams (logical request/response pairs) are interleaved on a single TCP connection. Each stream has a unique ID. Frames from different streams can be sent and received in any order.

HTTP/2 Multiplexing — Single TCP Connection

bash
  Time ──────────────────────────────────────────▶

  Stream 1  |HEADERS||DATA|          |DATA||END|
  Stream 3          |HEADERS||DATA|DATA||END|
  Stream 5  |HEADERS|                    |DATA||END|

    All frames interleaved on ONE TCP connection.
    No HTTP-layer HOL blocking — stream 3 can finish
    before stream 1 without waiting.

Tip

Ops insight: This is why HTTP/2 made domain sharding obsolete. With HTTP/1.1, sites served assets from multiple domains (cdn1.example.com, cdn2.example.com) to open more parallel connections. HTTP/2 multiplexing on a single connection outperforms that hack.

HPACK Header Compression

HTTP/1.1 headers are sent as uncompressed text on every request. For a typical page with 80+ resources, the same headers (User-Agent, Cookie, Accept, etc.) are sent 80+ times — often >1 KB per request just in headers.

HPACK Compression Strategy

Static Table

61 pre-defined common header name-value pairs (e.g., :method: GET = index 2). Referenced by index instead of sending the full string.

Dynamic Table

Connection-specific table built during the session. Headers seen before are added and referenced by index on subsequent requests. FIFO eviction.

Huffman Encoding

Header values that can't be indexed are Huffman-coded — a static Huffman table optimized for HTTP header byte frequencies. ~30% size reduction.

Note

Result: HPACK typically achieves 85-95% compression on repeated headers. The first request sends full headers; subsequent requests on the same connection send tiny index references.

Server Push

The server can proactively send resources to the client before the client requests them. For example, when the client requests index.html, the server can push style.css and app.js it knows the client will need.

Client
Server
GET /index.html (Stream 1)
PUSH_PROMISE /style.css (Stream 2)
HEADERS + DATA /index.html (Stream 1)
HEADERS + DATA /style.css (Stream 2)

Warning

Server push is being deprecated. In practice, it's extremely hard to use correctly: you can push resources the client already has cached (wasting bandwidth), the cache interaction semantics are complex, and CDNs/proxies often break it. Chrome removed support in 2022. The replacement pattern is 103 Early Hints — the server sends a 103 informational response with Link headers so the browser can start fetching resources early, but the client controls the actual requests.

Stream Priorities

HTTP/2 allows clients to assign priorities to streams using a dependency tree and weight values (1-256). This lets the browser signal that CSS should load before images, for example. In practice, implementations varied widely — both browsers and servers often handled priorities poorly, leading to the RFC 9218 (Extensible Priorities) overhaul.

HTTP/3 — QUIC and the End of TCP for HTTP

HTTP/2 solved HTTP-layer HOL blocking, but one problem remained: TCP-layer HOL blocking. Because HTTP/2 multiplexes all streams over a single TCP connection, a single lost TCP packet blocks ALL streams until retransmission completes — the TCP layer doesn't know about HTTP streams. HTTP/3 (RFC 9114, 2022) replaces TCP with QUIC (RFC 9000), a UDP-based transport that solves this fundamentally.

The TCP HOL Blocking Problem

HTTP/2 over TCP
bash
Stream 1: |HEADER|DATA|
Stream 3: |HEADER|DATA|DATA|
Stream 5: |HEADER|DATA|
           ↑
  TCP packet lost here
  ALL streams blocked until
  retransmission arrives

TCP is a single ordered byte stream. It doesn't understand multiplexed streams. Lost segment = everything waits.

HTTP/3 over QUIC
bash
Stream 1: |HEADER|DATA|
Stream 3: |HEADER| ✗ |DATA|
Stream 5: |HEADER|DATA| ✓
              ↑
  QUIC packet lost on stream 3
  Only stream 3 is blocked
  Streams 1 & 5 continue

QUIC has native stream awareness. Loss on one stream only blocks that stream. Others proceed independently.

QUIC Key Features

0-RTT Connection Establishment

TCP + TLS 1.3 requires 2-3 round trips before data can flow (1 RTT TCP handshake + 1 RTT TLS handshake). QUIC integrates transport and TLS into a single handshake:

  • First connection: 1-RTT (transport + crypto combined)
  • Resumed connection: 0-RTT — client sends data immediately using a PSK from a previous session

Connection Migration

TCP connections are identified by the 4-tuple: (src IP, src port, dst IP, dst port). If your IP changes (e.g., switching from WiFi to cellular), the TCP connection breaks and must be re-established.

QUIC connections are identified by a Connection ID — an opaque token independent of the IP/port. When the network changes, the connection survives.

Built-in TLS 1.3

QUIC does not run over a separate TLS layer — TLS 1.3 is embedded into the protocol. All QUIC payloads (including most header fields) are encrypted. There is no unencrypted QUIC — unlike TCP where encryption is optional. This means middleboxes (firewalls, NATs, DPI devices) cannot inspect or modify QUIC headers, which has both security benefits and operational challenges.

Warning

Ops note: Because QUIC runs over UDP, some corporate firewalls and network middleboxes block or rate-limit UDP/443. Browsers implement QUIC with a fallback: try QUIC first, fall back to HTTP/2 over TCP if it fails. You'll see the Alt-Svc header in HTTP/2 responses advertising QUIC support: Alt-Svc: h3=":443"; ma=86400.

HTTP/1.1 vs HTTP/2 vs HTTP/3 — Side by Side

Feature HTTP/1.1 HTTP/2 HTTP/3
Transport TCP TCP QUIC (UDP)
Wire format Text Binary frames Binary frames
Multiplexing No (1 req at a time per conn) Yes (many streams, 1 conn) Yes (many streams, 1 conn)
HOL blocking HTTP + TCP layer TCP layer only None
Header compression None HPACK QPACK
Server push No Yes (deprecated) Yes (rarely used)
Connection setup 1-RTT TCP + 1-RTT TLS 1-RTT TCP + 1-RTT TLS 1-RTT (0-RTT on resume)
Connection migration No No Yes (Connection ID)
Encryption Optional (HTTPS) Effectively required Mandatory (built-in TLS 1.3)
RFC 7230-7235 7540, 9113 9114 (QUIC: 9000)

Connection Setup Comparison

HTTP/1.1 + TLS 1.2 (3 RTTs)

  1. TCP SYN
  2. TCP SYN-ACK
  3. TCP ACK + TLS ClientHello
  4. TLS ServerHello + Cert
  5. TLS Key Exchange + Finished
  6. HTTP Request

3 RTTs before first byte of data

HTTP/2 + TLS 1.3 (2 RTTs)

  1. TCP SYN
  2. TCP SYN-ACK
  3. TCP ACK + TLS ClientHello
  4. TLS ServerHello + Finished
  5. HTTP Request

2 RTTs before first byte of data

HTTP/3 + QUIC (1 RTT / 0 RTT)

  1. QUIC Initial (ClientHello + transport params)
  2. QUIC Handshake (ServerHello + Finished)
  3. HTTP Request

1 RTT (0 RTT on resumption!)

Tip

Real-world impact: On a 100ms RTT connection (common for cross-region traffic), the difference between 3 RTTs (300ms) and 1 RTT (100ms) is huge — especially for mobile users on high-latency cellular networks. 0-RTT on resume means returning users get data flowing with their first packet.

Note

Checking protocol in production: Use curl -I --http2 https://example.com to check HTTP/2 support. For HTTP/3: curl --http3 https://example.com (requires curl built with HTTP/3 support). In Chrome DevTools, the Network tab's "Protocol" column shows h2 or h3.

Solidnines — solidnines.com