Computer Networks

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

  1. The Shape of HTTP
  2. Methods
  3. Status Codes
  4. Headers That Matter
  5. Statelessness, Cookies and Sessions
  6. Caching
  7. 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",
    }, body

7. 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

  1. Why would implementing a state-changing GET endpoint eventually cause data loss without anyone attacking you?
  2. Distinguish 401 from 403, and say what a client should do differently for each.
  3. Why does no-cache not 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