HTTP Messages: Methods, Status Codes, Headers, and Bodies

Capítulo 5

Estimated reading time: 13 minutes

+ Exercise

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

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

Continue in our app.
  • Listen to the audio with the screen off.
  • Earn a certificate upon completion.
  • Over 5000 courses for you to explore!
Or continue reading below...
Download App

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 OK

It 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 Protocols for 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/302 may cause some clients to change POST into GET (legacy behavior).
  • 307/308 preserve 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.com

Accept: Content negotiation. The client indicates what media types it can handle.

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: Preferred languages.

Accept-Language: en-US,en;q=0.9

Accept-Encoding: Compression algorithms the client supports (e.g., gzip, br). The server may respond with Content-Encoding.

Accept-Encoding: gzip, deflate, br

User-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/json

Content-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-8

Content-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-store for sensitive responses (banking pages, tokens).
  • Cache-Control: public, max-age=3600 for cacheable assets.
  • Cache-Control: private for 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-8 to avoid encoding ambiguity.
  • Incorrect caching: Accidentally caching personalized content can leak data. Use Cache-Control: private, no-store for sensitive user-specific responses.
  • Overusing Vary: Vary can 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:

  • Accept expresses the client wants JSON.
  • Authorization indicates the request is authenticated.
  • Content-Encoding: br means the body is compressed; the client decompresses before parsing JSON.
  • Cache-Control: private prevents 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/42

Look for:

  • Status line (e.g., HTTP/1.1 200 OK)
  • Content-Type to confirm the format
  • Cache-Control, ETag, Set-Cookie if relevant

Step 2: View Only Headers (No Body)

Use -I (HEAD request):

curl -I https://example.com/static/app.css

This 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/me

Compare 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 Created or 200 OK?
  • Is there a Location header?
  • 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.js

If the response includes ETag: "abc", send a conditional request:

curl -i -H 'If-None-Match: "abc"' https://example.com/static/app.js

If unchanged, the server may return:

HTTP/1.1 304 Not Modified

Notice 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 401 when the client should authenticate (or re-authenticate).
  • Use 403 when authentication is present but insufficient permissions.
  • Use 404 for missing resources; consider whether you want to return 404 for unauthorized access to avoid revealing existence.
  • Use 409 for conflicts (duplicate unique fields, version mismatch).
  • Use 429 for rate limits and include Retry-After when 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-store

For public static assets (versioned filenames like app.9c1a2.js), prefer long caching:

Cache-Control: public, max-age=31536000, immutable

This 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/orders might list orders.
  • POST /api/orders might create an order.
  • GET /api/orders/987 might fetch a specific order.
  • DELETE /api/orders/987 might 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 parser
  • application/x-www-form-urlencoded → form parser
  • multipart/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.

Now answer the exercise about the content:

When an API creates a new resource successfully, which response pattern best helps the client find the new resource?

You are right! Congratulations, now go to the next page

You missed! Try again.

201 Created signals a new resource was created, and the Location header tells the client where it can retrieve that resource later.

Next chapter

Keep-Alive and Connection Reuse: Reducing Latency Across Requests

Arrow Right Icon
Free Ebook cover Web Servers 101: How HTTP, DNS, TLS, and Reverse Proxies Work
38%

Web Servers 101: How HTTP, DNS, TLS, and Reverse Proxies Work

New course

13 pages

Download the app to earn free Certification and listen to the courses in the background, even with the screen off.