Skip to main content

URLs and HTTP Basics

TL;DR
  • A URL names a resource and tells the client how to reach it: the host goes to DNS, the port goes to TCP, the path and query go in the request line, and the fragment never leaves the browser.
  • An HTTP message is text lines, then a blank line, then an optional body: a start line, header fields, an empty line, the content. A 70-byte GET is a complete request.
  • Methods carry meaning. GET, HEAD and OPTIONS are safe; GET, HEAD, OPTIONS, PUT and DELETE are idempotent; POST is neither, and PATCH is not guaranteed to be (RFC 9110, section 9.2).
  • HTTP is stateless: each request must carry everything the server needs, and the server may forget you the moment it replies. Logins survive because the client re-sends a cookie or token every time.
  • Before the first byte of HTTP, a cold HTTPS fetch has already paid for DNS plus 3 round trips (TCP, TLS 1.3, then the request itself). Reusing the connection is what makes later requests cheap.

Mental model​

ANALOGYEveryday

Ordering at a counter with a paper form. The first line of the form says what you want (GET, the thing you want, the form version). The boxes underneath are headers: who you are, what you can accept, what language you prefer. A blank line ends the boxes, and anything stapled behind it is the body. The clerk reads the form, does the job, and hands back a form of their own: a result code, their own boxes, and the goods.

Where it breaks: a real counter clerk remembers your face. An HTTP server, by design, does not: the next form from you looks like a form from a stranger, so anything the clerk should remember (who you are, what is in your basket) has to be written on the form every time. A cookie is exactly that: a ticket the clerk gave you earlier, which you staple to every future form.

ANALOGYFor engineers

