HTTP Delivery Capstone: Design a Global Request Path

LESSON

HTTP Protocol and Content Delivery

024 25 min advanced CAPSTONE

HTTP Delivery Capstone: Design a Global Request Path

The core idea: a global HTTP delivery design is not one technology choice; it is a chain of resource contracts, cache decisions, credential boundaries, transport negotiation, routing, invalidation, realtime behavior, and observability that must fit together under failure.

Core Insight

Imagine a retail service that has just expanded into several regions. The public catalog must feel fast from Madrid, Sao Paulo, Singapore, and Toronto. Product pages and images can be cached aggressively, but prices change during campaigns, inventory can become scarce, and some pages include account-specific offers. Checkout must never leak private data through a shared cache. Order status should update quickly after payment, even if a worker or message queue is slow.

A naive design says, "Put a CDN in front of it and use HTTPS." That is not wrong, but it is not a design yet. The design starts when each request type has a contract: who may cache it, which headers prove freshness, which identity is trusted, which component is authoritative, which retries are safe, and which logs would explain a bad result later.

The non-obvious insight is that HTTP delivery is mostly about boundaries. DNS points a client toward an edge. TLS proves the server name and negotiates protocol capabilities. The CDN decides whether a response can be served from shared state. A reverse proxy chooses an origin and rewrites trust-sensitive headers. The application decides resource semantics. Observability ties the witnesses together. If any boundary makes an implicit decision, the whole path becomes harder to reason about.

The trade-off is global speed versus correctness, privacy, and explainability. Caching reduces latency and origin load, but it can serve the wrong representation if the cache key ignores language, currency, authorization, or device hints. Long-lived connections reduce reconnect cost, but they need explicit timeout and backpressure behavior. Redirects clean up URL shape, but they can break unsafe methods or hide policy mistakes. The capstone move is to design one request path where those trade-offs are explicit.

The Scenario and Success Criteria

Use this concrete system:

GlobalShop

Public:
  GET /products/{id}
  GET /assets/{hash}.jpg
  GET /search?q=...

Private:
  GET /account
  GET /cart
  POST /cart/items
  POST /checkout

Realtime:
  GET /orders/{id}/events

The product catalog is public, but it varies by language and currency. Images are immutable once content-hashed. Search is public but expensive and changes frequently. Account, cart, and checkout are private. Checkout talks to payment, inventory, and order services, so timeouts and retries can create duplicate side effects if the HTTP contract is vague. Order events can use Server-Sent Events because the server mostly pushes status updates to the browser.

The design is ready only if it can answer these questions:

Those questions turn the track into one design exercise. The goal is not to use every feature. The goal is to make the path inspectable.

Request Path Map

Start with the physical and logical path:

browser or mobile client
  -> recursive DNS resolver
  -> authoritative DNS / CDN routing
  -> CDN edge
  -> TLS termination and ALPN
  -> edge cache and edge policy
  -> origin shield or regional load balancer
  -> reverse proxy
  -> application service
  -> database, cache, queue, payment, inventory

Each hop sees and changes different facts. DNS sees names and routing hints, not HTTP methods. TLS sees server name indication, certificates, and protocol negotiation, but not application routing after encryption is established. The CDN sees host, path, method, selected headers, cache metadata, and origin health. The reverse proxy sees trusted internal topology. The application sees resource semantics and business state.

Designing the path means deciding what each hop is allowed to decide. For example, the CDN may redirect http:// to https://, normalize www, serve immutable assets, cache public catalog pages, and attach an edge request id. It should not decide whether a checkout is valid. The reverse proxy may strip untrusted forwarded headers from the public internet and replace them with trusted values. It should not let a client-provided X-Forwarded-Proto trick the application into generating insecure links.

A compact ownership map helps:

DNS:          choose nearby edge or provider endpoint
TLS/ALPN:     authenticate name, negotiate h2 or h3 when available
CDN cache:    serve only responses that are explicitly public and keyed correctly
Edge policy:  redirects, rewrites, header normalization, coarse routing
Origin LB:    choose healthy region or pool
Reverse proxy: trusted forwarding headers, timeouts, body limits
Application:  method semantics, authz, representation, side effects
Observability: request id, trace context, status producer, timing by boundary

This map also prevents a common mistake: treating the CDN as a magic performance layer. A CDN is shared state outside the application. It is useful only when the application gives it safe instructions.

Classify the Request Types

The simplest way to avoid accidental behavior is to classify routes before tuning them.

