or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

data-structures.mddev-server.mdexceptions.mdhttp-utilities.mdindex.mdmiddleware.mdrequest-response.mdrouting.mdsecurity.mdtesting.mdurl-wsgi-utils.md

middleware.mddocs/

0

# Middleware

1

2

Collection of WSGI middleware components for common functionality including static file serving, proxy handling, profiling, and application dispatching. These middleware classes provide reusable functionality that can be wrapped around WSGI applications.

3

4

## Capabilities

5

6

### Shared Data Middleware

7

8

Serves static files during development or for simple deployments without requiring a separate web server.

9

10

```python { .api }

11

class SharedDataMiddleware:

12

def __init__(self, app, exports, disallow=None, cache=True, cache_timeout=43200, fallback_mimetype="text/plain"):

13

"""

14

WSGI middleware for serving static files.

15

16

Parameters:

17

- app: WSGI application to wrap

18

- exports: Dict mapping URL paths to filesystem paths or package data

19

- disallow: List of fnmatch patterns for files to deny access to

20

- cache: Whether to send caching headers

21

- cache_timeout: Cache timeout in seconds (default: 12 hours)

22

- fallback_mimetype: Default MIME type for unknown files

23

"""

24

25

def is_allowed(self, filename):

26

"""

27

Check if a filename is allowed to be served.

28

29

Parameters:

30

- filename: Filename to check against disallow patterns

31

32

Returns:

33

True if file is allowed, False if blocked by disallow patterns

34

"""

35

36

def __call__(self, environ, start_response):

37

"""

38

WSGI application callable.

39

40

Serves static files if path matches exports, otherwise forwards

41

to the wrapped application.

42

"""

43

```

44

45

### Proxy Fix Middleware

46

47

Handles X-Forwarded headers when the application is behind a proxy server.

48

49

```python { .api }

50

class ProxyFix:

51

def __init__(self, app, x_for=1, x_proto=1, x_host=0, x_port=0, x_prefix=0):

52

"""

53

Adjust WSGI environ based on X-Forwarded headers from proxies.

54

55

Parameters:

56

- app: WSGI application to wrap

57

- x_for: Number of X-Forwarded-For values to trust (sets REMOTE_ADDR)

58

- x_proto: Number of X-Forwarded-Proto values to trust (sets wsgi.url_scheme)

59

- x_host: Number of X-Forwarded-Host values to trust (sets HTTP_HOST, SERVER_NAME, SERVER_PORT)

60

- x_port: Number of X-Forwarded-Port values to trust (sets HTTP_HOST, SERVER_PORT)

61

- x_prefix: Number of X-Forwarded-Prefix values to trust (sets SCRIPT_NAME)

62

63

Note: Only trust headers from the number of proxies you expect.

64

Trusting wrong values is a security vulnerability.

65

"""

66

67

def __call__(self, environ, start_response):

68

"""

69

WSGI application callable.

70

71

Processes X-Forwarded headers and updates environ accordingly.

72

Original values are preserved in environ['werkzeug.proxy_fix.orig'].

73

"""

74

```

75

76

### Dispatcher Middleware

77

78

Combines multiple WSGI applications into a single application based on URL paths.

79

80

```python { .api }

81

class DispatcherMiddleware:

82

def __init__(self, app, mounts=None):

83

"""

84

Dispatch requests to different applications based on path prefixes.

85

86

Parameters:

87

- app: Default WSGI application for unmatched paths

88

- mounts: Dict mapping path prefixes to WSGI applications

89

"""

90

91

def __call__(self, environ, start_response):

92

"""

93

WSGI application callable.

94

95

Dispatches request to appropriate mounted application or default app.

96

Updates SCRIPT_NAME and PATH_INFO appropriately for mounted apps.

97

"""

98

```

99

100

### Profiler Middleware

101

102

Profiles each request using Python's cProfile module to identify performance bottlenecks.

103

104

```python { .api }

105

class ProfilerMiddleware:

106

def __init__(self, app, stream=sys.stdout, sort_by=("time", "calls"), restrictions=(), profile_dir=None, filename_format="{method}.{path}.{elapsed:.0f}ms.{time:.0f}.prof"):

107

"""

108

Profile WSGI application performance.

109

110

Parameters:

111

- app: WSGI application to wrap

112

- stream: Stream to write profiling stats to (None to disable)

113

- sort_by: Tuple of columns to sort stats by

114

- restrictions: Tuple of restrictions to filter stats

115

- profile_dir: Directory to save profile data files

116

- filename_format: Format string for profile filenames or callable

117

"""

118

119

def __call__(self, environ, start_response):

120

"""

121

WSGI application callable.

122

123

Profiles the wrapped application and outputs stats according to configuration.

124

"""

125

```

