or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cli.mdconfig.mdindex.mdlogging.mdmiddleware.mdserver.mdsupervisors.mdtypes.md

middleware.mddocs/

0

# Middleware Components

1

2

ASGI middleware components for proxy header handling, protocol adapters, and debugging support.

3

4

## Imports

5

6

```python

7

from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware

8

from uvicorn.middleware.asgi2 import ASGI2Middleware

9

from uvicorn.middleware.wsgi import WSGIMiddleware

10

from uvicorn.middleware.message_logger import MessageLoggerMiddleware

11

```

12

13

## Capabilities

14

15

### Proxy Headers Middleware

16

17

Middleware for handling X-Forwarded-Proto and X-Forwarded-For proxy headers.

18

19

```python { .api }

20

class ProxyHeadersMiddleware:

21

"""

22

Middleware for handling proxy headers.

23

24

Parses X-Forwarded-Proto and X-Forwarded-For headers from trusted

25

proxies and updates the ASGI scope with the client's real IP address

26

and protocol scheme.

27

28

This is essential when running behind reverse proxies like Nginx,

29

Apache, or cloud load balancers.

30

"""

31

32

def __init__(

33

self,

34

app: ASGI3Application,

35

trusted_hosts: list[str] | str = "127.0.0.1",

36

) -> None:

37

"""

38

Initialize proxy headers middleware.

39

40

Args:

41

app: ASGI application to wrap

42

trusted_hosts: Trusted proxy hosts/networks as comma-separated string or list

43

Supports:

44

- "*" to trust all proxies (not recommended in production)

45

- IP addresses: "127.0.0.1", "192.168.1.1"

46

- CIDR networks: "10.0.0.0/8", "172.16.0.0/12"

47

- Multiple values: "127.0.0.1,10.0.0.0/8"

48

Default: "127.0.0.1"

49

50

The middleware only processes headers from trusted proxy addresses.

51

Headers from untrusted sources are ignored for security.

52

"""

53

54

async def __call__(

55

self,

56

scope: Scope,

57

receive: ASGIReceiveCallable,

58

send: ASGISendCallable,

59

) -> None:

60

"""

61

Process ASGI connection with proxy header handling.

62

63

Args:

64

scope: ASGI connection scope

65

receive: Receive callable for incoming messages

66

send: Send callable for outgoing messages

67

68

For HTTP and WebSocket scopes, this middleware:

69

1. Checks if the connection is from a trusted proxy

70

2. Parses X-Forwarded-Proto header to update scope["scheme"]

71

3. Parses X-Forwarded-For header to update scope["client"]

72

4. Calls the wrapped application with updated scope

73

"""

74

```

75

76

### ASGI2 Middleware

77

78

Adapter to run ASGI2 applications as ASGI3.

79

80

```python { .api }

81

class ASGI2Middleware:

82

"""

83

Adapter to run ASGI2 applications as ASGI3.

84

85

ASGI2 uses a class-based interface where the application is instantiated

86

with the scope and then called with receive/send. ASGI3 uses a single

87

callable that accepts all three arguments.

88

89

This middleware bridges the two interfaces, allowing ASGI2 applications

90

to run on ASGI3 servers like uvicorn.

91

"""

92

93

def __init__(self, app: ASGI2Application) -> None:

94

"""

95

Initialize ASGI2 middleware.

96

97

Args:

98

app: ASGI2 application class (not instance)

99

The app should have __init__(scope) and async __call__(receive, send)

100

"""

101

102

async def __call__(

103

self,

104

scope: Scope,

105

receive: ASGIReceiveCallable,

106

send: ASGISendCallable,

107

) -> None:

108

"""

109

Run ASGI2 application as ASGI3.

110

111

Args:

112

scope: ASGI connection scope

113

receive: Receive callable for incoming messages

114

send: Send callable for outgoing messages

115

116

This method:

117

1. Instantiates the ASGI2 app with the scope

118

2. Calls the instance with receive and send

119

"""

120

```

121

122

### WSGI Middleware

123

124

Adapter to run WSGI applications in ASGI.

125

126

