HTTP and HTTPS: Requests, Cookies and Caching

Application Layer and Security · 30 min

Core CS · Computer Networks

Ask once, get answered once, be forgotten

HTTP is one request, one reply, and no memory in between. Cookies are how you are recognised on the next request. Caching is how the next request never happens. HTTPS is the same conversation, sealed inside TLS.

Run three requests and watch the cookie and the cache appear
One request is a start line, some headers, a blank line, and an optional body. One response is the same shape with a status code on the front. The server keeps nothing between the two, which is why every request has to carry its own identification, and why the cheapest request is the one the browser never sends.

01 The idea

The server answers, and then forgets you completely

HTTP, the HyperText Transfer Protocol, is the application-layer protocol the web is built on. Up to HTTP/2 it runs on top of TCP, on port 80 in the clear and port 443 inside TLS, and HTTP/3 carries the identical messages over QUIC on UDP instead, which section 03 gets to. Whichever transport is underneath, the whole of it is one pattern repeated: the client sends a request, the server sends back exactly one response, and the exchange is over. There is no notion of a session in the protocol itself. There is no login state, no shopping cart and no “where you were”. There is a message and a reply.

That property has a name you will be asked to define. HTTP is stateless: the server retains nothing about a client between two requests. It is easy to hear this as a shortcoming, and it is not. It is a deliberate design decision and it is the reason the web scales at all. If a server kept per-client state in memory, every request from you would have to come back to the same machine. Because it keeps none, any of a thousand machines behind a load balancer can serve any request, a crashed server loses nothing that matters, and a new server can join the pool and start answering immediately.

The bill for that choice lands on the client. If the server remembers nothing, then every request must carry everything needed to answer it. Which page you want, which host you meant, what content types you accept, and, crucially, who you are. That last one is what cookies exist for. A cookie is a small named value the server hands the browser in a Set-Cookie response header, and the browser hands back in a Cookie request header on every later request to that host. The server has still forgotten you. It recognises you only because you re-introduced yourself, unprompted, in the message.

The second consequence is about cost. Every request is a fresh round trip over TCP, and a round trip across a continent is tens of milliseconds you cannot argue with. A page is not one request, it is dozens. So the single largest performance lever in HTTP is not making requests faster, it is not making them. That is what the caching headers are for: the server labels a response with how long it stays usable and with a version tag, and the browser uses those two facts to skip the network entirely, or, when it cannot skip it, to ask a question so small that the answer fits in four lines with no body attached.

So this lesson has three layers stacked on one protocol, and they are stacked in that order for a reason. The message format is the protocol. Cookies are state layered on top of a protocol that has none. Caching is the avoidance of the protocol altogether. HTTPS then wraps the entire stack in TLS without changing a single thing about it: the same methods, the same headers, the same status codes, carried inside an encrypted record on port 443.

HTTP is one request and one reply, and the server forgets you the instant it has answered. Cookies are how you are recognised on the next request, caching is how the next request is avoided altogether, and HTTPS is the whole conversation sealed inside TLS on port 443 rather than a different protocol.
Request and responseBoth are text messages with the same three parts: a start line, then zero or more headers one per line, then a blank line, then an optional body. The request’s start line is method target version; the response’s is version code reason. The blank line is what ends the headers, and it is present even when there is no body.
StatelessThe server keeps nothing about a client between requests. Two requests from the same browser are, as far as the protocol is concerned, from two strangers. Anything that makes them look connected, a session, a cart, a login, was put there by something layered on top.
ValidatorAn opaque marker the server attaches to a response so the client can later ask has this changed? without downloading it. The strong form is the ETag, a version tag for this exact body. The client never parses it; it only stores it and echoes it back.

02 Worked example

One GET and its 200, read header by header

One small exchange, and it is the same exchange for the rest of the lesson: the console in section 04 replays it three times, and every byte count in the lesson is measured on it. A browser wants https://examate.in/pricing.html, a 1,200-byte page it has never seen before. The scheme is https, and that is not a detail you can drop. The message text below is byte for byte what plain HTTP would look like. But the cookie in step 4 is marked Secure, and a browser will not store a Secure cookie handed to it over plain HTTP, nor send one back over one. Read left to right.

