Table of contents
- HTTP, the Common Language of the Web
- What Requests and Responses Look Like
- Methods — What to Tell the Server to Do
- Status Codes — The Result in Three Digits
- Headers — Metadata Exchanged Outside the Body
- HTTP/1.1 — The Long-Time Default and Its Limits
- HTTP/2 — Enter Multiplexing
- HTTP/3 — Going All the Way to UDP
- HTTP Has a Hole
- Why HTTPS Is Now the Default
HTTP, the Common Language of the Web
Right now, while you’re reading this post, HTTP (HyperText Transfer Protocol — the agreement for how clients and servers exchange data on the web) requests are ceaselessly flowing behind the browser. Every image, stylesheet, and API response rides on HTTP. It’s no exaggeration to say that if HTTP stopped, the web would stop.
As its name implies, HTTP started with hypertext transfer, but now it carries nearly everything — JSON, binary, video streams. In one sentence: HTTP is the agreement that “the client sends a request, and the server sends a response.” This simplicity is what grew the web into what it is today.
In Part 4, we saw how DNS converts a name to an IP. This post covers what messages actually flow once connected to that IP, how those messages become secure through HTTPS, and how HTTP itself has evolved through HTTP/2 and HTTP/3.
What Requests and Responses Look Like
HTTP messages are human-readable text. This is one of HTTP’s great strengths. With nothing but curl or telnet, you can handcraft a request.
A request looks like this.
GET /posts/42 HTTP/1.1
Host: api.example.com
User-Agent: curl/8.1.0
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1...
It splits into three parts: the start line (GET /posts/42 HTTP/1.1), headers (multiple key-value lines), and optionally a body. Since this is a GET, there’s no body. The Host header alone tells the server which domain was requested — an essential mechanism in the era of virtual hosting.
The server’s response has a similar shape.
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 58
Cache-Control: max-age=60
{"id":42,"title":"HTTP","author":"ioob"}
The start line now carries a status code (200 OK), and the body follows after a blank line separating it from the headers. That blank line is the delimiter signaling the boundary between headers and body. Simple, yet this agreement has powered the web for 40 years.
You can observe this structure directly with a single curl -v command.
curl -v https://api.github.com/zen
# > GET /zen HTTP/2
# > Host: api.github.com
# > User-Agent: curl/8.1.0
# < HTTP/2 200
# < content-type: text/plain;charset=utf-8
# < content-length: 67
# ...
# Non-validated stamps diminish your credibility.
> marks the request, < marks the response. The only thing to note is that starting with HTTP/2, header names are normalized to lowercase.
Methods — What to Tell the Server to Do
The method at the start of the request line expresses the intended action. Let’s look at the five most common ones.
- GET: Retrieves a resource. No body, and sending the same request multiple times doesn’t change server state (idempotent)
- POST: Creates a resource or submits data to the server. Not guaranteed idempotent — sending the same request twice may create two copies
- PUT: Completely replaces a resource. Sending the same request multiple times produces the same result (idempotent)
- PATCH: Modifies only part of a resource. Idempotency depends on implementation
- DELETE: Deletes a resource. Idempotent — deleting something already deleted still results in the “doesn’t exist” state
There’s a reason these five distinctions are never absent from REST API design documentation: retry strategies depend on method idempotency. When the network drops and you miss a response, GET/PUT/DELETE can be retried without worry, but POST needs safeguards against duplicate creation. When building auto-retry logic in client SDKs, idempotency is the first thing checked.
Methods like OPTIONS, HEAD, CONNECT, and TRACE exist too, but in practice you mainly work with the five above. OPTIONS is used for CORS (Cross-Origin Resource Sharing) preflight requests, so you’ll encounter it if you build front-end applications.
Status Codes — The Result in Three Digits
The status code on the first line of the response is a three-digit number. The first digit defines the group. Memorize just the groups and you’ll read logs significantly faster.
| Group | Meaning | Representative Codes |
|---|---|---|
| 1xx Informational | Intermediate state. “Continue” | 100 Continue, 101 Switching Protocols |
| 2xx Success | Request processed successfully | 200 OK, 201 Created, 204 No Content |
| 3xx Redirect | Look elsewhere | 301 Moved Permanently, 302 Found, 304 Not Modified |
| 4xx Client error | The request is wrong | 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 429 Too Many Requests |
| 5xx Server error | The server failed to process | 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout |
The most frequently confused pair in practice is 401 vs. 403. 401 means “I don’t know who you are — authenticate yourself.” 403 means “I know who you are, but you don’t have permission.” Another common mix-up is 502 vs. 504. 502 means the gateway received a bad response, while 504 means no response came at all. The troubleshooting approach is completely different.
304 is a response to a client’s conditional request. When the browser sends the cached resource’s ETag or Last-Modified to the server, the server determines “nothing has changed” and returns just 304 without a body. This is the core of cache optimization that saves bytes.
Headers — Metadata Exchanged Outside the Body
The body is the content; headers are the negotiation about how to handle that content. Mastering headers alone can substantially improve performance and security. Let’s look at four categories frequently seen in practice.
Content-Type — The Type of the Body
Tells whether the body is JSON, HTML, binary, etc. If missing or wrong, the server/browser fails to interpret the body.
Content-Type: application/json; charset=utf-8
Content-Type: text/html; charset=utf-8
Content-Type: multipart/form-data; boundary=----xyz123
Multipart is used for file uploads, with boundary separating each part.
Authorization — Who Are You
Carries authentication information in API requests. The two most common schemes.
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Authorization: Basic dXNlcjpwYXNz
Bearer is for tokens like JWT. Basic is the old-school method of Base64-encoding ID:password. Basic is encoding, not encryption — sending it over plain HTTP exposes everything. In this era, if you use Basic, it must be over HTTPS only.
Cookie / Set-Cookie — Maintaining State
HTTP is inherently stateless. Each request is independent. So to express “persistent state” like login sessions, an additional mechanism is needed — that’s the cookie.
# Server sets in response
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax; Max-Age=3600
# Client sends in subsequent requests
Cookie: session=abc123
HttpOnly prevents JavaScript from reading the cookie via document.cookie (XSS defense), Secure restricts it to HTTPS only, and SameSite limits cookie transmission in cross-site requests (CSRF defense). These three are effectively the default settings today.
Cache-Control — Where and How Long to Store
There’s no need to fetch the same resource from the server repeatedly. Browser caches, CDN caches, and proxy caches store responses and reuse them. The server dictates the rules with Cache-Control.
Cache-Control: public, max-age=31536000, immutable # Hashed static assets
Cache-Control: private, max-age=60 # Per-user, 1 minute
Cache-Control: no-store # Never store
public means intermediate caches can store it; private means only the browser can. max-age is in seconds. immutable promises “this resource will never change” — pairs well with hashed filenames (app.3f2b9e.js). A proper caching strategy alone can cut traffic costs by several times.
HTTP/1.1 — The Long-Time Default and Its Limits
Browsers and servers conversed in HTTP/1.1 for a long time. This version, released in 1997, introduced keep-alive (reusing TCP connections) and pipelining (sending multiple requests consecutively), fixing many performance issues from the 0.9/1.0 era.
But one fundamental limitation remained: on a single TCP connection, requests are processed in order. If the first request is slow, all subsequent requests are delayed too. This is called HOL (Head-of-Line) blocking.
sequenceDiagram
participant B as Browser
participant S as Server
B->>S: GET /a.js
B->>S: GET /b.css
B->>S: GET /c.png
S-->>B: a.js response (slow)
Note over B,S: b.css and c.png wait until a.js finishes
S-->>B: b.css response
S-->>B: c.png response
Browsers worked around this by opening up to 6 connections in parallel. That’s why early 2010s front-end optimization articles were full of advice like “sprite your images” and “concatenate your CSS.” Reducing the total number of requests was the only way to dodge HTTP/1.1’s limitations.
HTTP/2 — Enter Multiplexing
HTTP/2, standardized in 2015, was built on Google’s SPDY protocol and directly addressed HTTP/1.1’s performance ceiling. The biggest change is multiplexing.
Multiple streams run simultaneously on a single TCP connection. Both requests and responses are chopped into small frames and interleaved. Even if an earlier request is slow, a later request’s response can arrive first.
flowchart LR
subgraph C[Single TCP/TLS Connection]
direction LR
S1["Stream 1<br/>GET /a.js"] --- S2["Stream 2<br/>GET /b.css"]
S2 --- S3["Stream 3<br/>GET /c.png"]
end
C --> SRV[Server]
SRV -->|Frame interleaving| C
Other things HTTP/2 brought.
- Header compression (HPACK): Compresses repetitive headers (most headers are), reducing transfer size
- Server push: The server sends resources before the client requests them (usage declining recently due to debate over effectiveness)
- Binary framing: Parsing is simplified by switching from text-based to binary
Thanks to HTTP/2, old optimizations like “image sprites and file concatenation” can actually become counterproductive. Many small files arriving in parallel are often faster. As the era changes, optimization strategies change too.
HTTP/3 — Going All the Way to UDP
HTTP/2 left one problem unsolved. Since multiple streams ride on a single TCP connection, when one packet is lost at the TCP level, all streams stall together. This is TCP-level HOL blocking. Multiplexing solved HOL at the HTTP layer, but the bottleneck resurfaced at the transport layer.
HTTP/3 addresses this from the ground up. It replaces TCP with QUIC on top of UDP and implements HTTP above that. QUIC (Quick UDP Internet Connections — a transport protocol started by Google and standardized by the IETF) handles streams directly at the transport layer. Packet loss in one stream doesn’t affect other streams.
flowchart TB
subgraph H3["HTTP/3"]
HTTP3[HTTP semantics] --> QUIC[QUIC]
QUIC --> TLS13[TLS 1.3 built-in]
TLS13 --> UDP[UDP]
end
subgraph H2["HTTP/2"]
HTTP2[HTTP semantics] --> HF[Framing layer]
HF --> TLS[TLS 1.2/1.3]
TLS --> TCP[TCP]
end
Another advantage is connection establishment speed. TCP+TLS requires at least 2 RTTs (Round Trip Time — the time for a packet round trip), but QUIC achieves 1 RTT for the first connection and 0-RTT for reconnections. This difference is strongly felt in mobile environments where networks change frequently. Major services at Cloudflare, Google, and Meta already default to HTTP/3.
HTTP Has a Hole
That covers the HTTP story so far. But HTTP messages are fundamentally plaintext. Anyone on the same network running tcpdump or Wireshark can read everything — from the request body to the Authorization token in the headers. That’s why sending login information over HTTP on public Wi-Fi is dangerous.
There are three risks.
- Eavesdropping: A third party intercepts the communication
- Tampering: A man-in-the-middle alters the messages
- Impersonation: A rogue server pretends to be the real one
HTTPS defends against all three at once. HTTPS isn’t a separate protocol — it’s HTTP layered on top of TLS (Transport Layer Security — a protocol that creates an encrypted channel between two hosts; the successor to SSL).
flowchart LR
C[Browser] -->|TLS encrypted tunnel| S[Server]
EVE["Man-in-the-middle<br/>(eavesdrop attempt)"] -.->|"All they see is ciphertext"| C
EVE -.->|"All they see is ciphertext"| S
TLS blocks eavesdropping with encryption, prevents tampering with message authentication codes, and verifies the server’s identity with digital certificates. The lock icon in the browser address bar means all three checks have passed.
Why HTTPS Is Now the Default
Up until the mid-2010s, the common pattern was “HTTPS only for sensitive pages.” Login and payment used HTTPS; everything else used HTTP. Today, every page defaults to HTTPS. Several factors accumulated.
- Let’s Encrypt launched (2016): Free automated certificates. The cost barrier vanished
- Browser warnings: Chrome has displayed “Not Secure” on HTTP sites since 2018
- HTTP/2 and HTTP/3 effectively require TLS: Major browsers don’t support HTTP/2 without TLS
- Privacy regulations: Laws like GDPR effectively mandate encrypted communication
HTTPS is no longer an option reserved for security-sensitive areas. It’s the web’s default setting. Using plain HTTP itself is now considered abnormal.
So the natural next question is: how does TLS work to create an encrypted channel over a public network? Who issues certificates, and why does the browser trust them? Answering that question is the next post.
In the next post, we look inside TLS/SSL. The difference between symmetric and asymmetric keys, the role of certificates and Certificate Authorities (CAs), the handshake from ClientHello to Finished, and the critical differences between TLS 1.2 and 1.3 — all unpacked step by step.

Loading comments...