The middleware pipeline defines the ordered sequence of request interceptors that process every HTTP request before it reaches route handlers and every response before it returns to clients. This page documents the seven-layer middleware stack, execution order, individual middleware responsibilities, and integration with the asynchronous logging system.
For information about exception handling and response formatting, see Exception Handling & Response Codes. For details on the logging infrastructure and opera log service, see Logging & Operation Logs. For JWT authentication implementation, see JWT Authentication & Token Management.
Middleware components are registered in `backend/core/registrar.py140-174 using FastAPI's add_middleware method. Middleware execution follows LIFO (Last In, First Out) order: the last registered middleware executes first for incoming requests and last for outgoing responses.
The middleware stack is registered in `register_middleware()` in the following order:
Sources: `backend/core/registrar.py140-174
CORS (Cross-Origin Resource Sharing) middleware is registered separately as part of the ASGI middleware stack using a custom build_middleware_stack() override in `backend/core/registrar.py84-92:
CORS middleware executes before all other middleware because it wraps the entire application stack. Configuration is defined in `backend/core/conf.py128-139:
| Configuration | Type | Description |
|---|---|---|
MIDDLEWARE_CORS | bool | Enable/disable CORS middleware |
CORS_ALLOWED_ORIGINS | list[str] | Allowed origin URLs (without trailing slash) |
CORS_EXPOSE_HEADERS | list[str] | Headers exposed to browsers (includes X-Request-ID) |
Sources: `backend/core/registrar.py79-92 `backend/core/conf.py128-139
The following diagram shows the complete request processing pipeline from client to route handler, including all middleware layers and their interactions with external systems:
Sources: `backend/core/registrar.py140-174 `backend/middleware/access_middleware.py11-36 `backend/middleware/state_middleware.py8-34 `backend/middleware/opera_log_middleware.py25-121
Execution Position: First
Implementation: External library starlette-context with RequestIdPlugin
Registration: `backend/core/registrar.py167-174
This middleware establishes the request context using context variables and assigns a unique trace ID to each request:
Configuration:
settings.TRACE_ID_REQUEST_HEADER_KEY (default: X-Request-ID)RequestIdPlugin(validate=True) enforces UUID formatsettings.TRACE_ID_LOG_LENGTH (default: 32) truncates UUID in logsThe trace ID is accessible throughout the request lifecycle via `backend/utils/trace_id.py5-9:
Sources: `backend/core/registrar.py167-174 `backend/core/conf.py168-171 `backend/utils/trace_id.py5-9
Execution Position: Second
Implementation: `backend/middleware/access_middleware.py11-36
Purpose: Request timing and access logging initialization
This lightweight middleware captures request timing and logs the start of request processing:
Key Responsibilities:
ctx.perf_time using time.perf_counter() for high-precision elapsed time calculationctx.start_time using timezone.now() for opera log timestampsThe perf_time is later used by OperaLogMiddleware to calculate cost_time in `backend/middleware/opera_log_middleware.py54
Sources: `backend/middleware/access_middleware.py11-36
Execution Position: Third
Implementation: `backend/middleware/i18n_middleware.py` (file not provided, but referenced)
Purpose: Internationalization language detection
This middleware detects the client's preferred language from the Accept-Language header and sets the current language context. Configuration:
settings.I18N_DEFAULT_LANGUAGE (default: zh-CN)The detected language is used for error message localization, including Pydantic validation errors in `backend/common/exception/exception_handler.py46-56
Sources: `backend/core/registrar.py161 `backend/core/conf.py218 `backend/common/exception/exception_handler.py46-56
Execution Position: Fourth
Implementation: Custom backend for Starlette's AuthenticationMiddleware
Registration: `backend/core/registrar.py154-158
This middleware validates JWT tokens and enforces authentication requirements:
Path Whitelist Configuration:
The middleware consults two whitelist configurations in `backend/core/conf.py75-80:
| Setting | Type | Description |
|---|---|---|
TOKEN_REQUEST_PATH_EXCLUDE | list[str] | Exact path matches (e.g., /api/v1/auth/login) |
TOKEN_REQUEST_PATH_EXCLUDE_PATTERN | list[Pattern[str]] | Regex patterns (e.g., monitor endpoints) |
Token Storage and Validation:
Tokens are validated against Redis keys with the prefix settings.TOKEN_REDIS_PREFIX (default: fba:token). User information is cached with prefix settings.JWT_USER_REDIS_PREFIX (default: fba:user).
Error Handling:
Authentication errors are handled by `JwtAuthMiddleware.auth_exception_handler()` which returns a 401 response without raising exceptions to the application layer.
Sources: `backend/core/registrar.py154-158 `backend/core/conf.py75-80 `backend/core/conf.py100
Execution Position: Fifth
Implementation: `backend/middleware/state_middleware.py8-34
Purpose: Parse and store request metadata in context
This middleware extracts IP information and user agent details, storing them in the request context for downstream use:
IP Parsing:
The parse_ip_info() function extracts the client IP address and performs geolocation:
X-Forwarded-For header (for proxy/load balancer scenarios), falls back to request.client.hostsettings.IP_LOCATION_PARSE configuration:
'online': Uses online geolocation API'offline': Uses local IP database'false': Skips geolocationsettings.IP_LOCATION_REDIS_PREFIX for 24 hoursUser Agent Parsing:
The parse_user_agent_info() function uses the user-agents library to extract:
user_agent: Raw user agent stringos: Operating system (e.g., "Windows 10", "iOS 14.0")browser: Browser and version (e.g., "Chrome 96.0")device: Device type (e.g., "PC", "Mobile", "Tablet")Context Usage:
These context variables are consumed by:
OperaLogMiddleware for audit logging (`backend/middleware/opera_log_middleware.py100-107)LoginLogService for login audit trails (`backend/app/admin/service/login_log_service.py56-64)Sources: `backend/middleware/state_middleware.py8-34 `backend/core/conf.py163-166
Execution Position: Sixth (last middleware before route handler)
Implementation: `backend/middleware/opera_log_middleware.py25-214
Purpose: Capture operation logs and queue for asynchronous persistence
This is the most complex middleware, responsible for comprehensive audit logging:
Key Features:
Request Argument Capture (`backend/middleware/opera_log_middleware.py123-169):
dict(request.query_params)request.path_paramsawait request.json() (for application/json)await request.form() (distinguishes x-www-form-urlencoded vs multipart/form-data)Password Desensitization (`backend/middleware/opera_log_middleware.py172-194):
Configuration in `backend/core/conf.py201-207:
OPERA_LOG_ENCRYPT_TYPE: Encryption method (0=AES, 1=MD5, 2=ItsDangerous, 3=none, other=mask)OPERA_LOG_ENCRYPT_KEY_INCLUDE: Keys to encrypt (e.g., password, old_password)OPERA_LOG_ENCRYPT_SECRET_KEY: Secret key for AES/ItsDangerousException Integration (`backend/middleware/opera_log_middleware.py55-73):
The middleware checks ctx for exception details set by the exception handlers:
__request_http_exception__: HTTPException details__request_validation_exception__: RequestValidationError details__request_assertion_error__: AssertionError details__request_custom_exception__: BaseExceptionError detailsThis pattern allows the middleware to capture exception information without catching exceptions itself, maintaining proper exception propagation.
Asynchronous Queueing (`backend/middleware/opera_log_middleware.py115):
Logs are placed in an asyncio Queue with maxsize=100000 (`backend/middleware/opera_log_middleware.py28). This prevents blocking the request/response cycle while ensuring logs are persisted.
Path Exclusion:
Certain paths are excluded from opera logging via settings.OPERA_LOG_PATH_EXCLUDE (`backend/core/conf.py191-200):
/favicon.ico/docs, /redoc, /openapi/api/v1/oauth2/*/callback/api/v1/auth/login/swaggerSources: `backend/middleware/opera_log_middleware.py25-214 `backend/core/conf.py188-209
The opera log queue is consumed by a dedicated background task that performs batch database writes:
Consumer Implementation (`backend/middleware/opera_log_middleware.py196-213):
The consumer() classmethod runs an infinite loop that:
batch_dequeue() to collect up to settings.OPERA_LOG_QUEUE_BATCH_CONSUME_SIZE (default: 100) items with a timeout of settings.OPERA_LOG_QUEUE_TIMEOUT (default: 60 seconds)opera_log_service.bulk_create() to insert all logs in a single transactiontask_done() is called even on errorsStartup Registration:
The consumer task is created during application startup in `backend/core/registrar.py64-65:
This ensures the consumer begins processing immediately when the application starts.
Performance Benefits:
queue.put() will block, applying natural backpressureSources: `backend/middleware/opera_log_middleware.py196-213 `backend/core/registrar.py64-65 `backend/core/conf.py208-209
Middleware and exception handlers cooperate through the context variable system to capture exception details for logging:
Exception Context Keys:
Exception handlers set specific keys in ctx (`backend/common/exception/exception_handler.py`):
| Exception Type | Context Key | Set By |
|---|---|---|
HTTPException | __request_http_exception__ | `exception_handler.py97 |
RequestValidationError | __request_validation_exception__ | `exception_handler.py73 |
AssertionError | __request_assertion_error__ | `exception_handler.py145 |
BaseExceptionError | __request_custom_exception__ | `exception_handler.py166 |
Opera Log Consumption (`backend/middleware/opera_log_middleware.py55-66):
The middleware checks these context keys after call_next() returns, extracting code and msg for the opera log. This pattern ensures:
Sources: `backend/middleware/opera_log_middleware.py55-66 `backend/common/exception/exception_handler.py73-172
The following table summarizes all middleware-related configuration options:
| Setting | Type | Default | Purpose |
|---|---|---|---|
| CORS | |||
MIDDLEWARE_CORS | bool | True | Enable CORS middleware |
CORS_ALLOWED_ORIGINS | list[str] | ['http://127.0.0.1:8000', ...] | Allowed origins |
CORS_EXPOSE_HEADERS | list[str] | ['X-Request-ID'] | Exposed headers |
| Trace ID | |||
TRACE_ID_REQUEST_HEADER_KEY | str | 'X-Request-ID' | Header name for trace ID |
TRACE_ID_LOG_LENGTH | int | 32 | Truncate UUID in logs |
TRACE_ID_LOG_DEFAULT_VALUE | str | '-' | Default when no context exists |
| JWT Whitelist | |||
TOKEN_REQUEST_PATH_EXCLUDE | list[str] | ['/api/v1/auth/login'] | JWT-exempt paths |
TOKEN_REQUEST_PATH_EXCLUDE_PATTERN | list[Pattern] | Regex patterns | JWT-exempt patterns |
| IP Location | |||
IP_LOCATION_PARSE | str | 'offline' | online/offline/false |
IP_LOCATION_REDIS_PREFIX | str | 'fba:ip:location' | Cache key prefix |
IP_LOCATION_EXPIRE_SECONDS | int | 86400 | Cache TTL (1 day) |
| Opera Log | |||
OPERA_LOG_PATH_EXCLUDE | list[str] | ['/favicon.ico', '/docs', ...] | Excluded paths |
OPERA_LOG_ENCRYPT_TYPE | int | 1 | 0=AES, 1=MD5, 2=ItsDangerous, 3=plain, other=mask |
OPERA_LOG_ENCRYPT_KEY_INCLUDE | list[str] | ['password', ...] | Keys to encrypt |
OPERA_LOG_ENCRYPT_SECRET_KEY | str | Required | Encryption secret |
OPERA_LOG_QUEUE_BATCH_CONSUME_SIZE | int | 100 | Batch size for DB writes |
OPERA_LOG_QUEUE_TIMEOUT | int | 60 | Queue dequeue timeout (seconds) |
| Logging | |||
LOG_FORMAT | str | With colors | Log line format |
LOG_STD_LEVEL | str | 'INFO' | Console log level |
LOG_FILE_ACCESS_LEVEL | str | 'INFO' | Access log file level |
LOG_FILE_ERROR_LEVEL | str | 'ERROR' | Error log file level |
Sources: `backend/core/conf.py128-219
The following diagram illustrates the temporal relationship between middleware execution, route handling, and logging:
This timeline assumes:
Sources: All middleware files referenced above
Refresh this wiki
This wiki was recently refreshed. Please wait 6 days to refresh again.