revamping documentation
This commit is contained in:
564
docs/architecture/request-flow.md
Normal file
564
docs/architecture/request-flow.md
Normal file
@@ -0,0 +1,564 @@
|
||||
# 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 = <Vendor: Wizamart>
|
||||
request.state.vendor_id = 1
|
||||
request.state.clean_path = "/shop/products"
|
||||
```
|
||||
|
||||
### 4. PathRewriteMiddleware
|
||||
|
||||
**What happens**:
|
||||
- Checks if `clean_path` is different from original path
|
||||
- If different, rewrites the request path for FastAPI routing
|
||||
|
||||
**Example** (Path-Based Mode):
|
||||
|
||||
```python
|
||||
# Input (path-based mode)
|
||||
original_path = "/vendor/WIZAMART/shop/products"
|
||||
clean_path = "/shop/products" # From VendorContextMiddleware
|
||||
|
||||
# Path rewrite
|
||||
if clean_path != original_path:
|
||||
request.scope['path'] = clean_path
|
||||
request._url = request.url.replace(path=clean_path)
|
||||
```
|
||||
|
||||
**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
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ vendor.name }} - Products</title>
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: {{ theme.primary_color }};
|
||||
--secondary-color: {{ theme.secondary_color }};
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{ vendor.name }} Shop</h1>
|
||||
|
||||
<div class="products">
|
||||
{% for product in products %}
|
||||
<div class="product-card">
|
||||
<h2>{{ product.name }}</h2>
|
||||
<p>{{ product.price }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
**Rendered HTML**:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Wizamart - Products</title>
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #3B82F6;
|
||||
--secondary-color: #10B981;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Wizamart Shop</h1>
|
||||
<div class="products">
|
||||
<div class="product-card">
|
||||
<h2>Product 1</h2>
|
||||
<p>$29.99</p>
|
||||
</div>
|
||||
<!-- More products... -->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### 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<br/>(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<br/>(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<br/>(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<br/>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: Wizamart>,
|
||||
vendor_id: 1,
|
||||
clean_path: "/shop/products"
|
||||
}
|
||||
|
||||
After ContextDetectionMiddleware:
|
||||
{
|
||||
vendor: <Vendor: Wizamart>,
|
||||
vendor_id: 1,
|
||||
clean_path: "/shop/products",
|
||||
context_type: RequestContext.SHOP
|
||||
}
|
||||
|
||||
After ThemeContextMiddleware:
|
||||
{
|
||||
vendor: <Vendor: Wizamart>,
|
||||
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
|
||||
```
|
||||
app.add_middleware(ThemeContextMiddleware) # Runs fifth
|
||||
app.add_middleware(ContextDetectionMiddleware) # Runs fourth
|
||||
app.add_middleware(VendorContextMiddleware) # Runs second
|
||||
```
|
||||
Reference in New Issue
Block a user