1. The request lineGET /pricing.html HTTP/1.1. Three fields separated by single spaces: the method, the target and the version. Note what is missing. The target is the path only. The hostname is not in it.
2. The headers, then a blank lineHost: examate.in carries the hostname and is mandatory in HTTP/1.1. Then User-Agent, Accept, Connection: keep-alive. A blank line ends the block. A GET has no body, and the blank line is still there. 116 bytes for the whole request.
3. A server with no memoryIt parses the request, finds the file and builds a reply. Nothing in this message says whether this browser has ever visited before, and the server holds no record that could tell it. There is no Cookie header, because there is nothing yet to send.
4. The status line and six headersHTTP/1.1 200 OK, then Content-Type and Content-Length, then the three that matter: Cache-Control: max-age=60, ETag: "v7-9f2a" and Set-Cookie. 226 bytes of headers.
5. The body, and what was filed away1,200 bytes of HTML, so the response cost 1,426 bytes. The browser renders it and stores two separate things: a cookie in its cookie jar, and a cache entry holding the body, the tag "v7-9f2a" and a freshness window of 60 seconds.

Written out, the request is five lines and the response is seven, and both end with an empty line you cannot see when you print them. Here is the request exactly as it goes on the wire, with each line terminated by a carriage return and a line feed:

GET /pricing.html HTTP/1.1Host: examate.inUser-Agent: Mozilla/5.0Accept: text/htmlConnection: keep-alive                          <-- the blank line that ends the headers

Check the arithmetic, because the console recomputes it and it should agree with you. The five text lines are 26, 16, 23, 17 and 22 characters, which is 104. Each line is terminated by CRLF, two bytes, so add 10 to get 114. The final blank line is another CRLF, so the request is 116 bytes. Nothing in it is data. All of it is metadata, and that is worth holding on to, because it is exactly why HTTP/2 bothered to compress headers.

Now the reply, and the highlighted node is where the rest of the lesson comes from:

HTTP/1.1 200 OKDate: Mon, 06 Apr 2026 10:00:00 GMTContent-Type: text/html; charset=utf-8Content-Length: 1200Cache-Control: max-age=60ETag: "v7-9f2a"Set-Cookie: sid=8f31c2; Path=/; HttpOnly; Secure; SameSite=Lax                          <-- blank line, then 1200 bytes of HTML

The status line is HTTP/1.1 200 OK: version, then a three-digit code, then a human-readable reason phrase that no program should ever parse. Content-Type tells the browser to treat the bytes as HTML rather than offer them as a download, and the charset parameter is what stops the page rendering as mojibake. Content-Length tells the receiver exactly where this body ends, which matters because the connection is being kept open for the next request and something has to mark the boundary.

Then the three that do the work. Cache-Control: max-age=60 is a promise about time: this copy may be reused for 60 seconds from the moment it was stored, with no request to anyone. ETag: "v7-9f2a" is a promise about identity: this exact body has that tag, and if the tag ever changes the body has changed. Set-Cookie is a promise about nothing at all; it is an instruction. Store sid=8f31c2 and send it back to me every time.

Those three headers are three different answers to three different questions, and mixing them up is the classic confusion. Do I need to ask? is answered by max-age. Has it changed? is answered by the ETag. Who is asking? is answered by the cookie. The first saves a whole round trip, the second saves a whole body, and the third makes a stateless protocol behave as though it knew you.

One last thing about that Set-Cookie line, because it is the densest 62 bytes in the lesson. sid=8f31c2 is the name and value, and the value is deliberately meaningless: it is a lookup key into a session record the server keeps in its own database, not the session itself. Path=/ scopes it to the whole site. HttpOnly hides it from JavaScript. Secure means it will never be sent over plain HTTP. SameSite=Lax means it will not be attached to requests that another site caused your browser to make. Section 03 puts a specific attack next to each of those three, and being able to name the attack rather than the attribute is the whole of that question.

03 Mechanics

The methods, the codes, the cookies, the cache and the versions

Five tables, in the order the questions arrive, and a sixth on what HTTPS does and does not hide. Start with the methods, because the two properties in columns two and three are the whole of the PUT versus POST question. Safe means the request does not change server state, so a crawler may fire it at will. Idempotent means doing it twice has the same effect on the server as doing it once, which is what makes an automatic retry after a lost response harmless. Every safe method is idempotent; the reverse is not true, and PUT and DELETE are the two rows that prove it, because both change server state and both are still safe to retry.

