HTTP as an Application Protocol

LESSON

HTTP Protocol and Content Delivery

001 25 min intermediate

HTTP as an Application Protocol

The core idea: HTTP is not just a pipe that carries JSON; it is a shared contract for naming resources, asking for state changes, describing representations, and giving clients and intermediaries evidence about what happened.

Core Insight

Imagine a small commerce API behind api.shop.test. A product page calls GET /products/42, the checkout flow calls POST /orders, and a mobile app retries requests when its connection drops. In a local demo, every handler can return 200 with a JSON body and the system appears to work. Under real traffic, the same API passes through a CDN, a browser cache, a gateway, retrying clients, logs, tracing tools, and incident dashboards. Those components do not understand your internal service code. They understand HTTP messages.

The non-obvious shift is that HTTP is an application protocol, not merely a transport wrapper. TCP or QUIC can move bytes between endpoints, but HTTP gives those bytes shared meaning: this is the resource being addressed, this method is safe to repeat, this response is cacheable, this representation is JSON, this request depends on a previous entity tag, this failure is temporary, this redirect changes where the client should look next. A backend can ignore those meanings, but the rest of the delivery path will still make decisions from them.

A common misconception is that "real API design" lives only in the JSON schema or service handler. The JSON body matters, but the surrounding HTTP envelope decides what browsers, caches, load balancers, monitoring tools, SDKs, and humans can infer before they read the body. If every outcome is 200 OK, if mutations hide behind GET, or if freshness is described only in private application fields, the system has pushed public protocol decisions into private code. That may feel flexible at first, but it makes retries, caching, debugging, and compatibility harder.

The central trade-off is uniform semantics versus domain-specific shortcuts. HTTP's uniform semantics sometimes feel restrictive: resources, methods, status codes, headers, and representations force you to say what kind of operation is happening. The payoff is that many components can participate safely without knowing your business logic. Shortcuts can be useful, but they should be conscious shortcuts, not accidental silence.

The Contract HTTP Carries

An HTTP exchange has two sides: a request that expresses intent and a response that provides evidence. The request names a target resource, chooses a method, sends fields that shape the interaction, and may include a representation body. The response gives a status code, fields, and often a representation of the result. The important point is not the syntax alone. The important point is that each part narrows what downstream systems are allowed to assume.

For the commerce API, GET /products/42 says the caller is asking for a representation of product 42. If the server returns 200 OK with Content-Type: application/json, it says the request succeeded and the representation should be interpreted as JSON. If it also returns Cache-Control: public, max-age=60 and an ETag, an intermediary can reuse the response for a short time and later validate whether its copy is still current. No intermediary needed to know what a product is. It used HTTP's contract.

Notice what moved through the path before the application body mattered. The CDN saw a safe method, a target URI, cache fields, and a validator. The gateway saw the authority, method, path, and status code. The browser saw a media type and caching policy. The metrics system saw a route, status class, and latency. Each component made a local decision from public protocol evidence. That is the central mechanism of HTTP as an application protocol: it turns private application behavior into shared, inspectable signals that other software can act on without linking against your service code.

The same contract changes when the operation is a mutation. POST /orders says the client is asking the server to create or process something under the orders collection. If the client times out after sending the request, it may not know whether the order was created. HTTP cannot magically remove that ambiguity, but it gives the design a place to expose it. The service can return 201 Created with a Location header when creation is complete, 202 Accepted when work has only been queued, 409 Conflict when the request collides with existing state, or 503 Service Unavailable when the client should treat the server as temporarily unable to handle the request.

This is why status codes are not decorative labels. They are evidence at a boundary. A response code should help a client answer, "Did the server understand me? Did it perform the operation? Can I retry? Should I change the request? Is this a cacheable representation or an error?" The response body can add domain detail, but the HTTP-level signal should not contradict it.

A Worked Request Path

Trace one product lookup through the delivery path:

browser
  -> CDN or shared cache
  -> API gateway
  -> product service
  -> product database

