0
# Middleware Integration
1
2
Two middleware implementations for integrating context management into Starlette/FastAPI applications. Both create and manage request-scoped context, but differ in their integration approach and performance characteristics.
3
4
## Capabilities
5
6
### ContextMiddleware
7
8
HTTP middleware that extends Starlette's BaseHTTPMiddleware, providing easier integration and debugging at the cost of some performance overhead.
9
10
```python { .api }
11
class ContextMiddleware(BaseHTTPMiddleware):
12
def __init__(
13
self,
14
app: ASGIApp,
15
plugins: Optional[Sequence[Plugin]] = None,
16
default_error_response: Response = Response(status_code=400),
17
*args: Any,
18
**kwargs: Any
19
) -> None:
20
"""
21
Middleware that creates empty context for request it's used on.
22
23
Parameters:
24
- app: ASGI application
25
- plugins: Optional sequence of plugins to process requests
26
- default_error_response: Response to return on plugin validation errors
27
- *args, **kwargs: Additional arguments passed to BaseHTTPMiddleware
28
"""
29
30
async def set_context(self, request: Request) -> dict:
31
"""
32
You might want to override this method.
33
34
The dict it returns will be saved in the scope of a context. You can
35
always do that later.
36
37
Parameters:
38
- request: Starlette Request object
39
40
Returns:
41
dict: Initial context data from plugins
42
"""
43
44
async def dispatch(
45
self, request: Request, call_next: RequestResponseEndpoint
46
) -> Response:
47
"""
48
Process request with context lifecycle management.
49
50
Parameters:
51
- request: Starlette Request object
52
- call_next: Next middleware/handler in chain
53
54
Returns:
55
Response: HTTP response
56
57
Raises:
58
MiddleWareValidationError: If plugin validation fails
59
"""
60
```
61
62
### RawContextMiddleware
63
64
Low-level ASGI middleware that operates directly on ASGI scope, providing better performance by avoiding BaseHTTPMiddleware's overhead.
65
66
```python { .api }
67
class RawContextMiddleware:
68
def __init__(
69
self,
70
app: ASGIApp,
71
plugins: Optional[Sequence[Plugin]] = None,
72
default_error_response: Response = Response(status_code=400)
73
) -> None:
74
"""
75
Raw ASGI middleware for context management.
76
77
Parameters:
78
- app: ASGI application
79
- plugins: Optional sequence of plugins to process requests
80
- default_error_response: Response to return on plugin validation errors
81
"""
82
83
async def set_context(
84
self, request: Union[Request, HTTPConnection]
85
) -> dict:
86
"""
87
You might want to override this method.
88
89
The dict it returns will be saved in the scope of a context. You can
90
always do that later.
91
92
Parameters:
93
- request: Request or HTTPConnection object
94
95
Returns:
96
dict: Initial context data from plugins
97
"""
98
99
@staticmethod
100
def get_request_object(
101
scope: Scope, receive: Receive, send: Send
102
) -> Union[Request, HTTPConnection]:
103
"""
104
Create request object from ASGI components.
105
106
Parameters:
107
- scope: ASGI scope dict
108
- receive: ASGI receive callable
109
- send: ASGI send callable
110
111
Returns:
112
Union[Request, HTTPConnection]: Request object for header access
113
"""
114
115
async def send_response(
116
self, error_response: Response, send: Send
117
) -> None:
118
"""
119
Send error response via ASGI send interface.
120
121
Parameters:
122
- error_response: Response object to send
123
- send: ASGI send callable
124
"""
125
126
async def __call__(
127
self, scope: Scope, receive: Receive, send: Send
128
) -> None:
129
"""
130
ASGI application interface.
131
132
Parameters:
133
- scope: ASGI scope dict
134
- receive: ASGI receive callable
135
- send: ASGI send callable
136
"""
137
```
138
139
## Usage Examples
140
141
### Basic ContextMiddleware Setup
142
143
```python
144
from starlette.applications import Starlette
145
from starlette_context.middleware import ContextMiddleware
146
from starlette_context.plugins import RequestIdPlugin, CorrelationIdPlugin
147
148
app = Starlette()
149
150
# Add context middleware with plugins
151
app.add_middleware(
152
ContextMiddleware,
153
plugins=[
154
RequestIdPlugin(),
155
CorrelationIdPlugin(force_new_uuid=True)
156
]
157
)
158
```
159
160
### RawContextMiddleware Setup
161
162
```python
163
from starlette.applications import Starlette
164
from starlette_context.middleware import RawContextMiddleware
165
from starlette_context.plugins import RequestIdPlugin, UserAgentPlugin
166
167
app = Starlette()
168
169
# Add raw context middleware for better performance
170
app.add_middleware(
171
RawContextMiddleware,
172
plugins=[
173
RequestIdPlugin(),
174
UserAgentPlugin()
175
]
176
)
177
```
178
179
### Custom Error Response
180
181
```python
182
from starlette.responses import JSONResponse
183
from starlette_context.middleware import ContextMiddleware
184
from starlette_context.plugins import DateHeaderPlugin
185
186
# Custom error response for validation failures
187
error_response = JSONResponse(
188
{"error": "Invalid request format"},
189
status_code=422
190
)
191
192
app.add_middleware(
193
ContextMiddleware,
194
plugins=[DateHeaderPlugin()],
195
default_error_response=error_response
196
)
197
```
198
199
### Custom Context Setup
200
201
Override `set_context` to customize initial context data:
202
203
```python
204
from starlette_context.middleware import ContextMiddleware
205
from starlette_context.plugins import RequestIdPlugin
206
207
class CustomContextMiddleware(ContextMiddleware):
208
async def set_context(self, request):
209
# Get plugin data
210
context_data = await super().set_context(request)
211
212
# Add custom data
213
context_data.update({
214
"request_path": request.url.path,
215
"request_method": request.method,
216
"timestamp": time.time()
217
})
218
219
return context_data
220
221
app.add_middleware(
222
CustomContextMiddleware,
223
plugins=[RequestIdPlugin()]
224
)
225
```
226
227
### FastAPI Integration
228
229
```python
230
from fastapi import FastAPI
231
from starlette_context.middleware import ContextMiddleware
232
from starlette_context.plugins import RequestIdPlugin, CorrelationIdPlugin
233
from starlette_context import context
234
235
app = FastAPI()
236
237
# Add middleware
238
app.add_middleware(
239
ContextMiddleware,
240
plugins=[
241
RequestIdPlugin(),
242
CorrelationIdPlugin()
243
]
244
)
245
246
@app.get("/")
247
async def root():
248
# Context is automatically available
249
return {
250
"request_id": context["X-Request-ID"],
251
"correlation_id": context["X-Correlation-ID"]
252
}
253
```
254
255
### Multiple Middleware Layers
256
257
```python
258
from starlette.applications import Starlette
259
from starlette.middleware import Middleware
260
from starlette_context.middleware import ContextMiddleware
261
from starlette_context.plugins import RequestIdPlugin
262
263
# Define middleware stack
264
middleware = [
265
Middleware(
266
ContextMiddleware,
267
plugins=[RequestIdPlugin()]
268
),
269
# Other middleware...
270
]
271
272
app = Starlette(middleware=middleware)
273
```
274
275
## Performance Considerations
276
277
### ContextMiddleware vs RawContextMiddleware
278
279
**ContextMiddleware:**
280
- Easier to use and debug
281
- Integrates with Starlette's middleware system
282
- Slight performance overhead from BaseHTTPMiddleware
283
- Better for development and moderate traffic
284
285
**RawContextMiddleware:**
286
- Better performance (no BaseHTTPMiddleware overhead)
287
- Direct ASGI integration
288
- More complex to debug
289
- Better for high-performance production environments
290
291
### Plugin Performance
292
293
```python
294
# Efficient: Only essential plugins
295
app.add_middleware(
296
RawContextMiddleware,
297
plugins=[RequestIdPlugin()] # Minimal overhead
298
)
299
300
# Less efficient: Many complex plugins
301
app.add_middleware(
302
ContextMiddleware,
303
plugins=[
304
RequestIdPlugin(),
305
CorrelationIdPlugin(validate=True), # UUID validation
306
DateHeaderPlugin(), # Date parsing
307
ApiKeyPlugin(),
308
UserAgentPlugin(),
309
ForwardedForPlugin()
310
]
311
)
312
```
313
314
## Error Handling
315
316
Both middleware types handle plugin validation errors:
317
318
```python { .api }
319
class MiddleWareValidationError(StarletteContextError):
320
def __init__(self, *args: Any, error_response: Optional[Response] = None):
321
"""
322
Base exception for middleware validation errors.
323
324
Parameters:
325
- *args: Exception arguments
326
- error_response: Optional custom response for this error
327
"""
328
329
error_response: Optional[Response] # Custom error response
330
```
331
332
Error handling flow:
333
334
```python
335
try:
336
# Plugin processes request
337
context_data = await plugin.process_request(request)
338
except MiddleWareValidationError as e:
339
# Return plugin's custom error response or middleware default
340
return e.error_response or self.default_error_response
341
```
342
343
## Middleware Execution Flow
344
345
1. **Request arrives**: Middleware intercepts incoming request
346
2. **Plugin processing**: Each plugin extracts/validates data from request
347
3. **Context creation**: Context is created with plugin data using `request_cycle_context`
348
4. **Request processing**: Application handlers execute with context available
349
5. **Response enrichment**: Plugins can modify outgoing response
350
6. **Context cleanup**: Context is automatically reset after request completes
351
352
```python
353
# Middleware execution order
354
async def middleware_flow(request):
355
try:
356
# 1. Process plugins
357
context_data = {}
358
for plugin in plugins:
359
context_data[plugin.key] = await plugin.process_request(request)
360
361
# 2. Create context scope
362
with request_cycle_context(context_data):
363
# 3. Process request
364
response = await call_next(request)
365
366
# 4. Enrich response
367
for plugin in plugins:
368
await plugin.enrich_response(response)
369
370
return response
371
# 5. Context automatically cleaned up
372
except MiddleWareValidationError as e:
373
return e.error_response or default_error_response
374
```