MethodSafeIdempotentRequest bodyWhat it means
GETyesyesnoFetch the representation at this URL and change nothing. The default for every link and every address you type.
HEADyesyesnoIdentical to GET, but the server sends the headers only and no body. Used to check size, type or freshness without paying for the download.
OPTIONSyesyesnoAsk what the server permits on this URL. The browser sends one automatically as the preflight before certain cross-origin requests.
PUTnoyesyesReplace whatever is at this exact URL with this body. The client chooses the URL, which is what makes it idempotent: the same PUT twice leaves the same single resource in the same state.
DELETEnoyesnoRemove the resource at this URL. Idempotent because after the first one it is gone, and asking again for it to be gone changes nothing. The second call may answer 404, but the state is the same.
POSTnonoyesSubmit this data to the resource at this URL and let the server decide what to do with it. Two identical POSTs to /orders create two orders, which is precisely why it is not idempotent.
PATCHnonoyesApply a partial change rather than a full replacement. Not idempotent in general, because an instruction like add 10 to the balance applied twice is not the same as applied once.

Why PUT versus POST is the standard question. The distinction is not update against create; both can create. It is who names the resource. PUT /users/42 says here is the complete state of user 42, put it there, and repeating it is harmless. POST /users says here is a new user, you pick the address, and repeating it makes a second user. That is why a browser warns you before re-submitting a form and never warns you before reloading a page. It is also why a mobile client on a bad network can retry a PUT automatically but must not retry a POST without help.

Now the status codes. The first digit is the class and it is the part you should never get wrong, because it says whose fault it is. The specific codes below are the ones that come up by name.

ClassCodeNameWhat it actually means
1xx informational100ContinueCarry on and send the body you announced. Rare in browsers; you meet it with large uploads.
2xx success200OKIt worked, and the body is the answer.
201CreatedA POST or PUT made something new. The Location header names where it now lives.
204No ContentIt worked and there is deliberately nothing to send back. Common for a successful DELETE.
3xx redirection301Moved PermanentlyThe URL has changed for good. Browsers and search engines update their records, and the browser may cache the redirect for a long time.
302FoundTemporary. Go to the Location this once, but keep treating the original URL as the real one.
304Not ModifiedYour cached copy is still correct. Status line and headers, no body. This is a success, not a failure.
4xx client error400Bad RequestThe message itself is malformed and the server could not make sense of it. Nothing to do with permissions.
401UnauthorizedWe do not know who you are. Authenticate and try again. The name is a historical misnomer; it means unauthenticated.
403ForbiddenWe know exactly who you are, and you still may not have this. Signing in again will not help.
404Not FoundThere is no resource at this URL. Also used deliberately to hide the existence of one.
5xx server error500Internal Server ErrorThe server broke while handling a request that was perfectly valid. The generic crash.
502Bad GatewayThis server is acting as a proxy, and the server behind it gave an invalid response or none at all.
503Service UnavailableThis server is up and answering, but cannot serve right now: overloaded or in maintenance. May carry Retry-After.

The two pairs that get swapped. 401 against 403 is about identity: 401 means who are you, 403 means we know, and no. If a signed-in student requests the admin dashboard, that is 403; sending 401 tells the browser to prompt for credentials that will not help. 502 against 503 is about which machine is broken: 502 comes from a proxy whose upstream failed, so the thing you are talking to is fine and the thing behind it is not; 503 comes from a server that is talking to you perfectly well and telling you it cannot do the work. In practice a 502 from nginx almost always means the application process died or was never started, which is why it is the first thing you check after a deploy.

Cookies next. There is nothing to a cookie but a name, a value and a list of attributes, and the attributes are the entire security surface. The right-hand column is the one to memorise, because the question is never name the attributes, it is what does each one stop.

Written asWhat it doesWhat it stops
no Expires and no Max-AgeSession cookie. The browser holds it in memory and deletes it when the browsing session ends.Not a defence in itself. It limits how long a stolen value stays useful, and it is the right default for a login.
Max-Age=1209600 or Expires=…Persistent cookie. Written to disk and survives a browser restart until that moment. 1209600 seconds is 14 days.Nothing. This is a convenience feature, and it widens the window in which a stolen cookie still works.
SecureThe browser sends it only over HTTPS, never over a plain HTTP request.An eavesdropper on shared Wi-Fi reading the session id straight out of a plaintext request and replaying it.
HttpOnlydocument.cookie cannot see it, so no script running on the page can read it.Session theft through cross-site scripting. An injected script can still act as you, but it cannot walk off with the cookie.
SameSite=LaxNot attached to cross-site requests, except top-level navigations that use GET. This is the modern browser default.Cross-site request forgery. Another site cannot make your browser perform a state-changing action as you.
SameSite=StrictNever attached when the request originated on another site at all, including an ordinary link.The same, more tightly, at the cost of a user arriving from an external link and appearing to be signed out.
Domain and PathLimit which hosts and which URL prefixes the cookie is attached to.Handing your session cookie to more of your own infrastructure, and more subdomains, than actually needs it.

