WSGI vs ASGI
coreintermediateWSGI and ASGI are the two contracts a Python web app can implement so any compliant server knows how to call it. WSGI defines one synchronous function; ASGI defines three async functions and adds support for WebSockets and background tasks.
Think of it as
Both are a plug shape, not a brand — any WSGI server can run any WSGI app, and any ASGI server can run any ASGI app, because both sides agree on the same callable signature. WSGI's plug has one pin: a function that takes the request and returns the response, blocking until done. ASGI's plug has three: one to receive the incoming scope, one to await more messages, one to send messages back — so a single connection can send, wait, and receive without blocking every other request on the process.
What we're doing: Run both a WSGI app and an ASGI app by simulating exactly what the server side of each contract does, without opening a real socket.
- 3
- wsgi_app takes environ (a dict of request data) and start_response (a callback to set status/headers) — one synchronous call, one return value.
- 15
- asgi_app takes scope, receive, and send — three parameters instead of two, and the function itself is async.
- 17
- The app awaits receive() to get the next event instead of it being handed in as an argument — this is what lets it wait for more data without blocking the whole process.
- 19
- The response goes out as two separate send() calls (start, then body) instead of one return value — this is what lets ASGI stream a response.
WSGI: 200 OK Hello, /world
ASGI: {'type': 'http.response.start', 'status': 200, 'headers': [(b'content-type', b'text/plain')]}
ASGI: {'type': 'http.response.body', 'body': b'Hello, /world'}Why this works: Both callables do the same job — read the request, write a 200 with a text body — but WSGI does it as one function call that returns everything at once, while ASGI does it as a sequence of awaited messages. That difference is the entire reason ASGI exists: a WSGI worker cannot do anything else while wsgi_app runs, but an ASGI app can await receive() and let the event loop serve other connections while it waits.
Trying to run an ASGI app directly with a WSGI server
Wrong
Better
What you see: TypeError: app() missing 2 required positional arguments: 'start_response' — Gunicorn's default sync worker calls app(environ, start_response), the WSGI shape, but an ASGI app only accepts (scope, receive, send).
Why: Gunicorn's built-in sync worker only speaks WSGI. An ASGI app needs either an ASGI-aware server (Uvicorn) or Gunicorn told to load an ASGI worker class (uvicorn.workers.UvicornWorker) that translates between Gunicorn's process management and Uvicorn's ASGI event loop.
- WSGI
- app(environ, start_response)
- Returns the full response body at once
- Blocks the worker until the request finishes
- No native WebSocket or streaming support
- ASGI
- async def app(scope, receive, send)
- Sends response parts as separate messages
- Awaits I/O instead of blocking the whole worker
- Same interface handles HTTP, WebSocket, and lifespan
WSGI vs ASGI at a glance
Together
Remember: WSGI: one sync function, app(environ, start_response). ASGI: one async function with three parameters, app(scope, receive, send), adding WebSockets and lifespan events.
See also: uvicorn · gunicorn · application lifecycle and graceful shutdown · asynchronous programming