```python { .api }

127

class WSGIMiddleware:

128

"""

129

Adapter to run WSGI applications in ASGI.

130

131

Bridges the synchronous WSGI interface with the asynchronous ASGI

132

interface by running WSGI applications in a thread pool executor.

133

134

Note: This is uvicorn's implementation. The a2wsgi package provides

135

a more feature-complete WSGI adapter and is recommended for production use.

136

"""

137

138

def __init__(self, app: WSGIApp, workers: int = 10) -> None:

139

"""

140

Initialize WSGI middleware.

141

142

Args:

143

app: WSGI application callable

144

Should have signature: app(environ, start_response)

145

workers: Number of worker threads for handling WSGI calls (default: 10)

146

Each WSGI request runs in a thread from this pool since

147

WSGI is synchronous while ASGI is asynchronous.

148

"""

149

150

async def __call__(

151

self,

152

scope: Scope,

153

receive: ASGIReceiveCallable,

154

send: ASGISendCallable,

155

) -> None:

156

"""

157

Run WSGI application in ASGI context.

158

159

Args:

160

scope: ASGI connection scope (only HTTP supported)

161

receive: Receive callable for incoming messages

162

send: Send callable for outgoing messages

163

164

This method:

165

1. Receives the HTTP request body

166

2. Builds WSGI environ from ASGI scope

167

3. Runs WSGI app in thread pool

168

4. Sends WSGI response back through ASGI send

169

"""

170

```

171

172

### Message Logger Middleware

173

174

Middleware that logs all ASGI messages for debugging.

175

176

```python { .api }

177

class MessageLoggerMiddleware:

178

"""

179

Middleware that logs all ASGI messages at TRACE level.

180

181

Useful for debugging ASGI applications by showing the exact message

182

flow between server and application. Messages with large bodies are

183

logged with size placeholders instead of full content.

184

"""

185

186

def __init__(self, app: ASGI3Application) -> None:

187

"""

188

Initialize message logger middleware.

189

190

Args:

191

app: ASGI application to wrap

192

193

Attributes:

194

task_counter: Counter for assigning unique task IDs

195

app: Wrapped ASGI application

196

logger: Logger instance for message logging

197

"""

198

199

async def __call__(

200

self,

201

scope: Scope,

202

receive: ASGIReceiveCallable,

203

send: ASGISendCallable,

204

) -> None:

205

"""

206

Run application with message logging.

207

208

Args:

209

scope: ASGI connection scope

210

receive: Receive callable for incoming messages

211

send: Send callable for outgoing messages

212

213

This method wraps receive and send callables to log all messages

214

at TRACE level. Each connection is assigned a unique task ID for

215

tracking messages across the connection lifecycle.

216

"""

217

```

218

219

### WSGI Support Types

220

221

Type definitions for WSGI applications.

222

223

```python { .api }

224

# WSGI environ dictionary

225

Environ = MutableMapping[str, Any]

226

227

# WSGI exception info tuple

228

ExcInfo = tuple[type[BaseException], BaseException, Optional[types.TracebackType]]

229

230

# WSGI start_response callable

231

StartResponse = Callable[[str, Iterable[tuple[str, str]], Optional[ExcInfo]], None]

232

233

# WSGI application callable

234

WSGIApp = Callable[[Environ, StartResponse], Union[Iterable[bytes], BaseException]]

235

```

236

237

### WSGI Helper Functions

238

239

```python { .api }

240

def build_environ(

241

scope: HTTPScope,

242

message: ASGIReceiveEvent,

243

body: io.BytesIO,

244

) -> Environ:

245

"""

246

Build WSGI environ dictionary from ASGI scope.

247

248

Args:

249

scope: ASGI HTTP scope

250

message: ASGI receive event

251

body: Request body as BytesIO

252

253

Returns:

254

WSGI environ dictionary with all required and optional keys

255

256

The environ includes:

257

- CGI variables (REQUEST_METHOD, PATH_INFO, QUERY_STRING, etc.)

258

- Server variables (SERVER_NAME, SERVER_PORT, SERVER_PROTOCOL)

259

- WSGI variables (wsgi.version, wsgi.url_scheme, wsgi.input, wsgi.errors)

260

- HTTP headers (converted from ASGI format)

261

"""

262

```

263

264

## Usage Examples

265

266

### Enable Proxy Headers

267

268

```python

269

from uvicorn import run

270

from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware

271

272

async def app(scope, receive, send):

273

# Your ASGI application

274

...

275

276

# Wrap with proxy headers middleware

277

app_with_proxy = ProxyHeadersMiddleware(

278

app,

279

trusted_hosts="127.0.0.1,10.0.0.0/8",

280

)

281

282

# Run with middleware

283

run(app_with_proxy, host="0.0.0.0", port=8000)

284

```