The cookie is a key, not the state. Almost every real session cookie holds an opaque random identifier and nothing else, and the actual session data sits in the server’s store keyed by it. That matters for two reasons. A cookie is sent on every single request to that host, so a fat cookie is a tax on every page load. And anything you put in a cookie, the user can read and edit, so a cookie that says role=admin is not a session, it is a vulnerability. If state must live in the cookie, it has to be signed, which is what a JSON Web Token is doing.

Caching. Two mechanisms live here and they are commonly confused, so separate them before you read the table. Freshness decides whether a request is made at all, and it is driven by max-age. Validation decides whether a body is sent, and it is driven by the ETag and the conditional request. The first saves a round trip; the second saves a payload.

Header, and which message it rides onWhat it means
Cache-Control: max-age=60 · responseReusable for 60 seconds from the moment it was stored. Inside that window the browser makes no request at all, so the server never learns the page was viewed.
Cache-Control: no-cache · responseStore it, but check with the origin before every single reuse. The name is misleading: it does not mean do not cache, it means do not use without asking.
Cache-Control: no-store · responseDo not write it down anywhere, not to disk and not to memory. This is the one for a bank statement or a password reset page.
Cache-Control: private · responseThe browser may store it. A shared cache, meaning a proxy or a CDN, may not. Use it on anything personalised.
ETag: "v7-9f2a" · responseAn opaque version tag for this exact representation. The client never interprets it; it stores it and quotes it back.
If-None-Match: "v7-9f2a" · requestSend me the body only if the tag is no longer current. This turns a download into a question.
Last-Modified / If-Modified-SinceThe older, date-based validator. Its resolution is one second, so two edits inside the same second are indistinguishable. Prefer the ETag when both are available.
304 Not Modified · responseStatus line and headers, no body. Your copy is still correct, keep using it, and the freshness window starts again.
Vary: Accept-Encoding · responseWarns a cache that this one URL has more than one correct answer, keyed on the request headers named here. Without it a cache can serve a gzipped body to a client that cannot decode it.

Caches live in three places, and only one of them is yours. The browser cache sits on the user’s own disk and serves exactly one person. A forward or reverse proxy sits in the middle. A CDN is a shared cache with edge nodes placed near users all over the world. The browser cache and the CDN behave identically as far as the headers are concerned, and that is a trap: a cache keys its entries on the URL, not on who asked, so a personalised page that reaches a shared cache without private can be handed to the wrong person. Section 04 shows only the browser cache, because that is the one every request passes through first.

The versions, briefly, because the question is usually what problem did each one solve rather than the specification.

VersionRuns overWhat it introducedWhat still hurts
HTTP/1.0TCPThe status line, the header block and the method vocabulary that everything since has kept.one request per connection — a new TCP handshake for every image on the page
HTTP/1.1TCPPersistent connections by default, so one connection carries many requests. The mandatory Host header, which is what allows many sites on one IP address. Chunked transfer encoding for bodies of unknown length.head of line blocking — responses come back in the order the requests went out, so one slow response stalls everything queued behind it. Browsers worked around it by opening about six connections per host.
HTTP/2TCP, in practice always inside TLSA binary framing layer instead of text. Many independent streams multiplexed over a single connection, so responses may come back interleaved and in any order. HPACK header compression, which matters because those 116 bytes of pure metadata repeat on every request.TCP-level head of line blocking — TCP still delivers bytes in order, so one lost segment stalls every stream sharing that connection
HTTP/3QUIC, which runs on UDPLoss recovery per stream, so a lost packet stalls only its own stream. QPACK header compression. TLS 1.3 folded into the transport handshake, so a connection can be established in one round trip.UDP is blocked or rate limited on some corporate and campus networks, so clients keep an HTTP/2 fallback ready.

Read that table as one problem being chased down three layers. HTTP/1.1 removed the per-request connection but left ordering at the HTTP layer. HTTP/2 removed the ordering at the HTTP layer but inherited it from TCP underneath. HTTP/3 could only finish the job by leaving TCP, which is why it is built on UDP: not because UDP is faster, but because QUIC needed to implement its own ordering and could only do that on a transport that imposed none. Saying “HTTP/2 solved head of line blocking” without the TCP clause is the answer that gets marked down.

Finally HTTPS, stated precisely, because the loose version of this answer is very easy to catch. HTTPS is not a different protocol. It is the identical HTTP you have been reading, with identical methods, headers and status codes, carried as application data inside a TLS session, on port 443 instead of port 80. TCP connects first, TLS negotiates on top of it, and only then does the request line travel, encrypted. So it is fair to be exact about what that buys and what it does not.

