Introdução ao ASGI: Criando um ecossistema da Web Python assíncrono

Olá Habr! Apresento a você a tradução do artigo "Introdução ao ASGI: Surgimento de um Ecossistema Web Assíncrono em Python", de Florimond Manca.



"Tartarugas perto da lagoa", Ricard Baraham no unsplash.com


Python não se limita à Ciência de Dados, o desenvolvimento da Web em Python retornou com uma nova mudança assíncrona no desenvolvimento da linguagem!


Muitos eventos importantes estão ocorrendo no ecossistema de desenvolvimento da Web Python. Um dos principais drivers dessas alterações é o ASGI - Asynchronous Standard Gateway Interface.


Eu já mencionei o ASGI várias vezes no meu blog, em particular, quando anunciei o Bocadillo ( framework da Web Python de código aberto assíncrono - aprox. Por . ) E o tartiflette-starlette ( biblioteca para criação da API GraphQL sobre HTTP via ASGI - aprox. . ), mas nunca escrevi uma introdução detalhada sobre ele. Agora eu farei isso.


Este artigo é destinado a pessoas interessadas nas últimas tendências do desenvolvimento da Web em Python. Quero convidá-lo para uma excursão na qual você aprenderá o que é ASGI e o que isso significa para o desenvolvimento da web moderna no mundo Python.


Antes de começarmos, gostaria de dizer que criei recentemente awesome-asgi - uma ótima lista para rastrear o ecossistema ASGI em constante expansão.


Tudo começou com async / waitit


Ao contrário do JavaScript ou do Go, no momento de sua aparência, o Python não oferecia a capacidade de executar código de forma assíncrona. Por um longo tempo, a execução paralela de código no Python pôde ser realizada apenas com a ajuda de multithread ou multiprocessing, ou usando bibliotecas de rede especializadas, como eventlet, gevent ou Twisted. (Em 2008, o Twisted tinha uma API para corotinas assíncronas, por exemplo, na forma de inlineCallbacks e deferredGenerator )


Tudo mudou no Python 3.4+. No Python 3.4, o asyncio foi incluído na biblioteca padrão, resultando no suporte à multitarefa cooperativa baseada em geradores e no yield from sintaxe.


Posteriormente no Python 3.5 , a sintaxe async/await foi adicionada . Graças a isso, as corotinas nativas apareceram, independentemente da implementação subjacente, o que levou à corrida do ouro pela concorrência no Python.


A corrida louca começou! Desde o lançamento da versão 3.5, a comunidade literalmente assincronizou tudo. Se você estiver interessado, muitos dos projetos resultantes estão listados em aio-libs e awesome-asyncio .


Como você pode imaginar, isso também significa que servidores Web e aplicativos Python estão se movendo em direção à assincronia. Na verdade, todos os caras legais fazem isso! ( Mesmo Django ) ( Habr: Django 3.0 será assíncrono , já lançado em 02/02/2019 - aprox. Por. )


Revisão ASGI


Então, como o ASGI se encaixa em tudo isso?


O ASGI de nível superior pode ser considerado como um link que permite que servidores e aplicativos Python assíncronos interajam. Ele repete muitas das idéias arquitetônicas da WSGI e é frequentemente apresentado como seu sucessor com assincronia embutida.


É assim que pode ser representado no diagrama:



Em um nível muito alto, o ASGI é uma interface para comunicação entre aplicativos e servidores. Mas, de fato, tudo é um pouco mais complicado.


Para entender como o ASGI realmente funciona, vamos dar uma olhada na especificação ASGI .


O ASGI consiste em dois componentes diferentes:


  1. Servidor de protocolo - escuta nos soquetes e os converte em conexões e mensagens de eventos em cada conexão.
  2. Um aplicativo ( aplicativo ), que vive dentro do servidor de protocolo, sua instância é criada uma vez para cada conexão e processa as mensagens de evento conforme elas ocorrem.

