WSGI and ASGI

April 28, 2022

Overview

Historically, web servers did not understand how to run Python web applications. Various implemenations were built but none were used consistently, resulting in compatability, portability and vulnerability issues. To solve this, the Web Server Gateway Interface (WSGI) was introduced as the first standard specification to run Python web applications. Subsequently, the Asynchronous Server Gateway Interface (ASGI) was introduced to take advantage of asynchronous code.

WSGI and ASGI provide standard interfaces for running Python web applications. Therefore, any compliant web application (the application implementation) is able to work with any compliant web server (the server implementation).

  • Web Server <-> WSGI/ASGI <-> Web Application

Importance of an Interface

An interface seperates the implementation of a web application from the implementation of a web server, allowing either to evolve independently. This seperation of concern improves developer productivity, allowing web application developers and web server developers to focus on their preferred area of specialization. Furthermore, it seperates the choice of web application from the choice of web server, allowing users to choose the best pairing for their needs.

WSGI

WSGI is a specification that describes how a web server communicates with web applications, and how web applications can be chained together to process one request. The standard was described in PEP 3333, emphasising compatability and ease of implementation in order to incentivise developers to adopt the standard without a high initial investment.

WSGI is structured as a single, synchronous callable. The application provides a callable which the server then calls for each request. The interface sits in the middle, accepting a single incoming event and returning a single outgoing event at a time.

Minimal Example

Below is an example of a simple WSGI-compliant application:

def app(environ, start_response):
    start_response("200 OK", [("Content-Type", "text/plain")])
    return b"Hello, World!"
  • environ: a dictionary containing CGI like variables
  • start_response: a callback function that will be used by the application to send HTTP status code/message and HTTP headers to the server

The script can be run with any WSGI-compliant server, in this case gunicorn:

gunicorn wsgi:app

ASGI

ASGI is a specification that extends WSGI, providing an asynchronous interface with support for HTTP, HTTP/2, and WebSockets. ASGI is a superset of WSGI. Therefore, there is a defined way of translating between the two via a translation layer or threadpool.

ASGI is structured as a single, asynchronous callable. The application provides a callable which the server then calls for each request. The interface sits in the middle, accepting multiple incoming events and returning multiple outgoing events at a time, also allowing for a background coroutine so the application can perform other tasks.

Minimal Example

Below is an example of a simple ASGI-compliant application:

async def (scope, receive, send):
    await app(
        {
            "type": "http.response.start",
            "status": 200,
            "headers": [(b"Content-Type", "text/plain")],
        }
    )
    await send({"type": "http.response.body", "body": b"Hello, World!"})
  • scope: The connection scope information, a dictionary that contains at least a type key specifying the protocol that is incoming
  • receive: an awaitable callable that will yield a new event dictionary when one is available
  • send: an awaitable callable taking a single event dictionary as a positional argument that will return once the send has been completed or the connection has been closed

The script can be run with any ASGI-compliant server, in this case uvicorn:

uvicorn asgi:app

Moving From WSGI to ASGI

The move from WSGI to ASGi is influenced by the limitations of synchronous code and the benefits of asynchronous code.

WSGI is a thread-based interface where each request is handled sequentially. There is no support for asynchronous code or non-HTTP interfaces. This introduces limitations around handling long-lived connections or background coroutines and accepting multiple incoming and outgoing events at a time.

In contrast, ASGI's support for asynchronous code and non-HTTP interfaces provides big performance improvements around handling more requests per second, more I/O operations and better load handling.