Part of the exchangeHidden by TLS
The method, the path and the whole query stringyes — all of it is inside the encrypted record, so a URL containing a token is protected on the wire, though not in the server’s logs
Every header on both messages, including Cookie and Set-Cookie, and the request bodyyes
The status code and the response bodyyes
The destination IP address and port 443no — they sit in the IP and TCP headers, which have to be readable for the packet to be routed at all
The hostname in the TLS ClientHello, the SNI extensionno — it is sent before encryption begins, because the server needs it to choose which certificate to present. Encrypted Client Hello is the fix and is not yet universal.
The size and timing of what you transferno — enough on its own to guess which page of a known site you loaded
The DNS lookup that found the address in the first placeno — plaintext UDP on port 53 unless DNS over HTTPS or DNS over TLS is in use

The handshake, in the right order, with the round trips right. In TLS 1.2 the client sends ClientHello; the server answers with ServerHello, its Certificate and ServerHelloDone; the client sends ClientKeyExchange, ChangeCipherSpec and Finished; the server answers ChangeCipherSpec and Finished. That is two round trips before a single HTTP byte moves, on top of TCP’s one, and the certificate travels in the clear. TLS 1.3 collapses it: the client guesses the key exchange group and puts its key share in the ClientHello, the server replies with its own share in the ServerHello, and everything after the ServerHello is already encrypted, including the certificate. That is one round trip. Only the two Hello messages are plaintext in TLS 1.3. If you remember one thing here, remember that 1.3 is one round trip and 1.2 is two, and never say it the other way round.

What TLS actually gives you, in three words. Confidentiality, nobody in the middle can read it. Integrity, nobody in the middle can change it without being detected. Authentication, the certificate presented by the server was signed by a certificate authority your machine already trusts, and it binds that hostname to that public key. The third one is the part people leave out, and it is the one that stops an attacker from pretending to be the site. Encryption without authentication would be encryption with whoever intercepted you.

05 Cheat sheet

Fifteen answers you should be able to give in ten seconds

Every row is something an interviewer asks flat out. The right-hand column is the specific wrong answer that gets given, not a general caution.

What they askThe answerThe trap
The parts of an HTTP messagestart line · headers · blank line · optional bodyLeaving out the blank line. It is the only thing that ends the header block.
The header HTTP/1.1 made mandatoryHostAnswering User-Agent. Host is what lets many sites share one IP address.
Safe and idempotent, definedsafe = changes no server state · idempotent = twice equals oncetreating them as one property — DELETE is idempotent and not safe
Which methods are idempotentGET, HEAD, OPTIONS, PUT, DELETEincluding POST or PATCH — neither is
PUT or POSTPUT: the client names the URL · POST: the server decidesSaying “PUT updates, POST creates”. Both can create; the difference is who chooses the address.
401 against 403401 = we do not know you · 403 = we know you and no401 for a signed-in user without permission — that is 403
301 against 302301 permanent, caches and search engines update · 302 temporarySending 301 during a migration you may reverse. Browsers cache it and users never come back.
502 against 503502 = my upstream failed · 503 = I am up but cannot serveReading a 502 as “the site is down”. The proxy answered you perfectly.
What a 304 carriesstatus line and headers, no bodyreading 304 as a failure — it is a success meaning your copy is still right
Turning a stored copy into a questionIf-None-Match, carrying the stored ETagReaching for If-Modified-Since when an ETag exists. The date has one-second resolution.
no-cache against no-storeno-cache stores and revalidates · no-store never writes it downno-cache on a bank statement — it is still on disk
The three cookie attributesSecure = HTTPS only · HttpOnly = hidden from JS · SameSite = not cross-siteNaming the attributes without naming the attack each one stops.
What HTTP/2 actually fixedmultiplexed streams on one connection, HPACK header compression“it removed head of line blocking” — TCP still has it; HTTP/3 needed QUIC
HTTPS, preciselythe same HTTP inside a TLS session, port 443“it encrypts everything” — the IP, the port and the SNI hostname are visible
TLS 1.2 against TLS 1.3 setup1.2 needs two round trips · 1.3 needs oneSaying it the other way round, or forgetting TCP’s own round trip underneath both.
Stateless is a featureBecause the server keeps nothing between requests, any machine behind a load balancer can answer any request and a crash loses nothing. The cost is that identification has to travel in the message, which is exactly the job cookies do. Nothing about a cookie makes the connection stateful; it makes the request self-identifying.
Freshness and validation are different savingsmax-age decides whether a request happens at all and saves a whole round trip. The ETag decides whether a body is sent and saves the payload. A 304 means freshness ran out but validation succeeded, so you paid one round trip and no body.
HTTPS wraps, it does not replaceSame methods, same headers, same status codes, carried inside TLS on port 443. What it hides is the content of the exchange; what it cannot hide is who you connected to, because the address has to be routable and the SNI hostname is sent before encryption starts.

