- PVSM.RU - https://www.pvsm.ru -

Введение в ASGI: становление асинхронной веб-экосистемы Python

Привет! Представляю вашему вниманию перевод статьи "Introduction to ASGI: Emergence of an Async Python Web Ecosystem" [1] автора Florimond Manca.

Введение в ASGI: становление асинхронной веб-экосистемы Python - 1
"Черепахи рядом с водоемом", Ricard Baraham на unsplash.com [2]

Python не замыкается только на Data Science, веб-разработка на Python вернулась с новым асинхронным витком в развитии языка!

Сейчас происходит много важных событий в экосистеме веб-разработки на Python. Одним из основных драйверов этих изменений является ASGI [3] — Asynchronous Standard Gateway Interface.

Я уже несколько раз упоминал ASGI в моем блоге, в частности, когда анонсировал Bocadillo [4] (асинхронный open-source веб-фреймворк на Python — прим.пер.) и tartiflette-starlette [5] (библиотека для построения GraphQL API поверх HTTP через ASGI — прим.пер.), но я никогда не писал подробное введение о нем. Теперь я это сделаю.

Эта статья нацелена на людей, интересующихся последними трендами в веб-разработке на Python. Я хочу пригласить вас на экскурсию, из которой вы узнаете, что такое ASGI, и что он означает для современной веб-разработки в мире Python.

Прежде чем мы начнем, я хотел бы рассказать, что недавно создал awesome-asgi [6] — отличный список для отслеживания постоянно расширяющейся экосистемы ASGI.

Все началось с async/await

В отличие от JavaScript или Go, в момент появления Python не предоставлял возможность асинхронного исполнения кода. Долгое время параллельное выполнение кода в Python могло быть реализовано только с помощью многопоточной или многопроцессорной обработки, либо с использованием специализированных сетевых библиотек, таких как eventlet, gevent или Twisted. (Еще в 2008 году у Twisted был API для асинхронных корутин, например, в виде inlineCallbacks и deferredGenerator [7])

Все изменилось в Python 3.4+. В Python 3.4 в стандартную библиотеку включили asyncio [8], в результате появилась поддержка кооперативной многозадачности на основе генераторов и синтаксиса yield from.

Позже в Python 3.5 добавлен синтаксис [9] async/await. Благодаря этому появились нативные корутины, независящие от лежащей в основе реализации, что привело к золотой лихорадке вокруг параллелизма в Python.

Началась сумасшедшая гонка! С момента выпуска версии 3.5 сообщество буквально асинхронизовывает все вокруг. Если вам интересно, многие из получившихся проектов перечислены в aio-libs [10] и awesome-asyncio [11].

Как вы догадались, это также означает, что веб-серверы и приложения на Python движутся в сторону асинхронности. На самом деле, все крутые ребята делают это! (Даже Django [12]) (Habr: Django 3.0 будет асинхронным [13], уже вышла 02.12.2019 — прим.пер.)

Обзор ASGI

Итак, как же ASGI вписывается во все это?

Верхнеуровнево ASGI можно рассматривать как связующее звено, которое позволяет асинхронным Python серверам и приложениям взаимодействовать друг с другом. Он повторяет множество архитектурных идей из WSGI [14], и зачастую представляется как его преемник со встроенной асинхронностью.

Вот так его можно изобразить на диаграмме:

Введение в ASGI: становление асинхронной веб-экосистемы Python - 2

На очень высоком уровне ASGI — это интерфейс для коммуникации между приложениями и серверами. Но на самом деле, все немного сложнее.

Чтобы разобраться, как ASGI действительно работает, давайте взглянем на спецификацию ASGI [15].

ASGI состоит из двух различных компонентов:

  1. Сервера протокола (protocol server) — слушает сокеты и преобразует их в соединения и сообщения о событиях внутри каждого соединения.
  2. Приложения (application), которое живет внутри сервера протокола, его экземпляр создается один раз для каждого соединения и обрабатывает сообщения о событиях по мере их возникновения.