126

127

### Lint Middleware

128

129

Validates WSGI compliance of applications and middleware for debugging.

130

131

```python { .api }

132

class LintMiddleware:

133

def __init__(self, app):

134

"""

135

WSGI compliance checking middleware.

136

137

Parameters:

138

- app: WSGI application to validate

139

140

Note: This middleware checks for WSGI spec compliance and will

141

raise warnings or errors for violations. Use during development only.

142

"""

143

144

def __call__(self, environ, start_response):

145

"""

146

WSGI application callable.

147

148

Validates environ dict, start_response callback, and application

149

return value for WSGI spec compliance.

150

"""

151

```

152

153

### HTTP Proxy Middleware

154

155

Provides HTTP proxy functionality for forwarding requests to remote servers.

156

157

```python { .api }

158

class ProxyMiddleware:

159

def __init__(self, app, targets, chunk_size=2 << 13, timeout=10):

160

"""

161

HTTP proxy middleware for forwarding requests.

162

163

Parameters:

164

- app: WSGI application to wrap (fallback for non-proxied requests)

165

- targets: Dict mapping path prefixes to target URLs

166

- chunk_size: Size of chunks when streaming responses

167

- timeout: Timeout for proxy requests

168

"""

169

170

def __call__(self, environ, start_response):

171

"""

172

WSGI application callable.

173

174

Forwards requests matching target paths to remote servers,

175

otherwise passes to wrapped application.

176

"""

177

```

178

179

## Usage Examples

180

181

### Static File Serving

182

183

```python

184

from werkzeug.middleware.shared_data import SharedDataMiddleware

185

from werkzeug.wrappers import Request, Response

186

import os

187

188

def app(environ, start_response):

189

request = Request(environ)

190

response = Response(f'API endpoint: {request.path}')

191

return response(environ, start_response)

192

193

# Serve static files from multiple locations

194

wrapped_app = SharedDataMiddleware(app, {

195

'/static': os.path.join(os.path.dirname(__file__), 'static'),

196

'/uploads': '/var/www/uploads',

197

'/assets': ('mypackage', 'assets'), # Package data

198

})

199

200

# With custom configuration

201

wrapped_app = SharedDataMiddleware(

202

app,

203

{'/static': './static'},

204

disallow=['*.secret', '*.key', '.htaccess'], # Block sensitive files

205

cache=True,

206

cache_timeout=3600, # 1 hour cache

207

fallback_mimetype='application/octet-stream'

208

)

209

210

if __name__ == '__main__':

211

from werkzeug.serving import run_simple

212

run_simple('localhost', 8000, wrapped_app, use_reloader=True)

213

```

214

215

### Proxy Configuration

216

217

```python

218

from werkzeug.middleware.proxy_fix import ProxyFix

219

220

def app(environ, start_response):

221

# Access real client information after proxy processing

222

remote_addr = environ.get('REMOTE_ADDR')

223

scheme = environ.get('wsgi.url_scheme')

224

host = environ.get('HTTP_HOST')

225

226

response = Response(f'Client: {remote_addr}, Scheme: {scheme}, Host: {host}')

227

return response(environ, start_response)

228

229

# Configure for deployment behind nginx

230

# nginx sets X-Forwarded-For and X-Forwarded-Proto

231

proxy_app = ProxyFix(app, x_for=1, x_proto=1)

232

233

# Configure for deployment behind multiple proxies

234

# Load balancer -> nginx -> app

235

multi_proxy_app = ProxyFix(app, x_for=2, x_proto=1, x_host=1)

236

237

# Check original values if needed

238

def debug_app(environ, start_response):

239

orig = environ.get('werkzeug.proxy_fix.orig', {})

240

current_addr = environ.get('REMOTE_ADDR')

241

original_addr = orig.get('REMOTE_ADDR')

242

243

response = Response(f'Current: {current_addr}, Original: {original_addr}')

244

return response(environ, start_response)

245

246

if __name__ == '__main__':

247

from werkzeug.serving import run_simple

248

run_simple('localhost', 8000, proxy_app)

249

```

250

251

### Application Dispatching

252

253

