What an HTTP Message Is (and What It Is Not)
HTTP is a message-based protocol: clients send requests and servers send responses. Each message is made of a start line, a set of headers, and an optional body. The start line tells you the “shape” of the message (what action is requested, or what result occurred). Headers provide metadata (how to interpret the body, caching rules, authentication, content negotiation, and more). The body carries the actual payload (HTML, JSON, a file upload, an image, etc.).
HTTP messages are not the same thing as “a web page.” A web page is typically the result of multiple HTTP requests (HTML, CSS, JS, images, API calls). This chapter focuses on the structure and meaning of individual HTTP messages: methods, status codes, headers, and bodies.
Request Start Line: Method, Target, and Version
An HTTP/1.1 request start line looks like:
GET /products?category=shoes HTTP/1.1It contains:
- Method: what the client wants to do (GET, POST, etc.).
- Request target: usually a path and query (e.g.,
/products?category=shoes). - HTTP version: e.g., HTTP/1.1. (HTTP/2 and HTTP/3 encode messages differently on the wire, but the semantics—methods, headers, status codes—are largely the same.)
Common HTTP Methods and When to Use Them
Methods express intent. In practice, they also influence caching, idempotency, and how intermediaries (proxies, gateways) treat the request.
- Listen to the audio with the screen off.
- Earn a certificate upon completion.
- Over 5000 courses for you to explore!
Download the app
- GET: Retrieve a representation of a resource. Typically no request body. Safe and cacheable by default (depending on headers). Example: fetch a product page or API resource.
- HEAD: Same as GET but returns headers only (no response body). Useful for checking existence, size, or caching metadata without downloading the full content.
- POST: Submit data to be processed (create a resource, trigger an action, upload a file). Not idempotent by default. Often has a request body.
- PUT: Replace a resource at a known URL with the provided representation. Idempotent (sending the same PUT multiple times should result in the same state).
- PATCH: Partially update a resource. Not necessarily idempotent, but often designed to be.
- DELETE: Remove a resource. Idempotent in intent (deleting twice should not create a new effect beyond “already deleted”).
- OPTIONS: Ask the server what methods/headers are allowed. Frequently used by browsers for CORS preflight.
Safe vs. Idempotent: Practical Meaning
Two important properties are often discussed:
- Safe: the method should not change server state (GET, HEAD, OPTIONS are intended to be safe). This matters for things like prefetching or crawlers.
- Idempotent: repeating the same request should have the same effect as doing it once (GET, HEAD, PUT, DELETE are intended to be idempotent). This matters for retries: if a network error happens, a client may retry idempotent requests more confidently.
Even if a method is “supposed” to be safe, your server code can violate that (e.g., a GET that increments a counter). Avoid relying on “clients will never repeat this” for correctness.
Response Start Line: Status Code and Reason Phrase
An HTTP/1.1 response start line looks like:
HTTP/1.1 200 OKIt contains the version, a numeric status code, and a human-readable reason phrase (the phrase is mostly informational; clients should rely on the numeric code).
Status Code Classes
- 1xx Informational: Rare in typical app development. Example:
101 Switching Protocolsfor WebSocket upgrades. - 2xx Success: The request succeeded. Example:
200 OK,201 Created,204 No Content. - 3xx Redirection: The client should take another action, often follow a different URL. Example:
301,302,307,308,304 Not Modified. - 4xx Client Error: The request is invalid or cannot be fulfilled due to client-side issues (auth, validation, missing resource). Example:
400,401,403,404,409,429. - 5xx Server Error: The server failed to fulfill a valid request. Example:
500,502,503,504.
Frequently Used Status Codes (with Practical Guidance)
200 OK: Use when returning a successful response with a body (HTML, JSON, etc.).
201 Created: Use after creating a new resource (often via POST). Typically include a Location header pointing to the new resource URL.
HTTP/1.1 201 Created
Location: /api/orders/123
Content-Type: application/json
{"id":123,"status":"created"}204 No Content: Use when the operation succeeded but there is no response body (common for DELETE or a successful update where the client doesn’t need the updated representation).
301 Moved Permanently vs 302 Found vs 307 Temporary Redirect vs 308 Permanent Redirect: These redirect the client to another URL. The subtlety is whether the method can change on redirect. In practice:
301/302may cause some clients to change POST into GET (legacy behavior).307/308preserve the method and body.
304 Not Modified: Used with caching. If the client sends conditional headers (like If-None-Match), the server can respond 304 to indicate the cached version is still valid, with no body.
400 Bad Request: The server cannot parse or validate the request (malformed JSON, missing required fields, invalid query parameter).
401 Unauthorized: Authentication is required or failed. Typically paired with WWW-Authenticate for certain auth schemes.
403 Forbidden: The client is authenticated but not allowed to access the resource (authorization failure).
404 Not Found: The resource doesn’t exist (or you choose to hide its existence).
409 Conflict: The request conflicts with current state (e.g., trying to create a resource that already exists, or version conflict in optimistic concurrency).
415 Unsupported Media Type: The server refuses the request because Content-Type is not supported (e.g., sending XML to a JSON-only endpoint).
422 Unprocessable Content (often used in APIs): The request is syntactically correct but semantically invalid (validation errors). Not universally used, but common in REST APIs.
429 Too Many Requests: Rate limiting. Often includes Retry-After.
500 Internal Server Error: Generic server failure. Prefer more specific handling and logging; avoid leaking sensitive details in the body.
502 Bad Gateway / 504 Gateway Timeout: Common when a reverse proxy or gateway cannot get a valid response from an upstream service.
Headers: The Metadata That Controls Behavior
Headers are key-value pairs that describe the message and instruct clients, servers, and intermediaries how to handle it. Headers are case-insensitive, and multiple headers with the same name may be allowed depending on the header.
Request Headers You Will Use Often
Host (HTTP/1.1): Identifies the hostname the client is trying to reach. This enables virtual hosting (multiple sites on one IP). In HTTP/2+, this is represented as :authority internally, but the concept remains.
GET / HTTP/1.1
Host: example.comAccept: Content negotiation. The client indicates what media types it can handle.
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: Preferred languages.
Accept-Language: en-US,en;q=0.9Accept-Encoding: Compression algorithms the client supports (e.g., gzip, br). The server may respond with Content-Encoding.
Accept-Encoding: gzip, deflate, brUser-Agent: Identifies the client software. Useful for debugging, analytics, and compatibility workarounds, but do not rely on it for security decisions.
Authorization: Credentials for authentication (e.g., Bearer tokens).
Authorization: Bearer eyJhbGciOi...Cookie: Sends stored cookies back to the server (session IDs, preferences). Cookies are a major mechanism for browser state.
Content-Type (when there is a request body): Declares the media type of the body.
Content-Type: application/jsonContent-Length: Size of the body in bytes (mainly HTTP/1.1). Incorrect values can cause request smuggling or truncation issues; servers and proxies treat it carefully.
Referer: The previous page URL (spelled “Referer” historically). Often used for analytics and CSRF defenses, but may be absent due to privacy settings.
Response Headers You Will Use Often
Content-Type: Tells the client how to interpret the body.
Content-Type: text/html; charset=utf-8Content-Encoding: Indicates compression applied to the body (gzip, br). The client decompresses based on this header.
Cache-Control: Controls caching behavior in browsers and intermediaries. Examples:
Cache-Control: no-storefor sensitive responses (banking pages, tokens).Cache-Control: public, max-age=3600for cacheable assets.Cache-Control: privatefor user-specific content that should not be cached by shared caches.
ETag and Last-Modified: Validators for conditional requests. Clients can send If-None-Match (ETag) or If-Modified-Since (Last-Modified) to avoid re-downloading unchanged content.
Location: Used with redirects (3xx) and sometimes with 201 Created to point to the new resource.
Set-Cookie: Instructs the browser to store a cookie. Attributes like HttpOnly, Secure, and SameSite are important for security.
Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Lax; Path=/Vary: Tells caches which request headers affect the response. For example, if you serve different content based on Accept-Encoding, you should include Vary: Accept-Encoding so caches store separate variants.
WWW-Authenticate: Used with 401 to indicate how to authenticate (e.g., Basic, Bearer). In APIs, you may still return JSON error details in the body.
Retry-After: Used with 429 or 503 to indicate when the client should retry.
Header Pitfalls and Practical Checks
- Mismatched Content-Type: If you return JSON but set
Content-Type: text/plain, clients may parse incorrectly or refuse to parse. - Missing charset: For text, specify
charset=utf-8to avoid encoding ambiguity. - Incorrect caching: Accidentally caching personalized content can leak data. Use
Cache-Control: private, no-storefor sensitive user-specific responses. - Overusing Vary:
Varycan reduce cache efficiency. Only vary on headers that truly change the response.
Bodies: Payload Format, Length, and Streaming
The body is optional. Many GET requests have no body; many responses do. When a body exists, headers describe how to interpret it.
Content-Type and Media Types
Common body formats include:
- text/html: HTML documents.
- application/json: JSON APIs.
- application/x-www-form-urlencoded: Classic HTML form submissions.
- multipart/form-data: File uploads and complex forms.
- application/octet-stream: Arbitrary binary data.
For JSON APIs, a typical request might be:
POST /api/login HTTP/1.1
Host: example.com
Content-Type: application/json
Accept: application/json
{"email":"a@example.com","password":"secret"}Content-Length vs Transfer-Encoding (HTTP/1.1)
In HTTP/1.1, the receiver needs to know where the body ends. Two common approaches:
- Content-Length: The sender specifies the exact byte length.
- Transfer-Encoding: chunked: The body is sent in chunks, useful for streaming when the total size isn’t known upfront.
Chunked encoding example (simplified):
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: text/plain
5
Hello
6
world
0
Many modern deployments use HTTP/2 or HTTP/3 where framing is handled differently, but the idea of streaming responses still matters at the application level.
Compression and the Body
If the server compresses the response body, it sets Content-Encoding (e.g., gzip). The Content-Type still refers to the original media type (e.g., JSON), while Content-Encoding describes the transformation applied for transport.
Putting It Together: Full Request/Response Examples
Example 1: Fetching JSON with GET (and Understanding the Headers)
Request:
GET /api/products/42 HTTP/1.1
Host: shop.example
Accept: application/json
Accept-Encoding: gzip, br
Authorization: Bearer <token>Possible response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Encoding: br
Cache-Control: private, max-age=0
{"id":42,"name":"Trail Runner","price":129.99}What to notice:
Acceptexpresses the client wants JSON.Authorizationindicates the request is authenticated.Content-Encoding: brmeans the body is compressed; the client decompresses before parsing JSON.Cache-Control: privateprevents shared caches from storing user-specific results.
Example 2: Creating a Resource with POST (201 + Location)
Request:
POST /api/orders HTTP/1.1
Host: shop.example
Content-Type: application/json
Accept: application/json
{"items":[{"sku":"ABC","qty":2}]}Response:
HTTP/1.1 201 Created
Location: /api/orders/987
Content-Type: application/json
{"id":987,"status":"created"}Practical implication: the client can store or follow the Location to retrieve the full order representation later.
Example 3: Validation Errors (400/422) with a Useful Body
Request with missing fields:
POST /api/users HTTP/1.1
Host: api.example
Content-Type: application/json
{"email":"not-an-email"}Response (one common pattern):
HTTP/1.1 422 Unprocessable Content
Content-Type: application/json
{"error":"validation_failed","fields":{"email":"must be a valid email","password":"is required"}}Even though the status code signals failure, the body should still be structured and predictable so clients can display actionable messages.
Step-by-Step: Debugging HTTP Messages with curl
The fastest way to learn HTTP messages is to inspect them directly. The following steps use curl to view request/response lines, headers, and bodies.
Step 1: View Response Headers and Status
Use -i to include response headers:
curl -i https://example.com/api/products/42Look for:
- Status line (e.g.,
HTTP/1.1 200 OK) Content-Typeto confirm the formatCache-Control,ETag,Set-Cookieif relevant
Step 2: View Only Headers (No Body)
Use -I (HEAD request):
curl -I https://example.com/static/app.cssThis is useful to confirm caching headers and content type without downloading the full asset.
Step 3: Send Custom Request Headers
Use -H to set headers like Accept or Authorization:
curl -i -H "Accept: application/json" -H "Authorization: Bearer TOKEN" https://example.com/api/meCompare responses when you change Accept (some servers return HTML by default and JSON only when requested).
Step 4: Send a JSON Body with POST
Use -X POST and -d, and set Content-Type:
curl -i -X POST https://example.com/api/orders -H "Content-Type: application/json" -d '{"items":[{"sku":"ABC","qty":2}]}'Check:
- Do you get
201 Createdor200 OK? - Is there a
Locationheader? - Is the response body valid JSON?
Step 5: Reproduce Conditional Requests (ETag / 304)
First, fetch headers and capture the ETag:
curl -i https://example.com/static/app.jsIf the response includes ETag: "abc", send a conditional request:
curl -i -H 'If-None-Match: "abc"' https://example.com/static/app.jsIf unchanged, the server may return:
HTTP/1.1 304 Not ModifiedNotice that 304 responses typically have no body; the client uses its cached copy.
Designing API Responses: Choosing Codes, Headers, and Body Shape
Pick Status Codes That Match Client Behavior
- Use
401when the client should authenticate (or re-authenticate). - Use
403when authentication is present but insufficient permissions. - Use
404for missing resources; consider whether you want to return 404 for unauthorized access to avoid revealing existence. - Use
409for conflicts (duplicate unique fields, version mismatch). - Use
429for rate limits and includeRetry-Afterwhen possible.
Make Error Bodies Consistent
Clients benefit when error responses share a predictable JSON structure. A practical pattern is:
{"error":"some_code","message":"Human readable","details":{...}}Even if you vary status codes, keep the error body format stable so client code can handle it uniformly.
Use Headers to Control Caching and Security-Sensitive Data
When returning sensitive data (account pages, tokens, personalized API responses), prefer:
Cache-Control: no-storeFor public static assets (versioned filenames like app.9c1a2.js), prefer long caching:
Cache-Control: public, max-age=31536000, immutableThis reduces load and improves performance, while versioning ensures clients fetch new content when it changes.
Message Semantics in Practice: How Servers Decide What to Send
Routing Based on Method + Path
Most server frameworks route requests using both the method and the path. For example:
GET /api/ordersmight list orders.POST /api/ordersmight create an order.GET /api/orders/987might fetch a specific order.DELETE /api/orders/987might delete it.
This is why the method is part of the meaning of the message, not just the URL.
Parsing the Body Based on Content-Type
Servers typically choose a parser based on Content-Type:
application/json→ JSON parserapplication/x-www-form-urlencoded→ form parsermultipart/form-data→ multipart parser (files + fields)
If the client sends a body but forgets Content-Type, the server may treat it as plain text or reject it with 415 Unsupported Media Type. This is a common source of “it works in one client but not another” bugs.
Negotiating the Response Format with Accept
Some endpoints can return multiple representations. For example, a server might return HTML for browsers and JSON for API clients. The client’s Accept header guides that choice. If the server cannot produce any acceptable type, it may return 406 Not Acceptable.