285

286

### Automatic Proxy Headers with uvicorn.run

287

288

```python

289

import uvicorn

290

291

# Proxy headers middleware is automatically applied when proxy_headers=True

292

uvicorn.run(

293

"myapp:app",

294

host="0.0.0.0",

295

port=8000,

296

proxy_headers=True, # Enable proxy headers (default: True)

297

forwarded_allow_ips="127.0.0.1,192.168.0.0/16", # Trusted proxies

298

)

299

```

300

301

### Trust All Proxies (Development Only)

302

303

```python

304

from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware

305

306

# Trust all proxies - NOT RECOMMENDED IN PRODUCTION

307

app = ProxyHeadersMiddleware(

308

app,

309

trusted_hosts="*", # Trust all sources

310

)

311

```

312

313

### Run ASGI2 Application

314

315

```python

316

from uvicorn.middleware.asgi2 import ASGI2Middleware

317

318

# ASGI2 application (class-based)

319

class MyASGI2App:

320

def __init__(self, scope):

321

self.scope = scope

322

323

async def __call__(self, receive, send):

324

assert self.scope['type'] == 'http'

325

await send({

326

'type': 'http.response.start',

327

'status': 200,

328

'headers': [[b'content-type', b'text/plain']],

329

})

330

await send({

331

'type': 'http.response.body',

332

'body': b'Hello from ASGI2!',

333

})

334

335

# Wrap with ASGI2 middleware

336

app = ASGI2Middleware(MyASGI2App)

337

338

# Run with uvicorn

339

import uvicorn

340

uvicorn.run(app, host="127.0.0.1", port=8000)

341

```

342

343

### Automatic ASGI2 Detection

344

345

```python

346

import uvicorn

347

348

# Uvicorn automatically detects ASGI2 applications

349

# No need to manually wrap with ASGI2Middleware

350

351

class MyASGI2App:

352

def __init__(self, scope):

353

self.scope = scope

354

355

async def __call__(self, receive, send):

356

...

357

358

uvicorn.run(

359

MyASGI2App,

360

host="127.0.0.1",

361

port=8000,

362

interface="asgi2", # Or use "auto" for automatic detection

363

)

364

```

365

366

### Run WSGI Application

367

368

```python

369

from uvicorn.middleware.wsgi import WSGIMiddleware

370

371

# WSGI application (e.g., Flask)

372

def wsgi_app(environ, start_response):

373

status = '200 OK'

374

headers = [('Content-Type', 'text/plain')]

375

start_response(status, headers)

376

return [b'Hello from WSGI!']

377

378

# Wrap with WSGI middleware

379

app = WSGIMiddleware(wsgi_app, workers=10)

380

381

# Run with uvicorn

382

import uvicorn

383

uvicorn.run(app, host="127.0.0.1", port=8000)

384

```

385

386

### Run Flask with Uvicorn

387

388

```python

389

from flask import Flask

390

from uvicorn.middleware.wsgi import WSGIMiddleware

391

import uvicorn

392

393

# Create Flask app

394

flask_app = Flask(__name__)

395

396

@flask_app.route("/")

397

def hello():

398

return "Hello from Flask on Uvicorn!"

399

400

# Wrap Flask with WSGI middleware

401

app = WSGIMiddleware(flask_app)

402

403

# Run with uvicorn

404

uvicorn.run(app, host="127.0.0.1", port=8000)

405

```

406

407

### Automatic WSGI Detection

408

409

```python

410

import uvicorn

411

from flask import Flask

412

413

flask_app = Flask(__name__)

414

415

@flask_app.route("/")

416

def hello():

417

return "Hello, World!"

418

419

# Uvicorn automatically detects WSGI applications

420

uvicorn.run(

421

flask_app,

422

host="127.0.0.1",

423

port=8000,

424

interface="wsgi", # Or use "auto" for automatic detection

425

)

426

```

427

428

### Enable Message Logging

429

430

```python

431

from uvicorn.middleware.message_logger import MessageLoggerMiddleware

432

from uvicorn.logging import TRACE_LOG_LEVEL

433

import logging

434

435

# Enable TRACE logging

436

logging.addLevelName(TRACE_LOG_LEVEL, "TRACE")

437

logging.basicConfig(level=TRACE_LOG_LEVEL)

438

439

async def app(scope, receive, send):

440

# Your ASGI application

441

...

442

443

# Wrap with message logger

444

app_with_logging = MessageLoggerMiddleware(app)

445

446

# Run with uvicorn

447

import uvicorn

448

uvicorn.run(

449

app_with_logging,

450

host="127.0.0.1",

451

port=8000,

452

log_level="trace", # Enable trace logging

453

)

454

```

