# Request Flow Complete journey of a request through the Wizamart platform, from client to response. ## Overview This document traces how requests flow through the multi-tenant system, showing the path through middleware, routing, and response generation. ## High-Level Flow ```mermaid graph TB A[Client Request] --> B{Request Type?} B -->|API| C[REST API Flow] B -->|HTML Page| D[Page Rendering Flow] C --> E[Middleware Stack] D --> E E --> F[Vendor Detection] F --> G[Context Detection] G --> H[Theme Loading] H --> I[Router] I -->|API| J[API Handler] I -->|Page| K[Route Handler] J --> L[JSON Response] K --> M[Jinja2 Template] M --> N[HTML Response] L --> O[Client] N --> O ``` ## Detailed Request Flow ### 1. Client Sends Request **Example Requests**: ```http # Shop page request (subdomain mode) GET https://wizamart.platform.com/shop/products Host: wizamart.platform.com # API request GET https://platform.com/api/v1/products?vendor_id=1 Authorization: Bearer eyJ0eXAi... Host: platform.com # Admin page request GET https://platform.com/admin/vendors Authorization: Bearer eyJ0eXAi... Host: platform.com ``` ### 2. LoggingMiddleware (Entry Point) **What happens**: - Request enters the application - Timer starts - Request logged with method, path, client IP **Request State**: ```python # Start time recorded start_time = time.time() # Log entry logger.info(f"Request: GET /shop/products from 192.168.1.100") ``` **Output**: Nothing added to `request.state` yet ### 3. VendorContextMiddleware **What happens**: - Analyzes host header and path - Determines routing mode (custom domain / subdomain / path-based) - Queries database for vendor - Extracts clean path **Example Processing** (Subdomain Mode): ```python # Input host = "wizamart.platform.com" path = "/shop/products" # Detection logic if host != settings.platform_domain: # Subdomain detected vendor_code = host.split('.')[0] # "wizamart" # Query database vendor = db.query(Vendor).filter( Vendor.code == vendor_code ).first() # Set request state request.state.vendor = vendor request.state.vendor_id = vendor.id request.state.clean_path = "/shop/products" # Already clean ``` **Request State After**: ```python request.state.vendor = request.state.vendor_id = 1 request.state.clean_path = "/shop/products" ``` ### 4. Router Matching (FastAPI Native) **What happens**: - FastAPI matches the request path against registered routes - For path-based development mode, routes are registered with two prefixes: - `/shop/*` for subdomain/custom domain - `/vendors/{vendor_code}/shop/*` for path-based development **Example** (Path-Based Mode): ```python # In main.py - Double router mounting app.include_router(shop_pages.router, prefix="/shop") app.include_router(shop_pages.router, prefix="/vendors/{vendor_code}/shop") # Request: /vendors/WIZAMART/shop/products # Matches: Second router (/vendors/{vendor_code}/shop) # Route: @router.get("/products") # vendor_code available as path parameter = "WIZAMART" ``` **Note:** Previous implementations used `PathRewriteMiddleware` to rewrite paths. This has been replaced with FastAPI's native routing via double router mounting. **Request State After**: No changes to state, but internal path updated ### 5. ContextDetectionMiddleware **What happens**: - Analyzes the clean path - Determines request context type - Sets context in request state **Detection Logic**: ```python path = request.state.clean_path # "/shop/products" if path.startswith("/api/"): context = RequestContext.API elif path.startswith("/admin/"): context = RequestContext.ADMIN elif path.startswith("/vendor/"): context = RequestContext.VENDOR_DASHBOARD elif hasattr(request.state, 'vendor') and request.state.vendor: context = RequestContext.SHOP # ← Our example else: context = RequestContext.FALLBACK request.state.context_type = context ``` **Request State After**: ```python request.state.context_type = RequestContext.SHOP ``` ### 6. ThemeContextMiddleware **What happens**: - Checks if request has a vendor - Loads theme configuration from database - Injects theme into request state **Theme Loading**: ```python if hasattr(request.state, 'vendor_id'): theme = db.query(VendorTheme).filter( VendorTheme.vendor_id == request.state.vendor_id ).first() request.state.theme = { "primary_color": theme.primary_color, "secondary_color": theme.secondary_color, "logo_url": theme.logo_url, "custom_css": theme.custom_css } ``` **Request State After**: ```python request.state.theme = { "primary_color": "#3B82F6", "secondary_color": "#10B981", "logo_url": "/static/vendors/wizamart/logo.png", "custom_css": "..." } ``` ### 7. FastAPI Router **What happens**: - Request reaches FastAPI's router - Router matches path to registered route - Route dependencies are resolved - Handler function is called **Route Matching**: ```python # Request path (after rewrite): "/shop/products" # Matches this route @app.get("/shop/products") async def get_shop_products(request: Request): # Handler code pass ``` ### 8. Route Handler Execution **Example Handler**: ```python from app.routes import shop_pages @router.get("/shop/products") async def shop_products_page( request: Request, db: Session = Depends(get_db) ): # Access vendor from request state vendor = request.state.vendor vendor_id = request.state.vendor_id # Query products for this vendor products = db.query(Product).filter( Product.vendor_id == vendor_id ).all() # Render template with context return templates.TemplateResponse( "shop/products.html", { "request": request, "vendor": vendor, "products": products, "theme": request.state.theme } ) ``` ### 9. Template Rendering (Jinja2) **Template** (`templates/shop/products.html`): ```jinja2 {{ vendor.name }} - Products

{{ vendor.name }} Shop

{% for product in products %}

{{ product.name }}

{{ product.price }}

{% endfor %}
``` **Rendered HTML**: ```html Wizamart - Products

Wizamart Shop

Product 1

$29.99

