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_appadds five middlewares:CommitBeforeResponseMiddleware,ActivityLogMiddleware,GZipMiddleware,_console_cors, and_security_headers(src/cluster_erp/server/app.py:1317-1430). Counted from the threeadd_middlewarecalls 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_headersrefuses a body larger thanmax_request_body_byteswith HTTP 413 before reading it, and sets security headers on every response (src/cluster_erp/server/app.py:1390-1430).ActivityLogMiddlewarewrites 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).CommitBeforeResponseMiddlewareruns 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:
- Reads the request body of
POST,PUT, andPATCHrequests (src/cluster_erp/server/middleware.py:438-441). - 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). - Reads the response body for writes and for
GETrequests on a single record, and rebuilds the response with the raw header list (src/cluster_erp/server/middleware.py:455-487). - Starts
_write_logas 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/vserveswebapp/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)./appserves the same files withCache-Control: no-cache, and servesindex.htmlwith 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).
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-Lengthbefore the body is read, to bound memory use (src/cluster_erp/server/app.py:1392-1395). - The comment on
_console_corsstates 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). /healthand/readyare in the default skip list of the activity log (src/cluster_erp/config/settings.py:630-637).
Interaction with other parts¶
- Errors and exceptions builds the error bodies and the trace ids that the activity log stores.
- Database and sessions registers the commit hook.
- Logs owns the
activity_loganderror_logtables and their screens. - Settings reference lists the log and body-limit settings.
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 |
Related pages¶
- Platform core
- Server startup and app factory
- Database and sessions
- Errors and exceptions
- Cache and asset stamp
Verified against commit 92f8051 on 2026-11-13.