Skip to the content.

ASGI Servers: Beyond WSGI

For decades, Python web development was dominated by WSGI (Web Server Gateway Interface). However, WSGI was designed for synchronous, request-response cycles. It cannot handle long-lived connections like WebSockets or HTTP/2 streams. When your application needs to hold thousands of concurrent connections—the classic setup for the “midnight deployment spike”—WSGI becomes a bottleneck.

ASGI (Asynchronous Server Gateway Interface) is the spiritual successor to WSGI. It provides an asynchronous interface that allows your Python application to handle millions of events without blocking.

The Theory: The ASGI Specification

The ASGI specification splits the communication into two parts:

  1. Connection Scope: Information about the connection (headers, path, metadata).
  2. Event Queue: A stream of messages sent between the server and the application (e.g., http.request, websocket.receive).

Glossary for Beginners

Simple Implementation: A Minimal ASGI App

This is the simplest form of an ASGI application, functioning as an asynchronous callable.

async def app(scope, receive, send):
    # scope: connection info
    # receive: read from the client
    # send: write to the client
    assert scope['type'] == 'http'
    
    await send({
        'type': 'http.response.start',
        'status': 200,
        'headers': [[b'content-type', b'text/plain']],
    })
    
    await send({
        'type': 'http.response.body',
        'body': b'Hello, ASGI World!',
    })

Complex Implementation: Production-Grade Middleware

In enterprise setups, you often need middleware to inject headers or handle authentication across the entire ASGI stack.

class AuthenticationMiddleware:
    def __init__(self, app):
        self.app = app

    async def __call__(self, scope, receive, send):
        # Validate headers before passing to the inner app
        headers = dict(scope.get('headers', []))
        if b'authorization' not in headers:
            await send({'type': 'http.response.start', 'status': 401})
            await send({'type': 'http.response.body', 'body': b'Unauthorized'})
            return
        
        await self.app(scope, receive, send)

Quick Reference: ASGI vs. WSGI

Feature WSGI ASGI
Concurrency Synchronous (Thread-per-request) Asynchronous (Event loop)
Support HTTP/1.1 HTTP/1.1, HTTP/2, WebSockets
Throughput Capped by thread count High (Handles thousands of connections)
Complexity Low Medium/High

Why We Choose ASGI over WSGI

We choose ASGI because it offers Native Concurrency. In a microservices architecture, our services often need to push notifications to clients or maintain long-lived streams to other services. ASGI handles these states natively. WSGI requires workarounds (like long-polling or external message queues) that add significant latency to our deployment architecture.

Developer Checklist

Takeaways