Introduction à ASGI: création d'un écosystème Web Python asynchrone

Bonjour, Habr! Je vous présente la traduction de l'article "Introduction à ASGI: Emergence d'un écosystème Web Async Python" par Florimond Manca.



"Les tortues près de l'étang", Ricard Baraham sur unsplash.com


Python ne se limite pas à la science des données, le développement Web Python est de retour avec un nouveau tournant asynchrone dans le développement du langage!


De nombreux événements importants se déroulent dans l'écosystème de développement Web Python. L'un des principaux moteurs de ces modifications est ASGI - Interface de passerelle standard asynchrone.


J'ai déjà mentionné plusieurs fois ASGI dans mon blog, en particulier, lorsque j'ai annoncé Bocadillo ( framework web asynchrone open-source Python - environ Per . ) Et tartiflette-starlette ( bibliothèque pour construire l'API GraphQL sur HTTP via ASGI - env. . ), mais je n'ai jamais écrit une introduction détaillée à son sujet. Maintenant je vais le faire.


Cet article est destiné aux personnes intéressées par les dernières tendances du développement Web Python. Je veux vous inviter à une excursion à partir de laquelle vous apprendrez ce qu'est ASGI et ce que cela signifie pour le développement web moderne dans le monde Python.


Avant de commencer, je voudrais vous dire que j'ai récemment créé awesome-asgi - une excellente liste pour suivre l'écosystème ASGI en constante expansion.


Tout a commencé avec async / wait


Contrairement à JavaScript ou Go, au moment de son apparition, Python ne fournissait pas la possibilité d'exécuter du code de manière asynchrone. Pendant longtemps, l'exécution de code parallèle en Python n'a pu être réalisée qu'avec l'aide d'un traitement multi-thread ou multi-processeur, ou en utilisant des bibliothèques réseau spécialisées telles que eventlet, gevent ou Twisted. (En 2008, Twisted avait une API pour les coroutines asynchrones, par exemple, sous la forme de inlineCallbacks et de deferredGenerator )


Tout a changé dans Python 3.4+. Dans Python 3.4, asyncio a été inclus dans la bibliothèque standard, ce qui permet de prendre en charge le multitâche coopératif basé sur des générateurs et le yield from syntaxe.


Plus tard dans Python 3.5 , la syntaxe async/await été ajoutée . Grâce à cela, des coroutines natives sont apparues, indépendamment de l'implémentation sous-jacente, ce qui a conduit à la ruée vers l'or autour de la concurrence en Python.


La course folle a commencé! Depuis la sortie de la version 3.5, la communauté a littéralement tout asynchronisé. Si vous êtes intéressé, de nombreux projets résultants sont répertoriés dans aio-libs et awesome-asyncio .


Comme vous pouvez le deviner, cela signifie également que les serveurs Web et les applications Python évoluent vers l'asynchronie. En fait, tous les gars sympas le font! ( Même Django ) ( Habr: Django 3.0 sera asynchrone , déjà sorti le 12/02/2019 - environ par. )


Revue ASGI


Alors, comment ASGI s'intègre-t-il dans tout cela?


L'ASGI de niveau supérieur peut être considéré comme un lien qui permet aux serveurs et applications Python asynchrones d'interagir entre eux. Il répète plusieurs des idées architecturales du WSGI et est souvent présenté comme son successeur avec l'asynchronie intégrée.


Voici comment il peut être représenté sur le schéma:



À un très haut niveau, ASGI est une interface de communication entre les applications et les serveurs. Mais en fait, tout est un peu plus compliqué.


Pour comprendre comment ASGI fonctionne vraiment, jetons un coup d'œil à la spécification ASGI .


ASGI se compose de deux composants différents:


  1. Serveur de protocole - écoute les sockets et les convertit en connexions et messages d'événement dans chaque connexion.
  2. Une application ( application ), qui vit à l'intérieur du serveur de protocole, son instance est créée une fois pour chaque connexion et traite les messages d'événement au fur et à mesure qu'ils se produisent.

Ainsi, selon la spécification, ce qu'ASGI indique vraiment est le format du message et comment ces messages doivent être transférés entre l'application et le serveur de protocole qui l'exécute.


Nous pouvons maintenant créer une version plus détaillée du diagramme:



Il y a beaucoup plus de détails intéressants dans le protocole. Par exemple, vous pouvez consulter les spécifications HTTP et WebSocket .


En outre, bien que la spécification se concentre fortement sur l'interaction entre le serveur et l'application, ASGI parvient à couvrir de nombreux autres aspects.


Nous y arrivons dans une minute, mais d'abord ...


Principes de base d'ASGI


Maintenant que nous avons vu comment ASGI s'intègre dans l'écosystème Web Python, examinons de plus près comment cela se traduit en code.


ASGI s'appuie sur un modèle simple: lorsqu'un client se connecte à un serveur, une instance d'application est créée. Ensuite, les données entrantes sont transférées vers l'application et toutes les données qu'elle renvoie sont renvoyées.


Passer des données à l'application ici signifie en fait appeler l'application comme s'il s'agissait d'une fonction, c'est-à-dire quelque chose qui prend une entrée et renvoie une sortie.


En fait, tout ce qu'une application ASGI représente est un objet appelable (appelé objet). Les paramètres de cet objet appelé, là encore, sont déterminés par la spécification ASGI :


 async def app(scope, receive, send): ... 

La signature de cette fonction est exactement ce que «je» signifie dans «ASGI»: l'interface que l'application doit implémenter pour que le serveur l'appelle.


Regardons les paramètres de la fonction:


  • scope est un dictionnaire contenant des informations sur une demande entrante. Son contenu est différent pour les connexions HTTP et WebSocket .
  • receive est une fonction asynchrone pour recevoir des messages sur les événements ASGI.
  • send est une fonction asynchrone pour l'envoi de messages sur les événements ASGI.

En fait, ces paramètres vous permettent de recevoir ( receive() ) et de transmettre ( send() ) des données sur le canal de communication pris en charge par le serveur de protocole, ainsi que de comprendre dans quel contexte (ou scope ) ce canal a été créé.


Je ne sais pas pour vous, mais j'aime vraiment l'aspect général et la structure de cette interface. Dans tous les cas, regardons maintenant un exemple de code.


Montrez le code!


Pour avoir une idée pratique de ce à quoi ressemble ASGI, j'ai créé un projet minimal sur ASGI nu qui montre une application HTTP servie par uvicorn (un serveur ASGI populaire):


 async def app(scope, receive, send): 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, world!", }) 

Code source - https://glitch.com/edit/#!/asgi-hello-world


Ici, nous utilisons send() pour envoyer une réponse HTTP au client: nous envoyons d'abord les en-têtes, puis le corps de la réponse.


J'avoue qu'en raison de tous ces dictionnaires et des données binaires brutes, ASGI nu n'est pas très pratique pour le travail.


Heureusement, il existe des options de niveau supérieur - et c'est là que je commence à parler de Starlette .


Starlette est un projet vraiment fantastique et, à mon avis, un élément fondamental de l'écosystème ASGI.


En bref, il fournit un ensemble de composants de haut niveau, tels que des demandes et des réponses, que vous pouvez utiliser pour vous éloigner de certains détails d'ASGI. Ici, jetez un œil au "bonjour le monde" dans Starlette:


 # app.py from starlette.responses import PlainTextResponse async def app(scope, receive, send): assert scope["type"] == "http" response = PlainTextResponse("Hello, world!") await response(scope, receive, send) 

Starlette a tout ce que vous attendez d'un véritable framework web - routage, middleware, etc. Mais j'ai décidé de montrer cette version allégée pour faire allusion à la vraie puissance d'ASGI, qui est ...


Des tortues tout le long


Un concept intéressant et qui change les règles pour ASGI est Turtles All the Way , une expression inventée à l'origine (je pense?) Par Andrew Godwin, qui a créé Django Migrations et travaille actuellement sur Django pour prendre en charge l'asynchronie .


Mais qu'est-ce que cela signifie exactement?


Puisque ASGI est une abstraction qui nous permet de dire dans quel contexte nous nous trouvons et de recevoir et d'envoyer des données à tout moment, il y a l'idée que ASGI peut être utilisé non seulement entre les serveurs et les applications, mais aussi vraiment n'importe où dans la pile.


Par exemple, l'objet Starlette Response est l'application ASGI elle-même. En fait, nous pouvons raccourcir le code dans l'exemple d'application ci-dessus à ceci:


 # app.py app = PlainTextResponse("Hello, world!") 

À quel point cela semble-t-il ridicule?!


Mais attendez, ce n'est pas tout.


La conséquence la plus profonde de «tortues jusqu'au bout» est que nous pouvons créer toutes sortes d'applications, middleware, bibliothèques et autres projets et nous assurer qu'ils sont compatibles tant qu'ils implémentent tous l'interface d'application ASGI.


(De plus, d'après mon expérience personnelle dans la construction de Bocadillo , accepter l'interface ASGI très souvent (sinon toujours) conduit à un code beaucoup plus propre)


Par exemple, nous pouvons créer un middleware ASGI (c'est-à-dire une application qui encapsule une autre application) pour afficher le temps qu'il a fallu pour que la demande soit terminée:


 # app.py import time class TimingMiddleware: def __init__(self, app): self.app = app async def __call__(self, scope, receive, send): start_time = time.time() await self.app(scope, receive, send) end_time = time.time() print(f"Took {end_time - start_time:.2f} seconds") 

Pour l'utiliser, il suffit d'envelopper l'application avec ...


 # app.py import asyncio from starlette.responses import PlainTextResponse async def app(scope, receive, send): await asyncio.sleep(1) response = PlainTextResponse("Hello, world!") await response(scope, receive, send) app = TimingMiddleware(app) 

... et cela fonctionnera comme par magie.


 $ uvicorn app:app INFO: Started server process [59405] INFO: Waiting for application startup. INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ... INFO: ('127.0.0.1', 62718) - "GET / HTTP/1.1" 200 Took 1.00 seconds 

Il est TimingMiddleware que dans TimingMiddleware vous puissiez envelopper n'importe quelle application ASGI. L'application interne dans cet exemple est super simple, mais il peut s'agir d'un projet réel à part entière (imaginez des centaines d'API et de points de terminaison WebSocket) - cela n'a pas d'importance tant que l'interface est compatible avec ASGI.


(Il existe une version de ce middleware plus préparée pour un usage industriel: timing-asgi .)


Pourquoi cela devrait-il déranger?


Bien que je pense que la compatibilité est un argument très fort, il existe de nombreux autres avantages à utiliser des composants basés sur ASGI pour créer des applications Web Python.


  • Vitesse: La nature asynchrone des applications et des serveurs ASGI les rend très rapides (au moins pour Python) - nous parlons de 60 000 à 70 000 demandes par seconde (en supposant que Flask et Django n'atteignent que 10 à 20 000 dans une situation similaire).
  • Caractéristiques: Les serveurs et plates-formes ASGI vous donnent accès à des fonctions essentiellement parallèles (WebSocket, événements envoyés par le serveur, HTTP / 2) qui ne peuvent pas être implémentées à l'aide de code synchrone et WSGI.
  • Stabilité: ASGI en tant que spécification existe depuis 3 ans et la version 3.0 est considérée comme très stable. En conséquence, les principales parties de l'écosystème se stabilisent.

Du point de vue des bibliothèques et des outils, je ne pense pas que nous puissions dire que nous avons atteint le niveau requis. Mais grâce à une communauté très active, j'ai bon espoir que l'écosystème ASGI atteindra très prochainement la parité de fonctions avec l'écosystème synchrone / WSGI traditionnel.


Où puis-je trouver des composants compatibles avec ASGI?


En fait, de plus en plus de personnes construisent et améliorent des projets basés sur ASGI. Évidemment, ce sont des serveurs et des cadres Web, mais il existe également des middlewares et des applications orientées produit comme Datasette .


Voici quelques exemples de composants non Web qui m'intéressent:



Il est étonnant de constater que l'écosystème se développe avec succès, cependant, il m'a été personnellement difficile de suivre personnellement les changements.


C'est pourquoi j'ai créé awesome-asgi . J'espère que cela aide tout le monde à suivre toutes les choses incroyables qui se produisent dans le monde ASGI. (Et voyant qu'il a presque atteint 100 étoiles en quelques jours, j'ai le sentiment qu'il était vraiment nécessaire de rassembler des informations sur les ressources ASGI en un seul endroit.)


Conclusions


Bien que cela puisse ressembler à des détails d'implémentation, je suis sûr que ASGI a jeté les bases d'une nouvelle ère dans le développement Web Python.


Si vous souhaitez en savoir plus sur ASGI, consultez les différentes publications (articles et discours) répertoriées dans awesome-asgi . Si vous souhaitez le toucher, essayez l'un des projets suivants:



Ces projets ont été créés et sont soutenus par Encode, principalement Tom Christie. Il y a des discussions ouvertes sur la création d'une équipe de support Encode , donc si vous cherchez une opportunité de participer au développement open-source, alors vous avez une telle opportunité!


Amusez-vous à voyager dans le monde ASGI!

Source: https://habr.com/ru/post/fr482936/


All Articles