Assim, de acordo com a especificação, o que o ASGI realmente indica é o formato da mensagem e como essas mensagens devem ser transferidas entre o aplicativo e o servidor de protocolo que o executa.


Agora podemos fazer uma versão mais detalhada do diagrama:



Existem muitos detalhes mais interessantes no protocolo. Por exemplo, você pode dar uma olhada nas especificações HTTP e WebSocket .


Além disso, embora a especificação se concentre fortemente na interação entre o servidor e o aplicativo, o ASGI consegue cobrir muitos outros aspectos.


Chegamos a isso em um minuto, mas primeiro ...


Princípios ASGI


Agora que vimos como o ASGI se encaixa no ecossistema da Web Python, vamos dar uma olhada em como isso se traduz em código.


O ASGI depende de um modelo simples: quando um cliente se conecta a um servidor, uma instância do aplicativo é criada. Em seguida, os dados recebidos são transferidos para o aplicativo e todos os dados retornados são enviados de volta.


Passar dados para o aplicativo aqui significa realmente chamar o aplicativo como se fosse uma função, ou seja, algo que requer alguma entrada e retorna a saída.


De fato, tudo o que um aplicativo ASGI representa é uma chamada (objeto chamado). Os parâmetros deste objeto chamado, novamente, são determinados pela especificação ASGI :


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

A assinatura desta função é exatamente o que “I” significa em “ASGI”: a interface que o aplicativo deve implementar para que o servidor o chame.


Vejamos os parâmetros da função:


  • scope é um dicionário que contém informações sobre uma solicitação recebida. Seu conteúdo é diferente para conexões HTTP e WebSocket .
  • receive é uma função assíncrona para receber mensagens sobre eventos ASGI.
  • send é uma função assíncrona para enviar mensagens sobre eventos ASGI.

De fato, esses parâmetros permitem receber ( receive() ) e transmitir ( send() ) dados pelo canal de comunicação que o servidor de protocolo suporta, além de entender em que contexto (ou scope ) esse canal foi criado.


Não conheço você, mas gosto muito da aparência geral e da estrutura dessa interface. De qualquer forma, agora vamos ver um código de exemplo.


Mostre o código!


Para ter uma idéia prática da aparência do ASGI, criei um projeto mínimo no ASGI simples que demonstra um aplicativo HTTP servido por uvicorn (um servidor ASGI popular):


 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!", }) 

Código fonte - https://glitch.com/edit/#!/asgi-hello-world


Aqui usamos send() para enviar uma resposta HTTP ao cliente: primeiro enviamos os cabeçalhos e depois o corpo da resposta.


Admito que, devido a todos esses dicionários e dados binários brutos, o ASGI bare não é muito conveniente para o trabalho.


Felizmente, existem opções de nível superior - e é aí que começo a falar sobre a Starlette .


Starlette é um projeto verdadeiramente fantástico e, na minha opinião, uma parte fundamental do ecossistema ASGI.


Resumidamente, ele fornece um conjunto de componentes de alto nível, como solicitações e respostas, que você pode usar para abstrair alguns detalhes do ASGI. Aqui, dê uma olhada no "olá mundo" na 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) 

A Starlette tem tudo o que você espera de uma estrutura da Web real - roteamento, middleware etc. Mas eu decidi mostrar esta versão simplificada para sugerir o verdadeiro poder do ASGI, que é ...


Tartarugas por todo o caminho


Um conceito interessante e de mudança de regras para o ASGI é Turtles All the Way , uma expressão originalmente inventada (eu acho?) Por Andrew Godwin, que criou o Django Migrations e atualmente está trabalhando no Django para apoiar a assincronia .


Mas o que exatamente isso significa?


Como o ASGI é uma abstração que nos permite dizer em que contexto estamos e receber e enviar dados a qualquer momento, ou seja, a ideia de que o ASGI pode ser usado não apenas entre servidores e aplicativos, mas também em qualquer lugar da pilha.


Por exemplo, o objeto Starlette Response é o próprio aplicativo ASGI. De fato, podemos encurtar o código no aplicativo de exemplo acima para isso:


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