Route                         Cache policy                 Retry policy
GET /assets/{hash}.jpg         public, immutable, long TTL   safe
GET /products/{id}             public, short shared TTL      safe
GET /search?q=...              public/private by inputs      safe, maybe no-store for personalized search
GET /account                   private or no-store           safe, but credential-bound
GET /cart                      private or no-store           safe, but credential-bound
POST /cart/items               no-store                      not automatic without idempotency
POST /checkout                 no-store                      idempotency key required
GET /orders/{id}/events        no-store, streaming           reconnect with cursor

GET /assets/{hash}.jpg is the easiest case. The content hash is part of the URL, so a new asset gets a new address. The response can use a long freshness lifetime:

Cache-Control: public, max-age=31536000, immutable
Content-Type: image/jpeg

GET /products/{id} is different. The resource is public, but the representation may vary by language and currency. The cache key must include the normalized dimensions that change the response. If the same path can return euros for one user and dollars for another, the cache cannot key only on path.

Cache-Control: public, s-maxage=300, max-age=60, stale-while-revalidate=30
Vary: Accept-Language
ETag: "product-42-eur-v17"
Surrogate-Key: product:42 price-list:eu category:shoes

Currency should not be hidden in an arbitrary cookie if the CDN is expected to cache it. Prefer an explicit path, query parameter, or normalized header that the cache policy includes deliberately:

/eu/products/42
/products/42?currency=EUR

Either can work. The important point is that resource identity, representation variance, and cache key agree.

Private routes should default to conservative headers:

Cache-Control: no-store
Set-Cookie: session=...; Secure; HttpOnly; SameSite=Lax

private allows storage in a browser cache but not a shared cache. no-store says not to store the response at all. For account, cart, and checkout data, no-store is often the least surprising default. A shared cache must never reuse a credential-bound response for another user.

Worked Path: Product Page from Another Region

Now trace one request:

GET https://www.globalshop.test/products/42?currency=EUR
Accept-Language: es-ES,es;q=0.9,en;q=0.7
traceparent: 00-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbbbb-01

First, DNS resolves www.globalshop.test to a CDN endpoint. The answer may depend on geography, provider health, and routing policy. The client connects to the edge. TLS uses SNI to ask for www.globalshop.test; the certificate must match that name. ALPN negotiates HTTP/3 if the client and network support it, otherwise HTTP/2 or HTTP/1.1. This negotiation changes transport behavior, but it should not change the meaning of the HTTP request.

At the edge, policy checks the method, host, path, and query. The request is a safe GET. It has no authorization header and no session cookie that should personalize the catalog response. The edge normalizes the cache key:

method=GET
scheme=https
host=www.globalshop.test
path=/products/42
query.currency=EUR
accept_language_bucket=es
device_bucket=default

That key is a design choice. If it includes every raw query parameter and every raw Accept-Language value, hit ratio collapses. If it ignores currency or language, users can receive the wrong representation. Normalization is where performance and correctness meet.

On a cache miss, the edge forwards the request to an origin shield or regional load balancer. The reverse proxy strips untrusted forwarding headers from the outside request and sets trusted values:

Forwarded: proto=https;host=www.globalshop.test
X-Request-ID: req_9f42
traceparent: 00-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-bbbbbbbbbbbbbbbb-01

The application builds the representation. It reads product 42, applies the EUR price list and Spanish language fallback, and returns:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: public, s-maxage=300, max-age=60, stale-while-revalidate=30
ETag: "product-42-eur-es-v17"
Vary: Accept-Language
Surrogate-Key: product:42 price-list:eu locale:es
Server-Timing: app;dur=74, db;dur=18

The CDN stores the response under the normalized key. The browser may store it briefly for 60 seconds. The shared cache can serve it for 300 seconds and may serve stale for 30 more seconds while revalidating. The ETag gives a validator for conditional requests. The surrogate keys give operators a way to purge related objects without guessing every URL.

If a user later reports a stale price, the investigation has concrete evidence to inspect:

request_id=req_9f42
cache_status=HIT
cache_key=/products/42 currency=EUR lang=es
age=287
surrogate_keys=product:42 price-list:eu locale:es
origin_status=200
client_proto=h3
edge_status=200

The likely questions are now specific. Was price-list:eu purged after the campaign update? Did the response carry the right surrogate key? Did the cache key include the right currency dimension? Was the stale response within the designed stale window, or did invalidation fail?

Worked Path: Checkout with Side Effects

Checkout needs the opposite posture. It is private, side-effecting, and expensive to duplicate.

