Python-Webanwendungen orientieren sich seit jeher am WSGI-Standard (Web Server Gateway Interface), der beschreibt, wie die Applikationen mit Webservern kommunizieren. WSGI wurde ursprünglich 2003 eingeführt, 2010 aktualisiert und stützt sich nur auf Funktionen, die ab Version 2.2 nativ in Python verfügbar und leicht zu implementieren waren. Infolgedessen wurde WSGI schnell von allen wichtigen Python-Web-Frameworks übernommen und zu einem Eckpfeiler der Webentwicklung in Python.
Im Jahr 2022 ist Python 2 (endlich) veraltet - inzwischen verfügt die Programmiersprache über eine native Syntax, um asynchrone Operationen wie Netzwerkaufrufe zu händeln. WSGI und andere Standards, die standardmäßig ein synchrones Verhalten voraussetzen, können die Performance- und Effizienzvorteile von asynchronen Operationen nicht nutzen. Im Umkehrschluss bedeutet das, dass WSGI fortgeschrittene Protokolle wie WebSocket nicht effektiv handhaben kann.
An dieser Stelle kommt ASGI (Asynchronous Server Gateway Interface) ins Spiel: Wie WSGI beschreibt ASGI eine gemeinsame Schnittstelle zwischen einer Python-Webanwendung und dem Webserver. Anders als WSGI erlaubt ASGI mehrere asynchrone Ereignisse pro Anwendung. Außerdem unterstützt ASGI sowohl synchrone als auch asynchrone Anwendungen. Sie können Ihre alten, synchronen WSGI-Webanwendungen auf ASGI migrieren und ASGI nutzen, um neue, asynchrone Web-Apps zu erstellen.
Wie WSGI funktioniert
WSGI stellt dem Webserver eine Python-Funktion zur Verfügung, die normalerweise application
oder app
heißt. Diese Funktion benötigt zwei Parameter:
environ
: Ein Wörterbuch, das Informationen über die aktuelle Anfrage und die vom Webserver bereitgestellten Umgebungsvariablen enthält.start_response
: Eine Funktion, mit der eine HTTP-Antwort an den Client eingeleitet wird.
Die von der Funktion zurückgegebenen Daten bilden den Antwortkörper. Eine einfache application
-Funktion könnte wie folgt aussehen:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
return [b'Greetings universe']
Wenn Sie ein WSGI-kompatibles Web-Framework wie Flask verwenden, stellt dieses eine application
-Funktion bereit, bei der alle Komponenten automatisch miteinander verknüpft sind.
Die Nachteile von WSGI:
WSGI verarbeitet jeweils nur eine einzige Anfrage und Antwort, wobei davon ausgegangen wird, dass die Antwort sofort zurückgegeben wird. Es gibt keine Möglichkeit, mit lange gehaltenen Verbindungen umzugehen, wie z. B. eine WebSocket- oder eine lang andauernde HTTP-Verbindung.
WSGI ist nur synchron. Selbst wenn Sie einen Multithreading-Verbindungspool verwenden, wird jede Verbindung blockiert, bis sie eine Antwort zurückgibt. Viele WSGI-Konfigurationen sind in der Lage, Thread- und Prozess-Pools zu handhaben, aber diese sind durch die synchrone WSGI-Schnittstelle selbst eingeschränkt.
Wie ASGI funktioniert
Auf den ersten Blick weist ASGI Ähnlichkeiten zu WSGI auf: Wie bei WSGI definieren Sie ein application
function object, mit dem Unterschied, dass es sich um eine async
-Funktion mit drei statt zwei Parametern handelt:
scope
: Ein Wörterbuch mit Informationen über die aktuelle Anfrage, ähnlich wieenviron
in WSGI, aber mit einer etwas anderen Namenskonvention für die Details.send
: Eineasync
callable function, mit der die Anwendung Nachrichten an den Client zurückgeben kann.receive
: Eineasync
callable function, mit der die Anwendung Nachrichten vom Client empfangen kann.
Eine einfache application
-Funktion könnte im Fall von ASGI wie folgt aussehen:
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'Hallo, Welt!',
})
Wie ein WSGI-Web-Framework erzeugt auch das ASGI-Pendant seine eigene application()
-Funktion und verknüpft sie nach Bedarf. Der offensichtlichste Unterschied dabei ist, dass bei ASGI in der gesamten Funktion asynchrone Metaphern verwendet werden. Die Funktion selbst ist async
- die HTTP-Header und der Antwortkörper werden über zwei separate await send()
-Befehle gesendet. Auf diese Weise blockieren die Funktion selbst und ihre send
-Befehle nichts - sie können mit application
- und send
-Aufrufen von vielen anderen Verbindungen gleichzeitig verschachtelt werden.
Die Funktion receive
wird in diesem Beispiel nicht verwendet, aber auch sie ist eine async
-Funktion. Mit ihr lässt sich der request body empfangen, ohne andere Operationen zu blockieren. Anfragen und Antworten können auf diese Weise inkrementell zum oder vom Server gestreamt werden - etwas, das mit WSGI nicht auf elegante Art und Weise beziehungsweise gar nicht möglich ist.
Snyc- und Async-Funktionen mit ASGI nutzen
Wenn Sie ASGI verwenden, sollten Sie so oft wie möglich async
-Funktionen und async-freundliche Bibliotheken verwenden. Sich daran zu gewöhnen, zahlt sich aus, weil es erhebliche Probleme aufwerfen kann, Sync-Only-Code zu verwenden. Jeder lang andauernde Aufruf einer reinen Sync-Funktion blockiert die gesamte Call-Chain, wodurch sich die Vorteile der Verwendung von async nahezu in Luft auflösen.
Wenn Sie einen langlaufenden synchronen Call verwenden müssen, setzen Sie auf asyncio.run_in_executor
, um den Aufruf an einen Thread- oder Prozess-Pool auszulagern. Ein Thread-Pool sollte immer dann verwendet werden, wenn Sie auf ein externes Ereignis oder eine Aufgabe warten, die nicht CPU-intensiv ist. Ein Prozess-Pool sollte für lokale Aufgaben verwendet werden, die CPU-intensiv sind.
Wenn Sie beispielsweise eine Route in Ihrer Webanwendung haben, die eine entfernte Website aufruft, sollten Sie einen Thread verwenden - oder noch besser, die aiohttp
-Bibliothek, die asynchrone HTTP-Anfragen stellt. Wenn Sie die Pillow-Image-Bibliothek aufrufen wollen, um die Größe eines Bildes zu ändern, sollten Sie run_in_executor
mit einem Prozess-Pool verwenden. Obwohl ein leichter Overhead für das Hin- und Herschieben von Daten zwischen den Prozessen entsteht, werden andere Ereignisse durch run_in_executor
nicht blockiert.
ASGI-fähige Web-Frameworks
Es ist möglich, ASGI-Webanwendungen "von Hand" zu schreiben, indem Sie das application()
-Objekt implementieren. In den allermeisten Fällen wird es jedoch einfacher sein (und weniger Kopfschmerzen bereiten), ein async-natives, ASGI-zentriertes Python-Web-Framework zu verwenden. Diese gängigen Web-Frameworks, funktionieren gut im Zusammenspiel mit ASGI:
Starlette und FastAPI: Diese aufstrebenden Frameworks (FastAPI baut auf Starlette auf) sind beide async-first und unterstützen ASGI. Wenn Sie eine Web-App aus dem Nichts starten, sind das die modernsten und fortschrittlichsten Web-Frameworks für Python.
Quart: Während das Standard-Python-Web-Framework Flask ASGI unterstützt, ist es nicht darauf ausgelegt, die Vorteile asynchroner Metaphern zu nutzen. Quart von GitLab verwendet die Syntax und die Metaphern von Flask, erlaubt aber async route handlers.
Django 3.0 (und neuer): Seit Version 3.0 unterstützt dieses altehrwürdige Web-Framework ASGI. Der Support für asynchronen Code innerhalb einer Django-Anwendung wurde mit Django 3.1 hinzugefügt. Bei einem Framework, das nicht für seine Execution-Geschwindigkeit bekannt ist, bringt allein die Existenz von asynchronem Code mehr Performance für diejenigen, die sich dafür entscheiden, es zu nutzen. (fm)
Dieser Beitrag basiert auf einem Artikel unserer US-Schwesterpublikation Infoworld.