455

456

### Combine Multiple Middleware

457

458

```python

459

from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware

460

from uvicorn.middleware.message_logger import MessageLoggerMiddleware

461

462

async def app(scope, receive, send):

463

# Your ASGI application

464

...

465

466

# Apply middleware in order (innermost first)

467

app = MessageLoggerMiddleware(app) # Applied last

468

app = ProxyHeadersMiddleware(app, trusted_hosts="127.0.0.1") # Applied first

469

470

# Run with uvicorn

471

import uvicorn

472

uvicorn.run(app, host="0.0.0.0", port=8000)

473

```

474

475

### Custom Trusted Hosts

476

477

```python

478

from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware

479

480

async def app(scope, receive, send):

481

...

482

483

# Trust specific IP addresses

484

app = ProxyHeadersMiddleware(

485

app,

486

trusted_hosts=["127.0.0.1", "192.168.1.1", "192.168.1.2"],

487

)

488

489

# Trust CIDR networks

490

app = ProxyHeadersMiddleware(

491

app,

492

trusted_hosts=[

493

"10.0.0.0/8", # Private network

494

"172.16.0.0/12", # Private network

495

"192.168.0.0/16", # Private network

496

],

497

)

498

499

# Mix IPs and networks

500

app = ProxyHeadersMiddleware(

501

app,

502

trusted_hosts="127.0.0.1,10.0.0.0/8,192.168.1.100",

503

)

504

```

505

506

### WSGI with Custom Thread Pool

507

508

```python

509

from uvicorn.middleware.wsgi import WSGIMiddleware

510

511

def wsgi_app(environ, start_response):

512

# Blocking WSGI application

513

import time

514

time.sleep(0.1) # Simulate blocking I/O

515

516

status = '200 OK'

517

headers = [('Content-Type', 'text/plain')]

518

start_response(status, headers)

519

return [b'Done!']

520

521

# Increase workers for blocking applications

522

app = WSGIMiddleware(

523

wsgi_app,

524

workers=50, # More threads for handling blocking calls

525

)

526

527

import uvicorn

528

uvicorn.run(app, host="127.0.0.1", port=8000)

529

```

530

531

### Inspect Proxy Headers

532

533

```python

534

from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware

535

536

async def app(scope, receive, send):

537

# Access the modified scope

538

client_host, client_port = scope['client']

539

scheme = scope['scheme']

540

541

body = f"Client: {client_host}:{client_port}\nScheme: {scheme}".encode()

542

543

await send({

544

'type': 'http.response.start',

545

'status': 200,

546

'headers': [[b'content-type', b'text/plain']],

547

})

548

await send({

549

'type': 'http.response.body',

550

'body': body,

551

})

552

553

# Wrap with proxy headers

554

app = ProxyHeadersMiddleware(app, trusted_hosts="*")

555

556

# Test with curl:

557

# curl -H "X-Forwarded-For: 203.0.113.1" -H "X-Forwarded-Proto: https" http://localhost:8000

558

```

559

560

### Debug ASGI Messages

561

562

```python

563

from uvicorn.middleware.message_logger import MessageLoggerMiddleware

564

from uvicorn.logging import TRACE_LOG_LEVEL

565

import logging

566

567

# Configure trace logging

568

logging.addLevelName(TRACE_LOG_LEVEL, "TRACE")

569

logger = logging.getLogger("uvicorn")

570

logger.setLevel(TRACE_LOG_LEVEL)

571

572

handler = logging.StreamHandler()

573

handler.setLevel(TRACE_LOG_LEVEL)

574

logger.addHandler(handler)

575

576

async def app(scope, receive, send):

577

message = await receive() # Logged

578

await send({ # Logged

579

'type': 'http.response.start',

580

'status': 200,

581

'headers': [[b'content-type', b'text/plain']],

582

})

583

await send({ # Logged

584

'type': 'http.response.body',

585

'body': b'Hello!',

586

})

587

588

# Wrap with message logger

589

app = MessageLoggerMiddleware(app)

590

591

import uvicorn

592

uvicorn.run(app, host="127.0.0.1", port=8000, log_level=TRACE_LOG_LEVEL)

593

```

594