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). Theqvalue (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
ETagheader (a hash or version identifier). On subsequent requests, the client sendsIf-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=abc123in 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, requiresSecure).
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:
- For "simple" requests (GET, POST with standard Content-Types), the browser adds an
Originheader. The server responds withAccess-Control-Allow-Origin: https://app.com. - 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.
Access-Control-Allow-Credentials: trueallows cookies to be sent with cross-origin requests.
Caching and CORS in Practice
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.