Таким образом, согласно спецификации, то, что действительно указывает ASGI — это формат сообщения и то, как эти сообщения должны передаваться между приложением и сервером протокола, который его запускает.

Теперь мы можем составить более детальную версию диаграммы:

Введение в ASGI: становление асинхронной веб-экосистемы Python - 3

В протоколе есть еще много более интересных деталей. Например, вы можете взглянуть на спецификацию HTTP и WebSocket [16].

Кроме того, хотя спецификация сильно фокусируется на взаимодействии между сервером и приложением, ASGI удается охватить гораздо больше аспектов.

Мы доберемся до этого через минуту, но сначала…

Основы ASGI

Теперь, когда мы увидели, как ASGI вписывается в веб-экосистему Python, давайте более подробно рассмотрим, как это воплощается в коде.

ASGI опирается на простую модель: когда клиент подключается к серверу, создается экземпляр приложения. Затем входящие данные передаются в приложение и отправляются обратно все данные, которые оно возвращает.

Передача данных в приложение здесь в действительности означает вызов приложения, как если бы оно было функцией, т.е. чем-то, что принимает некоторые входные данные и возвращает выходные.

На самом деле, все, что представляет собой ASGI-приложение — это callable (вызываемый объект). Параметры этого вызываемого объекта, опять же, определяются спецификацией ASGI [17]:

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

Сигнатура этой функции — это как раз то, что означает "I" в "ASGI": интерфейс, который должно реализовать приложение, чтобы сервер смог его вызвать.

Давайте рассмотрим параметры функции:

  • scope — это словарь, содержащий информацию о входящем запросе. Его содержимое отличается для HTTP [18] и WebSocket [19] соединений.
  • receive — асинхронная функция для получения сообщений о событиях ASGI.
  • send — асинхронная функция для отправки сообщений о событиях ASGI.

По сути, эти параметры позволяют получать (receive()) и передавать (send()) данные по каналу связи, который поддерживает сервер протокола, а также понимать, в каком контексте (или scope) этот канал был создан.

Не знаю как вам, но мне очень нравятся общий вид и структура этого интерфейса. В любом случае, сейчас посмотрим на пример кода.

Покажите код!

Чтобы получить практическое представление о том, как выглядит ASGI, я создал минимальный проект на голом ASGI, который демонстрирует HTTP-приложение, обслуживаемое uvicorn [20] (популярный ASGI-сервер):

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

Исходный код — https://glitch.com/edit/#!/asgi-hello-world [21]

Здесь мы используем send() для отправки HTTP-ответа клиенту: сначала отправляем заголовки, а затем тело ответа.

Я признаю, что из-за всех этих словарей и необработанных бинарных данных голый ASGI не очень удобен для работы.

К счастью, есть варианты более высокого уровня — и именно тогда я начинаю говорить о Starlette [22].

Starlette — поистине фантастический проект, и, по-моему, фундаментальная часть экосистемы ASGI.

Кратко, он предоставляет набор высокоуровневых компонентов, таких как запросы и ответы, которые можно использовать, чтобы абстрагироваться от некоторых деталей ASGI. Вот, взгляните на "hello world" в 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 есть все, что вы ожидаете от настоящего веб-фреймворка — routing, middleware и т.д. Но я решил показать эту урезанную версию, чтобы намекнуть на реальную силу ASGI, которая является…

Черепахи на всем пути

Интересная и меняющая правила игры концепция ASGI — это "Черепахи на всем пути [23]", выражение, которое первоначально придумал (я думаю?) Andrew Godwin, создавший Django Migrations и сейчас занимающийся переработкой Django для поддержки асинхронности [24].

Но что именно это означает?

Поскольку ASGI это абстракция, которая позволяет сказать, в каком контексте мы находимся, и получать и отправлять данные в любое время, то есть идея, что ASGI можно использовать не только между серверами и приложениями, но и действительно в любой точке стека.

Например, объект Starlette Response это само приложение ASGI. На самом деле, мы можем сократить код в примере приложения выше до такого:

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

Насколько нелепо это выглядит?!

