Skip to content

Routing and Request Flow

This page describes how HTTP requests travel from the browser to the server and back, including URL routing, the two client-side communication channels (HTMX-managed and JavaScript fetch), and timeout/error handling.

Overview

Kittox is a single-page application. The browser loads one full HTML page at startup (_Page.html template), and all subsequent interactions happen via partial HTML exchanges — the server returns HTML fragments that replace portions of the DOM.

There are two mechanisms for these exchanges:

ChannelUsed byResponse typeTimeout managed by
HTMX (hx-get, hx-post)Pagination, filters, navigation, view loadingHTML fragment swapped into a target elementhtmx.config.timeout
JavaScript fetch()Forms, lookups, tools, charts, calendarHTML overlay appended to <body>, or JSON, or binary downloadAbortController via kxFetchWithTimeout wrapper

Both channels share the same URL namespace (kx/...) and the same server-side handler; the difference is entirely on the client side.

URL Structure

Every Kittox request follows this pattern:

{AppPath}/kx/{resource}/{ViewName}[/{operation}][?params]
SegmentExampleDescription
AppPath/taskittoApplication base path, set in Config.yamlServer/Port and executable name
kxFixed namespace prefix for all Kittox API calls
resourceview, loginResource type
ViewNameActivityInputName of the YAML view definition
operationdata, form, save, delete, tool/ExportExcelAction to perform on the view

Route Table

Kittox uses two routing layers, tried in order:

  1. Attribute-based routing (TKXRoutingRoute) — handlers decorated with [TKXPath] attributes, discovered via Delphi RTTI at startup. See Attribute-Based Routing for details.
  2. Legacy routing (TKWebApplication.DoHandleRequest) — if/else chain for handlers not yet migrated to attribute-based routing.

The attribute-based layer is checked first. If no match is found, the request falls through to the legacy layer. This design, inspired by MARS / WiRL, enables progressive migration and custom URL handlers from application code.

Attribute-routed handlers (Enterprise)

URL PatternHTTPHandler classModule
kx/view/{V}/chart-dataGETTKXChartHandlerEnterprise
kx/view/{V}/calendar-dataGETTKXCalendarHandlerEnterprise
kx/view/{V}/map-dataGETTKXMapHandlerEnterprise

Legacy handlers (Core)

URL PatternHTTPHandlerDescription
kx/loginPOSTHandleKXLoginRequestAuthenticate user
kx/resetpasswordPOSTHandleKXResetPasswordRequestPassword reset
kx/changepasswordPOSTHandleKXChangePasswordRequestChange password
kx/view/{V}/deletePOSTHandleKXDeleteRequestDelete a record
kx/view/{V}/tool/{T}POSTHandleKXToolRequestExecute a tool (export, action, upload)
kx/view/{V}/lookupGETHandleKXLookupRequestOpen lookup selection dialog
kx/view/{V}/blob/{F}GETHandleKXBlobRequestServe blob field content (image, file)
kx/view/{V}/formGETHandleKXFormRequestOpen form dialog (edit, add, view, dup)
kx/view/{V}/savePOSTHandleKXSaveRequestSave form data
kx/view/{V}/dataGET/POSTHandleKXDataRequestLoad list/grid rows + pager
kx/view/{V}GETHandleKXViewRequestRender a complete view panel

Route ordering

Attribute-routed handlers are automatically sorted by specificity (more literal URL segments = higher priority). In the legacy layer, more specific patterns (e.g. /delete, /tool/{T}) are checked before the general /data route.

HTMX-Managed Requests

The majority of user interactions — pagination, sorting, filtering, navigating between views — are handled by HTMX attributes declared directly in the HTML generated by the server:

html
<!-- Pagination: generated by TKXListPanelController -->
<button hx-get="kx/view/Activities/data?start=50&limit=50"
        hx-target="#kx-list-body-Activities"
        hx-swap="innerHTML"
        hx-include="[name^='f_']"
        hx-indicator="#kx-loading">
  Next
</button>

When the user clicks this button, HTMX:

  1. Sends a GET request to kx/view/Activities/data
  2. Shows the #kx-loading spinner overlay
  3. Receives an HTML fragment from the server
  4. Swaps it into the #kx-list-body-Activities target element
  5. Hides the loading spinner

The server responds with HTML fragments only — never a full page. HTMX handles the DOM replacement transparently. Common HTMX attributes used in Kittox:

AttributePurpose
hx-get / hx-postURL to call
hx-targetDOM element to update with the response
hx-swapHow to insert the response (innerHTML, outerHTML)
hx-includeAdditional form fields to include in the request
hx-indicatorElement to show/hide as loading indicator
hx-valsExtra values to send as JSON
hx-headersExtra HTTP headers (Kittox sends X-KittoX: true)