06 Where & why

These headers are printed by tools you will run this week

Nothing in this lesson is a teaching abstraction. Every header named above is a literal string that real software emits and real tools display, and three of the four systems below differ from the textbook in a way worth naming out loud.

Cloudflare
A shared cache that tells you what it did

Every response through Cloudflare carries cf-cache-status, and the values are the vocabulary of section 03: HIT served from the edge, MISS fetched from your origin, EXPIRED stale so it revalidated, REVALIDATED the origin answered 304, BYPASS your headers forbade caching. It honours Cache-Control: private and no-store by refusing to store, which is the whole reason those directives exist. TLS also terminates on the edge machine, so the certificate the browser validates is Cloudflare’s, not yours.

nginx
It generates the ETag for you, and it is the thing that returns 502

For a static file nginx builds a strong ETag automatically from the file’s last-modified time and its size, so If-None-Match and 304 work with no configuration at all. expires 1y; emits both Cache-Control: max-age=31536000 and a legacy Expires date. And when nginx is proxying to an application it is nginx, not your code, that answers 502 Bad Gateway when the upstream refuses the connection and 504 Gateway Timeout when it accepts but never replies. Reading which of the two you got tells you whether the process is dead or merely stuck.

Chrome DevTools
The Network panel is this lesson, live

The Size column reads (disk cache) or (memory cache) for a request that never left the machine, which is step 3 of the console with nothing on the wire. A row showing 304 has a Size of a few hundred bytes against a Content column of the full page, and that gap is the body you did not download. Ticking Disable cache makes the browser send Cache-Control: no-cache on every request, which is why a page behaves differently with DevTools open. The Application panel lists the cookie jar with separate columns for HttpOnly, Secure and SameSite.

curl
It prints the raw message, which is the point

curl -v https://examate.in/pricing.html prints the request line and every request header after > and the response after <, so the two panels in section 04 are literally what you see. curl -I sends a HEAD rather than a GET, which is how you read Cache-Control and ETag without pulling the body. Add -H 'If-None-Match: "v7-9f2a"' and you have made the conditional request by hand and can watch the 304 come back.

Everything in this lesson is a way of not sending something. Statelessness is the server not storing you, so the request has to carry your identity. Freshness is the browser not sending the request. Validation is the server not sending the body. Read that way, HTTP is one idea about who is allowed to keep what, seen from three sides, and HTTPS only changes who is allowed to read it.

07 Interview questions

The order these questions actually arrive in

This is the most asked topic in the application layer, and it is asked in layers. You describe the message, they ask what stateless means, then how login works anyway, then which method to use, then the codes, then caching, then HTTPS, then the versions. Give the header name every time you can. An answer with real header names in it reads as experience; one without reads as a memorised definition.

