WSGI и ASGI: sync vs async серверы
Когда пишешь веб-приложение на Python, между твоим кодом и HTTP-клиентом стоит веб-сервер. Чтобы они могли общаться, нужен стандартный интерфейс. WSGI (Web Server Gateway Interface) - синхронный стандарт с 2003 года. ASGI (Async Server Gateway Interface) - современный async-стандарт. В этом уроке - что они делают, чем отличаются и почему для нового кода обычно выбирают ASGI.
Зачем нужен интерфейс
Веб-сервер (gunicorn, uvicorn) принимает HTTP-запросы, парсит их и должен передать приложению. Приложение обрабатывает и возвращает response. Без стандарта каждое приложение нужно было бы вручную адаптировать под каждый сервер.
WSGI/ASGI - это контракт: какие функции вызывать, что передавать, что получать. Приложение реализует контракт, сервер вызывает по нему.
WSGI - синхронный стандарт
Минимальное WSGI-приложение:
def app(environ, start_response):
start_response("200 OK", [("Content-Type", "text/plain")])
return [b"Hello, World!"]
environ - dict с информацией о запросе (URL, headers, method). start_response - функция для статуса и headers. Возврат - iterable байтов.
Запускается через WSGI-сервер:
pip install gunicorn
gunicorn myapp:app
gunicorn принимает HTTP-запросы, конвертирует в WSGI вызовы, передаёт твоему приложению. На production - стандарт для синхронных Python-приложений (Django, Flask по умолчанию).
Frameworks на WSGI
# Flask - WSGI framework
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello"
# Django тоже WSGI (хотя поддерживает ASGI с 3.0)
WSGI-frameworks: Flask, Django (legacy mode), Pyramid, Bottle.
Проблема WSGI - синхронность
WSGI спроектирован под синхронную модель: один worker = один запрос за раз. Чтобы обработать 1000 одновременных connections, нужно 1000 workers - это много памяти.
def app(environ, start_response):
# Если внутри блокирующая операция (БД-запрос, HTTP-вызов),
# этот worker stuck до её завершения
data = slow_db_query() # блокирует
return [data.encode()]
Для I/O-heavy приложений (API gateway, веб-скрейпер, real-time chat) синхронная модель неэффективна.
ASGI - асинхронный стандарт
ASGI разработан в 2017 году для async Python. Поддерживает HTTP, WebSocket, lifespan events.
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!",
})
scope- информация о connection (как WSGI environ)receive- awaitable для чтения данных (тела запроса, WebSocket-сообщений)send- awaitable для отправки
Запускается через ASGI-сервер:
pip install uvicorn
uvicorn myapp:app
uvicorn - быстрый ASGI-сервер. Альтернативы: hypercorn, daphne. Именно uvicorn мы кладём в контейнер в уроке про Docker и CI.
Frameworks на ASGI
# FastAPI - ASGI framework
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def hello():
return {"message": "Hello"}
# Starlette - lightweight ASGI
# Django 3.0+ поддерживает оба
ASGI-frameworks: FastAPI, Starlette, Quart, Sanic, Django 3.0+ (опционально).
Главное преимущество ASGI
ASGI обрабатывает много параллельных connections одним worker'ом через event loop. Для I/O-bound нагрузки 1 ASGI worker может заменить 100 WSGI workers.
@app.get("/data")
async def get_data():
# Все три запроса параллельно - не блокируют worker
users, orders, stats = await asyncio.gather(
fetch_users(),
fetch_orders(),
fetch_stats(),
)
return {"users": users, "orders": orders, "stats": stats}
Это особенно важно для microservices - сервис делает много вызовов к другим сервисам, и ASGI справляется без огромного количества workers.
Сравнение производительности
| Сценарий | WSGI (gunicorn) | ASGI (uvicorn) |
|---|---|---|
| CPU-bound, простой | Лучше | Сравнимо |
| I/O-bound, много API | Хуже (много workers) | Лучше (один worker) |
| WebSocket | Не поддерживает | Нативно |
| Простота кода | Привычнее | async/await требует понимания |
| Эко-система | Зрелая (Django, Flask) | Растёт быстро (FastAPI) |
Для нового backend в 2026 году обычно выбирают ASGI (FastAPI). Для существующих Django-проектов часто остаются на WSGI.
ASGI поддерживает WebSocket
async def app(scope, receive, send):
if scope["type"] == "websocket":
await send({"type": "websocket.accept"})
while True:
event = await receive()
if event["type"] == "websocket.receive":
text = event.get("text", "")
await send({"type": "websocket.send", "text": f"Echo: {text}"})
elif event["type"] == "websocket.disconnect":
break
WSGI вообще не поддерживает WebSocket - нужны хаки или отдельный сервер. ASGI обрабатывает в одном протоколе.
Развёртывание - workers и async
WSGI: запускаешь N процессов (workers), каждый обрабатывает один запрос за раз:
gunicorn myapp:app --workers 4
ASGI: один процесс может обрабатывать тысячи concurrent connections. Для CPU-параллельности всё равно нужны несколько workers:
uvicorn myapp:app --workers 4
Но каждый worker - это event loop, не один-запрос-за-раз.
Mixed - WSGI приложение в ASGI
Можно запустить WSGI-приложение через ASGI-сервер через WsgiToAsgi adapter:
from asgiref.wsgi import WsgiToAsgi
from flask import Flask
flask_app = Flask(__name__)
asgi_app = WsgiToAsgi(flask_app)
# Теперь можно запускать через uvicorn:
# uvicorn myapp:asgi_app
Это compat-mode - не даёт async-преимуществ внутри Flask-handler'ов, но позволяет, например, использовать ASGI-middleware.
Stack изнутри FastAPI
HTTP request
↓
uvicorn (ASGI server) - парсит HTTP, создаёт scope/receive/send
↓
Starlette (ASGI framework) - routing, request/response
↓
FastAPI (Starlette + Pydantic) - validation, OpenAPI docs
↓
Твой код (async def handler)
Слои чётко разделены. Можно теоретически использовать каждый слой отдельно, но обычно работаешь только с FastAPI.
Middleware
В ASGI middleware - функция-обёртка вокруг app:
class LoggingMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
if scope["type"] == "http":
print(f"Request: {scope['method']} {scope['path']}")
await self.app(scope, receive, send)
# Применение
app = FastAPI()
app.add_middleware(LoggingMiddleware)
WSGI middleware похожий, но синхронный.
Встроенные middleware в FastAPI: CORS, GZip, Trusted Host, Session, etc.
Lifespan events
ASGI поддерживает startup/shutdown события:
from fastapi import FastAPI
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
print("Starting up")
app.state.db = await connect_db()
yield
# Shutdown
print("Shutting down")
await app.state.db.close()
app = FastAPI(lifespan=lifespan)
Удобно для setup ресурсов (DB pools, кеши) и cleanup. В WSGI такого стандарта нет - всё через init модуля.
Когда WSGI vs ASGI
Используй WSGI:
- Существующий Django проект без async needs
- Простой синхронный код без heavy I/O
- Команда не знакома с async
Используй ASGI:
- Новый проект backend в 2026
- Много API-вызовов (microservices)
- WebSocket, Server-Sent Events
- Real-time приложения
- Хочется FastAPI с Pydantic
Распространённые ошибки
1. Blocking sync в ASGI handler
@app.get("/data")
async def bad():
time.sleep(5) # БЛОКИРУЕТ event loop
requests.get("...") # БЛОКИРУЕТ (синхронный requests)
Внутри async-handler'а нельзя вызывать blocking sync. Используй await asyncio.sleep, aiohttp вместо requests. Или вынеси в executor:
result = await asyncio.to_thread(blocking_function, arg)
2. Mixing WSGI и ASGI в одном проекте без понимания
Можно через адаптеры, но overhead и сложность. Обычно проще выбрать один стек.
3. Не использовать lifespan для cleanup
@app.get("/")
def handler():
db = Database() # создаётся на каждый запрос - очень медленно
return db.query(...)
Используй lifespan для общих ресурсов (DB pool создаётся раз).
Сравнение с Go
В Go стандартная библиотека сразу async через горутины:
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello"))
})
http.ListenAndServe(":8080", nil)
Каждый запрос обрабатывается в отдельной горутине - параллельно из коробки. Нет WSGI/ASGI - Go's net/http это и сервер, и интерфейс.
В Python WSGI/ASGI нужны потому что Python был не designed для concurrency и история тащит синхронный код. Go это уровень выше для веба, Python требует выбора стека.
Мини-задание
- Минимальное WSGI-приложение:
# wsgi_app.py
def app(environ, start_response):
method = environ["REQUEST_METHOD"]
path = environ["PATH_INFO"]
start_response("200 OK", [("Content-Type", "text/plain")])
return [f"{method} {path}".encode()]
# Запуск
# pip install gunicorn
# gunicorn wsgi_app:app
- Минимальное ASGI-приложение:
# asgi_app.py
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 from ASGI",
})
# Запуск
# pip install uvicorn
# uvicorn asgi_app:app
- Адаптер WSGI → ASGI:
from asgiref.wsgi import WsgiToAsgi
def wsgi_app(environ, start_response):
start_response("200 OK", [("Content-Type", "text/plain")])
return [b"Hello"]
app = WsgiToAsgi(wsgi_app)
# Теперь можно: uvicorn module:app
Что дальше
Поняли что WSGI/ASGI это интерфейсы между сервером и приложением. В следующем уроке - FastAPI: современный ASGI-фреймворк, который мы будем использовать в этом и последующих уроках.