Factorizer/Cluster ERP WikiCluster ERP Türkçe
Home / Platform

Explanation

Request pipeline and middleware

The order and purpose of the five middlewares in the Cluster ERP server, the body size limit, security headers, the activity log, and the static web app mounts.

This page follows an HTTP request through the Cluster ERP server from the socket to the route and back. It covers the five middlewares added in server/app.py, the two middleware classes in server/middleware.py, the request context, the body limit, and the static mounts of the web app. Authentication and tenant routing happen in route dependencies and are documented in the identity and access section.

In short

  • create_app adds five middlewares: CommitBeforeResponseMiddleware, ActivityLogMiddleware, GZipMiddleware, _console_cors, and _security_headers (src/cluster_erp/server/app.py:1317-1430). Counted from the three add_middleware calls and the two @app.middleware("http") decorators.
  • Starlette places each newly added middleware outside the earlier ones, so the request passes them in the reverse order of registration.
  • _security_headers refuses a body larger than max_request_body_bytes with HTTP 413 before reading it, and sets security headers on every response (src/cluster_erp/server/app.py:1390-1430).
  • ActivityLogMiddleware writes one activity-log row per request, and an error-log row for handled errors, in a background task (src/cluster_erp/server/middleware.py:424-663).
  • CommitBeforeResponseMiddleware runs pre-response hooks, which commit the request's session before the first response byte (src/cluster_erp/server/middleware.py:25-57).

How it works

Order of the layers

Starlette's add_middleware inserts each middleware at the front of its list, and the stack is built so that the first entry is the outermost one (Starlette middleware documentation). @app.middleware("http") registers through the same list. The registration order in create_app is CommitBeforeResponseMiddleware, ActivityLogMiddleware, GZipMiddleware, _console_cors, _security_headers (src/cluster_erp/server/app.py:1317, src/cluster_erp/server/app.py:1318, src/cluster_erp/server/app.py:1323, src/cluster_erp/server/app.py:1365, src/cluster_erp/server/app.py:1390). A request therefore passes them in this order:

flowchart TD
 client["Client"] --> proxy["uvicorn ProxyHeadersMiddleware, when trust_proxy_headers is on"]
 proxy --> sec["_security_headers: body limit, security headers"]
 sec --> cors["_console_cors: CORS for the fleet endpoint only"]
 cors --> gzip["GZipMiddleware: responses over 1 KB"]
 gzip --> act["ActivityLogMiddleware: activity and error log"]
 act --> commit["CommitBeforeResponseMiddleware: pre-response hooks"]
 commit --> exc["Exception handlers"]
 exc --> route["Route and dependencies"]

The table lists each layer.

Layer What it does Source
Proxy headers (uvicorn) When trust_proxy_headers is on, rewrites the client address and scheme for connections from trusted_proxy_ips src/cluster_erp/server/main.py:45-51
_security_headers Refuses a declared Content-Length above max_request_body_bytes with 413 and code REQUEST_TOO_LARGE; sets Content-Security-Policy, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, and Permissions-Policy; sets Strict-Transport-Security only over HTTPS src/cluster_erp/server/app.py:1390-1430
_console_cors Answers CORS preflight and adds CORS headers only for /api/v1/vendor/fleet and only for origins listed in console_origins src/cluster_erp/server/app.py:1354-1388
GZipMiddleware Compresses responses of at least 1024 bytes src/cluster_erp/server/app.py:1319-1323
ActivityLogMiddleware Records the request in activity_log and, for handled errors, in error_log src/cluster_erp/server/middleware.py:424-663
CommitBeforeResponseMiddleware Creates request.state.pre_response_hooks and runs them before http.response.start is sent src/cluster_erp/server/middleware.py:25-57
Exception handlers Turn exceptions into JSON error bodies src/cluster_erp/server/error_handler.py:200-398

The content security policy allows scripts, styles, images, fonts, frames, and connections from the same origin only, with blob: and data: where noted, and no inline scripts (src/cluster_erp/server/app.py:1325-1352).

The activity log

For each request that is not in activity_log_skip_paths, ActivityLogMiddleware.dispatch:

  1. Reads the request body of POST, PUT, and PATCH requests (src/cluster_erp/server/middleware.py:438-441).
  2. Calls the next layer. If it raises, it logs the exception in both logs, stores a trace id on the request state, and re-raises it (src/cluster_erp/server/middleware.py:443-453, src/cluster_erp/server/middleware.py:498-528).
  3. Reads the response body for writes and for GET requests on a single record, and rebuilds the response with the raw header list (src/cluster_erp/server/middleware.py:455-487).
  4. Starts _write_log as a task and returns the response (src/cluster_erp/server/middleware.py:489-496).

_write_log stops when activity_log_enabled is off (src/cluster_erp/server/middleware.py:542-543). It picks the request's tenant database (src/cluster_erp/server/middleware.py:340-375, src/cluster_erp/server/middleware.py:561), resolves the actor and the user name, masks body keys listed in log_sanitize_keys and log_sanitize_path_keys, cuts the body summary to log_body_max_bytes, replaces token-like path segments with *** when log_redact_path_tokens is on, and commits an ActivityLog row and, when error data is present, an ErrorLog row (src/cluster_erp/server/middleware.py:530-663). A failure to write the log is logged at debug level and does not affect the response (src/cluster_erp/server/middleware.py:662-663).

Request context

The route's database dependency, not a middleware, publishes the acting user, the correlation id, and the deactivation reason (src/cluster_erp/server/dependencies.py:34-72). See database and sessions.

Static mounts

The web app is mounted last, so that it never shadows /api (src/cluster_erp/server/app.py:1573-1575):

  • /app/v serves webapp/ files under a version stamp, immutable for one year when the stamp is current (src/cluster_erp/server/app.py:1599-1614, src/cluster_erp/server/app.py:1635-1639).
  • /app serves the same files with Cache-Control: no-cache, and serves index.html with the stamp filled in and an ETag (src/cluster_erp/server/app.py:1591-1597, src/cluster_erp/server/app.py:1616-1625, src/cluster_erp/server/app.py:1640-1644).

See cache and asset stamp.

Why it is built this way

  • The comment on the security headers states that the content security policy is a defence-in-depth backstop against stored cross-site scripting, and that the brand typeface is self-hosted so that no third-party host appears (src/cluster_erp/server/app.py:1325-1334).
  • The body-limit comment states that the check runs on Content-Length before the body is read, to bound memory use (src/cluster_erp/server/app.py:1392-1395).
  • The comment on _console_cors states that the operator console runs in the operator's browser and reads each country node directly, so cross-origin access is granted only for the minimised fleet endpoint (src/cluster_erp/server/app.py:1354-1361).
  • The project's written engineering rules forbid breaking the "app-level plus middleware" architecture.

Rules and boundaries

  • Every response carries the security headers through setdefault, so a route may set its own value first (src/cluster_erp/server/app.py:1413-1429).
  • /health and /ready are in the default skip list of the activity log (src/cluster_erp/config/settings.py:630-637).

Interaction with other parts

Limits and open points

  • The activity log is written in a separate task after the response is returned (src/cluster_erp/server/middleware.py:491-494). A process that stops at that moment can lose the row.

Terms introduced

Term Code identifier Definition Turkish term Source
middleware add_middleware A layer that wraps every request and response of the application. ara katman src/cluster_erp/server/app.py:1317
activity log ActivityLogMiddleware The record of every logged request with actor, path, status, duration, and the affected record. etkinlik günlüğü src/cluster_erp/server/middleware.py:424

Verified against commit 92f8051 on 2026-11-13.

Verified against commit 92f8051 · 2026-11-13