What actually travels over the wire when I open a page? Describe the HTTP message.
Two text messages with the same shape. The request is a start line reading method target version, so GET /pricing.html HTTP/1.1, then one header per line, then a blank line, then an optional body. The response is a status line reading version code reason, so HTTP/1.1 200 OK, then headers, a blank line and the body. The blank line is what ends the header block and it is there even when there is no body. In HTTP/1.1 the Host header is mandatory, because the request line carries only the path and the server needs the hostname to know which site you meant.
HTTP is stateless. So how does a site remember that I am signed in?
It does not remember. You remind it on every request. When you sign in, the server replies with a Set-Cookie header carrying an opaque identifier, the browser stores it, and it attaches a Cookie header with that value to every subsequent request to that host. The server looks the identifier up in its own session store and finds out who you are. The connection is still stateless; what changed is that the request is now self-identifying. That is exactly why any machine behind a load balancer can serve you, and why signing out means invalidating the record on the server, not only deleting the cookie.
What do safe and idempotent mean, and which methods are which?
Safe means the request does not change server state, so it can be issued freely by a crawler or a prefetcher. Idempotent means performing it twice leaves the server in the same state as performing it once, which is what makes an automatic retry after a lost response harmless. GET, HEAD and OPTIONS are safe and therefore also idempotent. PUT and DELETE are not safe but are idempotent. POST is neither, and PATCH is generally neither, because an instruction like add ten applied twice is not the same as applied once. The two properties are independent and DELETE is the row that proves it.
PUT or POST to create something? How do you decide?
By asking who chooses the URL. PUT /users/42 means here is the complete state of the resource at this address, which the client has named, so sending it twice leaves one user in one state. POST /users means here is some data, you decide what to do with it and where it lives, so sending it twice creates two users. That is the difference, not update against create, because both can create. It is also why the browser silently reloads a page you reached with GET but warns you before re-submitting a form.
401 or 403? Which one, and why does it matter?
401 Unauthorized means we do not know who you are, so authenticate and try again; despite the name it signals unauthenticated. 403 Forbidden means we know exactly who you are and you still may not have this, so trying again with the same credentials is pointless. A signed-in student who requests the admin dashboard gets 403. Sending 401 there is wrong in a way the user feels, because it prompts them for credentials that cannot possibly help. Some services deliberately return 404 instead of 403 so an attacker cannot even confirm the resource exists.
301 or 302? Does it matter which one you send?
Very much, and the mistake is expensive. 301 Moved Permanently says this URL has changed for good: search engines transfer their ranking and browsers may cache the redirect for a long time, so a 301 you later regret keeps sending users to the wrong place from their own machines long after you fixed the server. 302 Found is temporary and the original URL stays authoritative. Send 301 for a real domain or path migration and 302 for anything you might reverse, such as a maintenance page or a temporary A/B split.
502 or 503? What does each one tell you about where to look?
502 Bad Gateway comes from a server acting as a proxy, and it means the server behind it returned an invalid response or none at all. The thing you are talking to is healthy; the thing behind it is not, so after a deploy a 502 usually means the application process died or never started. 503 Service Unavailable comes from a server that is up and answering you fine but cannot do the work right now, because it is overloaded or in maintenance, and it may carry a Retry-After header telling the client when to come back. Both are 5xx, so in neither case did the client do anything wrong.
A browser already has a copy of the page. How does it avoid downloading it again?
Two mechanisms, and they save different things. The 200 carried Cache-Control: max-age=60, so for 60 seconds after storing it the browser reuses its copy and sends no request at all, which saves the whole round trip. Once that window passes it does not re-download either; it sends a conditional request, the same GET plus If-None-Match carrying the ETag it stored. If the tag is still current the server answers 304 Not Modified, a status line and headers with no body, and the browser keeps using the copy it already had. So freshness saves the request and validation saves the payload.
no-cache and no-store sound like the same thing. What is the difference?
no-cache is badly named: it means store it, but you must check with the origin before every single reuse. The response is still written to disk. no-store means do not write it down anywhere, not on disk and not in memory, and it is the one you want for a bank statement, a password reset page or anything you would not want recovered from a shared machine. If someone tells you they used no-cache to keep sensitive data out of the cache, they have not: they have only forced a revalidation.
Is HTTPS a different protocol from HTTP?
No. It is exactly the same HTTP, with the same methods, the same headers and the same status codes, carried as application data inside a TLS session on port 443 instead of port 80. The order is TCP first, then the TLS handshake, then the ordinary HTTP request travelling as encrypted records. TLS gives three things worth naming separately: confidentiality, nobody in the middle can read it; integrity, nobody can alter it undetected; and authentication, the certificate binds that hostname to that key and was signed by an authority your machine already trusts. The third is the one people forget, and without it encryption would be encryption with whoever intercepted you.
On HTTPS, what is actually hidden and what is not?
Hidden: the method, the full path and query string, every header including Cookie, the request and response bodies, and the status code. Not hidden: the destination IP address and port, because the packet has to be routable; the hostname in the TLS ClientHello, which is sent in the SNI extension before encryption begins so the server can choose a certificate; and the size and timing of your traffic, which is often enough to guess which page of a known site you loaded. The DNS lookup that found the address is plaintext too, unless DNS over HTTPS or DNS over TLS is in use. Encrypted Client Hello is the fix for SNI and is not universal yet.
HTTP/1.1, HTTP/2, HTTP/3. What problem does each one solve?
HTTP/1.1 made connections persistent so one TCP connection carries many requests, and made Host mandatory so one IP address can serve many sites. What it left behind is head of line blocking: responses come back in the order the requests went out, so one slow response stalls the queue, and browsers worked around it by opening about six connections per host. HTTP/2 replaced text with a binary framing layer and multiplexed many streams over one connection, plus HPACK header compression, which killed the blocking at the HTTP layer but not at the TCP layer, because one lost segment still stalls every stream. HTTP/3 could only fix that by leaving TCP, so it runs on QUIC over UDP with per-stream loss recovery and TLS 1.3 folded into the transport handshake.