Но подождите, это еще не все.

Более глубокое следствие "черепах на всем пути" заключается в том, что мы можем создавать всевозможные приложения, middleware, библиотеки и другие проекты и гарантировать, что они будут совместимыми до тех пор пока они все реализуют интерфейс приложения ASGI.

(К тому же, из моего личного опыта построения Bocadillo [25], прием интерфейса ASGI очень часто (если не всегда) приводит к гораздо более чистому коду)

Например, мы можем создать ASGI middleware (т.е. приложение, которое обертывает другое приложение), чтобы отобразить время, за которое был выполнен запрос:

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

Чтобы использовать его, мы просто оборачиваем им приложение…

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

… и это будет волшебным образом просто работать.

$ 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 можно обернуть любое ASGI-приложение. Внутреннее приложение в этом примере супер-простое, но это может быть полноценный, реальный проект (представьте сотни API и WebSocket endpoint-ов) — это не имеет значения, пока интерфейс совместим с ASGI.

(Есть версия этого middleware более подготовленная к промышленному использованию: timing-asgi [26].)

Почему это должно волновать?

Хотя я думаю, что совместимость является очень сильным аргументом, есть еще много преимуществ в использовании компонентов на основе ASGI для построения веб-приложений Python.

  • Скорость: асинхронная природа ASGI приложений и серверов делает их действительно быстрыми [27] (по крайней мере, для Python) — мы говорим о 60k-70k запросов в секунду (полагая, что Flask и Django достигают только 10-20k в аналогичной ситуации).
  • Возможности: серверы и платформы ASGI предоставляют вам доступ к по существу параллельным функциям (WebSocket, Server-Sent Events, HTTP/2), которые невозможно реализовать с помощью синхронного кода и WSGI.
  • Стабильность: ASGI как спецификация существует уже около 3 лет, и версия 3.0 считается очень стабильной. В результате этого основные части экосистемы стабилизируются.

С точки зрения библиотек и инструментов, не думаю, что мы можем сказать, что добрались до необходимого уровня. Но благодаря очень активному сообществу, у меня есть большие надежды, что экосистема ASGI очень скоро достигнет паритета функций с традиционной синхронной/WSGI экосистемой.

Где можно найти компоненты, совместимые с ASGI?

На самом деле, все больше и больше людей строят и улучшают проекты на основе ASGI. Очевидно, это серверы и веб-фреймворки, но также есть middleware и ориентированные на продукт приложения, такие как Datasette [28].

Ниже несколько примеров компонентов, не являющихся веб-феймворками, которые меня интересуют:

  • Mangum [29]: поддержка ASGI для AWS Lambda.
  • datasette-auth-github [30]: аутентификация GitHub для ASGI-приложений.
  • tartiflette-starlette [31] (я написал его!): Поддержка ASGI для Tartiflette, асинхронного движка GraphQL.

Восхитительно наблюдать, что экосистема успешно развивается, однако, мне лично было трудно не отставать от изменений.

Именно поэтому я создал awesome-asgi [6]. Я надеюсь, что это поможет всем идти в ногу со всеми удивительными вещами, которые происходят в мире ASGI. (И видя, что он почти достиг 100 звезд за несколько дней, у меня есть ощущение, что действительно была необходимость собрать в одном месте информацию о ресурсах про ASGI.)

Выводы

Хотя это может выглядеть как подробности реализации, я уверен, что ASGI заложил основы для новой эры в веб-разработке на Python.

Если вы хотите узнать больше о ASGI, ознакомьтесь с различными публикациями [32] (статьями и выступлениями), перечисленными в awesome-asgi. Если хотите потрогать руками, попробуйте любой из следующих проектов:

  • uvicorn [20]: сервер ASGI.
  • Starlette [22]: ASGI фреймворк.
  • TypeSystem [33]: валидация данных и рендеринг форм.
  • Databases [34]: библиотека асинхронных баз данных.
  • orm [35]: асинхронный ORM.
  • HTTPX [36]: асинхронный HTTP client с поддержкой вызова ASGI-приложений (полезен как клиент для тестирования).

