WSGI vs. ASGI Architecture & Application Server Lifecycles

Python web applications interface with production web servers (Nginx, Apache) through standardized Gateway Interfaces: WSGI (Web Server Gateway Interface / PEP 3333) for synchronous frameworks (Flask, Django) and ASGI (Asynchronous Server Gateway Interface) for asynchronous frameworks (FastAPI, Starlette, Django Channels).

This chapter details WSGI environment callables, ASGI scope/receive/send onion architectures, Gunicorn pre-fork worker lifecycles, and Uvicorn ASGI event loops.


1. WSGI Architecture (PEP 3333)

WSGI (PEP 3333) defines a synchronous single-call interface between a web server (Gunicorn, uWSGI) and a Python application framework:

WSGI Synchronous Execution Flow:

[ Nginx (Reverse Proxy) ] ──> [ Gunicorn WSGI Server ]
                                      |
                                      v Calls: application(environ, start_response)
                               [ WSGI Application (Flask / Django) ]
                                      |
                                      v (Synchronous Execution)
                               [ Returns iterable of bytes ]

The WSGI Callable Signature:

A WSGI application is a single callable (function or object with __call__) accepting two positional parameters:

# Raw WSGI Application Callable
def application(environ: dict, start_response: callable):
    # 1. 'environ': Dictionary containing HTTP headers, PATH_INFO, QUERY_STRING
    path = environ.get("PATH_INFO", "/")

    # 2. 'start_response': Callback setting HTTP status and response headers
    status = "200 OK"
    headers = [("Content-Type", "text/plain; charset=utf-8")]
    start_response(status, headers)

    # 3. Return iterable of body bytes
    return [b"Hello, WSGI World!"]

WSGI Limitation:

WSGI is strictly synchronous and single-request-per-worker. It cannot support WebSockets, HTTP/2 Server Push, or Long-Polling without blocking worker processes.


2. ASGI Architecture (PEP 3090)

ASGI replaces WSGI’s synchronous callable with an asynchronous 3-argument callable: app(scope, receive, send).

ASGI Asynchronous Onion Architecture:

[ Client Socket ] ──> [ Uvicorn ASGI Server ]
                              |
                              v Calls: await app(scope, receive, send)
                       [ ASGI Application (FastAPI / Starlette) ]
                              |
                              β”œβ”€β”€ scope: Connection metadata (type="http" or "websocket")
                              β”œβ”€β”€ receive(): awaitable to read incoming bytes / frames
                              └── send(): awaitable to push response headers / frames
# Raw ASGI 3.0 Application Callable
async def application(scope: dict, receive: callable, send: callable):
    if scope["type"] == "http":
        # Wait for HTTP request body
        request_event = await receive()

        # Send response headers
        await send({
            "type": "http.response.start",
            "status": 200,
            "headers": [[b"content-type", b"text/plain"]],
        })

        # Send response body payload
        await send({
            "type": "http.response.body",
            "body": b"Hello, ASGI World!",
        })
    elif scope["type"] == "websocket":
        # Handle persistent WebSocket frames!
        pass

3. Production Server Architecture: Gunicorn & Uvicorn

In production, Python applications are run behind a process manager and reverse proxy:

Production Deployment Hierarchy:

[ Client Requests ] ──> [ Nginx (Port 80/443: SSL Termination & Static Files) ]
                                      |
                                      v (Unix Domain Socket / TCP)
[ Gunicorn Master Process (PID 1) ]
  β”œβ”€β”€ Worker Process 1 (Uvicorn Worker - Async Event Loop) ──> [ FastAPI App ]
  β”œβ”€β”€ Worker Process 2 (Uvicorn Worker - Async Event Loop) ──> [ FastAPI App ]
  └── Worker Process N (Uvicorn Worker - Async Event Loop) ──> [ FastAPI App ]
  • Gunicorn Pre-Fork Model: Master process manages worker process lifecycles, monitoring PIDs and restarting workers if memory limits or timeouts occur.
  • UvicornWorker: Runs Uvicorn’s high-speed uvloop async event loop inside each Gunicorn worker process, providing multi-process multi-core async processing!
# Production Gunicorn Command with Uvicorn Worker Class
gunicorn main:app \
  --workers 4 \
  --worker-class uvicorn.workers.UvicornWorker \
  --bind 0.0.0.0:8000
Display Options
Appearance
Text Size
100%