Quão ridículo isso parece ?!


Mas espere, isso não é tudo.


A consequência mais profunda das “tartarugas por todo o caminho” é que podemos criar todos os tipos de aplicativos, middleware, bibliotecas e outros projetos e garantir que eles sejam compatíveis, desde que implementem a interface do aplicativo ASGI.


(Além disso, pela minha experiência pessoal em construir Bocadillo , aceitar a interface ASGI com muita frequência (se não sempre) leva a um código muito mais limpo)


Por exemplo, podemos criar um middleware ASGI (ou seja, um aplicativo que envolve outro aplicativo) para exibir o tempo que levou para a solicitação ser concluída:


 # 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") 

Para usá-lo, simplesmente envolvemos o aplicativo com ele ...


 # 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) 

... e funcionará magicamente.


 $ 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 

É TimingMiddleware que no TimingMiddleware você possa TimingMiddleware qualquer aplicativo ASGI. O aplicativo interno neste exemplo é super simples, mas pode ser um projeto completo e real (imagine centenas de APIs e pontos de extremidade WebSocket) - não importa, desde que a interface seja compatível com ASGI.


(Existe uma versão deste middleware mais preparada para uso industrial: timing-asgi .)


Por que deveria incomodar?


Embora eu ache que a compatibilidade seja um argumento muito forte, há muito mais vantagens em usar componentes baseados em ASGI para criar aplicativos da Web Python.


  • Velocidade: a natureza assíncrona dos aplicativos e servidores ASGI os torna muito rápidos (pelo menos para Python) - estamos falando de solicitações de 60 a 70k por segundo (assumindo que o Flask e o Django atinjam de 10 a 20k em uma situação semelhante).
  • Recursos: Os servidores e plataformas ASGI oferecem acesso a funções essencialmente paralelas (WebSocket, Eventos Enviados pelo Servidor, HTTP / 2) que não podem ser implementadas usando código síncrono e WSGI.
  • Estabilidade: ASGI como uma especificação existe há 3 anos, e a versão 3.0 é considerada muito estável. Como resultado, as principais partes do ecossistema estão se estabilizando.

Do ponto de vista das bibliotecas e ferramentas, não acho que possamos dizer que alcançamos o nível necessário. Mas, graças a uma comunidade muito ativa, tenho grandes esperanças de que o ecossistema ASGI em breve alcance a paridade de funções com o ecossistema tradicional síncrono / WSGI.


Onde posso encontrar componentes compatíveis com ASGI?


De fato, mais e mais pessoas estão construindo e melhorando projetos baseados em ASGI. Obviamente, esses são servidores e estruturas da Web, mas também existem aplicativos orientados a produtos e middleware, como o Datasette .


Abaixo estão alguns exemplos de componentes não baseados na Web que me interessam:



É incrível observar que o ecossistema está se desenvolvendo com sucesso, no entanto, foi pessoalmente difícil para mim acompanhar pessoalmente as mudanças.


É por isso que eu criei awesome-asgi . Espero que isso ajude todos a acompanhar todas as coisas incríveis que acontecem no mundo ASGI. (E, visto que ele quase alcançou 100 estrelas em poucos dias, sinto que realmente era necessário reunir informações sobre os recursos ASGI em um só lugar.)


Conclusões


Embora isso possa parecer detalhes de implementação, tenho certeza de que a ASGI lançou as bases para uma nova era no desenvolvimento da Web em Python.


Se você quiser saber mais sobre o ASGI, consulte as várias publicações (artigos e discursos) listadas em awesome-asgi . Se você quiser tocá-lo, tente um dos seguintes projetos:



Esses projetos foram criados e são suportados pela Encode, principalmente Tom Christie. Há discussões abertas sobre a criação de uma equipe de suporte de codificação ; portanto, se você estava procurando uma oportunidade de participar no desenvolvimento de código-fonte aberto, teria uma oportunidade!


Divirta-se viajando para o mundo ASGI!

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


All Articles