Эти проекты были созданы и поддерживаются компанией Encode, в основном Томом Кристи (Tom Christie). Есть открытые обсуждения по созданию команды поддержки Encode [37], так что если вы искали возможность поучаствовать в разработке open-source, то у вас есть такая возможность!

Получайте удовольствие от путешествия в мир ASGI!

Автор: nvlbud

Источник [38]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/python/342134

Ссылки в тексте:

[1] "Introduction to ASGI: Emergence of an Async Python Web Ecosystem": https://florimond.dev/blog/articles/2019/08/introduction-to-asgi-async-python-web/

[2] unsplash.com: https://images.unsplash.com/photo-1482642302383-7ba0f8012849?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1051&q=80

[3] ASGI: https://asgi.readthedocs.io/en/latest/

[4] Bocadillo: https://florimond.dev/blog/articles/2018/12/how-i-built-a-web-framework-and-became-an-open-source-maintainer/

[5] tartiflette-starlette: https://florimond.dev/blog/articles/2019/07/introducing-tartiflette-starlette/

[6] awesome-asgi: https://github.com/florimondmanca/awesome-asgi

[7] inlineCallbacks и deferredGenerator: http://blog.mekk.waw.pl/archives/14-Twisted-inlineCallbacks-and-deferredGenerator.html

[8] asyncio: https://www.python.org/dev/peps/pep-3156

[9] добавлен синтаксис: https://www.python.org/dev/peps/pep-0492/

[10] aio-libs: https://github.com/aio-libs

[11] awesome-asyncio: https://github.com/timofurrer/awesome-asyncio

[12] Даже Django: https://github.com/django/deps/blob/master/accepted/0009-async.rst

[13] Django 3.0 будет асинхронным: https://habr.com/ru/post/461493/

[14] WSGI: https://www.python.org/dev/peps/pep-3333

[15] спецификацию ASGI: https://asgi.readthedocs.io/en/latest/specs/main.html#overview

[16] спецификацию HTTP и WebSocket: https://asgi.readthedocs.io/en/latest/specs/www.html

[17] определяются спецификацией ASGI: https://asgi.readthedocs.io/en/latest/specs/main.html#applications

[18] HTTP: https://asgi.readthedocs.io/en/latest/specs/www.html#connection-scope

[19] WebSocket: https://asgi.readthedocs.io/en/latest/specs/www.html#id1

[20] uvicorn: https://www.uvicorn.org/

[21] https://glitch.com/edit/#!/asgi-hello-world: https://glitch.com/edit/#!/asgi-hello-world

[22] Starlette: https://www.starlette.io/

[23] Черепахи на всем пути: https://simonwillison.net/2019/Jun/23/datasette-asgi/

[24] переработкой Django для поддержки асинхронности: https://www.youtube.com/watch?v=oMHrDy62kgE

[25] Bocadillo: https://bocadilloproject.github.io/

[26] timing-asgi: https://github.com/steinnes/timing-asgi

[27] действительно быстрыми: https://www.techempower.com/benchmarks/#section=data-r18&hw=ph&test=fortune&l=zijzen-f&w=zik0zh-zik0zj-e7&d=b

[28] Datasette: https://github.com/simonw/datasette

[29] Mangum: https://github.com/erm/mangum

[30] datasette-auth-github: https://github.com/simonw/datasette-auth-github

[31] tartiflette-starlette: https://github.com/tartiflette/tartiflette-starlette

[32] публикациями: https://github.com/florimondmanca/awesome-asgi#publications

[33] TypeSystem: https://www.encode.io/typesystem/

[34] Databases: https://www.encode.io/databases/

[35] orm: https://github.com/encode/orm

[36] HTTPX: https://www.encode.io/httpx/

[37] созданию команды поддержки Encode: https://discuss.encode.io/t/setting-up-a-maintainence-team/721/9

[38] Источник: https://habr.com/ru/post/482936/?utm_source=habrahabr&utm_medium=rss&utm_campaign=482936