- PVSM.RU - https://www.pvsm.ru -
Привет! Представляю вашему вниманию перевод статьи "Introduction to ASGI: Emergence of an Async Python Web Ecosystem" [1] автора Florimond Manca.
"Черепахи рядом с водоемом", 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.
В отличие от 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 можно рассматривать как связующее звено, которое позволяет асинхронным Python серверам и приложениям взаимодействовать друг с другом. Он повторяет множество архитектурных идей из WSGI [14], и зачастую представляется как его преемник со встроенной асинхронностью.
Вот так его можно изобразить на диаграмме:
На очень высоком уровне ASGI — это интерфейс для коммуникации между приложениями и серверами. Но на самом деле, все немного сложнее.
Чтобы разобраться, как ASGI действительно работает, давайте взглянем на спецификацию ASGI [15].
ASGI состоит из двух различных компонентов:
Таким образом, согласно спецификации, то, что действительно указывает ASGI — это формат сообщения и то, как эти сообщения должны передаваться между приложением и сервером протокола, который его запускает.
Теперь мы можем составить более детальную версию диаграммы:
В протоколе есть еще много более интересных деталей. Например, вы можете взглянуть на спецификацию HTTP и WebSocket [16].
Кроме того, хотя спецификация сильно фокусируется на взаимодействии между сервером и приложением, 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 очень скоро достигнет паритета функций с традиционной синхронной/WSGI экосистемой.
На самом деле, все больше и больше людей строят и улучшают проекты на основе ASGI. Очевидно, это серверы и веб-фреймворки, но также есть middleware и ориентированные на продукт приложения, такие как Datasette [28].
Ниже несколько примеров компонентов, не являющихся веб-феймворками, которые меня интересуют:
Восхитительно наблюдать, что экосистема успешно развивается, однако, мне лично было трудно не отставать от изменений.
Именно поэтому я создал awesome-asgi [6]. Я надеюсь, что это поможет всем идти в ногу со всеми удивительными вещами, которые происходят в мире ASGI. (И видя, что он почти достиг 100 звезд за несколько дней, у меня есть ощущение, что действительно была необходимость собрать в одном месте информацию о ресурсах про ASGI.)
Хотя это может выглядеть как подробности реализации, я уверен, что ASGI заложил основы для новой эры в веб-разработке на Python.
Если вы хотите узнать больше о ASGI, ознакомьтесь с различными публикациями [32] (статьями и выступлениями), перечисленными в awesome-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
Нажмите здесь для печати.