p2e: add Middleware tutorial — basic, CORS, secure headers, request logging, rate limiter pattern
swadhinbiswas committed 3 months ago
9776b10
1 parent 3bb94a5
commit 9776b10
1 file changed
+176 -0
Showing 1 changed file+176-0
▼docs_site/src/content/docs/tutorials/middleware.md
+176-0
| @@ -0,0 +1,176 @@ | ||
1 | +--- | |
2 | +title: Middleware | |
3 | +description: Intercept requests and responses with middleware for logging, timing, CORS, and custom processing. | |
4 | +--- | |
5 | + | |
6 | +Middleware lets you run code before and after every request. JustAPI supports the same middleware pattern as FastAPI and Starlette. | |
7 | + | |
8 | +## Basic HTTP Middleware | |
9 | + | |
10 | +Use the `@app.middleware("http")` decorator: | |
11 | + | |
12 | +```python | |
13 | +import time | |
14 | +from justapi import JustAPIApp | |
15 | + | |
16 | +app = JustAPIApp() | |
17 | + | |
18 | + | |
19 | +@app.middleware("http") | |
20 | +def timing_middleware(request, call_next): | |
21 | + start = time.time() | |
22 | + response = call_next(request) | |
23 | + elapsed = time.time() - start | |
24 | + response["headers"] = response.get("headers", []) + [ | |
25 | + (b"X-Process-Time", str(elapsed).encode()) | |
26 | + ] | |
27 | + return response | |
28 | +``` | |
29 | + | |
30 | +The middleware receives the `request` and a `call_next` function that dispatches the request down the chain. The return value is a response dict. | |
31 | + | |
32 | +## CORS Middleware | |
33 | + | |
34 | +JustAPI has a built-in CORS configuration method: | |
35 | + | |
36 | +```python | |
37 | +app.add_cors( | |
38 | + allow_origins=["https://example.com", "https://api.example.com"], | |
39 | + allow_methods=["GET", "POST", "PUT", "DELETE"], | |
40 | + allow_headers=["*"], | |
41 | + allow_credentials=True, | |
42 | +) | |
43 | +``` | |
44 | + | |
45 | +| Parameter | Type | Default | Description | | |
46 | +|---|---|---|---| | |
47 | +| `allow_origins` | list[str] | `["*"]` | Allowed origins | | |
48 | +| `allow_methods` | list[str] | All methods | Allowed HTTP methods | | |
49 | +| `allow_headers` | list[str] | `["*"]` | Allowed headers | | |
50 | +| `allow_credentials` | bool | `False` | Allow cookies/auth headers | | |
51 | + | |
52 | +## Secure Headers Middleware | |
53 | + | |
54 | +```python | |
55 | +app.enable_secure_headers(with_hsts=True) | |
56 | +``` | |
57 | + | |
58 | +This adds: | |
59 | +- `X-Content-Type-Options: nosniff` | |
60 | +- `X-Frame-Options: DENY` | |
61 | +- `Strict-Transport-Security` (when `with_hsts=True`) | |
62 | +- `Content-Security-Policy` | |
63 | +- `X-XSS-Protection: 0` | |
64 | +- `Referrer-Policy` | |
65 | + | |
66 | +## Multiple Middleware | |
67 | + | |
68 | +Middlewares are executed in registration order: | |
69 | + | |
70 | +```python | |
71 | +@app.middleware("http") | |
72 | +def middleware_one(request, call_next): | |
73 | + print("Before 1") | |
74 | + response = call_next(request) | |
75 | + print("After 1") | |
76 | + return response | |
77 | + | |
78 | + | |
79 | +@app.middleware("http") | |
80 | +def middleware_two(request, call_next): | |
81 | + print("Before 2") | |
82 | + response = call_next(request) | |
83 | + print("After 2") | |
84 | + return response | |
85 | +``` | |
86 | + | |
87 | +Output for a single request: | |
88 | + | |
89 | +``` | |
90 | +Before 1 | |
91 | +Before 2 | |
92 | +[handler executes] | |
93 | +After 2 | |
94 | +After 1 | |
95 | +``` | |
96 | + | |
97 | +## Route-Level Middleware | |
98 | + | |
99 | +You can add middleware to specific routes or routers using dependencies: | |
100 | + | |
101 | +```python | |
102 | +from justapi import Depends, HTTPException, Header | |
103 | + | |
104 | + | |
105 | +def require_admin(authorization: str = Header(...)): | |
106 | + if authorization != "Bearer admin-token": | |
107 | + raise HTTPException(403, "Admin access required") | |
108 | + | |
109 | + | |
110 | +@router.get("/admin/users", dependencies=[Depends(require_admin)]) | |
111 | +def list_all_users(request): | |
112 | + return {"users": [...]} | |
113 | + | |
114 | + | |
115 | +app.include_router(router, prefix="/api/v1") | |
116 | +``` | |
117 | + | |
118 | +## Real-World Patterns | |
119 | + | |
120 | +### Request Logging Middleware | |
121 | + | |
122 | +```python | |
123 | +import logging | |
124 | + | |
125 | +logger = logging.getLogger("justapi") | |
126 | + | |
127 | + | |
128 | +@app.middleware("http") | |
129 | +def log_requests(request, call_next): | |
130 | + method = request.method | |
131 | + path = request.path | |
132 | + logger.info(f"→ {method} {path}") | |
133 | + response = call_next(request) | |
134 | + status = response.get("status", 200) | |
135 | + logger.info(f"← {method} {path} → {status}") | |
136 | + return response | |
137 | +``` | |
138 | + | |
139 | +### Rate Limiting Middleware (via Dependency) | |
140 | + | |
141 | +```python | |
142 | +from justapi import Depends, HTTPException, Header | |
143 | +import time | |
144 | + | |
145 | +request_counts = {} | |
146 | + | |
147 | +def rate_limiter(ip: str = Header("X-Forwarded-For")): | |
148 | + now = time.time() | |
149 | + window = 60 # 1 minute | |
150 | + max_requests = 100 | |
151 | + | |
152 | + # Clean old entries | |
153 | + request_counts[ip] = [t for t in request_counts.get(ip, []) if now - t < window] | |
154 | + request_counts[ip].append(now) | |
155 | + | |
156 | + if len(request_counts[ip]) > max_requests: | |
157 | + raise HTTPException(429, "Rate limit exceeded") | |
158 | + | |
159 | + | |
160 | +@app.get("/api/", dependencies=[Depends(rate_limiter)]) | |
161 | +def api_endpoint(request): | |
162 | + return {"data": "ok"} | |
163 | +``` | |
164 | + | |
165 | +## How It Works Internally | |
166 | + | |
167 | +1. Middleware runs in the Rust middleware chain, not Python | |
168 | +2. `call_next` dispatches through the chain to the handler | |
169 | +3. The response dict flows back through each middleware's post-processing | |
170 | +4. Built-in middleware (CORS, rate limiting) runs in Rust with zero GIL overhead | |
171 | + | |
172 | +## Next Steps | |
173 | + | |
174 | +- [Error Handling](/tutorials/error-handling/) — Custom error responses | |
175 | +- [Dependency Injection](/tutorials/dependency-injection/) — Reusable components | |
176 | +- [Resilience Patterns](/advanced/resilience-patterns/) — Circuit breakers, rate limiting | |
0 | 177 | |