Что такое ASGI
Разбираем, что такое ASGI, как развернуть простое приложение и с какими ошибками при этом можно столкнуться.

Веб-приложения становятся все сложнее: пользователи ожидают мгновенного отклика, обновлений в реальном времени и поддержки интерактивных протоколов вроде WebSocket. Классический подход, основанный на синхронной модели обработки HTTP-запросов, уже не справляется с этими задачами.
ASGI (Asynchronous Server Gateway Interface) — логичное продолжение развития интерфейсов взаимодействия между Python-приложениями и серверами. Он позволяет создавать более гибкие сервисы, не отказываясь от опыта и наработок прошлого.
Эволюция от WSGI к ASGI
В 2003 году WSGI (Web Server Gateway Interface) стандартизировал способ взаимодействия Python-приложения с веб-сервером, положив конец разрозненности подходов и несовместимости фреймворков. Благодаря WSGI появилась возможность писать переносимый код, который одинаково хорошо работал с различными серверами.
Однако время шло, и требования к веб-приложениям росли. Асинхронность, которую активно развивали в JavaScript и Node.js, становилась все более необходимой и в Python-мире. Разработчики хотели обрабатывать несколько запросов одновременно, не блокируя их выполнение на время чтения из базы данных или внешнего API. Они стремились к масштабируемости, уменьшению времени отклика и поддержке новых протоколов — прежде всего WebSocket.
WSGI не справлялся с этими задачами. Он построен на синхронной модели исполнения, в которой каждый запрос обрабатывается последовательно. Даже если фреймворк или сама логика приложения могла бы быть асинхронной, WSGI-сервер все равно ожидал завершения обработки запроса, прежде чем перейти к следующему. Обходные пути вроде многопоточности или многопроцессности лишь частично решали проблему, создавая дополнительные сложности и накладные расходы.
ASGI был предложен как эволюционное продолжение WSGI. Он сохраняет знакомую концепцию точки входа в приложение, но меняет модель взаимодействия на асинхронную. Это значит, что теперь Python-приложение может приостановить выполнение, не блокируя сервер, и продолжить его позже — когда, например, придет ответ от базы данных или от клиента по WebSocket.
ASGI изначально задумывался как универсальный интерфейс, поддерживающий множественные протоколы. Помимо обычных HTTP-запросов он позволяет работать с WebSocket, lifespan, Server-Sent Events и в перспективе — с любыми другими двусторонними или событийными каналами. Это открывает путь к созданию действительно интерактивных, событийно-ориентированных приложений на Python.
При этом ASGI не требует полного отказа от старых решений. Он обеспечивает обратную совместимость: с помощью адаптеров можно запускать WSGI-приложения на ASGI-серверах, а также встраивать ASGI-компоненты в существующую инфраструктуру. Это позволяет внедрять новые технологии постепенно, без резкого перехода или переписывания всего приложения.
Как устроен ASGI под капотом
ASGI не библиотека, не сервер и не фреймворк. Это спецификация, формализованный контракт между сервером и Python-приложением. Она описывает, как сервер должен передавать запросы, а приложение — на них отвечать.
Сервер: первая точка входа
ASGI-сервер отвечает за прием входящих соединений, определение протокола и преобразование внешнего запроса в формат, который понимает ASGI-приложение.
Он создает scope — словарь с метаданными запроса: тип события, параметры соединения, путь запроса и т. д. Некоторые значения в scope представлены в виде массивов байтовых пар, как, например, заголовки:
[(b»host», b»example.com»), (b»user-agent», b»…»)]
— такой массив удобен для быстрой обработки и передачи низкоуровневых данных.
Этот scope передается приложению как часть вызова. Затем сервер инициирует асинхронный цикл взаимодействия: начинает отправлять события через функцию receive() и ждет ответа через send().
Сервер работает в фоне, управляя соединением, в то время как все управление логикой переходит к приложению.
ASGI-приложение
В ASGI-приложении все начинается с одного объекта — асинхронной функции (так называемой корутины) или класса, реализующего __call__.
async def app(scope, receive, send):
...
- scope — контекст соединения.
- receive() — корутина для получения события (например, HTTP-запрос или WebSocket-сообщение).
- send() — корутина для отправки события (например, HTTP-ответ или WebSocket-данные).
Таким образом, ASGI-приложение — это обработчик потока событий, способный приостанавливать выполнение, ждать внешних данных и продолжать работу, не блокируя другие соединения.
Протоколы и события
ASGI официально поддерживает три протокола, каждый из которых описывает отдельный тип взаимодействия между клиентом и приложением.
- HTTP — классический протокол, используемый в большинстве веб-приложений. Через него обрабатываются обычные GET- и POST-запросы, загрузка страниц, отправка форм и API-вызовы. Его событийный подход позволяет, например, стримить большой ответ по частям, не дожидаясь генерации всего содержимого.
- WebSocket — протокол для двустороннего общения в реальном времени. Приложение может не только принимать сообщения от клиента, но и инициировать отправку данных самостоятельно в любой момент. Это основа для чатов, игровых серверов и прочих интерактивных систем.
- lifespan — специальный протокол, через который приложение получает сигналы запуска и остановки. Он полезен для инициализации ресурсов, которые не нужно создавать на каждый запрос — например, подключение к базе данных, загрузка конфигурации или запуск фоновых задач.
Каждый протокол имеет свою структуру событий, но взаимодействие с приложением всегда происходит одинаково — через вызовы receive() и send(). Это делает интерфейс ASGI единообразным и расширяемым: можно добавлять новые протоколы (например, Server-Sent Events, gRPC или собственные), не ломая архитектуру или совместимость.
Что происходит при запуске ASGI-приложения
ASGI-приложение — это полноценный событийный механизм, который включает в себя несколько последовательных этапов:

