0
# WSGI Integration
1
2
Adapters for running WSGI applications within ASGI servers, enabling legacy web applications to benefit from async server capabilities. This allows gradual migration from WSGI to ASGI without requiring complete application rewrites.
3
4
## Capabilities
5
6
### WSGI-to-ASGI Adapter
7
8
Main adapter class that wraps WSGI applications to run in ASGI servers, handling protocol translation and request/response conversion.
9
10
```python { .api }
11
class WsgiToAsgi:
12
"""Main WSGI-to-ASGI adapter."""
13
14
def __init__(self, wsgi_application):
15
"""
16
Initialize adapter with WSGI application.
17
18
Parameters:
19
- wsgi_application: callable, WSGI application following PEP 3333
20
"""
21
22
def __call__(self, scope, receive, send):
23
"""
24
ASGI application interface.
25
26
Parameters:
27
- scope: dict, ASGI scope containing request information
28
- receive: callable, ASGI receive channel for request body
29
- send: callable, ASGI send channel for response
30
31
Returns:
32
Coroutine that handles the WSGI application execution
33
"""
34
```
35
36
### Per-Connection Instance
37
38
Internal adapter instance that handles individual connections, managing WSGI environ construction and response handling.
39
40
```python { .api }
41
class WsgiToAsgiInstance:
42
"""Per-connection WSGI-to-ASGI adapter instance."""
43
44
def __init__(self, wsgi_application):
45
"""
46
Initialize instance with WSGI application.
47
48
Parameters:
49
- wsgi_application: callable, WSGI application
50
"""
51
52
def __call__(self, scope, receive, send):
53
"""
54
Handle ASGI connection for this instance.
55
56
Parameters:
57
- scope: dict, ASGI scope
58
- receive: callable, ASGI receive channel
59
- send: callable, ASGI send channel
60
61
Returns:
62
Coroutine that processes the request
63
"""
64
65
def build_environ(self, scope, body):
66
"""
67
Build WSGI environ dictionary from ASGI scope.
68
69
Parameters:
70
- scope: dict, ASGI HTTP scope
71
- body: bytes, request body data
72
73
Returns:
74
dict: WSGI environ dictionary following PEP 3333
75
"""
76
77
def start_response(self, status, response_headers, exc_info=None):
78
"""
79
WSGI start_response callable implementation.
80
81
Parameters:
82
- status: str, HTTP status (e.g., '200 OK')
83
- response_headers: list, list of (name, value) header tuples
84
- exc_info: tuple, exception information (optional)
85
86
Returns:
87
callable: Function to write response body (for compatibility)
88
"""
89
```
90
91
## Usage Examples
92
93
### Basic WSGI Application Adaptation
94
95
```python
96
from asgiref.wsgi import WsgiToAsgi
97
98
# Simple WSGI application
99
def simple_wsgi_app(environ, start_response):
100
status = '200 OK'
101
headers = [('Content-Type', 'text/plain')]
102
start_response(status, headers)
103
return [b'Hello from WSGI!']
104
105
# Convert to ASGI
106
asgi_app = WsgiToAsgi(simple_wsgi_app)
107
108
# Can now be used with ASGI servers
109
# await asgi_app(scope, receive, send)
110
```
111
112
### Flask Application Integration
113
114
```python
115
from flask import Flask
116
from asgiref.wsgi import WsgiToAsgi
117
118
# Create Flask application
119
app = Flask(__name__)
120
121
@app.route('/')
122
def hello():
123
return 'Hello from Flask via ASGI!'
124
125
@app.route('/api/data')
126
def api_data():
127
return {'message': 'API response', 'status': 'success'}
128
129
# Convert Flask WSGI app to ASGI
130
asgi_app = WsgiToAsgi(app.wsgi_app)
131
132
# Now compatible with ASGI servers like Uvicorn, Hypercorn, etc.
133
```
134
135
### Django WSGI Integration
136
137
```python
138
import os
139
import django
140
from django.core.wsgi import get_wsgi_application
141
from asgiref.wsgi import WsgiToAsgi
142
143
# Configure Django
144
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
145
django.setup()
146
147
# Get Django WSGI application
148
django_wsgi_app = get_wsgi_application()
149
150
# Convert to ASGI
151
django_asgi_app = WsgiToAsgi(django_wsgi_app)
152
153
# Can be served by ASGI servers alongside native ASGI applications
154
```
155
156
### Mixed ASGI/WSGI Application Router
157
158
```python
159
from asgiref.wsgi import WsgiToAsgi
160
161
class MixedRouter:
162
"""Router that can handle both ASGI and WSGI applications."""
163
164
def __init__(self):
165
self.routes = []
166
167
def add_wsgi_app(self, path_prefix, wsgi_app):
168
"""Add a WSGI application at the given path prefix."""
169
asgi_app = WsgiToAsgi(wsgi_app)
170
self.routes.append((path_prefix, asgi_app, 'wsgi'))
171
172
def add_asgi_app(self, path_prefix, asgi_app):
173
"""Add an ASGI application at the given path prefix."""
174
self.routes.append((path_prefix, asgi_app, 'asgi'))
175
176
async def __call__(self, scope, receive, send):
177
path = scope['path']
178
179
for prefix, app, app_type in self.routes:
180
if path.startswith(prefix):
181
# Modify scope to remove prefix
182
new_scope = {**scope, 'path': path[len(prefix):]}
183
await app(new_scope, receive, send)
184
return
185
186
# 404 response
187
await send({
188
'type': 'http.response.start',
189
'status': 404,
190
'headers': [[b'content-type', b'text/plain']],
191
})
192
await send({
193
'type': 'http.response.body',
194
'body': b'Not Found',
195
})
196
197
# Usage
198
router = MixedRouter()
199
200
# Add WSGI applications (automatically converted)
201
router.add_wsgi_app('/legacy', simple_wsgi_app)
202
router.add_wsgi_app('/flask', app.wsgi_app)
203
204
# Add native ASGI applications
205
async def native_asgi_app(scope, receive, send):
206
await send({
207
'type': 'http.response.start',
208
'status': 200,
209
'headers': [[b'content-type', b'application/json']],
210
})
211
await send({
212
'type': 'http.response.body',
213
'body': b'{"message": "Native ASGI response"}',
214
})
215
216
router.add_asgi_app('/api', native_asgi_app)
217
```
218
219
### Middleware with WSGI Applications
220
221
```python
222
from asgiref.wsgi import WsgiToAsgi
223
import time
224
225
class TimingMiddleware:
226
"""Middleware that adds timing headers to responses."""
227
228
def __init__(self, app):
229
self.app = app
230
231
async def __call__(self, scope, receive, send):
232
start_time = time.time()
233
234
async def send_wrapper(message):
235
if message['type'] == 'http.response.start':
236
duration = time.time() - start_time
237
headers = list(message.get('headers', []))
238
headers.append([
239
b'x-response-time',
240
f'{duration:.3f}s'.encode()
241
])
242
message = {**message, 'headers': headers}
243
await send(message)
244
245
await self.app(scope, receive, send_wrapper)
246
247
# Apply middleware to WSGI application
248
timed_wsgi_app = TimingMiddleware(WsgiToAsgi(simple_wsgi_app))
249
```
250
251
### Custom WSGI Application with Complex Logic
252
253
```python
254
from asgiref.wsgi import WsgiToAsgi
255
import json
256
from urllib.parse import parse_qs
257
258
def api_wsgi_app(environ, start_response):
259
"""WSGI application with API-like behavior."""
260
method = environ['REQUEST_METHOD']
261
path = environ['PATH_INFO']
262
263
if path == '/health' and method == 'GET':
264
status = '200 OK'
265
headers = [('Content-Type', 'application/json')]
266
start_response(status, headers)
267
return [json.dumps({'status': 'healthy'}).encode()]
268
269
elif path == '/echo' and method == 'POST':
270
# Read request body
271
try:
272
content_length = int(environ.get('CONTENT_LENGTH', 0))
273
except ValueError:
274
content_length = 0
275
276
body = environ['wsgi.input'].read(content_length)
277
278
status = '200 OK'
279
headers = [('Content-Type', 'application/json')]
280
start_response(status, headers)
281
282
response = {
283
'method': method,
284
'path': path,
285
'body': body.decode('utf-8'),
286
'headers': dict(environ.items())
287
}
288
return [json.dumps(response, default=str).encode()]
289
290
else:
291
status = '404 Not Found'
292
headers = [('Content-Type', 'text/plain')]
293
start_response(status, headers)
294
return [b'Not Found']
295
296
# Convert to ASGI for modern server compatibility
297
api_asgi_app = WsgiToAsgi(api_wsgi_app)
298
```
299
300
## Protocol Translation
301
302
The adapter handles several important protocol differences:
303
304
- **Request Body**: Collects ASGI message stream into WSGI-compatible input
305
- **Response Streaming**: Converts WSGI iterator to ASGI message stream
306
- **Headers**: Transforms between ASGI bytes headers and WSGI string headers
307
- **Environment Variables**: Maps ASGI scope to WSGI environ dictionary
308
- **Error Handling**: Translates exceptions between protocols
309
- **Context Variables**: Maintains context across sync/async boundaries