``` ### 10. Response Sent Back **LoggingMiddleware (Response Phase)**: - Calculates total request time - Logs response status and duration - Adds performance header **Logging**: ```python duration = time.time() - start_time # 0.143 seconds logger.info( f"Response: 200 for GET /shop/products (0.143s)" ) # Add header response.headers["X-Process-Time"] = "0.143" ``` **Final Response**: ```http HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 X-Process-Time: 0.143 Content-Length: 2847 ... ``` ## Flow Diagrams by Request Type ### API Request Flow ```mermaid sequenceDiagram participant Client participant Logging participant Vendor participant Context participant Router participant Handler participant DB Client->>Logging: GET /api/v1/products?vendor_id=1 Logging->>Vendor: Pass request Note over Vendor: No vendor detection
(API uses query param) Vendor->>Context: Pass request Context->>Context: Detect API context Note over Context: context_type = API Context->>Router: Route request Router->>Handler: Call API handler Handler->>DB: Query products DB-->>Handler: Product data Handler-->>Router: JSON response Router-->>Client: {products: [...]} ``` ### Admin Page Flow ```mermaid sequenceDiagram participant Client participant Logging participant Vendor participant Context participant Theme participant Router participant Handler participant Template Client->>Logging: GET /admin/vendors Logging->>Vendor: Pass request Note over Vendor: No vendor
(Admin area) Vendor->>Context: Pass request Context->>Context: Detect Admin context Note over Context: context_type = ADMIN Context->>Theme: Pass request Note over Theme: Skip theme
(No vendor) Theme->>Router: Route request Router->>Handler: Call handler Handler->>Template: Render admin template Template-->>Client: Admin HTML page ``` ### Shop Page Flow (Full Multi-Tenant) ```mermaid sequenceDiagram participant Client participant Logging participant Vendor participant Path participant Context participant Theme participant Router participant Handler participant DB participant Template Client->>Logging: GET /shop/products
Host: wizamart.platform.com Logging->>Vendor: Pass request Vendor->>DB: Query vendor by subdomain DB-->>Vendor: Vendor object Note over Vendor: Set vendor, vendor_id, clean_path Vendor->>Path: Pass request Note over Path: Path already clean Path->>Context: Pass request Context->>Context: Detect Shop context Note over Context: context_type = SHOP Context->>Theme: Pass request Theme->>DB: Query theme DB-->>Theme: Theme config Note over Theme: Set theme in request.state Theme->>Router: Route request Router->>Handler: Call handler Handler->>DB: Query products for vendor DB-->>Handler: Product list Handler->>Template: Render with theme Template-->>Client: Themed shop HTML ``` ## Request State Timeline Showing how `request.state` is built up through the middleware stack: ``` Initial State: {} After VendorContextMiddleware: { vendor: , vendor_id: 1, clean_path: "/shop/products" } After ContextDetectionMiddleware: { vendor: , vendor_id: 1, clean_path: "/shop/products", context_type: RequestContext.SHOP } After ThemeContextMiddleware: { vendor: , vendor_id: 1, clean_path: "/shop/products", context_type: RequestContext.SHOP, theme: { primary_color: "#3B82F6", secondary_color: "#10B981", logo_url: "/static/vendors/wizamart/logo.png", custom_css: "..." } } ``` ## Performance Metrics Typical request timings: | Component | Time | Percentage | |-----------|------|------------| | Middleware Stack | 5ms | 3% | | - VendorContextMiddleware | 2ms | 1% | | - ContextDetectionMiddleware | <1ms | <1% | | - ThemeContextMiddleware | 2ms | 1% | | Database Queries | 15ms | 10% | | Business Logic | 50ms | 35% | | Template Rendering | 75ms | 52% | | **Total** | **145ms** | **100%** | ## Error Handling in Flow ### Middleware Errors If middleware encounters an error: ```python try: # Middleware logic vendor = detect_vendor(request) except Exception as e: logger.error(f"Vendor detection failed: {e}") # Set default/None request.state.vendor = None # Continue to next middleware ``` ### Handler Errors If route handler raises an exception: ```python try: response = await handler(request) except HTTPException as e: # FastAPI handles HTTP exceptions return error_response(e.status_code, e.detail) except Exception as e: # Custom exception handler logger.error(f"Handler error: {e}") return error_response(500, "Internal Server Error") ``` ## Related Documentation - [Middleware Stack](middleware.md) - Detailed middleware documentation - [Multi-Tenant System](multi-tenant.md) - Tenant routing modes - [Authentication & RBAC](auth-rbac.md) - Security flow - [Architecture Overview](overview.md) - System architecture ## Debugging Request Flow ### Enable Request Logging ```python import logging logging.getLogger("middleware").setLevel(logging.DEBUG) logging.getLogger("fastapi").setLevel(logging.DEBUG) ``` ### Add Debug Endpoint ```python @app.get("/debug/request-state") async def debug_state(request: Request): return { "path": request.url.path, "host": request.headers.get("host"), "vendor": request.state.vendor.name if hasattr(request.state, 'vendor') else None, "vendor_id": getattr(request.state, 'vendor_id', None), "clean_path": getattr(request.state, 'clean_path', None), "context_type": request.state.context_type.value if hasattr(request.state, 'context_type') else None, "has_theme": bool(getattr(request.state, 'theme', None)) } ``` ### Check Middleware Order In `main.py`, middleware registration order is critical: ```python # REVERSE order (Last In, First Out) app.add_middleware(LoggingMiddleware) # Runs first app.add_middleware(ThemeContextMiddleware) # Runs fifth app.add_middleware(ContextDetectionMiddleware) # Runs fourth app.add_middleware(VendorContextMiddleware) # Runs second ```