JavaScript fetch() Requests

Some operations cannot use the HTMX swap model and require explicit JavaScript. These use the fetch() API (wrapped by kxFetchWithTimeout for timeout handling). The reasons fall into five categories:

1. Modal overlays appended to <body>

Affected: Open form (/form), open lookup (/lookup)

The server returns a complete dialog overlay (backdrop + dialog HTML). This must be appended to document.body as a new element, not swapped into an existing target container. HTMX's model requires a pre-existing target element, which doesn't exist for dynamically created modals.

javascript
// kxgrid.js — openForm
kxFetchWithTimeout(url, { headers: { 'X-KittoX': 'true' } })
  .then(function(r) { return r.text(); })
  .then(function(html) {
    var div = document.createElement('div');
    div.innerHTML = html;
    if (div.firstElementChild)
      document.body.appendChild(div.firstElementChild);  // append, not swap
  });

2. JSON responses (not HTML)

Affected: Chart data (/chart-data), calendar events (/calendar-data)

These endpoints return JSON, not HTML. The client passes the data to Chart.js or the calendar widget programmatically. HTMX is designed for HTML-in, HTML-out exchanges.

javascript
// kxgrid.js — kxChart.refresh
kxFetchWithTimeout('kx/view/' + viewName + '/chart-data', { ... })
  .then(function(r) { return r.json(); })
  .then(function(data) {
    chart.data.labels = data.labels;
    chart.data.datasets[0].data = data.data;
    chart.update();  // Chart.js animated update
  });

3. Binary downloads

Affected: Tool execution (/tool/{T}) for download-type tools (Excel export, PDF, etc.)

The client must inspect the Content-Disposition response header to determine if the response is a file download, then create a temporary blob URL and trigger the browser's save dialog. HTMX has no mechanism for binary download handling or response header inspection.

4. Inline script execution

Affected: Save (/save)

The save response may contain HTML with <script> tags (e.g., close-form commands, refresh triggers). HTMX's innerHTML swap does not execute inline scripts. The save handler manually extracts and executes them:

javascript
div.querySelectorAll('script').forEach(function(script) {
  var s = document.createElement('script');
  s.textContent = script.textContent;
  document.head.appendChild(s);
  document.head.removeChild(s);
});

5. Post-response JavaScript logic

Affected: Lookup (/lookup), calendar delete (/delete)

After inserting the response HTML, these operations need additional JavaScript steps: attaching event handlers (e.g., double-click for quick lookup selection), calling htmx.process() to activate HTMX attributes in the new content, updating toolbar button state, or triggering a calendar refresh. These multi-step workflows go beyond what a simple HTMX swap can express.

Summary: Which Channel Handles What

OperationChannelWhy
Load list rows / paginationHTMXStandard swap into #kx-list-body-{view}
Apply filtersHTMXStandard swap with hx-include for filter values
Sort columnsHTMXStandard swap with sort params
Navigate views / menuHTMXStandard swap into content area
Open edit/add formfetchModal overlay appended to body
Save formfetchResponse contains executable scripts
Delete recordHTMXStandard swap (refreshes grid)
Open lookup dialogfetchModal overlay + post-insert JS logic
Execute toolfetchMay return binary download or needs header inspection
Load chart datafetchJSON response for Chart.js
Load calendar datafetchJSON response for calendar widget
Calendar deletefetchPost-response state update + calendar refresh
Login / logoutHTMXStandard form submission

Timeout and Error Handling

Both communication channels are protected by a configurable timeout. The timeout value is set in Config.yaml:

yaml
# Timeout in milliseconds (default: 100000 = 100 seconds)
AjaxTimeout: 100000

HTMX timeout

The _Page.html template configures HTMX at page load:

javascript
htmx.config.timeout = {{ajaxTimeout}};  // injected from Config.yaml

When an HTMX request exceeds the timeout, the htmx:timeout event fires. Kittox listens for this event (and also htmx:sendError for network failures) and shows an error dialog:

javascript
document.body.addEventListener("htmx:timeout", function(evt) {
  var rc = evt.detail.requestConfig;
  kxGrid.showConfirm('Error', 'Server is not responding',
    'Retry',   // re-issues the same HTMX call
    'Reset',   // reloads the page
    function() { htmx.ajax(rc.verb, rc.path, {target: rc.target, ...}); },
    function() { window.location.reload(); }
  );
});

fetch() timeout

All fetch() calls go through the kxFetchWithTimeout wrapper, which uses the standard AbortController API with the same timeout value:

