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.
property Path: string read GetPath;Request path (URL.Path), used for logging and gate decisions.
property HttpMethod: string read GetHttpMethod;HTTP method of the request ('GET'/'POST'/…).
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).
property AllowSessionLost: Boolean read GetAllowSessionLost;The endpoint is exempt from the "session lost" fatal check (recovery endpoints only: home + login/reset/change).
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.
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.
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.
procedure BeforeInvoke(const AContext: IKXRequestContext);Runs before dispatch (in registration order). May set AContext.Handled.
procedure AfterInvoke(const AContext: IKXRequestContext);Runs after dispatch (in reverse registration order), always, like a finally.
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.
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.
constructor Create;Creates the registry with an empty filter list.
destructor Destroy;Frees the filter list (the filter interfaces are ref-counted).
class destructor DestroyClass;Frees the singleton instance at unit finalization.
procedure RegisterFilter(const AFilter: IKXRequestFilter);Appends a filter; registration order defines nesting (first = outermost).
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.
constructor Create(const AContext: IKXRequestContext);Creates a chain bound to the given per-request context.
procedure RunBefore;BeforeInvoke for all filters, in order. Stops early if Context.Handled.
function HandleException(E: Exception): Boolean;Reverse OnException walk over the filters that ran; True if one handled it.
procedure RunAfter;AfterInvoke in reverse over the filters that ran. Call from a finally.
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.
