ASGI (Asynchronous Server Gateway Interface) is the spiritual successor to WSGI (Web Server Gateway Interface), designed to support asynchronous communication, making it suitable for modern web applications that require real-time data handling, long-lived connections, and non-blocking I/O. It provides a standardized interface between web servers and Python web applications or frameworks, with the key addition of asynchronous support.
ASGI has become the foundation for handling WebSockets, HTTP/2, and background tasks in Django and other Python frameworks. Let’s break it down with examples and key concepts.
Why ASGI?
WSGI, which powers traditional Django applications, is inherently synchronous and works well for request-response HTTP models. However, modern applications often need:
- WebSockets: Bi-directional communication channels for real-time applications.
- HTTP/2: For multiplexing multiple requests over a single connection.
- Server-Sent Events (SSE): Streaming updates from the server to the client.
- Asynchronous I/O: To handle large numbers of concurrent connections without blocking the server.
ASGI addresses these requirements by providing an asynchronous interface to handle both traditional HTTP requests and asynchronous protocols like WebSockets.
Key Components of ASGI
ASGI Application
An ASGI application is a callable, just like a WSGI application, but it can be either synchronous or asynchronous. It handles both HTTP and WebSocket protocols.The application callable takes two arguments:
- scope: Metadata about the connection (e.g., HTTP request, WebSocket connection).
- receive: An asynchronous callable to receive messages.
- send: An asynchronous callable to send messages back to the client.
ASGI Server
- The ASGI server handles incoming client connections and passes them to the ASGI application. Popular ASGI servers include Daphne (used by Django Channels) and Uvicorn.
Scopes, Events, and Lifespan
- Scopes: Provide metadata about a connection, such as HTTP headers, path, method, etc.
- Events: Represent actual messages or data flowing in and out (e.g., an HTTP request or WebSocket message).
- Lifespan: ASGI also provides an optional “lifespan” protocol for managing application lifecycle events (startup/shutdown).
ASGI Application Example
Here’s a simple ASGI application that handles HTTP requests asynchronously:
async def app(scope, receive, send):
if scope['type'] == 'http':
# We expect the request to be an HTTP request
# Wait to receive an event (the HTTP request body)
event = await receive()
# Construct the HTTP response
response_body = b"Hello, ASGI!"
# Send the HTTP response headers
await send({
'type': 'http.response.start',
'status': 200,
'headers': [
(b'content-type', b'text/plain'),
],
})
# Send the HTTP response body
await send({
'type': 'http.response.body',
'body': response_body,
})
This is an asynchronousASGI app. Note the asynckeyword in the function definition and the use of await when calling receiveand send.
Django and ASGI
Django 3.0 introduced partial support for ASGI, which has been extended further in Django 4.x, allowing Django applications to handle both HTTP and WebSocket requests asynchronously. Django achieves this via Django Channels, an extension that adds asynchronous capabilities.
Setting up Django with ASGI
Django projects still use WSGI by default for deployment, but they can be switched to ASGI by configuring the asgi.py file.
- Install Django Channels:
pip install channels
- Configure asgi.py(Django’s entry point for ASGI):
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
application = get_asgi_application()
- Update settings.pyto use Channels as the default ASGI application:
INSTALLED_APPS = [
'channels', # Add channels to installed apps
# Other Django apps
]
ASGI_APPLICATION = 'myproject.asgi.application' # Set the ASGI application
ASGI Middleware
Similar to WSGI middleware, ASGI supports middleware to process requests before they reach the application. The difference is that ASGI middleware can be asynchronous, enabling non-blocking I/O.
Example of ASGI middleware to log requests:
class LoggingMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
print(f"Request: {scope['path']} {scope['method']}")
await self.app(scope, receive, send)
To use this middleware, wrap the ASGI app with the middleware:
app = LoggingMiddleware(app)
Working with WebSockets
WebSockets allow bidirectional communication between the client and the server, and ASGI handles WebSocket connections natively.
Here’s a simple example of an ASGI application that handles WebSocket connections:
async def app(scope, receive, send):
if scope['type'] == 'websocket':
# Accept the WebSocket connection
await send({
'type': 'websocket.accept',
})
while True:
event = await receive()
if event['type'] == 'websocket.disconnect':
break
if event['type'] == 'websocket.receive':
# Echo the received message back to the client
await send({
'type': 'websocket.send',
'text': event['text'],
})
Common Mistakes
- Mixing Synchronous and Asynchronous Code: In ASGI applications, it’s crucial not to block the event loop with synchronous code. If you need to run synchronous code (like a traditional Django database query), use sync_to_async or the @database_sync_to_async decorator.
from asgiref.sync import sync_to_async
async def fetch_data():
result = await sync_to_async(my_synchronous_db_query)()
return result
- Managing Lifespan Events: The ASGI specification includes an optional lifespan protocol for startup and shutdown events. Ensure your application properly manages resources (e.g., database connections) during startup and shutdown events to prevent issues in production.
- Concurrency Management: With ASGI, you can handle thousands of connections concurrently. However, you must be cautious of shared resources (e.g., databases) that may not scale as easily. Use connection pooling and caching to reduce bottlenecks.