Back to Articles

Unpacking the basics of HTTP: Part 2

Raghu Anand
Raghu AnandSoftware Engineer

Welcome back to our deep dive into HTTP! In Part 1, we covered the foundational concepts: HTTP's stateless nature, the client-server model, its evolution, and the basic structure of request and response messages.

Now, it's time to zoom in on two of the most critical elements within those messages: HTTP Headers and HTTP Methods. These aren't just technical details; they're the brains that add crucial context and the brawn that defines the intent of every web interaction.

Remember our analogy of writing details on a parcel? HTTP headers are exactly that: key-value pairs of metadata attached to both requests and responses. They provide vital information about the message itself, rather than being the main message content (which goes in the body).

Why do we need headers?

Imagine you're shipping a package. You wouldn't put the recipient's address, your return address, or handling instructions inside the box, right? You'd write them on the outside. Why? Because the postal service, couriers, and delivery drivers need to see that information quickly to handle, route, and deliver the package correctly, without having to open it.

HTTP headers work similarly. They allow various components in the web's infrastructure (proxies, caches, firewalls, and ultimately the server) to quickly understand and process the request or response without needing to parse the potentially large and complex message body.

Categories of HTTP Headers

Headers fall into several categories, each serving a distinct purpose:

  1. Request Headers: These are sent by the client to tell the server about the request, the client's environment, its preferences, and its capabilities.

    • User-Agent: Identifies the client software (e.g., "Mozilla/5.0...", "Postman Runtime/7.x"). The server can use this to optimize content for different browsers or devices.

    • Authorization: Carries credentials (like a JWT token) to authenticate the user for protected resources.

    • Accept: Tells the server what media types the client expects in return (e.g., application/json, text/html, image/jpeg).

    • Accept-Language: Specifies the preferred human language (e.g., en-US, es).

    • Accept-Encoding: Indicates which content encoding (compression) the client can handle (e.g., gzip, deflate).

  2. General Headers: These contain metadata about the message itself, applicable to both requests and responses.

    • Date: The date and time the message was originated.

    • Connection: Specifies whether the connection should be kept open after the current transaction (keep-alive) or closed (close).

    • Cache-Control: Directives for caching mechanisms (e.g., no-cache, max-age=600).

  3. Representation Headers (or Entity Headers): These describe the body of the message, whether it's a request body or a response body.

    • Content-Type: The media type of the message body (e.g., application/json for JSON data, text/html for an HTML page).

    • Content-Length: The size of the message body in bytes.

    • Content-Encoding: Any encoding or compression applied to the body (e.g., gzip).

    • ETag: A unique identifier (often a hash) for a specific version of a resource, primarily used for caching (more on this in Part 3!).

  4. Security Headers: These are vital for enhancing the security of web applications by controlling browser behavior and enforcing policies.

    • Strict-Transport-Security (HSTS): Forces browsers to interact with the server only over HTTPS, preventing downgrade attacks.

    • Content-Security-Policy (CSP): Restricts the sources from which content (scripts, stylesheets, images) can be loaded, mitigating Cross-Site Scripting (XSS) attacks.

    • X-Frame-Options: Prevents web pages from being embedded in <iframe> elements, thwarting clickjacking attacks.

    • Set-Cookie (with flags like HttpOnly, Secure): Helps secure cookies by making them inaccessible to JavaScript and ensuring they are only sent over HTTPS.

The Power of Headers: Extensibility and Remote Control

HTTP headers provide two powerful capabilities:

  1. Extensibility: Headers make HTTP incredibly adaptable. New headers can be easily defined and added for various purposes without changing the fundamental protocol. This allows HTTP to evolve and support new technologies (like web security standards) and custom application-specific needs.

    • Example: A developer could create a custom header like X-App-Tracking-ID for their specific use case.

  2. Remote Control: Clients can use headers to "remotely control" how a server processes a request or how a browser behaves with a response.

    • Example:

      • Client specifies Accept: application/xml – the server then knows to send XML data if available.

      • Server sends Cache-Control: max-age=3600 – the client's browser now knows to cache that resource for an hour.

      • Client sends Authorization: Bearer <token> – the server uses this to determine if the user has access.