An HTTP exchange is a function call over the wire. The method is the verb, the URL identifies the resource being operated on, headers are metadata and context (like a call's options or an auth token), the body is the payload argument, and the status code is the return code.

Where it breaks: a function call shares memory and either returns or throws. An HTTP call shares nothing (every argument must be serialised into the message, which is why servers are "stateless"), and it can fail in the worst possible way: the request was applied but the reply was lost, so the caller sees only a timeout and cannot tell whether the work happened. That uncertainty is why idempotency matters so much later on this page.

How it works​

Anatomy of a URL​

A URL is five or six parts glued together by punctuation, defined by RFC 3986 section 3. Each part has a different consumer, and that is the useful way to remember them.

https://www.example.com:8443/shop/cart?item=42#reviewsschemehostportpathqueryfragmentchooses TLSresolved by DNSTCP connectsrequest linerequest linebrowser onlynever sentWhat the server receives:GET /shop/cart?item=42 HTTP/1.1Host: www.example.com:8443
Six parts, four different consumers. The scheme decides TLS and the default port, the host feeds DNS, the port feeds TCP, and the path and query travel in the request line. The fragment is dashed because it is never sent.
  • Scheme (http, https) picks the protocol and the default port: 80 for http, 443 for https. A URL may omit the port when it is the default, and Host omits it the same way.
  • Host is looked up by DNS to get an IP address. It is case-insensitive.
  • Port is the TCP port the client connects to. See how a TCP connection opens.
  • Path names the resource on that server. It is case-sensitive in general: /Cart and /cart may be different resources (RFC 3986 section 6.2.2.1 normalises only scheme and host).
  • Query (after ?) is extra parameters for the resource. Path and query together are the request target in the request line.
  • Fragment (after #) names a part of the document the response returns, such as a heading. The client strips it before it builds the request, so the server never sees it (RFC 3986 section 3.5; the request target grammar in RFC 9112 section 3.2 has no fragment).

What an HTTP message looks like​

HTTP/1.1 messages are plain text. A request is a request line, zero or more header fields, an empty line, and an optional body. A response has the same shape with a status line first (RFC 9112 section 2.1).

RequestResponseGET /index.html HTTP/1.1Host: www.example.comAccept: text/htmlHTTP/1.1 200 OKDate: Sat, 10 Oct 2026 09:30:00 GMTServer: nginxContent-Type: text/htmlContent-Length: 137blank line: ends the headersno body for a GETblank line: ends the headersbody: 137 bytes of HTMLstart lineheadersblank linebodystart lineheadersblank linebody70 bytes, no body117-byte head + 137-byte body
The same shape on both sides: a start line, header lines, one blank line, then the body. The blank line is how the parser knows the headers are over. Byte counts are derived on this page, not estimated.

Here is that request exactly as it goes on the wire. Every line ends with CRLF (a carriage return then a line feed, two bytes, written \r\n in most languages); the last line shown below is the empty line that ends the headers.

GET /index.html HTTP/1.1
Host: www.example.com
Accept: text/html

The byte count, derived line by line (text length plus the 2-byte CRLF):

LineText bytesWith CRLF
GET /index.html HTTP/1.12426
Host: www.example.com2123
Accept: text/html1719
(empty line)02
Total6270

So the request is 70 bytes, and it has 2 header lines (Host and Accept) plus the request line. That fits in a single TCP segment with a huge margin (a typical Ethernet segment carries up to 1,460 bytes of payload). A minimal reply to it:

HTTP/1.1 200 OK
Date: Sat, 10 Oct 2026 09:30:00 GMT
Server: nginx
Content-Type: text/html
Content-Length: 137
 
<html>... 137 bytes of markup ...</html>

The status line and four headers are 117 bytes including the final empty line (15 + 35 + 13 + 23 + 19 = 105 text bytes, plus 12 bytes of CRLF for the six lines), followed by the 137-byte body. For a small page the headers are close to half the response, which is one reason HTTP/2 compresses them (see HTTP/1.1 and HTTP/2). Status codes, redirects and the rules for retrying are covered on the HTTP status and semantics page; here we only need to know that the three-digit code in the status line is the return value.

Pick a scenario and step through a cold HTTPS exchange: DNS, TCP, TLS, then the request and response, with the raw bytes in a curl -v style transcript. The scenarios cover the main status classes; tick Edit to change the method, headers, body or status yourself:

current stepdonepending2xx success3xx redirection4xx client error5xx server error
  1. nowDNS lookup+1 RTT, total 1
  2. pendingTCP handshake+1 RTT, total 2
  3. pendingTLS 1.3 handshake+1 RTT, total 3
  4. pendingGET /index.htmlno RTT yet, total 3
  5. pendingHTTP/1.1 200 OK+1 RTT, total 4
* Host www.example.com resolved to 203.0.113.10
Rule check
No HTTP rule violations in this exchange.
DNS lookup. Resolve www.example.com to an IP address. One round trip to the resolver; free if cached. Round trips so far: 1 (DNS 1, TCP 1, TLS 1, then one per request).
step 0 / 4
CAUTION

Every scenario is a valid pairing. Edit mode lets you build pairs a real server would not send, such as a 304 with a body or a 201 with no Location; the rule check lists each one as text, but it does not stop you.

Methods: the verb, and what each one promises​

The method tells the server what to do with the target and, more importantly for infrastructure, what the client promises about side effects. RFC 9110 section 9 gives each method three properties that caches, proxies and retry logic rely on.

MethodSafeIdempotentRequest bodyCacheableTypical use
GETyesyesno defined meaningyesread a resource
HEADyesyesno defined meaningyesheaders only: existence, size, freshness
OPTIONSyesyesoptionalnoask what a resource allows; CORS preflight
POSTnonoyesonly with explicit freshness info (rare)create or process: the catch-all
PUTnoyesyesnoreplace the resource at a known URL
PATCHnonot guaranteedyesonly with explicit freshness info (rare)partial update
DELETEnoyesno defined meaningnoremove a resource
  • Safe means the client does not request or expect any state change on the server, so it can be called freely by crawlers, prefetchers and retries.
  • Idempotent means that sending the identical request N times has the same intended effect on the server as sending it once. The response may differ: the second DELETE of the same URL typically reports that the thing is already gone.
  • Cacheable means a cache is allowed to reuse the response. RFC 9110 defines caching for GET, HEAD and POST, though almost every cache implements only GET and HEAD. PATCH is defined in RFC 5789.

Headers: metadata about the message​

Header fields are Name: value lines. They split by direction, though many apply to both. The old HTTP/1.1 term "entity headers" (describing the body) is called representation metadata in RFC 9110; in practice people still say entity headers for Content-Type, Content-Length and Content-Encoding.

HeaderDirectionPurposeExample
Hostrequestwhich site on this server (required in HTTP/1.1)Host: www.example.com
Acceptrequestmedia types the client can handle, with weightsAccept: text/html,/;q=0.8
Accept-Encodingrequestcompressions the client can decodeAccept-Encoding: gzip, br
Accept-Languagerequestpreferred natural languagesAccept-Language: en-US,en;q=0.9
User-Agentrequestclient software name and versionUser-Agent: curl/8.7.1
Authorizationrequestcredentials for the targetAuthorization: Bearer TOKEN
Cookierequeststate the server asked the client to keepCookie: sid=abc123
Content-Typebothmedia type of the bodyContent-Type: application/json
Content-Lengthbothsize of the body in bytesContent-Length: 137
Transfer-Encodingbothhow the body is framedTransfer-Encoding: chunked
Connectionbothoptions for this connection onlyConnection: close
Cache-Controlbothcaching rules (see HTTP caching)Cache-Control: max-age=3600
Content-Encodingresponsecompression applied to the bodyContent-Encoding: gzip
Dateresponsewhen the response was generatedDate: Sat, 10 Oct 2026 09:30:00 GMT
Serverresponseserver softwareServer: nginx
Locationresponsewhere to go next (redirects, created resources)Location: /login
Set-Cookieresponseask the client to store a cookieSet-Cookie: sid=abc123; HttpOnly

Cache-Control and validators are owned by HTTP caching. Cookies, sessions and tokens are owned by Cookies, sessions and CORS.

What happens before the request​

HTTP does not start on its own. Before the first byte of the request the client has to find the server and open a channel. For https://www.example.com/ on a cold start:

ClientDNS resolverServerDNS queryDNS answerSYNSYN-ACKACK + ClientHelloServerHello to FinishedFinished + GET /200 OK + bodyDNS: 1 RTTTCP: 1 RTTTLS 1.3: 1 RTTHTTP: 1 RTTto the resolver; 0 if cachedSYN to SYN-ACKTLS 1.2 would take 2GET to first byte
A cold HTTPS fetch. Each phase costs one round trip, so the first byte of the page arrives after DNS plus 3 RTT. Slanted arrows show time passing as a message crosses the network.
  1. DNS: turn www.example.com into an IP address (DNS). Free if cached; otherwise at least one round trip to your resolver, more if the resolver has to walk the hierarchy.
  2. TCP: the three-way handshake costs 1 RTT before the client can send data (TCP connection).
  3. TLS: for https, a TLS 1.3 handshake adds 1 RTT, and TLS 1.2 adds 2 (TLS and HTTPS).
  4. HTTP: the request and its response take 1 RTT plus the server's think time.

That is DNS + 3 RTT to the first byte of a cold HTTPS page, the same budget What Happens When You Type a URL walks through with caches and a CDN. HTTP/3 over QUIC merges the transport and TLS steps and lowers it.

One connection per object, or reuse it​

Early HTTP (version 0.9 and 1.0) opened a fresh TCP connection for every object and closed it after the response: non-persistent. HTTP/1.1 keeps the connection open by default and reuses it: persistent.

non-persistentpersistentclose after each responseconnection reusedTCPGET 1TCPGET 2TCPGET 3TCPGET 1GET 2GET 30123456 RTTdashed: TCP handshake. solid: one request and response.total: 4 RTT
Fetching three objects one after another, with 1 RTT per handshake and 1 RTT per request and response (TLS and transfer time ignored). Reuse pays the handshake once.

For N objects fetched sequentially the totals are 2N RTT non-persistent (a handshake and a request each) and N + 1 RTT persistent (one handshake, then one request each). With N = 10 and a 50 ms RTT that is 20 RTT = 1,000 ms against 11 RTT = 550 ms, before counting TLS (which makes non-persistent worse again, one handshake per object). How browsers recover parallelism, and why one slow response still holds up the connection, is the next page: HTTP/1.1 and HTTP/2.

Deep dive​

Safe, idempotent, cacheable: why the distinction exists​

These three words are a contract between the client and the infrastructure in between.

  • Safe lets a crawler or a link prefetcher call a URL without asking permission. A GET /delete?id=7 that actually deletes breaks that contract, and the first prefetcher or search crawler to visit the link will fire it.
  • Idempotent lets a client or proxy retry automatically after a communication failure. RFC 9110 section 9.2.2 says a client can repeat an idempotent request if the connection closed before it read the response, and that a client SHOULD NOT automatically retry a non-idempotent method and a proxy MUST NOT. Idempotency is about the intended effect on the server, not about identical responses.
  • Cacheable lets a shared cache answer without bothering the origin.

PUT is idempotent because it replaces the resource with the supplied content: sending it five times leaves the same state as once. POST is not, because "process this" may mean "append another order". PATCH carries a delta, and a delta like "append this item" is not idempotent while "set status to shipped" is, so the RFC makes no promise either way. DELETE is idempotent even though the second call usually reports "not found": the intended effect, the resource being gone, is the same.

Why Host is mandatory​

An IP address names a machine, not a website. Early servers hosted one site per IP address, but one server can host many sites on one address (virtual hosting), and IPv4 addresses were scarce. The only place the name can travel is inside the request, so HTTP/1.1 requires it: a client MUST send a Host header in every request, and a server MUST answer 400 to any HTTP/1.1 request with no Host, or with more than one (RFC 9112 section 3.2). HTTP/2 and HTTP/3 carry the same information in the :authority pseudo-header.

Host is not the only place the name appears. For https, the TLS ClientHello also carries it as SNI so the server can pick the right certificate before any HTTP is decrypted (see TLS and HTTPS). SNI chooses the certificate; Host chooses the site. If they disagree the server may reject the request; HTTP defines 421 Misdirected Request for requests sent to a server that cannot answer for that name (RFC 9110 section 15.5.20).

Where the body ends: Content-Length or chunked​

A persistent connection carries message after message, so the receiver needs to know exactly where each body stops. HTTP/1.1 offers two ways, and a response to HEAD, a 204 and a 304 have no body at all (RFC 9112 section 6.3).

  • Content-Length: N: the body is exactly N bytes. Simple, but the sender must know the size before it starts.
  • Transfer-Encoding: chunked: the body is a series of chunks, each prefixed with its size in hex, ending with a zero-size chunk. The sender can start streaming before it knows the total (generated pages, streamed responses). The curl --http1.1 run in Hands-on shows a real server doing this.

A sender must not use both. If a message arrives with both, Transfer-Encoding wins and the receiver should treat the message as suspect.

HEAD and OPTIONS​

HEAD is GET without the body: the server returns the same status and headers it would for a GET, which is how you check that a resource exists, how large it is (Content-Length) and whether it changed, without paying for the transfer. OPTIONS asks what a target supports, answered in an Allow header; browsers also use it for the CORS preflight (covered on the cookies, sessions and CORS page).

Content negotiation​

One URL can have several representations (a language, a compression, a media type). In proactive negotiation the client states its preferences in request headers and the server chooses (RFC 9110 section 12):

  • Accept for media type (application/json, text/html).
  • Accept-Encoding for compression (gzip, br).
  • Accept-Language for language (en-US).

Each value may carry a quality weight q from 0 to 1 (default 1): Accept: text/html,application/json;q=0.9,*/*;q=0.8 means "HTML first, JSON almost as good, anything else if you must". The server answers with Content-Type, Content-Encoding and Content-Language to say what it chose, and a Vary header to tell caches which request headers affected the choice (see HTTP caching).

Statelessness​

RFC 9110 section 3 describes HTTP as a stateless request/response protocol: the server does not have to keep anything about a client between requests. That is a feature: any server behind a load balancer can answer any request, and a crashed server loses nothing about the protocol itself. Applications still need memory (who is logged in, what is in the basket), so they put it back by having the client send an identifier or credential on every request, usually a cookie or an Authorization header. The details belong to the cookies, sessions and CORS page.

Request smuggling, briefly​

When a front-end proxy and a back-end server disagree about where one request ends (for example about Content-Length versus Transfer-Encoding), an attacker can hide a second request inside the first, and it is processed as if it arrived on its own. RFC 9112 section 11.2 names the risk, and PortSwigger's HTTP Desync Attacks research showed it working against real deployments. The defence is strict parsing and refusing ambiguous messages; see network attacks for the wider attack picture.

Textbook vs real world​

The textbook saysProduction doesWhy
HTTP is the protocol of web pagesMost clients are apps, mobile SDKs, service-to-service calls and webhooks; a browser is one client among manyThe same request/response semantics work for any client that can open a TCP (or QUIC) connection, so APIs reuse them
Methods map to CRUD: POST create, GET read, PUT update, DELETE deletePOST is used for everything that is not a clean read, including reads that need a body (GraphQL queries are commonly POSTed), and GET sometimes mutates by mistakePOST is the catch-all with no stricter promise; a GET that mutates gets triggered by crawlers, prefetchers and retries
URLs are case-insensitiveScheme and host are; path and query are case-sensitive on most serversRFC 3986 normalises only scheme and host; the path is whatever the server decides it means
Every request opens a connectionConnections are persistent and browsers keep several open per hostHandshakes cost round trips; reuse pays them once
A header is a small thingHeaders are repeated in full on every HTTP/1.1 request, which is the overhead HTTP/2 and HTTP/3 compressHTTP/1.1 has no compression for header fields, so a cookie is re-sent byte for byte on each request

In production​

  • Stripe makes POST safe to retry with an Idempotency-Key header: for the first request with a given key it saves the status code and body (after execution begins), returns that saved response on retries, errors if the retry's parameters differ, and may remove keys after they are at least 24 hours old (Stripe API: idempotent requests).
  • AWS EC2 takes a different route to the same end: a ClientToken request parameter makes an API call idempotent, so a retried RunInstances does not launch a second set of instances (EC2 API: ensuring idempotency). It is a parameter, not a header, which is why this page does not claim AWS "uses Idempotency-Key".
  • Idempotency keys are heading toward a standard. An IETF HTTPAPI working-group Internet-Draft defines an Idempotency-Key header field (draft-ietf-httpapi-idempotency-key-header). Check its datatracker page for current status rather than trusting this page.
  • Reverse proxies add the client's address to the request because the origin otherwise sees only the proxy: the standardised header is Forwarded (RFC 7239) and the older de facto one is X-Forwarded-For. The origin should trust only the entries added by proxies it controls, since a client can send its own.
  • Protocol versions in the wild move over time. Cloudflare Radar publishes the live split of HTTP/1.x, HTTP/2 and HTTP/3 requests (Radar adoption and usage); read the current figure there rather than a number frozen in a page.

History and evolution​

HISTORY
  • 1989 Tim Berners-Lee proposes a linked information system at CERN. By 1991 the prototype speaks what is now called HTTP/0.9: a single line, GET followed by a path, answered with the raw document and then the connection closes. No headers, no status codes, no other methods (W3C: HTTP as implemented in 1991).
  • 1996 HTTP/1.0, RFC 1945. The web needed more than HTML: it adds the version in the request line, a status line, header fields, HEAD and POST, and Content-Type. Still one TCP connection per request, which becomes slow as pages gain images.
  • 1997 HTTP/1.1, RFC 2068, revised in 1999 as RFC 2616. Connections become persistent by default, the Host header becomes mandatory so one IP address can serve many sites, and chunked transfer and richer caching arrive.
  • 2014 the 2616 text is rewritten as six documents, RFC 7230 to 7235, to clarify and tighten the 1999 text.
  • 2022 the specification is restructured again as RFC 9110 (semantics: methods, headers, status codes), RFC 9111 (caching) and RFC 9112 (HTTP/1.1 syntax). Splitting meaning from syntax lets HTTP/2 (RFC 9113) and HTTP/3 reuse the same semantics over a different wire format.
  • Now HTTP/1.1 remains everywhere, with HTTP/2 and HTTP/3 carrying a large share of web traffic; the live split is on Cloudflare Radar. See HTTP/1.1 and HTTP/2 and QUIC and HTTP/3.
  • Next the semantics stay stable while the wire changes underneath, and new methods arrive at the semantic layer. A QUERY method (a safe request that carries a body) has been an IETF HTTP working-group draft; check its status before relying on it.

Hands-on​

Needs only curl; the commands are the same on macOS and Linux. The output below is abbreviated from a real run on macOS with curl 8.7.1 (LibreSSL and SecureTransport); the TLS lines in the banner differ by OS and curl build (OpenSSL on most Linux), but the lines that start with > and < are the HTTP messages and look the same everywhere.

Read a request and response with curl -v:

curl -sv https://example.com -o /dev/null
* using HTTP/2
> GET / HTTP/2
> Host: example.com
> User-Agent: curl/8.7.1
> Accept: */*
>
< HTTP/2 200
< content-type: text/html; charset=utf-8
< server: cloudflare

Lines starting with > are what curl sent and < is what came back; lines starting * are curl's own notes. By default curl offers HTTP/2 over TLS (ALPN), so the request line says HTTP/2 and header names come back in lowercase. Force the text protocol described on this page:

curl --http1.1 -sv https://example.com -o /dev/null
> GET / HTTP/1.1
> Host: example.com
> User-Agent: curl/8.7.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sat, 10 Oct 2026 18:03:43 GMT
< Content-Type: text/html; charset=utf-8
< Transfer-Encoding: chunked
< Connection: keep-alive

Compare it with the 70-byte request on this page: curl adds User-Agent and Accept: */*. And Transfer-Encoding: chunked with no Content-Length is the chunked framing from the Deep dive. The final blank > and < lines are the empty lines that end the headers.

Send only the headers with HEAD:

curl --http1.1 -sI https://example.com
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Connection: keep-alive
allow: GET, HEAD

-I sends HEAD, so there is no body. The allow: header shows this server permits GET and HEAD on that URL.

Send a form POST:

curl -s -d 'a=1' -H 'Content-Type: application/x-www-form-urlencoded' https://httpbin.org/post
{
"form": { "a": "1" },
"headers": {
"Content-Length": "3",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org"
},
"url": "https://httpbin.org/post"
}

-d already switches the method to POST, so adding -X POST is redundant. Content-Length: 3 is the size of a=1, the body. The service is a public echo server and its output can change.

Type the protocol by hand with nc:

nc example.com 80

Then type a request line, a Host header, and press Enter on an empty line (the blank line is what makes the server answer):

GET / HTTP/1.1
Host: example.com
Connection: close

The reply is the status line, headers and body as plain text, or a redirect to https since many sites no longer serve port 80 directly. nc sends a bare LF when you press Enter, not CRLF; servers generally accept it (RFC 9112 section 2.2 lets a recipient treat a lone LF as a line end), but a strict one may not. If it hangs, you probably missed the blank line.

Gotchas​

GOTCHAS5
GET cannot have a body
POST is for create, PUT is for update
HTTP is connection-oriented
The fragment goes to the server
A timeout means the request failed

Interview questions​

INTERVIEWWhat is the difference between GET and POST?junior
Show answer

GET retrieves a resource: it is safe (no state change expected), idempotent and cacheable, and a body has no defined meaning, so parameters go in the query string. POST submits data for the server to process: it is neither safe nor idempotent, it carries a body, and its responses are cacheable only with explicit freshness information. In practice that means GET can be cached, prefetched and retried freely, and POST cannot.

Follow-ups:

  • Why not put secrets in a query string? URLs get written to server and proxy logs, browser history and bookmarks, and sent in the Referer header to other sites (RFC 9110 warns about disclosing sensitive information in URIs). TLS encrypts the query in transit, so the leak is at the endpoints, not on the wire.
  • Is POST therefore secure? No. A POST body is not encrypted by being in the body; TLS protects both. POST only avoids the logging and history exposure of a URL.
INTERVIEWWhat is in an HTTP request and an HTTP response?junior
Show answer

A request is a start line (method, target, version), header fields, an empty line, and an optional body. A response is a status line (version, status code, reason phrase), header fields, an empty line, and an optional body. Headers carry metadata such as Host, Content-Type and Content-Length; the body carries the content.

Follow-ups:

  • What is the blank line for? It marks the end of the headers, so the parser knows where the body starts. After it, Content-Length or chunked framing says how many body bytes to read.
  • How big is a small request? The 70-byte GET on this page, line by line: 26 + 23 + 19 + 2.
INTERVIEWHTTP is stateless: what does that mean, and how do logins persist?junior
Show answer

Each request carries everything the server needs to handle it; the server is not required to remember anything between requests, so any server behind a load balancer can answer any request. Logins persist because the server sends an identifier with Set-Cookie and the client returns it in a Cookie header on every later request (or sends an Authorization token), and the server looks the user up from it.

Follow-ups:

  • Where does the session state live? Either server-side (the cookie is an ID into a store) or inside a signed token the client carries; Cookies, sessions and CORS covers the tradeoffs.
  • Is that a violation of statelessness? No: the state is carried in each request rather than kept in the protocol.
INTERVIEWPUT vs PATCH vs POST: how do they differ?mid
Show answer

PUT replaces the resource at a known URL with the body you send, and is idempotent. PATCH applies a partial change (a delta) and is not guaranteed to be idempotent: "set status to shipped" is, "append this item" is not. POST asks the server to process the body, often to create a new resource under a collection with a server-chosen URL, and is not idempotent.

Follow-ups:

  • Why does idempotency matter for retries? If a connection drops before the response arrives, a client can automatically retry an idempotent request knowing the intended effect is unchanged; it should not do that for a non-idempotent one without extra protection (RFC 9110 section 9.2.2).
  • Does PUT creating a resource and PUT updating it break idempotency? No: the second call changes nothing; only the status code may differ.
INTERVIEWWhat is the Host header and why is it required?mid
Show answer

Host carries the site name and optional port from the URL, so a server with one IP address can host many sites (virtual hosting). The IP and port tell TCP which machine to connect to, but only the request can say which site, so HTTP/1.1 requires Host and a server must reject an HTTP/1.1 request without it with 400.

Follow-ups:

  • How does it relate to SNI? The TLS ClientHello carries the name too, so the server can choose a certificate before any HTTP is read. SNI picks the certificate, Host picks the site. HTTP/2 uses :authority instead of Host.
  • What if they disagree? The server may reject the request; HTTP defines 421 Misdirected Request for a request sent to a server that cannot answer for that name.
INTERVIEWWhat does each part of a URL do, and who consumes it?mid
Show answer

The scheme picks the protocol (and with it TLS and the default port), the host goes to DNS, the port goes to TCP, the path and query go into the request line, and the fragment is used only by the browser. So of https://www.example.com:8443/shop/cart?item=42#reviews the server sees GET /shop/cart?item=42 plus Host: www.example.com:8443.

Follow-ups:

  • What happens to the fragment? It is stripped before the request is built, never sent, and resolved against the returned document in the browser.
  • Which parts are case-sensitive? Scheme and host are not; path and query generally are.
INTERVIEWHow do you make a POST safe to retry?mid
Show answer

Have the client generate a unique idempotency key per logical operation and send it in a header (commonly Idempotency-Key) on every attempt of that operation. The server records the key with the outcome the first time, and when it sees the key again it returns the stored result instead of repeating the work.

Follow-ups:

  • What does the server store? The key, enough of the request to detect misuse (Stripe errors if the same key arrives with different parameters), and the status and body of the first response, kept for a bounded time (Stripe's documentation says keys may be removed after at least 24 hours).
  • What about two attempts racing in parallel? The server needs a lock or in-progress marker on the key so the second attempt waits or gets a conflict rather than running the operation twice.
INTERVIEWCompare persistent and non-persistent connections for N objects, and pipelining vs multiplexing.senior
Show answer

Non-persistent HTTP pays a TCP handshake per object, so fetching N objects in sequence costs 2N RTT (1 for the handshake, 1 for the request and response). Persistent connections pay one handshake and then one RTT per object: N + 1 RTT. With 10 objects at 50 ms that is 1,000 ms against 550 ms, before TLS. Browsers add parallelism by opening several connections per host.

Follow-ups:

  • What is pipelining? Sending request 2 before response 1 has finished on the same connection. Responses must still come back in request order (RFC 9112 section 9.3.2), so one slow response blocks the rest, and buggy intermediaries made it unreliable, so browsers have disabled or removed it.
  • How is multiplexing different? HTTP/2 splits messages into frames tagged with a stream ID and interleaves them on one connection, so responses can complete in any order. That removes HTTP-layer head-of-line blocking but not TCP's; see HTTP/1.1 and HTTP/2.

Fun facts​

FACTSTrivia worth knowing
  • The header is spelled Referer (a misspelling of "referrer") in every HTTP specification. RFC 9110 keeps it and marks it with "[sic]" in its section 10.1.3.
  • HTTP/0.9 had exactly one method, GET, a one-line request, no headers and no status codes. The server sent the document and closed the connection.
  • HTTP/1.1 messages end every line with CRLF, but RFC 9112 lets a recipient accept a lone LF. That leniency is part of why parsers disagree and request smuggling exists.
  • The 70-byte request on this page is under 5 percent of the 1,460 bytes a typical Ethernet TCP segment can carry, so almost the whole segment is headroom.

Cheat sheet​

NUMBERSNumbers to remember
  • 70 bytes - the minimal GET /index.html request on this page (24 + 21 + 17 text bytes plus 4 CRLFs, 8 bytes)
  • 117 bytes - the status line and four headers of the sample response, before its 137-byte body
  • DNS + 3 RTT - cold HTTPS to first byte with TLS 1.3 (TCP, TLS, then the request); 0 DNS if cached, 1 RTT if the connection is reused
  • 2N vs N + 1 RTT - N sequential objects, non-persistent vs persistent, ignoring TLS and transfer time
  • 80 and 443 - default ports for http and https
  • Safe: GET, HEAD, OPTIONS. Idempotent: those plus PUT and DELETE. Neither: POST, and PATCH is not guaranteed.