ASGI Explained: The Future of Python Web Development

0

Python web applications have long adhered to the Web Server Gateway Interface (WSGI) standard, which describes how they communicate with web servers. WSGI, originally introduced in 2003 and updated in 2010, relies only on features that were natively available in Python from version 2.2 and were easy to implement. As a result, WSGI enjoyed rapid adoption with all major Python web frameworks and became the cornerstone of Python web development.

Fast forward to 2022. Python 2 is deprecated (finally) and Python now has a native syntax to handle asynchronous operations such as network calls. WSGI and other standards that assume synchronous behaviors by default cannot take advantage of the performance and efficiency gains of async. This in turn means that WSGI cannot effectively handle advanced protocols like WebSocket.

Enter ASGI, the asynchronous server gateway interface. Like WSGI, ASGI describes a common interface between a Python web application and the web server. Unlike WSGI, ASGI allows multiple asynchronous events per application. Additionally, ASGI supports both synchronized and asynchronous applications. You can migrate your old synchronous WSGI web applications to ASGI, as well as use ASGI to create new asynchronous web applications.

How WSGI works

WSGI works by exposing a Python function, usually named application Where app, to the web server. This function takes two parameters:

  • environ: a dictionary containing information about the current request and the environment variables provided by the Web server.
  • start_response: A function that will be used to initiate sending an HTTP response to the client.

The data returned by the function is the body of the response.

A simple application the function could look like this:

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return [b'Greetings universe']

If you are using a WSGI compatible web framework like Flask, the framework itself will provide a application function, with all its components automatically wired.

The disadvantages of WSGI are twofold. First, WSGI only handles one request and response at a time, assuming the response will be returned immediately. There is no way to handle long-running connections, such as a WebSocket connection or a long polling HTTP connection.

Second, WSGI is synchronous only. Even if you are using a multithreaded connection pool, every connection will be blocked until it returns a response. Many WSGI setups have the ability to manage thread and process pools, but these are limited by the WSGI interface himself being synchronous.

How ASGI works

ASGI is externally similar to WSGI. As with WSGI, you define a application function object, except that it is a async function with three parameters instead of two:

  • scope: A dictionary containing information about the current query, similar to environ in WSGI, but with a slightly different naming convention for details.
  • send: A async callable (function) that allows the application to send messages back to the client.
  • receive: A async callable which allows the application to receive messages from the client.

A simple ASGI application the function could look like this:

 async def application(scope, receive, send):
    await send({
        'type': 'http.response.start',
        'status': 200,
        'headers': [
            [b'content-type', b'text/plain'],
        ],
    })

    await send({
        'type': 'http.response.body',
        'body': b'Hello, world!',
    })

Like a WSGI web framework, an ASGI web framework will generate its own application() function and wire it as needed.

The most obvious difference with ASGI is that we use asynchronous metaphors throughout the function. The function itself is asyncand we send HTTP headers and response body by means of two await send() orders. In this way, the function itself and its send commands, do not block anything; they can be intertwined with invocations of application and send many other connections at once.

We don’t use receive in this example, but it is also a async function. This allows us to receive the request body without blocking other operations. Requests and responses can be streamed to or from the server this way, which we couldn’t do gracefully, or perhaps at all, using WSGI.

Using sync and async functions with ASGI

When using ASGI you will want to use async asynchronous functions and libraries, as much as possible. It is worth getting into the habit of using async, because issues with using sync-only code can be significant. Any long-running call to a sync-only function will block the whole chain of calls, which will wipe out the benefits of using async.

If you’re stuck using a long running synchronous call for something, use asyncio.run_in_executor to exploit the call to a thread or process pool. A thread pool should be used whenever you are waiting for an external event or a task that is not CPU intensive. A process pool should be used for local CPU-intensive tasks.

For example, if you have a route in your web application that calls a remote website, you should use a thread, or better yet, use the aiohttp library, which makes asynchronous HTTP requests. If you want to call the Pillow image library to resize an image, you should probably use run_in_executor with a process pool. Although there is a slight overhead for shuttling between processes, using run_in_executor will not block other events.

ASGI compatible web frameworks

It is possible to write ASGI web applications “by hand” by implementing the application() object. But the vast majority of the time, it will be easier (and less headache-inducing) to use an asynchronous, ASGI-centric Python web framework. Here are some common web framework choices that work well with ASGI:

  • Starlet and FastAPI: These promising frameworks (FastAPI is built on Starlette) are both asynchronous first, so it’s no surprise that they both support ASGI. If you are starting a web application from a blank slate, these are the most modern and advanced web frameworks for Python.
  • Liter: Although the core Python web framework Flask supports ASGI, Flask is not internally designed to take advantage of asynchronous metaphors. Quart, from GitLab, uses Flask syntax and metaphors, but allows asynchronous route handlers.
  • Django 3.0 and later: As of Django 3.0, the venerable Django web framework supports ASGI. Support for asynchronous code in a Django application, instead of just being able to mount Django on an ASGI handler, was added in Django 3.1. For a framework not known for its execution speed, the mere presence of async unlocks better performance for those who choose to exploit it.

Copyright © 2022 IDG Communications, Inc.

Share.

Comments are closed.