or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

context-management.mderror-handling.mdindex.mdmiddleware.mdplugins.md

middleware.mddocs/

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

```