Back to DAG

HTTP Semantics

networking

HTTP Method Semantics, Caching, Cookies, and CORS

HTTP is more than a wire protocol — its semantics define what methods mean, how caching works, how state is maintained, and how cross-origin security is enforced. These semantics are defined independently of any particular HTTP version (RFC 9110).

Method Semantics

Each HTTP method carries specific guarantees:

  • GET — retrieve a resource. Safe (does not modify server state) and idempotent (repeating the request has the same effect as making it once). Responses are cacheable by default.
  • POST — submit data for processing (create a resource, submit a form). Not safe and not idempotent. Sending the same POST twice may create two resources.
  • PUT — replace the entire resource at the target URI. Idempotent but not safe — PUTting the same data twice leaves the resource in the same state.
  • DELETE — remove the resource. Idempotent — deleting an already-deleted resource returns the same result (success or 404).
  • PATCH — apply partial modifications to a resource. Not idempotent in general — a PATCH that says "increment counter by 1" produces different results each time.
  • HEAD — identical to GET but returns only headers, no body. Useful for checking if a resource exists or reading its Content-Length without downloading it.
  • OPTIONS — describes the communication options for the target resource. Used by CORS preflight requests.

Content Negotiation

The client and server negotiate the representation format using headers:

  • Accept: what the client wants (e.g., Accept: application/json, text/html;q=0.9). The q value (0 to 1) indicates preference.
  • Content-Type: what the server (or client in POST/PUT) is actually sending (e.g., Content-Type: application/json; charset=utf-8).
  • Accept-Encoding: compression algorithms the client supports (gzip, br, zstd).
  • Accept-Language: preferred languages (en-US, en;q=0.9, fr;q=0.5).

Caching

HTTP caching avoids redundant transfers and reduces latency:

  • Cache-Control header directs caching behavior:

    • max-age=3600 — the response is fresh for 3600 seconds.
    • no-cache — the response can be stored but must be revalidated with the server before each use.
    • no-store — the response must not be stored at all (sensitive data like banking pages).
    • public — any cache (including CDNs) may store it. private — only the user's browser may store it.
  • Conditional GET with ETag: the server sends an ETag header (a hash or version identifier). On subsequent requests, the client sends If-None-Match: <etag>. If the resource hasn't changed, the server responds 304 Not Modified with no body — saving bandwidth.

  • Last-Modified / If-Modified-Since: similar to ETag but uses timestamps. Less precise (only second-level granularity) and can break if clocks are skewed.

Cookies

HTTP is stateless — each request is independent. Cookies add state:

  • The server sends Set-Cookie: session=abc123; Path=/; HttpOnly; Secure; SameSite=Lax.
  • The browser stores the cookie and includes Cookie: session=abc123 in every subsequent request to that domain.
  • HttpOnly — the cookie is inaccessible to JavaScript (document.cookie), preventing XSS theft.
  • Secure — the cookie is sent only over HTTPS.
  • SameSite — controls cross-site sending: Strict (never sent cross-site), Lax (sent on top-level navigations), None (always sent, requires Secure).

CORS (Cross-Origin Resource Sharing)

Browsers enforce the same-origin policy: JavaScript on https://app.com cannot fetch from https://api.other.com by default. CORS relaxes this:

  1. For "simple" requests (GET, POST with standard Content-Types), the browser adds an Origin header. The server responds with Access-Control-Allow-Origin: https://app.com.
  2. For "non-simple" requests (PUT, DELETE, custom headers, JSON Content-Type), the browser first sends a preflight OPTIONS request. The server must respond with allowed methods, headers, and origins. Only then does the browser send the actual request.
  3. Access-Control-Allow-Credentials: true allows cookies to be sent with cross-origin requests.

Caching and CORS in Practice

Real-World Example

Conditional GET with ETag:

First request:

GET /api/users/42 HTTP/1.1
Host: api.example.com

Response:

HTTP/1.1 200 OK
ETag: "a1b2c3d4"
Cache-Control: max-age=60
Content-Type: application/json

{"id": 42, "name": "Alice"}

After 60 seconds (cache expired), the browser revalidates:

GET /api/users/42 HTTP/1.1
If-None-Match: "a1b2c3d4"

If unchanged:

HTTP/1.1 304 Not Modified
ETag: "a1b2c3d4"

No body is sent — the browser uses its cached copy. Saves bandwidth and server processing.

CORS preflight for a JSON API call:

JavaScript on https://app.com calls fetch("https://api.other.com/data", {method: "POST", headers: {"Content-Type": "application/json"}, body: "..."}).

Browser sends preflight:

OPTIONS /data HTTP/1.1
Origin: https://app.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type

Server responds:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.com
Access-Control-Allow-Methods: POST, GET
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 86400

The browser caches the preflight result for 86400 seconds and proceeds with the actual POST request.

HTTP Caching: ETag Conditional GET Flow

ETag Conditional GET Browser Cache Server Cache MISS GET /users/42 GET /users/42 200 OK + ETag:"a1b2" 200 OK + body Stored! Cache HIT GET /users/42 200 OK (from cache) No server contact! STALE GET /users/42 If-None-Match:"a1b2" 304 Not Modified 200 OK (from cache) No body sent! 304 saves bandwidth: server confirms data unchanged, no body transferred. ETag is more reliable than Last-Modified (sub-second precision, content-based).
Step 1 of 2