HTTP headers are much more than just extra information; they are essential for flexible, secure, and efficient web communication.

HTTP Methods: Defining the Intent

If headers provide context, HTTP Methods define the intent of the client's request. Instead of every request just doing "something," methods give clear semantic meaning to the action being performed on a resource.

Think of them as verbs in the client-server conversation.

Here are the most common HTTP methods:

  • GET (Read/Retrieve):

    • Purpose: To request data from a specified resource.

    • Key Characteristic: Should never modify data on the server. It's safe and idempotent (more on this below).

    • Example: Fetching a user's profile (GET /users/123), retrieving a list of products (GET /products).

  • POST (Create/Submit):

    • Purpose: To submit data to be processed to a specified resource. This often results in the creation of a new resource.

    • Key Characteristic: Requests usually include a body containing the data to be sent. It is not idempotent.

    • Example: Creating a new user account (POST /users), submitting a form (POST /orders).

  • PUT (Complete Update/Replace):

    • Purpose: To completely replace all current representations of the target resource with the content of the request body.

    • Key Characteristic: If the resource exists, it's fully updated; if it doesn't, it might be created. It is idempotent.

    • Example: Updating all details of a user profile (PUT /users/123 with a full user object).

  • PATCH (Partial Update):

    • Purpose: To apply partial modifications to a resource.

    • Key Characteristic: Only sends the changes needed to update the resource, not the entire resource. It is not inherently idempotent (though it can be designed to be). Use PATCH when you want to make selective changes.

    • Example: Updating only a user's email address (PATCH /users/123 with just an email field).

  • DELETE (Remove):

    • Purpose: To delete the specified resource.

    • Key Characteristic: It is idempotent.

    • Example: Deleting a product (DELETE /products/456).

Idempotency: Predictable Actions