```python

254

from werkzeug.middleware.dispatcher import DispatcherMiddleware

255

from werkzeug.wrappers import Request, Response

256

257

# API application

258

def api_app(environ, start_response):

259

request = Request(environ)

260

261

if request.path == '/users':

262

response = Response('{"users": []}', mimetype='application/json')

263

else:

264

response = Response('{"error": "Not found"}', status=404)

265

266

return response(environ, start_response)

267

268

# Admin application

269

def admin_app(environ, start_response):

270

request = Request(environ)

271

response = Response(f'<h1>Admin Panel</h1><p>Path: {request.path}</p>',

272

mimetype='text/html')

273

return response(environ, start_response)

274

275

# Frontend application (catch-all)

276

def frontend_app(environ, start_response):

277

request = Request(environ)

278

279

# Serve SPA for all frontend routes

280

if request.path.startswith('/app/'):

281

html = '''

282

<html>

283

<head><title>My App</title></head>

284

<body>

285

<div id="app">Single Page Application</div>

286

<script>console.log("Route: " + location.pathname)</script>

287

</body>

288

</html>

289

'''

290

response = Response(html, mimetype='text/html')

291

else:

292

response = Response('Welcome! Try /app/, /api/, or /admin/')

293

294

return response(environ, start_response)

295

296

# Combine applications

297

combined_app = DispatcherMiddleware(frontend_app, {

298

'/api': api_app,

299

'/admin': admin_app,

300

})

301

302

if __name__ == '__main__':

303

from werkzeug.serving import run_simple

304

run_simple('localhost', 8000, combined_app, use_reloader=True)

305

```

306

307

### Performance Profiling

308

309

```python

310

from werkzeug.middleware.profiler import ProfilerMiddleware

311

from werkzeug.wrappers import Request, Response

312

import time

313

import random

314

315

def slow_app(environ, start_response):

316

request = Request(environ)

317

318

# Simulate different performance characteristics

319

if request.path == '/slow':

320

time.sleep(0.5) # Simulate slow operation

321

322

elif request.path == '/compute':

323

# CPU intensive operation

324

result = sum(i**2 for i in range(10000))

325

326

elif request.path == '/random':

327

# Variable performance

328

time.sleep(random.uniform(0.1, 0.3))

329

330

response = Response(f'Processed {request.path}')

331

return response(environ, start_response)

332

333

# Profile to stdout with custom sorting

334

profiled_app = ProfilerMiddleware(

335

slow_app,

336

sort_by=('cumulative', 'calls'),

337

restrictions=(30,) # Show top 30 functions

338

)

339

340

# Save profile data to files

341

import os

342

profile_dir = './profiles'

343

os.makedirs(profile_dir, exist_ok=True)

344

345

file_profiled_app = ProfilerMiddleware(

346

slow_app,

347

stream=None, # Don't print to stdout

348

profile_dir=profile_dir,

349

filename_format='{method}-{path}-{elapsed:.0f}ms.prof'

350

)

351

352

# Custom filename generator

353

def custom_filename(environ):

354

profiler_info = environ['werkzeug.profiler']

355

path = environ.get('PATH_INFO', 'root').replace('/', '-')

356

elapsed = profiler_info['elapsed']

357

return f'profile-{path}-{elapsed:.1f}ms.prof'

358

359

custom_profiled_app = ProfilerMiddleware(

360

slow_app,

361

profile_dir=profile_dir,

362

filename_format=custom_filename

363

)

364

365

if __name__ == '__main__':

366

from werkzeug.serving import run_simple

367

print("Visit /slow, /compute, or /random to see profiling output")

368

run_simple('localhost', 8000, profiled_app, use_reloader=True)

369

```

370

371

### WSGI Compliance Testing

372

373

```python

374

from werkzeug.middleware.lint import LintMiddleware

375

from werkzeug.wrappers import Response

376

377

# Application with potential WSGI violations

378

def problematic_app(environ, start_response):

379

# This app has some WSGI compliance issues

380

381

# Issue 1: Not checking if start_response was called

382

response = Response('Hello')

383

384

# Issue 2: Returning string instead of bytes iterable

385

# return 'This would cause a lint error' # Wrong!

386

387

# Correct WSGI return

388

return response(environ, start_response)

389

390

# Wrap with lint middleware for development

391

linted_app = LintMiddleware(problematic_app)

392

393

# This will catch and warn about WSGI spec violations

394

if __name__ == '__main__':

395

from werkzeug.serving import run_simple

396

run_simple('localhost', 8000, linted_app, use_reloader=True)

397

```

