skip to content

How do the server-edge, pipeline, and handler-aware hook altitudes in a web framework differ in what they can observe?

level: middleimportance: must knowfreq 72%

answer

  1. three altitudes, one visibility ladder
  2. deeper means more meaning, fewer requests
  3. route appears only after matching
  4. arguments and return value are innermost
  5. facts travel inward, never outward

basics

~20 s

Each altitude sees more application meaning and less of the wire: the server edge sees connection facts and raw bytes, a pipeline hook the framework's request and response objects plus the route, a handler-aware hook the bound arguments and return value.

solid answer

~50 s

Think of it as a visibility ladder. At the **server edge** the request is a message on a connection: peer address, transport details, raw target string, headers, an unread body, and outbound the final status and serialized bytes. In the **framework pipeline** the request is the framework's own request and response abstraction, with per-request attributes you can write, and once matching has happened the route template and handler metadata too — but not the handler's arguments or its result. A **handler-aware** hook wraps the invocation: it sees the handler and its declared metadata, the arguments after conversion and validation, the value returned before serialization, and an exception as a thrown object. The trade is symmetrical: the deeper you go, the more application meaning you get and the fewer requests you see, because everything refused earlier never arrives.

go deeper

for a junior

Learn the ladder in order: edge sees the connection and raw message; pipeline sees the framework's request and response; handler-aware sees arguments and the returned value. Deeper means more meaning.

for a middle

Explain why each rung sees what it does — the request's meaning is computed in stages, and a hook only sees what already exists. Name one thing each altitude cannot observe.

for a senior

Show the operational half: coverage shrinks as you go inward, so counts from different altitudes legitimately disagree, and only the outermost one knows what actually left the process.

for a principal

Argue the visibility contract as a design constraint. Facts flow inward only, so a concern needing both transport and application facts must be split, and that split is a coupling you own.

## The ladder in one idea Cross-cutting code attaches at one of three altitudes, and what it can see is decided by **how much work the framework has already done when it runs**. Going inward, each altitude gains application meaning and loses proximity to the wire — and loses requests, because anything refused further out never reaches it. ## Altitude 1 — the server edge The outermost attachment point wraps the whole exchange, before the framework has interpreted anything. - Sees the **connection**: peer address and port, whether the transport is encrypted, the negotiated protocol version. - Sees the **raw message**: method token, request-target string as sent, headers and cookies as text, the body as an unread stream. - Sees **every accepted request**, including the ones the framework will refuse. - Outbound, sees the **final status code and the bytes that actually left**, regardless of which stage produced them. - Cannot see: route template, path variables, handler identity, bound arguments, return value, or a framework exception as an object — only the response that resulted from it. ## Altitude 2 — the framework pipeline Here the request has become the framework's own request/response abstraction and the hook is one element of the chain that wraps the rest of processing. - Sees a **parsed request object**: method, a normalized path, typed access to headers, query values and cookies, and usually a per-request attribute map it can write into for later stages to read. - Can **replace or modify the response** while it is still an object — but only until the response is committed, after which status and headers are already on the wire. - Once route matching has happened, sees the **route template and the handler's declared metadata**, which is what makes low-cardinality, route-labelled work possible at this altitude. - Cannot see: the arguments actually bound for the handler, the value it returns, or its internal state. ## Altitude 3 — handler-aware The innermost attachment point wraps the invocation of the resolved handler itself. - Sees the **handler and its declared metadata** — the concrete operation being performed. - Sees the **bound arguments**: values already parsed, converted to their declared types and validated, which is the first altitude where "which customer id" is a value rather than a substring. - Sees the **return value before serialization**, so it can inspect or replace a result while it is still a domain object. - Sees an **exception the handler threw**, as a thrown object rather than as whatever response it eventually becomes. - Cannot see: raw bytes (they are already parsed), connection facts beyond what the framework surfaces on its request object, requests that never reached dispatch, and often not the final serialized status either, because rendering happens further out. ## The comparison, side by side | Fact | Server edge | Pipeline | Handler-aware | |---|---|---|---| | Client address, transport details | Yes, from the connection | As far as the framework surfaces it | As far as the framework surfaces it | | Raw target string and unparsed bytes | Yes | Partly, normally normalized | No | | Framework request/response object | No | Yes | Yes | | Matched route template | No | After matching | Yes | | Bound, converted arguments | No | No | Yes | | Return value before serialization | No | No | Yes | | Thrown exception as an object | No | Yes, as it unwinds | Yes, at the throw site | | Final status and serialized bytes | Yes | As an object, until committed | Usually not | | Requests that never matched a route | Yes | Yes, if registered before matching | No | ## Two consequences interviewers listen for 1. **Visibility and coverage pull in opposite directions.** The altitude that sees the most requests understands them the least, and the altitude that understands a request best sees the fewest of them. Any claim built on counts has to say which altitude produced them. 2. **Facts travel inward, never outward.** A hook can stash something it observed into per-request state for a deeper hook to read. It cannot pull a fact that will only exist later — no amount of effort makes the handler's identity visible before routing. ## Frameworks differ here Frameworks draw these lines in different places: some run route matching inside the chain, so early elements are route-blind and later ones are not, while others match first and let every chain element see the route; some expose a distinct handler-aware attachment point, while others let you achieve the same by wrapping the handler yourself. The names vary and the boundaries move, but the ordering of the ladder does not, because it follows the order in which the request's meaning is computed.

  • Which altitude should produce route-labelled measurements, and why not the outermost one?
    One that runs after matching, because the route template is what keeps label cardinality bounded. At the edge only the raw path string exists, so identifiers embedded in it each become their own series. The cost is coverage: a route-labelled measurement cannot describe requests that matched nothing, so it is usually paired with a coarse count taken further out.
  • How does a fact observed at the edge become usable to a handler-aware hook?
    By being written into per-request state — an attribute map or context object the framework carries with the request — and read later. That is the only direction that works: earlier stages can hand facts inward, but a later fact cannot be made visible to a hook that already ran. It also creates a coupling, since removing the capture silently empties the reader.
  • Why can a handler-aware hook usually not report the status code the client received?
    Because it returns a value, and turning that value into a status, headers and bytes happens further out. Content negotiation, serialization failures, and outer stages that translate an exception or rewrite the response all land after it has finished, so the only altitude that reliably knows what left the process is the outermost one.
  • What does a hook at the pipeline altitude gain over one at the edge for modifying a response?
    It works with the response as an object before it is committed, so it can change status, add headers, or replace the body wholesale. The edge sees bytes on the way out, by which point the status and headers are typically already flushed and only the framing of what remains can be affected.

Three checkpoints into an office: the street turnstile sees everyone who arrives and how they got there but not who they are visiting; the floor receptionist knows the meeting; the person in the room sees the papers you actually brought — and only meets the few who got that far.

saying these in an interview costs you the question

  • Treats all three altitudes as interchangeable places to put a hook
  • Expects bound arguments to be visible to a pipeline hook
  • Assumes the handler-aware altitude sees every request the server received
  • Thinks the edge can read the matched route template
  • Believes a later fact can be made visible to an earlier hook
  • Assumes the handler-aware hook knows the status code finally sent