0
# Development Server
1
2
Feature-rich WSGI development server with automatic reloading, interactive debugging, SSL support, and multi-threading capabilities. The development server provides a convenient way to run and test applications during development with extensive debugging and monitoring features.
3
4
**⚠️ Warning: This server is designed for development only. Never use it in production environments as it is not designed to be stable, secure, or efficient.**
5
6
## Capabilities
7
8
### Main Server Function
9
10
The primary function for starting the development server with comprehensive configuration options.
11
12
```python { .api }
13
def run_simple(hostname, port, application, use_reloader=False, use_debugger=False, use_evalex=True, extra_files=None, exclude_patterns=None, reloader_interval=1, reloader_type="auto", threaded=False, processes=1, request_handler=None, static_files=None, passthrough_errors=False, ssl_context=None):
14
"""
15
Start a development server for a WSGI application.
16
17
Parameters:
18
- hostname: Host to bind to (e.g., 'localhost', '0.0.0.0')
19
- port: Port number to bind to (use 0 for random free port)
20
- application: WSGI application callable to serve
21
- use_reloader: Enable automatic reloading when files change
22
- use_debugger: Enable interactive debugging on exceptions
23
- use_evalex: Allow interactive Python console in debugger
24
- extra_files: Additional files to watch for reloading
25
- exclude_patterns: File patterns to ignore for reloading (fnmatch format)
26
- reloader_interval: How often to check for file changes (seconds)
27
- reloader_type: Type of reloader ('auto', 'stat', 'watchdog')
28
- threaded: Enable multi-threading for concurrent requests
29
- processes: Number of processes for handling requests (alternative to threading)
30
- request_handler: Custom request handler class
31
- static_files: Dict mapping URL prefixes to directory paths for static files
32
- passthrough_errors: Don't catch unhandled exceptions at server level
33
- ssl_context: SSL configuration for HTTPS ('adhoc', SSLContext, or (cert, key) tuple)
34
"""
35
```
36
37
### Server Factory Function
38
39
Lower-level function for creating server instances with more control.
40
41
```python { .api }
42
def make_server(host, port, app, threaded=False, processes=1, request_handler=None, passthrough_errors=False, ssl_context=None, fd=None):
43
"""
44
Create a WSGI server instance.
45
46
This provides more control than run_simple() and returns the server object
47
for advanced usage like running in separate threads.
48
49
Parameters:
50
- host: Host address to bind to
51
- port: Port number to bind to
52
- app: WSGI application to serve
53
- threaded: Enable threading for concurrent requests
54
- processes: Number of worker processes
55
- request_handler: Custom request handler class
56
- passthrough_errors: Don't catch exceptions at server level
57
- ssl_context: SSL configuration
58
- fd: Existing file descriptor to use
59
60
Returns:
61
BaseWSGIServer instance (or subclass based on threading/process options)
62
"""
63
```
64
65
### Server Classes
66
67
The server class hierarchy provides different concurrency models.
68
69
```python { .api }
70
class BaseWSGIServer(HTTPServer):
71
def __init__(self, host, port, app, handler=None, passthrough_errors=False, ssl_context=None, fd=None):
72
"""
73
Single-threaded WSGI server.
74
75
Parameters:
76
- host: Host address
77
- port: Port number
78
- app: WSGI application
79
- handler: Request handler class
80
- passthrough_errors: Error handling mode
81
- ssl_context: SSL configuration
82
- fd: File descriptor for socket
83
"""
84
85
# Properties
86
multithread: bool = False # Server supports threading
87
multiprocess: bool = False # Server supports multiple processes
88
89
def serve_forever(self, poll_interval=0.5):
90
"""
91
Handle requests until shutdown.
92
93
Parameters:
94
- poll_interval: Time between polling for shutdown
95
"""
96
97
def shutdown_request(self, request):
98
"""Clean up after handling a request."""
99
100
def handle_error(self, request, client_address):
101
"""Handle errors during request processing."""
102
103
class ThreadedWSGIServer(socketserver.ThreadingMixIn, BaseWSGIServer):
104
def __init__(self, host, port, app, handler=None, passthrough_errors=False, ssl_context=None, fd=None):
105
"""
106
Multi-threaded WSGI server for concurrent request handling.
107
108
Uses ThreadingMixIn to spawn a new thread for each request.
109
"""
110
111
multithread: bool = True
112
daemon_threads: bool = True # Threads don't prevent server shutdown
113
114
class ForkingWSGIServer(ForkingMixIn, BaseWSGIServer):
115
def __init__(self, host, port, app, processes, handler=None, passthrough_errors=False, ssl_context=None, fd=None):
116
"""
117
Multi-process WSGI server for concurrent request handling.
118
119
Forks worker processes to handle requests. Only available on Unix-like systems.
120
121
Parameters:
122
- processes: Maximum number of worker processes
123
"""
124
125
multiprocess: bool = True
126
max_children: int # Maximum number of child processes
127
```
128
129
### Request Handler
130
131
Custom request handler for WSGI applications with enhanced features.
132
133
```python { .api }
134
class WSGIRequestHandler(BaseHTTPRequestHandler):
135
def __init__(self, request, client_address, server):
136
"""
137
WSGI-compatible HTTP request handler.
138
139
Handles the conversion between HTTP and WSGI protocols.
140
"""
141
142
# Properties
143
server: BaseWSGIServer
144
environ: dict # WSGI environment
145
146
def handle_one_request(self):
147
"""Handle a single HTTP request and convert to WSGI."""
148
149
def run_wsgi(self):
150
"""Execute the WSGI application and return response."""
151
152
def log_request(self, code='-', size='-'):
153
"""Log successful requests."""
154
155
def log_error(self, format, *args):
156
"""Log error messages."""
157
```
158
159
### SSL Functions
160
161
Functions for SSL/TLS configuration and certificate management.
162
163
```python { .api }
164
def generate_adhoc_ssl_context():
165
"""
166
Generate a self-signed SSL context for development.
167
168
Creates temporary certificates that are valid for localhost.
169
Requires the cryptography library.
170
171
Returns:
172
ssl.SSLContext configured with self-signed certificate
173
"""
174
175
def load_ssl_context(cert_file, key_file=None, protocol=None):
176
"""
177
Load SSL context from certificate files.
178
179
Parameters:
180
- cert_file: Path to certificate file (.pem or .crt)
181
- key_file: Path to private key file (optional if cert_file contains both)
182
- protocol: SSL protocol version to use
183
184
Returns:
185
ssl.SSLContext configured with the provided certificates
186
"""
187
188
def make_ssl_devcert(base_path, host="localhost", cn=None):
189
"""
190
Create a self-signed development certificate.
191
192
Parameters:
193
- base_path: Base path for certificate files (will create .crt and .key files)
194
- host: Hostname for certificate
195
- cn: Common name (defaults to host)
196
197
Returns:
198
Tuple of (cert_file_path, key_file_path)
199
"""
200
201
def generate_adhoc_ssl_pair(cn=None):
202
"""
203
Generate a self-signed certificate pair.
204
205
Parameters:
206
- cn: Common name for certificate
207
208
Returns:
209
Tuple of (Certificate, RSAPrivateKey) objects
210
"""
211
```
212
213
### Utility Functions
214
215
Helper functions for server management and detection.
216
217
```python { .api }
218
def is_running_from_reloader():
219
"""
220
Check if the current process is running from the Werkzeug reloader.
221
222
Returns:
223
True if running as a reloader subprocess
224
"""
225
226
def select_address_family(host, port):
227
"""
228
Automatically select appropriate address family (IPv4/IPv6) for host.
229
230
Parameters:
231
- host: Host address
232
- port: Port number
233
234
Returns:
235
socket.AddressFamily (AF_INET or AF_INET6)
236
"""
237
238
def get_interface_ip(family):
239
"""
240
Get the IP address of the network interface.
241
242
Parameters:
243
- family: Address family (AF_INET or AF_INET6)
244
245
Returns:
246
IP address string
247
"""
248
```
249
250
## Usage Examples
251
252
### Basic Development Server
253
254
```python
255
from werkzeug.serving import run_simple
256
from werkzeug.wrappers import Request, Response
257
258
def application(environ, start_response):
259
request = Request(environ)
260
response = Response(f'Hello from {request.path}!')
261
return response(environ, start_response)
262
263
if __name__ == '__main__':
264
# Basic server
265
run_simple('localhost', 8000, application)
266
```
267
268
### Server with Debugging and Reloading
269
270
```python
271
from werkzeug.serving import run_simple
272
from werkzeug.debug import DebuggedApplication
273
274
def create_app():
275
def app(environ, start_response):
276
request = Request(environ)
277
278
if request.path == '/error':
279
# Trigger an error for debugging
280
raise ValueError("This is a test error!")
281
282
response = Response(f'Path: {request.path}')
283
return response(environ, start_response)
284
285
return app
286
287
if __name__ == '__main__':
288
app = create_app()
289
290
# Development server with debugging and auto-reload
291
run_simple(
292
'localhost',
293
8000,
294
app,
295
use_debugger=True, # Interactive debugger on errors
296
use_reloader=True, # Auto-reload on file changes
297
use_evalex=True # Interactive Python console in debugger
298
)
299
```
300
301
### Multi-threaded Server
302
303
```python
304
from werkzeug.serving import run_simple
305
import time
306
import threading
307
308
def slow_application(environ, start_response):
309
request = Request(environ)
310
311
# Simulate slow processing
312
time.sleep(2)
313
314
thread_id = threading.get_ident()
315
response = Response(f'Processed by thread {thread_id}')
316
return response(environ, start_response)
317
318
if __name__ == '__main__':
319
# Multi-threaded server for concurrent requests
320
run_simple(
321
'localhost',
322
8000,
323
slow_application,
324
threaded=True # Handle multiple requests concurrently
325
)
326
```
327
328
### HTTPS Development Server
329
330
```python
331
from werkzeug.serving import run_simple, generate_adhoc_ssl_context
332
333
def secure_app(environ, start_response):
334
request = Request(environ)
335
336
# Check if request is secure
337
is_secure = request.environ.get('wsgi.url_scheme') == 'https'
338
339
response = Response(f'Secure: {is_secure}')
340
return response(environ, start_response)
341
342
if __name__ == '__main__':
343
# Option 1: Self-signed certificate
344
run_simple(
345
'localhost',
346
8000,
347
secure_app,
348
ssl_context='adhoc' # Auto-generate self-signed cert
349
)
350
351
# Option 2: Custom certificate files
352
# run_simple(
353
# 'localhost',
354
# 8000,
355
# secure_app,
356
# ssl_context=('cert.pem', 'key.pem')
357
# )
358
```
359
360
### Server with Static File Serving
361
362
```python
363
from werkzeug.serving import run_simple
364
365
def api_app(environ, start_response):
366
request = Request(environ)
367
368
if request.path.startswith('/api/'):
369
# Handle API requests
370
response = Response('{"status": "ok"}', mimetype='application/json')
371
else:
372
# Let static file middleware handle other requests
373
response = Response('Not Found', status=404)
374
375
return response(environ, start_response)
376
377
if __name__ == '__main__':
378
run_simple(
379
'localhost',
380
8000,
381
api_app,
382
static_files={
383
'/': '/path/to/static/files', # Serve static files from root
384
'/uploads': '/path/to/upload/dir', # Serve uploads from /uploads
385
'/assets': ('/path/to/assets', 'text/css') # With custom MIME type
386
}
387
)
388
```
389
390
### Advanced Server Configuration
391
392
```python
393
from werkzeug.serving import run_simple, make_server
394
import os
395
import threading
396
397
def advanced_app(environ, start_response):
398
request = Request(environ)
399
response = Response(f'Hello from PID {os.getpid()}')
400
return response(environ, start_response)
401
402
def run_with_custom_config():
403
# Advanced reloader configuration
404
run_simple(
405
'0.0.0.0', # Bind to all interfaces
406
8000,
407
advanced_app,
408
use_reloader=True,
409
reloader_type='watchdog', # Efficient file watching
410
reloader_interval=0.5, # Check every 0.5 seconds
411
extra_files=[ # Watch additional files
412
'config.yaml',
413
'templates/base.html'
414
],
415
exclude_patterns=[ # Ignore certain files
416
'*.pyc',
417
'*/__pycache__/*',
418
'*.log'
419
]
420
)
421
422
def run_with_server_object():
423
# Using make_server for more control
424
server = make_server(
425
'localhost',
426
8000,
427
advanced_app,
428
threaded=True
429
)
430
431
print(f"Server running on {server.server_address}")
432
433
# Run in separate thread
434
server_thread = threading.Thread(target=server.serve_forever)
435
server_thread.daemon = True
436
server_thread.start()
437
438
# Do other work...
439
input("Press Enter to stop server...")
440
server.shutdown()
441
server.server_close()
442
443
if __name__ == '__main__':
444
# Choose configuration
445
run_with_custom_config()
446
# run_with_server_object()
447
```
448
449
### Development Server with Custom Request Handler
450
451
```python
452
from werkzeug.serving import run_simple, WSGIRequestHandler
453
from werkzeug.wrappers import Request, Response
454
import logging
455
456
class CustomRequestHandler(WSGIRequestHandler):
457
def log_request(self, code='-', size='-'):
458
# Custom request logging
459
logging.info(f'{self.client_address[0]} - {self.command} {self.path} - {code}')
460
461
def log_error(self, format, *args):
462
# Custom error logging
463
logging.error(f'{self.client_address[0]} - ERROR: {format % args}')
464
465
def logged_app(environ, start_response):
466
request = Request(environ)
467
468
# Log request details
469
logging.info(f'Processing {request.method} {request.path}')
470
471
response = Response('Logged request')
472
return response(environ, start_response)
473
474
if __name__ == '__main__':
475
# Setup logging
476
logging.basicConfig(level=logging.INFO)
477
478
run_simple(
479
'localhost',
480
8000,
481
logged_app,
482
request_handler=CustomRequestHandler
483
)
484
```
485
486
### SSL Certificate Generation
487
488
```python
489
from werkzeug.serving import make_ssl_devcert, load_ssl_context, run_simple
490
491
def setup_ssl_server():
492
# Generate development certificates
493
cert_path, key_path = make_ssl_devcert('dev-cert', host='localhost')
494
495
# Load SSL context
496
ssl_context = load_ssl_context(cert_path, key_path)
497
498
# Or use the files directly
499
run_simple(
500
'localhost',
501
8000,
502
app,
503
ssl_context=(cert_path, key_path)
504
)
505
506
def app(environ, start_response):
507
request = Request(environ)
508
509
# SSL information available in environ
510
ssl_info = {
511
'scheme': request.scheme,
512
'is_secure': request.is_secure,
513
'url': request.url
514
}
515
516
response = Response(f'SSL Info: {ssl_info}')
517
return response(environ, start_response)
518
519
if __name__ == '__main__':
520
setup_ssl_server()
521
```
522
523
### Error Handling and Debugging
524
525
```python
526
from werkzeug.serving import run_simple
527
from werkzeug.debug import DebuggedApplication
528
from werkzeug.exceptions import HTTPException, NotFound
529
530
def debug_app(environ, start_response):
531
request = Request(environ)
532
533
if request.path == '/error':
534
# Unhandled exception - will show in debugger
535
raise ValueError("This is an unhandled error")
536
537
elif request.path == '/http-error':
538
# HTTP exception - proper error response
539
raise NotFound("Page not found")
540
541
elif request.path == '/debug-info':
542
# Show debug information
543
debug_info = {
544
'path': request.path,
545
'method': request.method,
546
'headers': dict(request.headers),
547
'args': dict(request.args)
548
}
549
response = Response(str(debug_info))
550
551
else:
552
response = Response('Hello! Try /error, /http-error, or /debug-info')
553
554
return response(environ, start_response)
555
556
if __name__ == '__main__':
557
# Wrap with debugger for development
558
debugged_app = DebuggedApplication(debug_app, evalex=True)
559
560
run_simple(
561
'localhost',
562
8000,
563
debugged_app,
564
use_debugger=True, # Enable Werkzeug debugger
565
passthrough_errors=False # Let debugger catch errors
566
)
567
```
568
569
### Production Transition
570
571
```python
572
from werkzeug.serving import run_simple, is_running_from_reloader
573
import os
574
575
def production_ready_app(environ, start_response):
576
request = Request(environ)
577
response = Response('Production app')
578
return response(environ, start_response)
579
580
def main():
581
# Check environment
582
is_development = os.getenv('FLASK_ENV') == 'development'
583
584
if is_development:
585
# Development configuration
586
print("Running in development mode")
587
run_simple(
588
'localhost',
589
8000,
590
production_ready_app,
591
use_debugger=True,
592
use_reloader=True,
593
use_evalex=True
594
)
595
else:
596
# Production reminder
597
print("⚠️ Don't use run_simple in production!")
598
print("Use a proper WSGI server like Gunicorn, uWSGI, or mod_wsgi")
599
600
# For demonstration only - don't do this in real production
601
run_simple(
602
'127.0.0.1', # Only local access
603
8000,
604
production_ready_app,
605
threaded=True,
606
use_debugger=False,
607
use_reloader=False
608
)
609
610
if __name__ == '__main__':
611
main()
612
```