08 Practice problems

Six to work through with a pen

Write out the header lines before you answer any of these. Three of the six turn on caching, one turns on what a cookie attribute is actually defending against, one turns on a property a method does not have, and the last cannot be answered without deciding what a filename is allowed to promise.

What the 304 was worth

Easy
A page has a 62,000-byte body. The request that fetched it was 130 bytes and the 200 response carried 240 bytes of headers. Later the browser revalidates: the conditional request is 180 bytes and the 304 that comes back is 120 bytes with no body. Give the bytes on the wire for the first fetch and for the revalidation, give the saving as a percentage of the first fetch, then redo both figures for a page whose body is 620 bytes instead of 62,000 and say what the two percentages together tell you.
Follow-up
Revalidation is not free, and the percentage is not the same number for both pages even though every header count is identical. The second calculation is the point of the question, not a repeat of the first.
Show the hint
Add both directions of each exchange before you divide anything, then ask what the saving is a percentage of once the body shrinks a hundredfold.

Write the Set-Cookie line

Easy
A bank issues a session identifier that must disappear when the browser is closed, must never travel over plain HTTP, must be unreadable by any script running on the page, and must not be attached to a request that another website caused the browser to make. Write the single Set-Cookie header that satisfies all four, then name, for each attribute you wrote, the one attack it prevents.
Follow-up
Three of the four requirements are attributes you add and the fourth is an attribute you must not write, so part of the correct answer is an absence.
Show the hint
A cookie with no lifetime written on it already has a lifetime. Work out what that lifetime is before you decide whether anything needs adding.

The tag that changed underneath

Medium
A browser holds a cached copy of /prices.json stored at t = 0 with ETag "k4" and Cache-Control: max-age=30. At t = 20 the file is edited on the server and its tag becomes "k9". The page requests it at t = 45 and again at t = 90. For each of the two requests say what the browser sent, what the server answered, whether a body crossed the network, and what the stored entry holds afterwards. Then give the total number of times the body of /prices.json crossed the network, counting the fetch that filled the cache at t = 0.
Follow-up
Nothing told the browser about the edit at t = 20, and the request at t = 45 is not wasted even though it does return a body. The freshness clock also does not restart where you probably expect.
Show the hint
The browser can only ask a question about the tag it is currently holding, so settle what tag it holds at each moment before you decide what the server is able to answer.

The payment that ran twice

Medium
A checkout page issues POST /payments. The response is lost in the network, the client retries the byte-identical request, and the customer is charged twice. State in one clause why HTTP permits this, then describe the change to the request that makes the retry harmless without changing the method, and say what the server must do when it sees the second copy and which status code it should return.
Follow-up
The fix is not a different method, and it is something the client has to decide before the first attempt rather than after the failure. The status code in the last part is not an error.
Show the hint
The two attempts are indistinguishable to the server unless the client hands it something that tells them apart, and only the client is in a position to decide what that something is.

The page that leaked between users

Medium
An account page showing the signed-in user’s name is served with Cache-Control: max-age=300 and nothing else, through a CDN that sits between every user and the origin. Describe the failure this produces, name the single directive that fixes it while leaving the browser’s own cache working normally, and say which of the three places a cache can live is at fault and why the other two are not.
Follow-up
Nothing is wrong with the browser cache here. The bug exists only because one of the three caches has more than one person behind it, and the response headers never told it so.
Show the hint
Ask what a cache uses as the key for a stored entry, and whether anything in that key says who asked for it.

Cache the whole application

Hard
An app ships index.html (2,000 bytes), app.4f9c1b.js (300,000 bytes) and logo.a71e33.svg (8,000 bytes). The two hashed filenames change whenever their contents change; index.html keeps its name forever. Choose a Cache-Control value for each of the three and justify each in one sentence. Then, assuming every exchange costs 200 bytes of headers and a 304 costs 120 bytes in total, work out the bytes a returning visitor transfers on a deploy that changed only the JavaScript, and the bytes on a visit where nothing changed at all.
Follow-up
One of the three files must never be cached for long and the other two can be cached for a year, and the reason is a property of the filename rather than of the content. Note that index.html itself changes on that deploy.
Show the hint
Ask which files a browser could be told to keep for a year without ever being wrong, then ask what has to happen to a file’s name to make that promise safe.