HTTP
Type a URL into a browser and, within a few hundred milliseconds, a page appears. Everything this course has covered so far — the TCP three-way handshake, sliding windows, MSS — exists to move bytes reliably between two hosts. HTTP is what those bytes actually say. It's the application-layer protocol that turns a raw, reliable byte stream into a structured request for a resource and a structured answer, and it's the thing every browser, mobile app, and backend service is really speaking when people loosely say "it called the API."
What problem HTTP solves
Before HTTP, and outside of it today, two programs that want to exchange structured information over a network still need to agree on a format: how does the receiver know where one message ends and the next begins, how does it know what the sender is asking for, and how does it signal back whether the request succeeded or failed? TCP guarantees the bytes arrive in order and intact; it has no opinion at all about what they mean.
HTTP, first specified in the early 1990s at CERN by Tim Berners-Lee alongside HTML and URLs, answered that with a deliberately simple model: a client sends a request naming a resource and an action, and a server sends back a response containing a status and, usually, a body. That's the entire shape of the protocol. Everything else — cookies, caching, content negotiation, authentication headers — is built as metadata layered onto that one request/response exchange, not as a change to the basic model.
Where HTTP sits, and what it assumes below it
HTTP is an application-layer protocol in the TCP/IP model. Classic HTTP (versions 1.0 and 1.1, still the most widely deployed today) runs on top of TCP, almost always on port 80 for plaintext traffic — a mapping this course already introduced in well-known ports. That choice isn't incidental: HTTP requests and responses are exchanged as ordinary text (a body may carry binary data, but the request line and headers are plain ASCII), and the protocol simply assumes the transport underneath has already solved ordering and loss. HTTP itself has no sequence numbers, no retransmission logic, nothing — it hands a request to TCP and trusts that whatever comes back on that same connection is the matching response, in order. Later HTTP versions relax parts of this assumption — that's the subject of the next few articles in this module — but for now, picture HTTP/1.1 running over a single already-established TCP connection.
The request
A client — typically a browser, but just as often a mobile app, a curl invocation, or another server — opens a TCP connection to the target host's port 80 (or reuses one already open) and sends a request built from three parts.
The request line names the method, the target, and the protocol version:
Headers follow, one per line, each a Name: value pair carrying metadata about the request:
The Host header deserves a specific callout: a single physical server, and a single IP address, routinely hosts many unrelated websites — a setup called virtual hosting. TCP and IP get the packet to the right machine; only the Host header, read by the web server after the TCP connection is already established, tells it which site the request is actually for. Without it, HTTP/1.1 couldn't distinguish a request for example.com from one for another-site.com sitting on the same server, which is exactly why Host has been mandatory since HTTP/1.1 (it was optional and frequently absent in HTTP/1.0).
A blank line marks the end of headers, and then, for methods that send data, a body:
POST /login HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 29
username=alice&password=hunter2
Content-Length tells the receiving side exactly how many bytes of body to expect — one of the mechanisms, along with Transfer-Encoding: chunked for cases where the total size isn't known upfront, that lets HTTP know where a message ends inside a byte stream that has no message boundaries of its own.
Methods: what the request is asking for
HTTP defines a fixed set of methods, each carrying a specific meaning the client and server are both expected to honor:
| Method | Purpose | Has a body? | Safe? | Idempotent? |
|---|---|---|---|---|
GET |
Retrieve a resource | No (by convention) | Yes | Yes |
POST |
Submit data, often creating something | Yes | No | No |
PUT |
Replace a resource entirely | Yes | No | Yes |
PATCH |
Partially modify a resource | Yes | No | No |
DELETE |
Remove a resource | Usually not | No | Yes |
HEAD |
Like GET, but headers only, no body |
No | Yes | Yes |
OPTIONS |
Ask what methods/headers a resource supports | No | Yes | Yes |
Safe means the method isn't supposed to change server state — a GET request is expected to be a pure read, which is exactly why prefetching, caching, and search-engine crawling all assume it's fine to issue GET requests without asking permission first, and why building a "delete this record" link that responds to GET is a real, well-documented mistake: a crawler or a browser's link-prefetching can trigger it accidentally.
Idempotent means sending the same request multiple times leaves the server in the same state as sending it once. DELETE /users/42 is idempotent — the user is gone whether you ask once or five times — but POST /users typically isn't, since each call is likely to create a new user. This distinction matters directly for retries: a client (or an intermediate proxy) can safely retry an idempotent request after a timeout without worrying about duplicating an effect, but retrying a bare POST after an ambiguous failure risks double-submission, which is why payment and order-creation APIs commonly add an explicit idempotency key rather than relying on the method alone.
The response
A response mirrors the request's shape — a status line, headers, a blank line, then an optional body:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 1256
Cache-Control: max-age=3600
<!DOCTYPE html>
<html>...
The status code is a three-digit number, grouped by its first digit into five classes:
- 1xx — Informational. The request was received and processing continues; rarely seen directly (
101 Switching Protocolsis one you'll actually encounter, used when upgrading a connection to WebSocket). - 2xx — Success.
200 OKis the everyday case;201 Createdafter a successfulPOST;204 No Contentwhen the request succeeded but there's nothing to send back. - 3xx — Redirection.
301 Moved Permanentlyand302 Foundtell the client to make a new request elsewhere;304 Not Modifiedtells a caching client its cached copy is still valid, saving the server from resending a body it knows the client already has. - 4xx — Client error. The request itself is the problem:
400 Bad Requestfor malformed syntax,401 Unauthorizedfor missing or invalid credentials,403 Forbiddenwhen the credentials are valid but access is denied anyway,404 Not Found,405 Method Not Allowedwhen the resource exists but doesn't support the method used,429 Too Many Requestswhen rate limiting kicks in. - 5xx — Server error. The request was probably fine, but the server failed to fulfill it:
500 Internal Server Erroras the generic catch-all,502 Bad Gatewaywhen a reverse proxy got an invalid response from the backend it's forwarding to,503 Service Unavailablewhen the server is overloaded or in maintenance,504 Gateway Timeoutwhen that backend didn't respond in time.
The distinction between 4xx and 5xx is a genuinely useful debugging signal, not just a numbering convention: a 4xx means look at what the client sent, a 5xx means look at what the server did with it. A backend developer staring at a spike of 502s should be checking the health of whatever's behind the reverse proxy, not the client's request format.
HTTP is stateless, and what that actually costs
Every HTTP request is, by design, independent — the server has no built-in memory of any previous request from the same client. This was a deliberate simplicity choice in the original design: a stateless server doesn't need to track per-client session data, which makes it dramatically easier to scale, since any request can be handled by any server behind a load balancer without needing to route a client back to the exact machine that "remembers" them.
The cost shows up the moment an application needs to recognize the same user across multiple requests — staying logged in being the obvious case. HTTP itself doesn't solve this; cookies (a header mechanism, Set-Cookie on the response and Cookie on subsequent requests) are the standard workaround, letting the server hand the client an opaque identifier it's expected to echo back on every future request, and letting the server look that identifier up in its own session store. The statelessness didn't go away — the server still treats each request independently — but the cookie lets it reconstruct enough context to fake continuity.
Reading a real exchange
curl -v shows both directions of an HTTP exchange directly, prefixing outgoing lines with > and incoming ones with <:
* Trying 93.184.215.14:80...
* Connected to example.com (93.184.215.14) port 80
> GET / HTTP/1.1
> Host: example.com
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/html; charset=UTF-8
< Content-Length: 1256
< Cache-Control: max-age=1209600
<
* Connection #0 to host example.com left intact
The lines starting with * are curl's own status commentary, not part of the protocol exchange. The > block is the exact request curl sent — request line, then headers, then the blank line — and the < block is the server's response in the same shape. Note that curl filled in Host and User-Agent automatically; a raw request built by hand needs to set these explicitly, or a virtual-hosted server has no way to know which site is being asked for.
Practical scenario: a 404 that's actually a routing problem
An API team ships a new endpoint, GET /api/v2/orders/{id}, and a client integration immediately starts reporting 404 Not Found for orders that definitely exist in the database. The instinct is to suspect the database query, but a 404 at the HTTP layer means the server couldn't map the request to any resource at all — it never got far enough to run a query.
curl -v against the exact URL the client is using shows the request line reads GET /api/v2/orders/1042 HTTP/1.1, while the route registered on the server is /api/v1/orders/{id} — the v2 path was documented ahead of the actual deployment, and the reverse proxy in front of the API has no rule for it, so the request never reaches the application code; the 404 is coming from the proxy's own default handler, not from the application. Confirming this takes one more step: a curl -v against the known-good v1 path returns 200 OK with a body, proving the backend and database are fine and isolating the fault to routing configuration alone.
Common mistakes
- Treating
GETas safe to use for a state-changing action because it's convenient to trigger from a plain link. Search engine crawlers and browser prefetching both issueGETrequests without asking, so aGET /cart/clearendpoint will eventually get triggered by something other than a deliberate user click. - Assuming a
200response means the operation actually succeeded at the application level. A server can return200 OKwith a body like{"error": "insufficient funds"}— the HTTP layer succeeded (a well-formed response was returned), but the business logic failed. Status codes describe the HTTP transaction, not necessarily the outcome an application cares about; reading the body still matters. - Omitting the
Hostheader when constructing raw requests by hand against a virtually-hosted server, and getting a confusing response for the wrong site instead of the one intended.
Practice exercises
- Using
curl -vagainst a site you control or a public test endpoint, identify which headerscurlsent that you didn't specify explicitly, and explain where each one came from. - Explain, in terms of idempotency, why a payment API would reject a duplicate
POST /chargesrequest with the same idempotency key rather than creating a second charge — and why the same protection isn't needed forDELETE /charges/{id}. - A client reports getting
304 Not Modifiedresponses with no body, and asks whether this is a bug. Explain what's actually happening and why it's the expected, correct behavior for a caching client.
Everything above describes HTTP without regard to whether the connection is encrypted — and in production, it almost always is. HTTPS is next: the same request/response model, carried over a connection that's been wrapped for confidentiality and integrity before a single HTTP byte is sent.
Sources
- IETF, RFC 9110 – HTTP Semantics — methods, status codes, headers.
- IETF, RFC 9112 – HTTP/1.1 — message syntax and framing.
- MDN Web Docs, Overview of HTTP
- MDN Web Docs, HTTP response status codes