The Application Layer
HTTP Fundamentals
GET /articles/42 HTTP/1.1 ← method, path,
JrCodex·9 min read
Jr Codex Computer Networks Notes
Level: Intermediate Prerequisites: Chapter 1: DNS Time to complete: ~25 minutes
Table of Contents
- The Shape of HTTP
- Methods
- Status Codes
- Headers That Matter
- Statelessness, Cookies and Sessions
- Caching
- Summary & Next Steps
1. The Shape of HTTP
A Request
─────────────────────────────────────────
GET /articles/42 HTTP/1.1 ← method, path,
Host: example.com version
User-Agent: curl/8.4.0
Accept: application/json
Accept-Encoding: gzip, br
← BLANK LINE ends
the headers
(optional body)
A Response
─────────────────────────────────────────
HTTP/1.1 200 OK ← version, code,
Content-Type: application/json reason
Content-Length: 61
Cache-Control: max-age=300
ETag: "a3f9c1"
{"id":42,"title":"Networks","author":"Asha"}
─────────────────────────────────────────
Text, and Why That Matters
─────────────────────────────────────────
HTTP/1.1 is plain text, line-oriented, with a
blank line separating headers from body.
You can speak it by hand:
printf 'GET / HTTP/1.1\r\nHost: example.com\r\n\
Connection: close\r\n\r\n' | nc example.com 80
That readability is a large part of why HTTP won.
It also cost bandwidth and parsing time — which
HTTP/2 reversed by going binary (Chapter 3).
NOTE: line endings are CRLF (\r\n), not \n. A
hand-written client using \n alone will hang.
─────────────────────────────────────────
Why the Host Header Exists
─────────────────────────────────────────
TCP connects to an IP ADDRESS. One address may
host a thousand websites.
The server cannot know which site you want from
the connection alone — so the Host header carries
it. Mandatory since HTTP/1.1.
This is VIRTUAL HOSTING, and it is why shared
hosting is possible at all. With HTTPS the same
problem appears one layer down, solved by SNI
(Module 6, Chapter 3).
─────────────────────────────────────────
2. Methods
The Methods and Their Properties
─────────────────────────────────────────
METHOD SAFE IDEMPOTENT BODY Purpose
─────────────────────────────────────────
GET ✓ ✓ no retrieve
HEAD ✓ ✓ no headers only
OPTIONS ✓ ✓ no what is allowed
PUT ✗ ✓ yes replace wholly
DELETE ✗ ✓ no remove
POST ✗ ✗ yes create, or
anything else
PATCH ✗ ✗ yes partial update
─────────────────────────────────────────
The Two Properties, Precisely
─────────────────────────────────────────
SAFE does not modify state. A crawler may
call it freely.
IDEMPOTENT calling it N times has the SAME
EFFECT as calling it once.
These are not academic. They determine what
infrastructure may do WITHOUT ASKING YOU:
- a browser may PREFETCH safe methods
- a proxy may CACHE safe methods
- a client library may AUTOMATICALLY RETRY
idempotent methods on a timeout
- a load balancer may replay an idempotent
request to another server
Implement a GET that deletes something, and some
crawler will eventually delete your data. This
has happened to real systems.
─────────────────────────────────────────
Why DELETE Is Idempotent but POST Is Not
─────────────────────────────────────────
DELETE /articles/42 twice: the article is gone
after the first, and still gone after the second.
Same end state. The second returns 404, and that
is fine — idempotence is about EFFECT, not
response.
POST /articles twice: two articles created. The
effect differs.
This is exactly why a payment API needs an
idempotency key (DBMS Notes, Module 7, Chapter 5)
— POST cannot be safely retried without one.
─────────────────────────────────────────
3. Status Codes
The Five Classes
─────────────────────────────────────────
1xx informational — rare (100 Continue,
101 Switching Protocols)
2xx SUCCESS
3xx REDIRECTION — more action needed
4xx CLIENT ERROR — your request was wrong
5xx SERVER ERROR — the request was fine, the
server failed
The 4xx/5xx split is the important one: it says
WHO should fix it.
─────────────────────────────────────────
The Ones Worth Knowing Precisely
─────────────────────────────────────────
200 OK
201 Created include a Location header
204 No Content success, deliberately
empty body
301 Moved Permanently CACHED BY BROWSERS,
sometimes forever. Be
certain.
302 Found temporary
304 Not Modified your cached copy is still
valid (Section 6)
307/308 like 302/301 but the
METHOD is preserved
400 Bad Request malformed
401 Unauthorized actually means
UNAUTHENTICATED — you have
not identified yourself
403 Forbidden authenticated, and not
allowed
404 Not Found
405 Method Not Allowed
409 Conflict e.g. a version mismatch
422 Unprocessable syntactically valid,
semantically wrong
429 Too Many Requests include Retry-After
500 Internal Error an unhandled exception
502 Bad Gateway an upstream returned
garbage
503 Unavailable overloaded or in
maintenance; retryable
504 Gateway Timeout an upstream did not
respond in time
─────────────────────────────────────────
Distinctions People Get Wrong
─────────────────────────────────────────
401 vs 403
401 = "I do not know who you are." Retry WITH
credentials.
403 = "I know who you are, and no." Retrying
is pointless.
301 vs 302
301 is cached aggressively and hard to undo.
Use 302 or 307 unless the move is genuinely
permanent.
502 vs 503 vs 504
502 the upstream replied with something invalid
503 the server itself cannot serve right now
504 the upstream did not reply in time
Three different components to investigate.
─────────────────────────────────────────
4. Headers That Matter
Request Headers
─────────────────────────────────────────
Host which virtual host (Section 1)
Authorization credentials
Accept preferred response types
Accept-Encoding compression the client
supports
If-None-Match conditional on an ETag
(Section 6)
Range partial content — resumable
downloads
User-Agent client identification
Response Headers
─────────────────────────────────────────
Content-Type the MIME type. Get this wrong
and browsers guess, sometimes
dangerously.
Content-Length body size — needed for framing
Content-Encoding gzip, br
Cache-Control caching policy (Section 6)
ETag a version identifier
Location where to go (with 3xx or 201)
Set-Cookie state (Section 5)
Strict-Transport- force HTTPS
Security
─────────────────────────────────────────
FRAMING, AGAIN
─────────────────────────────────────────
Module 4, Chapter 1 said TCP has no message
boundaries, so every protocol must define its
own.
HTTP does it two ways:
Content-Length: 61
read exactly 61 bytes of body
Transfer-Encoding: chunked
a series of size-prefixed chunks, ending with
a zero-size chunk
── used when the length is not known in
advance, e.g. streamed output
Getting this wrong is exactly the class of bug
called REQUEST SMUGGLING: when a proxy and a
server disagree about where one request ends,
an attacker can hide a second request inside the
first.
─────────────────────────────────────────
5. Statelessness, Cookies and Sessions
HTTP Is Stateless
─────────────────────────────────────────
Every request is independent. The server
remembers nothing between them.
+ any server can handle any request, so scaling
is trivial
+ no per-client state to clean up
- "who is this?" must be answered on EVERY
request
─────────────────────────────────────────
Cookies
─────────────────────────────────────────
Server: Set-Cookie: session=abc123; HttpOnly;
Secure; SameSite=Lax;
Max-Age=3600
Client: Cookie: session=abc123
── on every subsequent request to that
domain
THE SECURITY ATTRIBUTES ARE NOT OPTIONAL
HttpOnly JavaScript cannot read it. Blocks
cookie theft via cross-site
scripting.
Secure sent only over HTTPS. Blocks
interception.
SameSite controls sending on cross-site
requests. The main defence against
CSRF.
Lax is a reasonable default;
Strict is safer and breaks some
flows.
A session cookie without HttpOnly and Secure is a
vulnerability, not a configuration preference.
─────────────────────────────────────────
Sessions vs Tokens
─────────────────────────────────────────
SERVER-SIDE SESSIONS
The cookie holds an opaque ID; the server holds
the data.
+ revocable instantly, small cookie
- server state, so it must be shared across
instances
TOKENS (JWT)
The token itself carries signed claims.
+ stateless, so any server can validate it
- CANNOT BE REVOKED before expiry without
reintroducing state
- larger, sent on every request
The trade is revocability against statelessness.
Short expiry plus refresh tokens is the usual
compromise.
─────────────────────────────────────────
6. Caching
Cache-Control Directives
─────────────────────────────────────────
max-age=3600 fresh for 3600 seconds
no-cache MUST revalidate before use
(it does NOT mean "do not
store")
no-store genuinely do not store —
for sensitive data
private browsers only; not shared
proxies
public any cache may store it
immutable never revalidate; the content
will never change
stale-while- serve stale while fetching
revalidate=60 fresh in the background
─────────────────────────────────────────
CONDITIONAL REQUESTS — the 304 mechanism
─────────────────────────────────────────
First response:
ETag: "a3f9c1"
Last-Modified: Mon, 01 Sep 2026 12:00:00 GMT
Later request:
If-None-Match: "a3f9c1"
Server replies:
304 Not Modified ── NO BODY
The client reuses its cached copy. The round trip
still costs latency, but the BODY — which may be
megabytes — is not transferred.
─────────────────────────────────────────
THE PATTERN THAT WORKS
─────────────────────────────────────────
For assets you control the URLs of:
CONTENT-HASHED FILENAMES
app.a3f9c1.js
Cache-Control: max-age=31536000, immutable
Cache forever. When the content changes, the
FILENAME changes, so there is nothing to
invalidate.
For the HTML that references them:
Cache-Control: no-cache
── always revalidated, so new asset URLs are
picked up immediately
This combination gives near-perfect caching with
instant deploys, and it is why every modern build
tool hashes filenames.
─────────────────────────────────────────
import hashlib
def conditional_response(request_headers, body: bytes, content_type: str):
"""The whole 304 mechanism in a dozen lines."""
etag = '"' + hashlib.sha256(body).hexdigest()[:16] + '"'
if request_headers.get("If-None-Match") == etag:
return 304, {"ETag": etag, "Cache-Control": "max-age=300"}, b"" # NO body
return 200, {
"ETag": etag,
"Content-Type": content_type,
"Content-Length": str(len(body)),
"Cache-Control": "max-age=300",
}, body7. Summary & Next Steps
Key Takeaways
- The Host header exists because TCP connects to an address rather than a site, and it is what makes virtual hosting possible.
- Safe and idempotent are contracts infrastructure acts on without asking: prefetching, caching and automatic retries all depend on them.
- 401 means unauthenticated and 403 means authenticated-but-forbidden; 301 is cached aggressively and should be used only when the move is genuinely permanent.
- Content-hashed filenames with immutable caching, plus a revalidated HTML document, gives perfect caching and instant deploys.
Concept Check
- Why would implementing a state-changing GET endpoint eventually cause data loss without anyone attacking you?
- Distinguish 401 from 403, and say what a client should do differently for each.
- Why does
no-cachenot mean "do not store", and which directive does?
Next Chapter
→ Chapter 3: HTTP/2, HTTP/3 and Performance
Jr Codex — 1-on-1 Personalized Coaching | Back to Module Index | Back to Computer Networks Index