How a request becomes a page

Everything enters through a single host method, called on every web request. It runs to about 1600 lines, and knowing its order is worth more than reading it: most "my code never runs" questions are really "it runs before, or after, the step I assumed".

The order of steps

  1. Reset the previous request's context. V21 reuses web processes from a pool, so process variables survive from one request to the next. Without that reset at the very top, a page would read the previous visitor's entity, rights cache or language.
  2. Resolve the client's real address. Behind nginx the socket address is 127.0.0.1 for everyone; a header carries the public one. Computed at the top, because later steps read it.
  3. The special routes, served directly and returning immediately: file manager thumbnails, the original file, icon picker data, dashboard previews.
  4. Load the entity when the URL carries a primary key.
  5. Host hook: the host database gets its say before routing.
  6. Security check on any request carrying form data.
  7. Render, or answer with JSON.

The security step is not optional

Every form submission goes through the security check before anything is executed. A refusal has a visible shape, by design:

  • on a normal page: the error page, with its code;
  • on an action call: JSON, not HTML. Returning the full error page with a 200 made the browser's fetch fail on a syntax error — a message that says nothing about the actual refusal. The answer now carries the code and a toast.

Worth knowing: if a button does nothing and the network tab shows a refusal, read the refusal code rather than hunting for a bug in your function: your function was never called.

Rendering

The page shell comes from the domain: one site can have several of them. From there the block tree is walked, and each block is rendered by its template or by its 4D function, depending on what its type declares.

Two response shapes

RequestResponse
Normal pageHTML, with the form controller serialised and injected just before the end of the document. That is how server-side decisions — toasts, block reloads — reach the browser.
Action call or WebSocketJSON: the form controller itself.

A redirect is a 302 by default; a language equivalence uses a 301.

Process variables: the trap in both directions

Process variables are what lets a block's 4D expressions read context — the page entity, the URL, the current language. Two symmetrical mistakes come up:

  • Assuming they persist between requests. They must not — hence the reset. To carry state from one request to the next, use the session.
  • Assuming they are clean. A variable the reset does not cover keeps its previous value.

Trap: host and component do not share process variables. Setting one from the component does not make it visible from the host — which is exactly why a dedicated hook exists for that handover.

The URL your code reads is the request's

During a render triggered by the dev panel, the request URL is the action call, not the page being edited. Code that splits that URL to work out where it is will therefore read something other than what it expects — and an index out of bounds is the usual symptom.

Trap: never identify a screen from the request URL. The editing context has its own variables, made for that.

What comes next

Events and form submission, the security check, translations: all of it is covered under Mechanisms.