The browser sends:

GET /products/42 HTTP/1.1
Host: api.shop.test
Accept: application/json

The request is small, but it already carries meaning. The method is safe: the client is not asking to change server state. The path names the resource. The Host field selects the authority. The Accept field says which representation format the client can use.

The product service replies:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: public, max-age=60
ETag: "p42-v17"

{"id":"42","name":"Trail Mug","price":18}

Now several components can act without private coordination. The browser knows it received JSON. The CDN can cache the representation for 60 seconds. A later client can validate with If-None-Match: "p42-v17". If the product has not changed, the origin can respond 304 Not Modified, saving bandwidth and preserving a clear freshness story.

Change one detail and the whole behavior changes. If the response includes Cache-Control: no-store, intermediaries should not keep it. If the representation varies by language, Vary: Accept-Language tells caches that different request fields produce different representations. If the product does not exist, 404 Not Found gives a different operational signal than 200 OK with {"error":"missing"}. The latter may be easier for one handler, but it makes caches, client libraries, metrics, and humans do extra work to discover the truth.

Design Pressure

HTTP design becomes important when the request path is no longer a single caller talking to a single process. The first pressure is retry behavior. A GET can normally be retried because it is safe. A PUT is defined as idempotent: sending the same full replacement twice should leave the resource in the same state. A POST is more open-ended, so retrying after a timeout can duplicate work unless the API adds a domain mechanism such as an idempotency key. The method choice does not implement the guarantee by itself, but it states the promise the implementation should honor.

The second pressure is caching and freshness. A service that treats HTTP as a pipe often invents private fields such as "fromCache": true or "validUntil": "..." while leaving Cache-Control, validators, and status codes vague. That hides freshness from the infrastructure that could help manage it. A service that uses HTTP semantics well can decide which representations are public, private, stale-tolerant, revalidatable, or never safe to store.

The third pressure is observability. During an incident, operators often start with coarse signals: status code distribution, latency by route, cache hit ratio, request method, user agent, and upstream error type. If protocol signals are precise, those measurements tell a useful story. If every failure is wrapped in 200 OK, the metrics say "success" while users see broken behavior. That mismatch slows diagnosis.

The trade-off is that uniform protocol semantics require discipline. Teams must decide what their resources are, which methods fit the operation, which headers are meaningful, and which status codes distinguish client mistakes from server failure. The benefit is that the API becomes legible to more than the code that implements it.

Failure Modes and Boundaries

One failure mode is using GET for actions because it is convenient. A link previewer, crawler, browser prefetch, or cache may issue a GET without the user intending a mutation. If GET /orders/123/cancel changes state, the design has violated a boundary that the wider web relies on.

Another failure mode is compressing all outcomes into one status code. Returning 200 OK for validation errors, authorization failures, missing resources, and upstream outages makes the body carry all meaning. That may work for one custom client, but it weakens gateway policies, retry libraries, alerts, and cache behavior. HTTP status codes should not replace domain errors; they should classify the outer shape of the outcome.

A third failure mode is ignoring intermediaries. A response that is personalized but marked cacheable can leak data. A response that is safely reusable but lacks validators can overload the origin. A redirect that looks harmless in a backend test can change browser behavior, cache keys, cookies, and cross-origin rules. Treat the delivery path as part of the application, because HTTP makes it part of the application.

Boundary discipline does not mean every API must be perfectly RESTful or avoid pragmatic choices. It means the public protocol contract should be explicit enough that clients and operators know which component owns the decision, what evidence proves success, and what is still uncertain after a failure.

Readiness Check

Close the lesson and trace one endpoint you have used or built. Name the resource, the method, the success status, one failure status, the representation type, and the caching or retry expectation. Then ask: if a timeout, cache, or proxy were involved, would the HTTP message itself give enough evidence to make the next decision safely?

Resources

Key Takeaways

NEXT URLs, Authorities, Origins, and Resource Identity