398

399

### HTTP Proxy

400

401

```python

402

from werkzeug.middleware.http_proxy import ProxyMiddleware

403

404

def local_app(environ, start_response):

405

request = Request(environ)

406

response = Response(f'Local app handling: {request.path}')

407

return response(environ, start_response)

408

409

# Proxy certain paths to remote services

410

proxied_app = ProxyMiddleware(local_app, {

411

'/api/external': 'https://api.example.com',

412

'/cdn': 'https://cdn.example.com',

413

})

414

415

if __name__ == '__main__':

416

from werkzeug.serving import run_simple

417

# Requests to /api/external/* will be forwarded to api.example.com

418

# Other requests handled by local_app

419

run_simple('localhost', 8000, proxied_app)

420

```

421

422

### Combining Multiple Middleware

423

424

```python

425

from werkzeug.middleware.shared_data import SharedDataMiddleware

426

from werkzeug.middleware.proxy_fix import ProxyFix

427

from werkzeug.middleware.profiler import ProfilerMiddleware

428

from werkzeug.middleware.dispatcher import DispatcherMiddleware

429

430

def api_app(environ, start_response):

431

response = Response('{"api": "v1"}', mimetype='application/json')

432

return response(environ, start_response)

433

434

def web_app(environ, start_response):

435

response = Response('<h1>Web Application</h1>', mimetype='text/html')

436

return response(environ, start_response)

437

438

# Layer middleware (applied inside-out)

439

app = DispatcherMiddleware(web_app, {'/api': api_app})

440

441

# Add static file serving

442

app = SharedDataMiddleware(app, {

443

'/static': './static',

444

'/favicon.ico': './static/favicon.ico'

445

})

446

447

# Add proxy fix for production deployment

448

app = ProxyFix(app, x_for=1, x_proto=1)

449

450

# Add profiling for development

451

if __name__ == '__main__':

452

# Only add profiler in development

453

app = ProfilerMiddleware(app, sort_by=('cumulative',))

454

455

from werkzeug.serving import run_simple

456

run_simple('localhost', 8000, app, use_reloader=True)

457

```

458

459

### Development vs Production Configuration

460

461

```python

462

import os

463

from werkzeug.middleware.shared_data import SharedDataMiddleware

464

from werkzeug.middleware.proxy_fix import ProxyFix

465

from werkzeug.middleware.profiler import ProfilerMiddleware

466

467

def create_app():

468

def app(environ, start_response):

469

response = Response('Hello World!')

470

return response(environ, start_response)

471

472

return app

473

474

def wrap_app_for_environment(app):

475

environment = os.getenv('ENVIRONMENT', 'development')

476

477

if environment == 'development':

478

# Development middleware

479

app = SharedDataMiddleware(app, {'/static': './static'})

480

app = ProfilerMiddleware(app)

481

482

elif environment == 'production':

483

# Production middleware

484

app = ProxyFix(app, x_for=1, x_proto=1, x_host=1)

485

# Note: Use proper web server for static files in production

486

487

return app

488

489

if __name__ == '__main__':

490

from werkzeug.serving import run_simple

491

492

app = create_app()

493

app = wrap_app_for_environment(app)

494

495

run_simple('localhost', 8000, app, use_reloader=True)

496

```

497

498

### Custom Middleware Example

499

500

```python

501

from werkzeug.wrappers import Request, Response

502

503

class RequestTimingMiddleware:

504

"""Custom middleware to add request timing headers."""

505

506

def __init__(self, app):

507

self.app = app

508

509

def __call__(self, environ, start_response):

510

import time

511

512

start_time = time.time()

513

514

def timing_start_response(status, headers, exc_info=None):

515

# Add timing header

516

elapsed = time.time() - start_time

517

headers.append(('X-Response-Time', f'{elapsed:.3f}s'))

518

return start_response(status, headers, exc_info)

519

520

return self.app(environ, timing_start_response)

521

522

def app(environ, start_response):

523

import time

524

time.sleep(0.1) # Simulate processing

525

response = Response('Timed response')

526

return response(environ, start_response)

527

528

# Apply custom middleware

529

timed_app = RequestTimingMiddleware(app)

530

531

if __name__ == '__main__':

532

from werkzeug.serving import run_simple

533

run_simple('localhost', 8000, timed_app)

534

```