1. Запуск ASGI-сервера
Сервер запускается, инициализирует необходимые ресурсы и начинает прослушивать указанный порт. Он готов принимать входящие TCP-соединения и взаимодействовать по интерфейсу ASGI.
2. lifespan.startup — событие запуска
Сразу после запуска сервер посылает приложению специальное событие lifespan.startup. Это часть протокола lifespan, который предназначен для управления жизненным циклом.
На этом этапе приложение может:
- установить подключение к базе данных;
- загрузить конфигурационные файлы;
- запустить фоновые задачи.
Все, что должно произойти один раз при запуске, делается здесь.
3. Ожидание входящих соединений
После успешного старта сервер входит в режим ожидания и начинает принимать новые соединения от клиентов.
Этот шаг начинает основной цикл обработки соединений, который повторяется для каждого нового клиента.
4. Прием соединения и определение протокола
Когда приходит новое соединение, сервер анализирует его и определяет, по какому протоколу будет идти дальнейшая работа.
5. Формирование scope
Для каждого запроса или соединения сервер формирует scope — словарь с параметрами взаимодействия:
- тип протокола;
- путь и метод запроса;
- заголовки;
- IP клиента;
- другие метаданные.
Этот scope станет контекстом для дальнейшей работы.
6. Вызов ASGI-приложения
Затем сервер вызывает приложение:
await app(scope, receive, send).
7. Обработка событий через receive()
Приложение получает данные по мере поступления событий от клиента. Для HTTP это будет тело запроса, для WebSocket — полученные сообщения.
receive() позволяет приложению читать входящие события асинхронно, не блокируя другие соединения.
8. Формирование ответа и send()
Приложение подготавливает ответ и отправляет его серверу через send(). Для HTTP это начало ответа (http.response.start) и тело (http.response.body), для WebSocket — события websocket.send.
Шаги №7 и №8 могут повторяться многократно при использовании постоянных соединений (например, WebSocket).
После завершения обработки соединения сервер возвращается к шагу №3 (ожидание входящих соединений), чтобы обслужить следующее соединение. Это и есть основной рабочий цикл ASGI-сервера.
9. lifespan.shutdown — завершение приложения
При получении сигнала завершения сервер отправляет приложению событие lifespan.shutdown.
На этом этапе закрываются подключения к БД, останавливаются фоновые задачи и освобождаются ресурсы.
На всем этом пути между сервером и конечным кодом могут находиться промежуточные слои — middleware. Подробнее разберем их в следующем разделе.
Middleware в ASGI
Middleware — это промежуточный слой между сервером и приложением, способный перехватывать, изменять и контролировать поток данных.
В контексте ASGI middleware работают с асинхронными вызовами, могут оборачивать как запросы, так и ответы, и при этом полностью сохраняют совместимость со спецификацией ASGI.
Иначе говоря, middleware — это адаптер: он получает scope, receive и send, может их модифицировать и затем либо передает управление дальше, либо обрабатывает запрос самостоятельно. Такой подход позволяет построить цепочку логики: одно middleware оборачивает другое, и каждое может влиять на входящие и исходящие данные — при этом не зная, что происходит внутри других компонентов.
Middleware решают задачи, которые не относятся напрямую к бизнес-логике, но важны для инфраструктуры приложения. Среди типичных применений:
- аутентификация и авторизация,
- логирование и трассировка,
- обработка исключений,
- сжатие или шифрование тела ответа,
- измерение времени ответа,
- добавление пользовательского контекста в scope,
- работа с кешем,
- заглушки (моки) в тестах.
Механизм middleware позволяет реализовать все это без изменения кода основного приложения. Каждое поведение изолировано, переиспользуемо и может быть активировано в нужной последовательности.
Как работает ASGI-middleware
Middleware реализуется по контракту ASGI-приложения: это вызываемый объект, принимающий три аргумента:
async def app(scope, receive, send): ...
Функциональный вариант
Самый простой способ реализации — функция, оборачивающая приложение:
def middleware_factory(app):
async def middleware(scope, receive, send):
# логика до — можно, например, засечь время
await app(scope, receive, send)
# логика после — можно залогировать результат
return middleware
Такой шаблон отлично подходит для задач вроде логирования, подсчета времени ответа или простой обработки исключений. Внутри middleware() можно модифицировать scope, обернуть receive() и send() или просто выполнить дополнительное действие до/после вызова app().
Классовый подход
Если требуется сохранять состояние, делить поведение на методы или просто поддерживать более масштабную архитектуру, используют классы:
class TimerMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
start = time.perf_counter()
await self.app(scope, receive, send)
duration = time.perf_counter() - start
print(f"Request took {duration:.2f} seconds")
Здесь мы измеряем, сколько времени заняло выполнение запроса. Это удобно, например, для мониторинга производительности.
Оборачивание receive() и send()
ASGI дает middleware доступ не только к scope, но и к каналам обмена сообщениями — receive() и send(). Это позволяет реализовывать тонкий контроль:
- фильтрацию или модификацию входящих и исходящих данных,
- буферизацию тела запроса,
- автоматическую сериализацию/десериализацию,
- дешифровку и валидацию данных,
- логирование сетевых событий в реальном времени.
Пример логирования тела запроса (HTTP):
class BodyLoggerMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
if scope["type"] != "http":
return await self.app(scope, receive, send)
body = b""
more_body = True
async def wrapped_receive():
nonlocal body, more_body
message = await receive()
if message["type"] == "http.request":
# Накопление тела запроса
body += message.get("body", b"")
more_body = message.get("more_body", False)
return message
await self.app(scope, wrapped_receive, send)
if not more_body:
print("Request body:", body.decode())
Мы оборачиваем receive() и сохраняем все тело запроса в переменную body, а затем логируем его. Такой подход позволяет перехватывать и анализировать данные, не нарушая поток выполнения приложения.
Контроль над потоком: прерывание запроса
Middleware может полностью перехватить запрос и вернуть собственный ответ, не передавая управление дальше. Это полезно, например, при авторизации:
class AuthMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
headers = dict(scope.get("headers") or [])
if b"x-token" not in headers:
await send({
"type": "http.response.start",
"status": 403,
"headers": [(b"content-type", b"text/plain")],
})
await send({
"type": "http.response.body",
"body": b"Forbidden",
})
return
await self.app(scope, receive, send)
Обратите внимание: мы не вызываем self.app(…), если условие не выполнено. Это означает, что дальнейшая цепочка middleware и приложение не будут вызваны вообще.
Порядок имеет значение
Middleware вызываются в том порядке, в котором они обернуты:
app = LoggerMiddleware(AuthMiddleware(app))
- LoggerMiddleware получает запрос первым и последним видит ответ.
- AuthMiddleware обрабатывает запрос дальше по цепочке.
- Только после всех middleware вызывается основное приложение.
Ответ идет в обратном порядке — от приложения. Это означает, что внешние middleware могут обернуть не только входящий, но и исходящий поток — например, изменить заголовки ответа, добавить cookie или зафиксировать ошибку.
Концепция Turtles all the way down
Иногда архитектурные концепции вдохновлены не только инженерной логикой, но и философией. Такова идея «turtles all the way down» — известная философская метафора, иллюстрирующая бесконечную вложенность.
В одном из вариантов рассказа:
— Земля держится на спинах слонов, — говорит ученый.
— А слоны стоят на гигантской черепахе, — добавляет слушатель.
— А под черепахой? — спрашивает ученый.
— Под ней другая черепаха.
— И под той?
— Там еще одна черепаха. И так до самого дна.
И хотя изначально это была ироничная иллюстрация бесконечной вложенности без конца, идея оказалась неожиданно живучей. В программировании она обрела новое значение: архитектура, в которой каждый уровень системы устроен одинаково, независимо от глубины. Каждый слой — такой же, как предыдущий. Черепахи на черепахах. Интерфейсы — те же. Логика — аналогична. И эта идея идеально описывает то, как работает стек ASGI.
Универсальность интерфейса
ASGI не проводит четкой границы между «приложением» и «middleware». Наоборот, оно утверждает, что всё есть приложение — достаточно лишь соблюдать протокол: функция или объект, принимающий scope, receive и send.
Это значит, что любой элемент можно обернуть как оболочку и при этом не задумываться, что именно внутри. Фреймворк, middleware, роутер, контроллер — они все говорят на одном языке и в любой момент могут быть подменены или обернуты другим компонентом.
Так появляется стек: верхний слой оборачивает нижний, добавляя поведение до и после или даже перехватывая выполнение. Каждый новый уровень получает те же аргументы и возвращает тот же результат, что и предыдущий.
Эта рекурсивность не только философски красива, но и практична. Поскольку компоненты совместимы между собой, их можно комбинировать, переиспользовать, тестировать по отдельности. Даже в тестах middleware можно запускать как отдельное приложение — ведь это все те же scope, receive, send.
Абстрактное равенство
В ASGI нет «высших» и «низших» уровней. Middleware не стоит выше приложения. Роутер не отличается по важности от логгера. Все компоненты функционально равны.
Такой подход упрощает инфраструктуру и снижает количество «особых случаев». Вы не думаете в терминах «это фреймворк», «это промежуточный слой», «это конечная точка» — вы просто компонуете приложения, которые вызываются одинаково и ведут себя предсказуемо.
Именно в этом — философия ASGI. Архитектура не просто технически продуманная. Она подчинена идее универсальности, в которой любое поведение можно выразить как приложение. Здесь нет магии, скрытой за абстракцией. Здесь только черепахи. И все они одинаковые.
Преимущества ASGI перед WSGI
Философская идея ASGI — в универсальности и самоподобии компонентов. Но за элегантной архитектурой скрывается и практическое превосходство.
Прежде всего — асинхронность. В отличие от WSGI, где каждое соединение обрабатывается синхронно и блокирующе, ASGI позволяет приостанавливать выполнение и обслуживать другие запросы. Это особенно важно при работе с базами данных, внешними API и другими источниками задержек.
Вторая ключевая особенность — поддержка двунаправленных протоколов. WSGI был создан исключительно для HTTP, а ASGI одинаково уверенно работает и с HTTP, и с WebSocket, и с другими типами соединений. Это делает возможной реализацию real-time сценариев, например: уведомления, чаты, стриминг и онлайн-мониторинг.
Наконец, большинство современных фреймворков (например, FastAPI, Starlette, Starlite) спроектированы именно под ASGI. Они используют возможности протокола по максимуму: асинхронность, типизацию, авто-документацию и расширяемость. При этом ASGI не отказывается от совместимости: синхронные обработчики по-прежнему поддерживаются, а WSGI-приложения можно адаптировать к новому окружению.
ASGI делает шаг вперед там, где WSGI уже не справляется. А как этот шаг воплощается на практике — мы рассмотрим в следующем разделе.
Как развернуть ASGI-приложение
Все начинается с самого приложения. В ASGI нет жестких рамок: можно обойтись простой функцией, а можно использовать фреймворки. Например, с помощью таких фреймворков, как FastAPI или Starlette, можно быстро собрать и развернуть как API, так и полноценный сайт с асинхронной логикой.
Предположим, у вас есть файл main.py, в котором определено приложение на FastAPI:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello, ASGI!"}
Это и есть ваше ASGI-приложение. Оно уже полностью готово к запуску. Остается решить, как и где его запускать. В мире ASGI для этого используется сервер, поддерживающий соответствующий протокол, например, Uvicorn. Его можно запускать прямо из командной строки, указав путь до объекта приложения:
uvicorn main:app
Однако в продакшене такой запуск непрактичен. Обычно приложение разворачивают в контейнере — это создает изолированное, управляемое окружение, которое можно масштабировать, разворачивать в разных средах и переносить без изменений. Самый распространенный инструмент для этого — Docker.
Создание Docker-контейнера начинается с описания в специальном файле — Dockerfile. В нем указывается базовый образ, установка зависимостей, копирование исходного кода и финальная команда для запуска приложения. Простейший пример Dockerfile может выглядеть так:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Этот контейнер после сборки будет запускать ASGI-приложение на порту 8000, готовое принимать запросы. Чтобы собрать образ и запустить контейнер, достаточно выполнить стандартные команды Docker:
docker build
docker run
Важно также удостовериться, что файл requirements.txt содержит все необходимые зависимости — например, fastapi и uvicorn.
На этом этапе приложение уже работает, но чтобы сервис был надежен и устойчив, стоит учитывать несколько дополнительных аспектов. Например, следует контролировать количество подключений и время жизни запросов, ограничивать потребление памяти и следить за тем, чтобы сервер корректно завершал работу при перезапуске. Если приложение масштабируется (а ASGI позволяет это делать эффективно), необходимо настроить балансировщик, например NGINX, и следить за корректной передачей заголовков и маршрутизацией трафика.
Иногда вместо чистого Uvicorn используется связка Gunicorn с UvicornWorker — это позволяет использовать привычные возможности Gunicorn для управления воркерами и процессов. В других случаях может использоваться Daphne, особенно если приложение тесно интегрировано с Django Channels. Но выбор сервера зависит скорее от предпочтений и требований проекта, чем от рекомендаций ASGI. Он одинаково хорошо поддерживается всеми этими решениями.
По мере усложнения инфраструктуры можно переходить к инструментам оркестрации вроде Docker Compose, Kubernetes или systemd. Но суть остается прежней: запуск осуществляется через ASGI-сервер, которому передается приложение. И все, что окружает его — контейнеры, прокси, логи, мониторинг — лишь помогает управлять этим процессом.
Инструменты для ASGI-разработки
ASGI-приложения открывают доступ к современным возможностям Python-разработки, но вместе с гибкостью приходят и новые задачи. Нужны инструменты, которые помогут управлять асинхронным поведением, тестировать код, отслеживать ошибки, удобно писать и развертывать сервисы. К счастью, за последние годы вокруг ASGI сложилась полноценная экосистема из зрелых и разнообразных инструментов, делающий работу с ASGI-компонентами простой и предсказуемой.
Разработка
При локальной работе удобно, чтобы приложение автоматически перезапускалось при изменении кода. Многие ASGI-серверы, включая Uvicorn, уже умеют работать в режиме автообновления — достаточно запустить их с флагом —reload. Внутри используется библиотека watchgod, которая отслеживает изменения файлов и перезапускает сервер без вмешательства пользователя. Это удобно на этапе разработки, но в продакшене от подобной автоматики, конечно, стоит отказаться.
Тестирование
На этапе тестирования особенно полезны две библиотеки: pytest-asyncio и httpx. Первая расширяет возможности pytest, позволяя писать асинхронные тесты с использованием async def. Вторая — это асинхронный HTTP-клиент, с помощью которого можно тестировать поведение приложения так же, как это делает настоящий клиент.
Управление жизненным циклом приложения
В рамках спецификации ASGI предусмотрены специальные события, которые сообщают приложению о старте и завершении работы. Обрабатывать их вручную необязательно: можно использовать библиотеку asgi-lifespan, которая корректно передает события старта и останова.
Логирование и отладка
В мире ASGI эти процессы ничем не отличаются от стандартных практик в Python. Можно использовать встроенный модуль logging, подключать сторонние системы вроде Sentry или Prometheus, выводить данные в консоль или файл. Главное — не забывать, что в асинхронной среде некоторые обработчики могут работать иначе: важно проверять, поддерживают ли они неблокирующую запись или интеграцию с asyncio.
Типичные ошибки при работе с ASGI
ASGI предоставляет широкие возможности, но требует внимательного отношения к деталям. Некоторые ошибки встречаются особенно часто. Ниже — несколько типичных ситуаций.
Блокирующий код в асинхронном контексте
ASGI работает поверх event loop, и все приложение, по сути, разделяет один поток исполнения. Если внутри async-функции выполнить обычную блокирующую операцию, она «заморозит» всю обработку — никакие другие запросы не смогут обрабатываться, пока не завершится эта операция.
Пример с проблемой:
@app.get("/")
async def slow_handler():
time.sleep(2) # Блокирует event loop на две секунды
return {"status": "done"}
Пример с решением:
@app.get("/")
async def slow_handler():
await asyncio.sleep(2) # Позволяет event loop работать дальше
return {"status": "done"}
Такая ошибка особенно часто возникает при использовании сторонних библиотек, которые не поддерживают asyncio. Если библиотека не предоставляет асинхронный интерфейс, лучше запускать ее в отдельном потоке через run_in_executor, чтобы не блокировать основной цикл.
Использование синхронных драйверов
Даже если вы не пишете sleep вручную, синхронный код может пробраться в приложение через базу данных, HTTP-клиенты, файловую систему и другие внешние источники.
Пример с проблемой:
@app.get("/users")
async def get_users():
users = sync_db.query_all() # Синхронный доступ к базе
return users
Пример с решением:
@app.get("/users")
async def get_users():
users = await async_db.fetch_all() # Асинхронный драйвер
return users
Используйте asyncpg, aiomysql, httpx, aiofiles и другие совместимые с asyncio библиотеки. Подключение синхронных клиентов нарушает масштабируемость и делает поведение приложения непредсказуемым под нагрузкой.
Ошибки в middleware
Middleware — мощный инструмент, позволяющий модифицировать поведение приложения: добавлять логирование, трассировку, обработку ошибок. Однако их реализация требует аккуратности: легко забыть вызвать await self.app(…), нарушить цепочку, проглотить исключение или изменить заголовки в неподходящий момент.
Пример с проблемой:
class SilentMiddleware:
async def __call__(self, scope, receive, send):
try:
await self.app(scope, receive, send)
except Exception:
pass # Ошибка скрыта, приложение молча «ломается»
Пример с решением:
class LoggingMiddleware:
async def __call__(self, scope, receive, send):
try:
await self.app(scope, receive, send)
except Exception as e:
logger.exception("Unhandled error")
raise
Важно соблюдать порядок подключения middleware: логирование — наружу, безопасность и авторизация — ближе к бизнес-логике.
Полезное от Selectel
Разработка современных веб-приложений на ASGI требует не только качественного кода, но и правильно настроенной инфраструктуры. Облачная платформа Selectel предоставляет все необходимое для комфортной разработки и запуска современных Python-проектов.
Вы можете развернуть приложение на виртуальных машинах или воспользоваться Managed Kubernetes для оркестрации контейнеров. Встроенные инструменты автоматического масштабирования помогают поддерживать производительность даже при высоких нагрузках.
Для хранения данных доступны различные варианты готовых решений, которые легко интегрируются в ваш проект. А если приложению важна скорость отклика для пользователей из разных регионов, можно дополнительно подключить CDN. Все компоненты инфраструктуры работают в единой экосистеме, что значительно упрощает развертывание и управление.
Заключение
ASGI — это современный стандарт, который меняет подход к созданию Python-приложений. Он открывает путь к асинхронным, масштабируемым и интерактивным веб-сервисам — от API до real-time систем. Если WSGI — это проверенная классика, то ASGI — выбор для проектов следующего поколения.