removing legacy code on path_rewrite_middleware

This commit is contained in:
2025-11-18 23:32:07 +01:00
parent f14686c131
commit d947fa5ca0
9 changed files with 114 additions and 302 deletions

View File

@@ -64,28 +64,9 @@ Injects: request.state.vendor = <Vendor object>
**See**: [Multi-Tenant System](multi-tenant.md) for routing modes
### 3. Path Rewrite Middleware
**Note on Path-Based Routing:** Previous implementations used a `PathRewriteMiddleware` to rewrite paths at runtime. This has been replaced with **double router mounting** in `main.py`, where shop routes are registered twice with different prefixes (`/shop` and `/vendors/{vendor_code}/shop`). This approach is simpler and uses FastAPI's native routing capabilities.
**Purpose**: Rewrite request paths for proper FastAPI routing
**What it does**:
- Uses the `clean_path` extracted by VendorContextMiddleware
- Rewrites `request.scope['path']` to remove vendor prefix
- Allows FastAPI routes to match correctly
**Example** (Path-Based Development Mode):
```
Original path: /vendors/WIZAMART/shop/products
Clean path: /shop/products (set by VendorContextMiddleware)
Path Rewrite Middleware changes request path to: /shop/products
FastAPI router can now match: @app.get("/shop/products")
```
**Why it's needed**: FastAPI routes don't include vendor prefix, so we strip it
### 4. Context Detection Middleware
### 3. Context Detection Middleware
**Purpose**: Determine the type/context of the request
@@ -115,7 +96,7 @@ else:
**Why it's useful**: Error handlers and templates adapt based on context
### 5. Theme Context Middleware
### 4. Theme Context Middleware
**Purpose**: Load vendor-specific theme settings
@@ -146,13 +127,12 @@ else:
graph TD
A[Client Request] --> B[1. LoggingMiddleware]
B --> C[2. VendorContextMiddleware]
C --> D[3. PathRewriteMiddleware]
D --> E[4. ContextDetectionMiddleware]
E --> F[5. ThemeContextMiddleware]
F --> G[6. FastAPI Router]
G --> H[Route Handler]
H --> I[Response]
I --> J[Client]
C --> D[3. ContextDetectionMiddleware]
D --> E[4. ThemeContextMiddleware]
E --> F[5. FastAPI Router]
F --> G[Route Handler]
G --> H[Response]
H --> I[Client]
```
### Why This Order Matters
@@ -164,25 +144,21 @@ graph TD
- Must log errors from all other middleware
2. **VendorContextMiddleware second**
- Must run before PathRewriteMiddleware (provides clean_path)
- Must run before ContextDetectionMiddleware (provides vendor)
- Must run before ContextDetectionMiddleware (provides vendor and clean_path)
- Must run before ThemeContextMiddleware (provides vendor_id)
3. **PathRewriteMiddleware third**
- Depends on clean_path from VendorContextMiddleware
- Must run before ContextDetectionMiddleware (rewrites path)
4. **ContextDetectionMiddleware fourth**
3. **ContextDetectionMiddleware third**
- Uses clean_path from VendorContextMiddleware
- Uses rewritten path from PathRewriteMiddleware
- Provides context_type for ThemeContextMiddleware
5. **ThemeContextMiddleware last**
4. **ThemeContextMiddleware last**
- Depends on vendor from VendorContextMiddleware
- Depends on context_type from ContextDetectionMiddleware
**Breaking this order will break the application!**
**Note:** Path-based routing (e.g., `/vendors/{code}/shop/*`) is handled by double router mounting in `main.py`, not by middleware.
## Request State Variables
Middleware components inject these variables into `request.state`:
@@ -191,7 +167,7 @@ Middleware components inject these variables into `request.state`:
|----------|--------|------|---------|-------------|
| `vendor` | VendorContextMiddleware | Vendor | Theme, Templates | Current vendor object |
| `vendor_id` | VendorContextMiddleware | int | Queries, Theme | Current vendor ID |
| `clean_path` | VendorContextMiddleware | str | PathRewrite, Context | Path without vendor prefix |
| `clean_path` | VendorContextMiddleware | str | Context | Path without vendor prefix (for context detection) |
| `context_type` | ContextDetectionMiddleware | RequestContext | Theme, Error handlers | Request context enum |
| `theme` | ThemeContextMiddleware | dict | Templates | Vendor theme config |
@@ -260,25 +236,21 @@ async def get_products(request: Request):
↓ Sets: request.state.vendor_id = 1
↓ Sets: request.state.clean_path = "/shop/products"
3. PathRewriteMiddleware
↓ Path already clean (no rewrite needed for subdomain mode)
↓ request.scope['path'] = "/shop/products"
4. ContextDetectionMiddleware
3. ContextDetectionMiddleware
↓ Analyzes path: "/shop/products"
↓ Has vendor: Yes
↓ Not admin/api/vendor dashboard
↓ Sets: request.state.context_type = RequestContext.SHOP
5. ThemeContextMiddleware
4. ThemeContextMiddleware
↓ Loads theme for vendor_id = 1
↓ Sets: request.state.theme = {...theme config...}
6. FastAPI Router
5. FastAPI Router
↓ Matches route: @app.get("/shop/products")
↓ Calls handler function
7. Route Handler
6. Route Handler
↓ Accesses: request.state.vendor_id
↓ Queries: products WHERE vendor_id = 1
↓ Renders template with vendor data

View File

@@ -452,10 +452,12 @@ Host: myplatform.com
- Sets: request.state.vendor = <Vendor>
- Sets: request.state.clean_path = "/shop/products"
2. PathRewriteMiddleware
- Rewrites: request.scope['path'] = "/shop/products"
2. FastAPI Router
- Routes registered with prefix: /vendors/{vendor_code}/shop
- Matches: /vendors/WIZAMART/shop/products
- vendor_code path parameter = "WIZAMART"
3-4. Same as previous examples
3-4. Same as previous examples (Context, Theme middleware)
```
## Testing Multi-Tenancy

View File

@@ -82,20 +82,19 @@ Custom middleware handles:
graph TB
A[Client Request] --> B[Logging Middleware]
B --> C[Vendor Context Middleware]
C --> D[Path Rewrite Middleware]
D --> E[Context Detection Middleware]
E --> F[Theme Context Middleware]
F --> G{Request Type?}
C --> D[Context Detection Middleware]
D --> E[Theme Context Middleware]
E --> F{Request Type?}
G -->|API /api/*| H[API Router]
G -->|Admin /admin/*| I[Admin Page Router]
G -->|Vendor /vendor/*| J[Vendor Page Router]
G -->|Shop /shop/*| K[Shop Page Router]
F -->|API /api/*| G[API Router]
F -->|Admin /admin/*| H[Admin Page Router]
F -->|Vendor /vendor/*| I[Vendor Page Router]
F -->|Shop /shop/*| J[Shop Page Router]
H --> L[JSON Response]
I --> M[Admin HTML]
J --> N[Vendor HTML]
K --> O[Shop HTML]
G --> K[JSON Response]
H --> L[Admin HTML]
I --> M[Vendor HTML]
J --> N[Shop HTML]
```
**See:** [Request Flow](request-flow.md) for detailed journey

View File

@@ -112,25 +112,29 @@ request.state.vendor_id = 1
request.state.clean_path = "/shop/products"
```
### 4. PathRewriteMiddleware
### 4. Router Matching (FastAPI Native)
**What happens**:
- Checks if `clean_path` is different from original path
- If different, rewrites the request path for FastAPI routing
- 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
# Input (path-based development mode)
original_path = "/vendors/WIZAMART/shop/products"
clean_path = "/shop/products" # From VendorContextMiddleware
# 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")
# Path rewrite
if clean_path != original_path:
request.scope['path'] = clean_path
request._url = request.url.replace(path=clean_path)
# 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

View File

@@ -345,40 +345,38 @@ In Jinja2 template:
---
## Potential Issue: Path-Based Development Mode
## Path-Based Routing Implementation
⚠️ **Current Implementation Gap:**
**Current Solution: Double Router Mounting**
The `vendor_context_middleware` sets `clean_path` for path-based URLs, but this isn't used for FastAPI routing.
**Problem:**
- Incoming: `GET http://localhost:8000/vendors/acme/shop/products`
- Routes registered: `@router.get("/shop/products")`
- FastAPI tries to match `/vendors/acme/shop/products` against `/shop/products`
- Result: ❌ 404 Not Found
**Solution (Recommended):**
Add a path rewriting middleware in `main.py`:
The application handles path-based routing by registering shop routes **twice** with different prefixes:
```python
async def path_rewrite_middleware(request: Request, call_next):
"""Rewrite path for path-based vendor routing in development mode."""
if hasattr(request.state, 'clean_path'):
# Replace request path for FastAPI routing
request._url = request._url.replace(path=request.state.clean_path)
return await call_next(request)
# In main.py, add after vendor_context_middleware:
app.middleware("http")(path_rewrite_middleware)
```
Or alternatively, mount the router twice:
```python
# In main.py
app.include_router(shop_pages.router, prefix="/shop")
app.include_router(shop_pages.router, prefix="/vendors/{vendor_code}/shop") # Path-based development mode
app.include_router(shop_pages.router, prefix="/vendors/{vendor_code}/shop")
```
**How This Works:**
1. **For Subdomain/Custom Domain Mode:**
- URL: `https://acme.wizamart.com/shop/products`
- Matches: First router with `/shop` prefix
- Route: `@router.get("/products")` → Full path: `/shop/products`
2. **For Path-Based Development Mode:**
- URL: `http://localhost:8000/vendors/acme/shop/products`
- Matches: Second router with `/vendors/{vendor_code}/shop` prefix
- Route: `@router.get("/products")` → Full path: `/vendors/{vendor_code}/shop/products`
- Bonus: `vendor_code` available as path parameter!
**Benefits:**
- ✅ No middleware complexity or path manipulation
- ✅ FastAPI native routing
- ✅ Explicit and maintainable
- ✅ Vendor code accessible via path parameter when needed
- ✅ Both deployment modes supported cleanly
---
## Authentication in Multi-Tenant Shop

View File

@@ -200,23 +200,31 @@ Middleware for request/response logging and performance monitoring.
---
## Path Rewriting
## Path-Based Routing Solution
### path_rewrite_middleware
**Modern Approach: Double Router Mounting**
Middleware function that rewrites request paths for path-based vendor routing.
Instead of using middleware to rewrite paths, the application registers shop routes **twice** with different prefixes:
**Purpose:**
Allows `/vendors/VENDORCODE/shop/products` (path-based development mode) to be internally routed as `/shop/products` for proper FastAPI route matching.
```python
# In main.py
app.include_router(shop_pages.router, prefix="/shop")
app.include_router(shop_pages.router, prefix="/vendors/{vendor_code}/shop")
```
**Execution Order:**
Must run AFTER VendorContextMiddleware and BEFORE ContextDetectionMiddleware.
**How It Works:**
- **Subdomain/Custom Domain Mode**: Routes match `/shop/*` prefix
- **Path-Based Development Mode**: Routes match `/vendors/{vendor_code}/shop/*` prefix
- FastAPI handles routing naturally without path manipulation
- Vendor code is available as a path parameter when needed
::: middleware.path_rewrite_middleware.path_rewrite_middleware
options:
show_source: true
heading_level: 4
show_root_heading: false
**Benefits:**
- ✅ No middleware complexity
- ✅ Explicit route definitions
- ✅ FastAPI native routing
- ✅ Vendor code accessible via path parameter
**Note:** Previous implementations used `path_rewrite_middleware` to rewrite paths at runtime. This approach has been deprecated in favor of double mounting, which is simpler and more maintainable.
---
@@ -228,18 +236,19 @@ The middleware stack must be configured in the correct order for proper function
graph TD
A[Request] --> B[LoggingMiddleware]
B --> C[VendorContextMiddleware]
C --> D[PathRewriteMiddleware]
D --> E[ContextMiddleware]
E --> F[ThemeContextMiddleware]
F --> G[Application Routes]
G --> H[Response]
C --> D[ContextMiddleware]
D --> E[ThemeContextMiddleware]
E --> F[Application Routes]
F --> G[Response]
```
**Critical Dependencies:**
1. **VendorContextMiddleware** must run first to detect vendor
2. **PathRewriteMiddleware** needs `clean_path` from VendorContext
3. **ContextMiddleware** needs rewritten path
4. **ThemeContextMiddleware** needs context type
1. **LoggingMiddleware** runs first for request timing
2. **VendorContextMiddleware** detects vendor and sets clean_path
3. **ContextMiddleware** detects context type (API/Admin/Vendor/Shop)
4. **ThemeContextMiddleware** loads vendor theme based on context
**Note:** Path-based routing (e.g., `/vendors/{code}/shop/*`) is handled by double router mounting in `main.py`, not by middleware.
---

View File

@@ -289,13 +289,12 @@ Understanding how a request flows through Wizamart:
graph LR
A[Client Request] --> B[LoggingMiddleware]
B --> C[VendorContextMiddleware]
C --> D[PathRewriteMiddleware]
D --> E[ContextDetectionMiddleware]
E --> F[ThemeContextMiddleware]
F --> G[FastAPI Router]
G --> H{Request Type}
H -->|API| I[JSON Response]
H -->|Page| J[HTML Template]
C --> D[ContextDetectionMiddleware]
D --> E[ThemeContextMiddleware]
E --> F[FastAPI Router]
F --> G{Request Type}
G -->|API| H[JSON Response]
G -->|Page| I[HTML Template]
```
**Learn more**: [Request Flow](architecture/request-flow.md)