Menu

Middleware Pipeline

Relevant source files

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 Registration and Execution Order

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.

Registration Sequence

The middleware stack is registered in `register_middleware()` in the following order:

Sources: `backend/core/registrar.py140-174

CORS Middleware

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:

ConfigurationTypeDescription
MIDDLEWARE_CORSboolEnable/disable CORS middleware
CORS_ALLOWED_ORIGINSlist[str]Allowed origin URLs (without trailing slash)
CORS_EXPOSE_HEADERSlist[str]Headers exposed to browsers (includes X-Request-ID)

Sources: `backend/core/registrar.py79-92 `backend/core/conf.py128-139

Complete Request Flow

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

Middleware Components

1. ContextMiddleware (starlette-context)

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:

  • Trace ID Header Key: Configured via settings.TRACE_ID_REQUEST_HEADER_KEY (default: X-Request-ID)
  • Validation: RequestIdPlugin(validate=True) enforces UUID format
  • Log Length: settings.TRACE_ID_LOG_LENGTH (default: 32) truncates UUID in logs

The 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

2. AccessMiddleware

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:

  • Stores ctx.perf_time using time.perf_counter() for high-precision elapsed time calculation
  • Stores ctx.start_time using timezone.now() for opera log timestamps
  • Logs request start with debug level (skips OPTIONS requests)

The 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

3. I18nMiddleware

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:

  • Default Language: settings.I18N_DEFAULT_LANGUAGE (default: zh-CN)
  • Available Languages: Determined by translation files in the i18n system

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

4. JwtAuthMiddleware (AuthenticationMiddleware)

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:

SettingTypeDescription
TOKEN_REQUEST_PATH_EXCLUDElist[str]Exact path matches (e.g., /api/v1/auth/login)
TOKEN_REQUEST_PATH_EXCLUDE_PATTERNlist[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

5. StateMiddleware

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:

  1. IP Extraction: Checks X-Forwarded-For header (for proxy/load balancer scenarios), falls back to request.client.host
  2. Geolocation: Based on settings.IP_LOCATION_PARSE configuration:
    • 'online': Uses online geolocation API
    • 'offline': Uses local IP database
    • 'false': Skips geolocation
  3. Caching: Results cached in Redis with prefix settings.IP_LOCATION_REDIS_PREFIX for 24 hours

User Agent Parsing:

The parse_user_agent_info() function uses the user-agents library to extract:

  • user_agent: Raw user agent string
  • os: 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:

Sources: `backend/middleware/state_middleware.py8-34 `backend/core/conf.py163-166

6. OperaLogMiddleware

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:

  1. Request Argument Capture (`backend/middleware/opera_log_middleware.py123-169):

    • Query parameters: dict(request.query_params)
    • Path parameters: request.path_params
    • JSON body: await request.json() (for application/json)
    • Form data: await request.form() (distinguishes x-www-form-urlencoded vs multipart/form-data)
    • Handles file uploads by storing filename instead of content
  2. 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/ItsDangerous
  3. Exception 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 details

    This pattern allows the middleware to capture exception information without catching exceptions itself, maintaining proper exception propagation.

  4. 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):

  • Static assets: /favicon.ico
  • API docs: /docs, /redoc, /openapi
  • OAuth2 callbacks: /api/v1/oauth2/*/callback
  • Swagger login endpoint: /api/v1/auth/login/swagger

Sources: `backend/middleware/opera_log_middleware.py25-214 `backend/core/conf.py188-209

Asynchronous Opera Log Consumer

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:

  1. Batch Dequeue: Calls 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)
  2. Bulk Insert: Uses opera_log_service.bulk_create() to insert all logs in a single transaction
  3. Error Handling: Wraps database operations in try/finally to ensure task_done() is called even on errors

Startup 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:

  • Non-blocking: Middleware queues logs in O(1) time without database I/O
  • Batch writes: Reduces database connections and transaction overhead
  • Backpressure handling: Queue maxsize prevents memory exhaustion under high load
  • Graceful degradation: If queue fills, queue.put() will block, applying natural backpressure

Sources: `backend/middleware/opera_log_middleware.py196-213 `backend/core/registrar.py64-65 `backend/core/conf.py208-209

Exception Handling Integration

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 TypeContext KeySet 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:

  • Exception details are captured even when exceptions are handled by FastAPI
  • The middleware doesn't need to catch exceptions itself
  • Opera logs reflect the actual error codes returned to clients

Sources: `backend/middleware/opera_log_middleware.py55-66 `backend/common/exception/exception_handler.py73-172

Configuration Summary

The following table summarizes all middleware-related configuration options:

SettingTypeDefaultPurpose
CORS
MIDDLEWARE_CORSboolTrueEnable CORS middleware
CORS_ALLOWED_ORIGINSlist[str]['http://127.0.0.1:8000', ...]Allowed origins
CORS_EXPOSE_HEADERSlist[str]['X-Request-ID']Exposed headers
Trace ID
TRACE_ID_REQUEST_HEADER_KEYstr'X-Request-ID'Header name for trace ID
TRACE_ID_LOG_LENGTHint32Truncate UUID in logs
TRACE_ID_LOG_DEFAULT_VALUEstr'-'Default when no context exists
JWT Whitelist
TOKEN_REQUEST_PATH_EXCLUDElist[str]['/api/v1/auth/login']JWT-exempt paths
TOKEN_REQUEST_PATH_EXCLUDE_PATTERNlist[Pattern]Regex patternsJWT-exempt patterns
IP Location
IP_LOCATION_PARSEstr'offline'online/offline/false
IP_LOCATION_REDIS_PREFIXstr'fba:ip:location'Cache key prefix
IP_LOCATION_EXPIRE_SECONDSint86400Cache TTL (1 day)
Opera Log
OPERA_LOG_PATH_EXCLUDElist[str]['/favicon.ico', '/docs', ...]Excluded paths
OPERA_LOG_ENCRYPT_TYPEint10=AES, 1=MD5, 2=ItsDangerous, 3=plain, other=mask
OPERA_LOG_ENCRYPT_KEY_INCLUDElist[str]['password', ...]Keys to encrypt
OPERA_LOG_ENCRYPT_SECRET_KEYstrRequiredEncryption secret
OPERA_LOG_QUEUE_BATCH_CONSUME_SIZEint100Batch size for DB writes
OPERA_LOG_QUEUE_TIMEOUTint60Queue dequeue timeout (seconds)
Logging
LOG_FORMATstrWith colorsLog line format
LOG_STD_LEVELstr'INFO'Console log level
LOG_FILE_ACCESS_LEVELstr'INFO'Access log file level
LOG_FILE_ERROR_LEVELstr'ERROR'Error log file level

Sources: `backend/core/conf.py128-219

Middleware Execution Timeline

The following diagram illustrates the temporal relationship between middleware execution, route handling, and logging:

This timeline assumes:

  • IP geolocation is enabled (adds ~15ms)
  • Redis operations take 5ms each
  • Route handler processes for ~58ms
  • Total request time: ~105ms

Sources: All middleware files referenced above