javascript
function kxFetchWithTimeout(url, options) {
  var timeout = htmx.config.timeout || 100000;
  var controller = new AbortController();
  var timeoutId = setTimeout(function() { controller.abort(); }, timeout);
  return fetch(url, Object.assign({}, options, { signal: controller.signal }))
    .then(function(response) {
      clearTimeout(timeoutId);
      return response;
    })
    .catch(function(err) {
      clearTimeout(timeoutId);
      if (err.name === 'AbortError') {
        // Show Retry / Reset dialog
        // Retry re-calls kxFetchWithTimeout with the same arguments
        // and feeds the result back into the original promise chain
      }
      throw err;
    });
}

The Retry button re-issues the exact same request transparently — the original .then() chain continues as if the first attempt had succeeded. The Reset button reloads the page entirely.

Error dialog

Both channels show the same modal error dialog (kxGrid.showConfirm in error mode):

ButtonAction
RetryRe-issues the exact same request (HTMX or fetch)
ResetCalls window.location.reload() to return to the initial page

Session Lost (Server Restart)

When the server is restarted, all in-memory sessions are lost. If the browser still has a session cookie from the previous server instance, the server detects this situation and shows a fatal error dialog.

How it works

  1. The client sends a request with the old session cookie
  2. TKWebEngine.EnsureSession calls FindOrCreateSession — an atomic operation that finds or creates a session under a single lock
  3. If no existing session matches the cookie, a new session is created and flagged as IsSessionLost = True
  4. TKWebApplication.DoHandleRequest checks the flag before routing:
    • API requests (kx/...): raises an exception "Session lost or expired, please restart!" → the standard error handler shows a fatal error dialog
    • Home/page requests (F5, page load): pass through normally — the login page is displayed

Atomic session creation

When the user presses F5, the browser sends multiple parallel requests (the page itself plus static resources like CSS and images). Without protection, each request could create a separate session, leading to race conditions and crashes.

TKWebSessions.FindOrCreateSession solves this by performing the find-or-create under a single MonitorEnter lock:

  1. Lock the session list
  2. Look up the session by cookie ID (or by client address as fallback)
  3. If found, return it; if not, create and add a new session atomically
  4. Unlock

This guarantees that all parallel requests from the same browser are routed to the same session, even when they arrive simultaneously after a restart or F5.

Page refresh (F5) handling

When the user presses F5 on an active session, EnsureSession detects this via IsPageRefresh (a non-Ajax request to the application root). In this case:

  1. The existing session's authentication state is preserved (user name, auth data, language)
  2. The old session is removed
  3. A new session is created with the same session ID (so that in-flight parallel requests still find it)
  4. The preserved auth state is transferred to the new session

This allows F5 to act as a session reset without losing the login, and without creating duplicate sessions from the parallel resource requests that accompany the page reload.

Fatal error dialog

When any fatal error occurs (including session lost), the error dialog behaves differently from non-fatal errors:

Fatal errorNon-fatal error
OK buttonwindow.location.reload() — returns to loginCloses the dialog
Overlay clickDisabled (must click OK)Closes the dialog

This is correct for all fatal errors because SignalFatalError destroys the session after the response — the user must reload to get a new session.

No client-side changes needed

Both communication channels handle the error dialog HTML natively:

  • HTMX: the HX-Retarget: body and HX-Reswap: beforeend response headers tell HTMX to append the dialog overlay to the body
  • fetch: callers (openForm, save, tools, etc.) already read the response as HTML text and append it to document.body

Request Processing on the Server

Browser request
  --> TKWebEngine.HandleRequest()
        |-- Session lookup/creation (cookie-based)
        |-- TKWebApplication.DoHandleRequest()
              |-- URL pattern matching (route table above)
              |-- Handler function executes:
              |     1. Finds the View by name in the metadata catalog
              |     2. Creates/loads data store from the database
              |     3. Creates/invokes controller (TKXListPanelController, TKXFormPanelController, etc.)
              |     4. Controller renders HTML fragment (or JSON, or binary stream)
              |-- TKWebResponse sends the result
  <-- HTTP response (HTML fragment / JSON / binary)

Key parameter encoding

Record keys are encoded as URL query strings: FieldName1=Value1&FieldName2=Value2.

Example: key=ID%3D123%26Code%3DABC decodes to ID=123&Code=ABC.

Only fields marked as IsKey: True in the model are accepted, to prevent injection attacks.

The X-Kittox header

All Kittox requests (both HTMX and fetch) include the X-KittoX: true HTTP header. This allows the server to distinguish Kittox API calls from regular page loads and static resource requests. In the _Page.html template, HTMX is configured globally:

html
<body hx-headers='{"X-KittoX": "true"}'>

JavaScript fetch() calls include it explicitly in the headers option.

Released under Apache License, Version 2.0.