POST /checkout HTTP/2
Host: www.globalshop.test
Cookie: session=...
Content-Type: application/json
Idempotency-Key: checkout_2026_06_19_abc123
traceparent: 00-cccccccccccccccccccccccccccccccc-dddddddddddddddd-01

The CDN should bypass shared cache because the request has credentials and uses an unsafe method. The edge may enforce body size limits and route to the checkout origin, but it should not retry the POST just because the origin connection is ambiguous. A timeout after bytes were sent can mean the origin performed the operation but the response was lost.

The application owns the business idempotency contract. It stores the idempotency key with the authenticated account and operation. If the client retries after a network failure, the application can return the original result or current operation state instead of charging twice.

idempotency_key new
  -> reserve inventory
  -> authorize payment
  -> create order
  -> store result
  -> return 201 Created

same idempotency_key seen again
  -> verify same account and same operation shape
  -> return stored result or 202 Accepted with status URL

The response is explicit:

HTTP/2 201 Created
Location: /orders/ord_123
Cache-Control: no-store

If checkout work continues asynchronously, use 202 Accepted with a status resource instead of pretending the order is complete:

HTTP/2 202 Accepted
Location: /orders/ord_123
Retry-After: 2
Cache-Control: no-store

This is where method semantics, status codes, redirects, cache policy, and observability meet. A client can retry a failed GET for product data easily. A client can retry checkout only because the application provides an idempotency key contract. A proxy cannot infer that safely from HTTP alone.

Realtime Order Updates

For order status, the system needs server-to-client updates, not arbitrary bidirectional messaging. Server-Sent Events are a good default:

GET /orders/ord_123/events HTTP/2
Accept: text/event-stream
Last-Event-ID: evt_17
Cookie: session=...

The response should not be stored:

Content-Type: text/event-stream
Cache-Control: no-store

The edge and proxy need longer read timeouts for this route than for ordinary requests, plus connection limits and observability for disconnects. If the connection drops, Last-Event-ID lets the server resume from a known point. If the system cannot guarantee replay, the event stream should be treated as a notification channel and the client should re-fetch the authoritative order resource.

WebSockets would be reasonable if the client also needed to send frequent messages over the same channel, such as collaborative editing or live bidding. Long polling would be simpler when infrastructure does not handle streaming well. The capstone decision is not "which realtime technology is coolest." It is "what communication shape does this resource need, and what failure behavior can the system operate?"

Failure Drills

Run the design through a few failures before calling it complete.

Price update serves stale data. The product team changes a campaign price for Europe. The expected purge touches price-list:eu and affected product keys. If users still see old prices, inspect Age, Cache-Status, surrogate keys, and the purge log. If the response did not include price-list:eu, the invalidation model was incomplete.

HTTP/3 fails on one network. Some clients cannot use QUIC because UDP is blocked. ALPN should fall back to HTTP/2 without changing resource semantics. Observability should show client protocol, fallback rate, connection errors, and latency by protocol.

Checkout times out at the edge. The edge returns 504 after 30 seconds, but the payment provider later confirms authorization. The design should show which timeout fired first, whether the application recorded the idempotency key, and what the client sees on retry. Without that evidence, operators cannot tell whether to retry, reconcile, or compensate.

A private response is cached publicly. A user sees another user's cart. Treat this as a severe cache policy failure. Check whether Cache-Control: no-store was missing, whether the CDN ignored Set-Cookie, whether the cache key omitted credentials, or whether an edge rule overrode origin headers.

A redirect breaks an unsafe method. A legacy path redirects checkout POST /buy to POST /checkout. If the redirect uses the wrong status code, some clients may turn the request into GET or drop the body. Unsafe method migrations need careful use of 307 or 308, or a client-visible API version change.

Architecture Review Prompt

Close the lesson and review the design from memory. For each route family, write one line with:

resource identity -> method semantics -> credentials -> cache policy -> invalidation -> retry behavior -> observability fields

Then answer these checks:

If the design cannot answer one of those questions, add the missing contract before adding another tool.

Connections

The early HTTP lessons gave the resource, method, status, representation, and conditional request vocabulary. Those concepts decide whether a response is cacheable, retryable, complete, or only accepted.

The middle of the track added browser boundaries, streaming, HTTP/2 and HTTP/3 behavior, and HTTPS edge details. Those concepts decide which failures happen before application code runs and which headers or protocols are trustworthy.

The final delivery lessons added proxies, DNS, CDNs, invalidation, realtime connections, redirects, and observability. Those concepts decide whether a global request path is fast enough to use and clear enough to debug.

Resources

Key Takeaways

PREVIOUS Observability for HTTP: Logs, Traces, and Wire Symptoms