A crucial concept related to HTTP methods is idempotency:

  • Idempotent Methods: An operation is idempotent if executing it multiple times produces the same result as executing it once.

    • GET: Fetching the same data multiple times will always return the same data (it doesn't change anything).

    • PUT: Replacing a resource with specific data multiple times will always leave the resource in the exact same final state.

    • DELETE: Deleting a resource once makes it disappear. Trying to delete it again will result in the same outcome (it's still gone).

    • Why it matters: Idempotency is great for scenarios where a request might be retried (e.g., due to network issues). The client can safely re-send the request knowing it won't cause unintended side effects.

  • Non-Idempotent Methods: Executing the operation multiple times may produce different results.

    • POST: If you send a POST request to create a new note twice, you'll likely end up with two new notes, not just one. Each request creates a new resource.

    • PATCH: Depending on how PATCH is implemented, it might not be idempotent. For example, a PATCH that "adds 1 to a counter" will have a different result each time it's called.

The OPTIONS Method and CORS Pre-flight

You might encounter one more method, OPTIONS, typically in a specific security context:

  • OPTIONS (Server Capabilities / CORS Pre-flight):

    • Purpose: To query the server about its communication options for a target resource. Most commonly, it's used by browsers to perform a "pre-flight" check for CORS (Cross-Origin Resource Sharing).

    • You won't use it directly much as a developer, but it's essential for how browsers handle cross-origin requests securely.

Let's talk about CORS now, as it directly involves OPTIONS requests and is fundamental to web security.

CORS: Ensuring Secure Cross-Origin Interactions

Imagine your website example.com wants to fetch data from an API hosted at api.anotherexample.com. This is called a cross-origin request because the domain (origin) of your website is different from the domain of the API.

By default, browsers enforce a strict security policy called the Same-Origin Policy. This policy states:

  • A web page can only make requests to resources from the same origin (same scheme, host, and port) that served the web page itself.

  • Requests to different origins are generally blocked by default for security reasons, preventing malicious scripts from one site accessing data on another.

CORS (Cross-Origin Resource Sharing) is a mechanism that allows servers to explicitly loosen this restriction selectively. It gives servers control over who (which origins) can access their resources and how.

There are two main types of CORS request flows:

1. Simple Request Flow

A request qualifies as "simple" if all of these conditions are met:

  • It uses a GET, POST, or HEAD method.

  • It only uses a limited set of "simple" headers (e.g., Accept, Accept-Language, Content-Language, Content-Type). Crucially, headers like Authorization or custom headers make it non-simple.

  • The Content-Type header (if present) is one of application/x-www-form-urlencoded, multipart/form-data, or text/plain.

How it works:

  1. Client Sends Request: Your browser sends the cross-origin request directly. It automatically includes an Origin header (e.g., Origin: http://example.com) to tell the server where the request is coming from.

  2. Server Responds: The server processes the request. If it allows requests from example.com, it includes an Access-Control-Allow-Origin header in its response (e.g., Access-Control-Allow-Origin: http://example.com or Access-Control-Allow-Origin: * to allow all origins).

  3. Browser Checks: The browser receives the response. It checks if the Access-Control-Allow-Origin header is present and if its value matches your client's origin (example.com) or is a wildcard (*).

  4. Allowed or Blocked:

    • If it matches, the browser allows the response to be passed to your JavaScript code.

    • If the header is missing or doesn't match, the browser blocks the response (even if the server processed the request successfully) and you'll see a CORS error in your console.

2. Pre-flighted Request Flow (This is where OPTIONS comes in!)

If a cross-origin request doesn't meet the "simple" criteria, the browser performs a pre-flight request before sending the actual request. This is a security measure to ask the server "Is it okay if I send the real request with these specific methods/headers/content types?"

A pre-flight is triggered if any of these conditions are true (in addition to being cross-origin):

  • The method is not GET, POST, or HEAD (e.g., PUT, DELETE, PATCH).

  • The request includes "non-simple" headers (e.g., Authorization, custom headers like X-App-ID).

  • The Content-Type is not application/x-www-form-urlencoded, multipart/form-data, or text/plain (e.g., application/json - which is very common in modern APIs!).

How it works:

  1. Browser Sends OPTIONS Request (The Pre-flight):

    • Your browser first sends an OPTIONS request to the server.

    • This request includes headers like Access-Control-Request-Method (e.g., PUT) and Access-Control-Request-Headers (e.g., Authorization, Content-Type) to inform the server about the actual request it intends to send.

    • Crucially, this OPTIONS request has no body and contains no actual data. It's just an inquiry.

  2. Server Responds to Pre-flight:

    • If the server's CORS policy allows the intended method and headers, it responds to the OPTIONS request with a 204 No Content status code and several Access-Control-* headers:

      • Access-Control-Allow-Origin: (e.g., http://example.com or *)

      • Access-Control-Allow-Methods: Lists all allowed methods (e.g., GET, POST, PUT, DELETE).

      • Access-Control-Allow-Headers: Lists all allowed non-simple headers (e.g., Authorization, Content-Type).

      • Access-Control-Max-Age: Tells the browser how long it can cache this pre-flight response, avoiding repeated pre-flights for the same resource within that time.

  3. Browser Validates Pre-flight:

    • The browser checks if the server's pre-flight response confirms that the actual request (method, headers, etc.) is allowed.

    • If everything aligns, the browser proceeds to send the actual, original request.

    • If the pre-flight fails (e.g., server doesn't allow the method or headers), the browser blocks the original request, and you get a CORS error.

CORS in a Nutshell: CORS is a security handshake. The browser asks permission (sometimes explicitly with a pre-flight, sometimes implicitly), and the server grants or denies it through specific response headers. If permission isn't granted, the browser protects you by blocking the response.

Conclusion of Part 2

You've now got a solid understanding of HTTP Headers (the metadata) and HTTP Methods (the intent), along with the crucial role of CORS in securing cross-origin web communication. These concepts are foundational for building any web application.

In Part 3, we'll conclude our series by exploring HTTP Status Codes (the universal language of outcomes), HTTP Caching (how we make the web faster), and other advanced topics like content negotiation and large file handling.

Stay tuned for the final part!