Part 1 covered how a name becomes an address and how a TCP connection gets established. This article picks up right after that handshake completes.
A TCP connection guarantees that bytes arrive in order and intact. It guarantees nothing about who's on the other end, and nothing about privacy. Anything sent over a plain TCP connection travels as readable bytes, visible to anything sitting between the two endpoints: a router, a shared network, an ISP. That's the problem this article starts with, and it's also why almost nothing meaningful happens on the modern web over plain TCP alone.
Two things need to happen next: the connection needs to become private and verifiable, which is TLS's job, and once it is, both sides need a shared language for actually making requests and returning responses, which is HTTP's job.
The Problem TLS Solves
Before any application data is sent, two properties need to be established that TCP doesn't provide on its own:
- Privacy: nobody in between the client and server should be able to read the data.
- Identity: the client needs some assurance it's actually talking to the server it intended to reach, not an impostor intercepting the connection.
TLS (Transport Layer Security) provides both, and it does so through a handshake that runs after the TCP handshake completes, before any application data (like an HTTP request) is sent.
The TLS Handshake, Conceptually
At a high level, the TLS handshake has to accomplish two things: prove identity, and agree on a shared secret that both sides can use to encrypt everything that follows.
sequenceDiagram
participant Client
participant Server
Client->>Server: ClientHello (supported versions, cipher suites)
Server-->>Client: ServerHello + certificate
Note over Client: Verify certificate against trusted CAs
Client->>Server: Key exchange material
Note over Client,Server: Both sides derive the same shared secret
Note over Client,Server: Switch to symmetric encryption for all further data
The server presents a certificate, which is a signed statement, issued by a certificate authority (CA), that a given public key belongs to a given domain. The client checks that signature against a set of CAs it already trusts (bundled with the browser or operating system). If the chain of signatures checks out, the client has reasonable assurance the server is who it claims to be.
Here's the part that trips people up: why does TLS use two different kinds of encryption? The handshake itself uses asymmetric encryption (a public/private key pair) to let both sides agree on a secret without ever transmitting that secret in the clear. But asymmetric encryption is computationally expensive, far too slow to encrypt every byte of an entire session. So once both sides have used the asymmetric handshake to agree on a shared secret, they switch to symmetric encryption, which is much faster, for the actual bulk data transfer. Asymmetric encryption's whole job is to safely bootstrap a symmetric key; after that, it's out of the picture until the next new connection.
That's also why session resumption exists. Running the full handshake, especially the asymmetric key exchange, for every single connection is expensive. If a client reconnects to a server it already has a recent, trusted session with, both sides can skip most of the handshake and resume using previously negotiated key material. This is a pure performance optimization: repeat visits to the same site get to skip the expensive part.
What a Certificate Actually Proves
This is worth stating plainly, because it's one of the most common misunderstandings about the web: a certificate proves that a public key belongs to a specific domain, verified by a CA. It says nothing about whether the content served over that connection is trustworthy, safe, or legitimate.
The padlock icon in a browser means the connection is encrypted and the certificate matches the domain you're connected to. It does not mean the site is honest, well-run, or free of malicious content. A phishing site can have a perfectly valid certificate for its own domain; the certificate just proves you're talking to that domain, not that the domain deserves your trust. Conflating "encrypted" with "safe" is a mistake that has a real security cost, because it turns a purely technical guarantee into a false sense of trust in the content itself.
HTTP: A Shared Language on Top of the Connection
Once a connection is both reliable (TCP) and private and verified (TLS), the two sides still need to agree on what a "request" and a "response" actually mean. That's HTTP: a text-based (in HTTP/1.1) application protocol that defines the structure of a request and a response, and nothing else. It doesn't know about connections, encryption, or routing; those are handled by the layers underneath it.
Methods and Their Actual Semantics
HTTP methods aren't just a list of verbs; each one carries a specific meaning that well-behaved clients and servers are expected to honor:
- GET retrieves a resource and should have no side effects. It's also idempotent: making the same GET request multiple times should produce the same result as making it once (assuming nothing else changed the resource in between).
- PUT replaces a resource with the provided representation. It's idempotent too: sending the same PUT twice results in the same final state as sending it once.
- DELETE removes a resource. Also idempotent: deleting something that's already gone should still be treated as a successful "it's gone" outcome, not an error caused by repeating the action.
- POST creates something or triggers an action, and is explicitly not guaranteed idempotent: sending the same POST twice can create two separate resources or trigger an action twice.
That idempotency distinction isn't a technicality. It's why browsers warn you before resubmitting a form (a POST) but not before reloading a page (a GET), and why retry logic that's safe for a GET or a PUT can be actively dangerous for a POST unless the server has additional safeguards.
Status Codes: Categories, Not Just Numbers
Status codes are grouped into five categories, and the grouping itself carries meaning that a client can act on even without recognizing the specific code:
- 1xx: informational, the request was received and processing continues.
- 2xx: success, the request was understood, accepted, and processed as expected.
- 3xx: redirection, further action is needed to complete the request, typically fetching a different URL.
- 4xx: client error, the request itself was malformed or not allowed.
- 5xx: server error, the request was probably fine, but the server failed to fulfill it.
A client that doesn't recognize a specific status code (say, an uncommon 4xx variant) can still fall back to treating it correctly based on its leading digit alone. That's the entire reason for the category-based numbering scheme: it makes the protocol forward-compatible with codes that didn't exist yet when older clients were written.
Statelessness, and Why Cookies Exist
HTTP is deliberately stateless: each request is handled independently, with no memory of previous requests from the same client baked into the protocol itself. This is a design choice, not an oversight. It keeps servers simple and lets any server in a pool handle any request without needing shared memory of a client's history.
The obvious problem: a lot of what people build on the web requires remembering something between requests, like "this user is logged in." Since HTTP itself won't do that, the mechanism that fills the gap is the cookie: a small piece of data the server asks the client to store (via a Set-Cookie response header), which the client then automatically re-attaches to every subsequent request to that domain (via a Cookie request header). The server never "remembers" the client between requests; the client is doing the remembering and handing it back every time, and the server just recognizes it. Sessions and tokens (like a session ID stored in a cookie, or a bearer token attached as a header) are built on exactly this same mechanism: state is pushed to the client and handed back, because the protocol itself refuses to hold onto it.
What You Have Now
At this point in the series, you have a connection that's reliable (TCP), private and identity-verified (TLS), and carrying requests and responses that mean something specific and well-defined (HTTP), with a client-driven workaround (cookies) for the fact that none of this remembers anything between requests on its own.
That's enough to describe a single request and response cleanly. But it says nothing about what happens when a real page needs dozens of requests at once, which is exactly the situation HTTP/1.1 was never built to handle gracefully. That performance problem, and how it reshaped the transport underneath HTTP itself, is where Part 3 picks up.
Comments
Join the discussion on GitHub.