Skip to content

Kitto.Web.Routing.Filters

Request-filter middleware for the attribute-based router (Sprint E.1 of the routing refactor). A filter wraps the dispatch of a request with BeforeInvoke / AfterInvoke / OnException hooks, letting cross-cutting concerns (JWT hydration, authorization gate, error handling) live outside the individual handlers and outside TKWebApplication.DoHandleRequest.

This unit is intentionally dependency-free (no uses of Kitto.Web.Application / Request / Response): it defines only the contracts, the mutable request context data-holder, the global filter registry and the chain runner. The concrete filters (which need the application/session/response) live in Kitto.Web.Routing.AppFilters, to avoid a circular unit dependency.

The chain exposes two shapes because of a Delphi limitation: an anonymous method cannot call the enclosing routine's nested functions. The legacy TKWebApplication.DoHandleRequest dispatch is a big if/else built on ~24 nested IsKX*Request probes, so it uses the PHASED api (RunBefore / HandleException / RunAfter) with the dispatch kept as inline code. The attribute pipeline, whose dispatch is a single method call, uses the convenience closure api Run().

IKXRequestContext interface

Per-request context handed to every filter. The router populates it before running the chain.

  • AllowUnauthenticated: the request is exempt from the authentication gate (login / reset / change / logout, the app root/home, or a view declared public via ACName).
  • AllowSessionLost: the request is exempt from the "session lost" fatal check (the recovery endpoints only: home + login/reset/change). These two differ intentionally: a public view is served without auth but still raises on a lost session, matching the legacy behaviour.

A filter's BeforeInvoke may fully satisfy the request (e.g. the auth gate writing a 404): it sets Handled := True, and the chain then skips the dispatch and any remaining BeforeInvoke.

pascal
property Path: string read GetPath;

Request path (URL.Path), used for logging and gate decisions.

pascal
property HttpMethod: string read GetHttpMethod;

HTTP method of the request ('GET'/'POST'/…).

pascal
property AllowUnauthenticated: Boolean read GetAllowUnauthenticated;

The endpoint may be served to a non-authenticated session (login/reset/change/logout, home, or a view public via ACName).

pascal
property AllowSessionLost: Boolean read GetAllowSessionLost;

The endpoint is exempt from the "session lost" fatal check (recovery endpoints only: home + login/reset/change).

pascal
property AllowDirectNavigation: Boolean read GetAllowDirectNavigation;

The matched endpoint may be reached by a top-level browser navigation (address bar / opened link / window.open) rather than only as an SPA sub-request. True for the home page, [TKXAnonymous] and [TKXNavigable] endpoints (e.g. blob downloads); False for the HTML-fragment endpoints, which the navigation guard bounces back to the app root.

pascal
property MatchedViewName: string read GetMatchedViewName;

Name of the view the matched endpoint operates on, empty when the endpoint is not view-scoped (the home page, the auth endpoints). Taken from the path parameter the router has already parsed, so a gate does not have to re-parse the URL: used by the authorization filter to let through exactly the view an imposed step needs (ChangePassword / ConfirmAccess) while blocking the rest.

pascal
property Handled: Boolean read GetHandled write SetHandled;

Set by a filter's BeforeInvoke to fully satisfy the request (e.g. a 404 or a redirect): the chain then skips dispatch and the remaining BeforeInvoke.

IKXRequestFilter interface

A request filter. Registered globally via TKXFilterRegistry and run by TKXFilterChain around the request dispatch.

pascal
procedure BeforeInvoke(const AContext: IKXRequestContext);

Runs before dispatch (in registration order). May set AContext.Handled.

pascal
procedure AfterInvoke(const AContext: IKXRequestContext);

Runs after dispatch (in reverse registration order), always, like a finally.

pascal
function OnException(const AContext: IKXRequestContext;

Given an exception escaping dispatch or a BeforeInvoke, returns True if it handled it (e.g. rendered an error dialog); False to let it propagate.

TKXRequestContext class

Concrete, mutable request context populated by the router.

pascal
constructor Create(const APath, AHttpMethod: string;

Creates the context with the gate flags computed by the router (AllowDirectNavigation defaults True — the legacy home branch is navigable).

TKXFilterRegistry class

Global, ordered list of request filters. Populated at startup from unit initialization sections. Registration order defines nesting: the first registered filter is the OUTERMOST layer (its BeforeInvoke runs first and its OnException runs last) — register the error handler first so it wraps everything.

pascal
constructor Create;

Creates the registry with an empty filter list.

pascal
destructor Destroy;

Frees the filter list (the filter interfaces are ref-counted).

pascal
class destructor DestroyClass;

Frees the singleton instance at unit finalization.

pascal
procedure RegisterFilter(const AFilter: IKXRequestFilter);

Appends a filter; registration order defines nesting (first = outermost).

pascal
property Filters: TList<IKXRequestFilter> read FFilters;

The registered filters, in registration (outermost-first) order.

TKXFilterChain class

Runs the filter chain around a request dispatch. Use the phased instance api when the dispatch is inline code (legacy DoHandleRequest), or the class Run() convenience when the dispatch is a closure (attribute route). With no filters registered both are transparent pass-throughs.

pascal
constructor Create(const AContext: IKXRequestContext);

Creates a chain bound to the given per-request context.

pascal
procedure RunBefore;

BeforeInvoke for all filters, in order. Stops early if Context.Handled.

pascal
function HandleException(E: Exception): Boolean;

Reverse OnException walk over the filters that ran; True if one handled it.

pascal
procedure RunAfter;

AfterInvoke in reverse over the filters that ran. Call from a finally.

pascal
class function Run(const AContext: IKXRequestContext;

Convenience runner for a closure dispatch (the attribute route): runs BeforeInvoke, the dispatch (unless already Handled), OnException on failure, and AfterInvoke in a finally. Transparent if no filters.

Released under Apache License, Version 2.0.