FastAPI WebSocket: уведомления в браузер

Сделаем маленькое FastAPI-приложение:.

Ниже — разделы про что мы соберем, когда FastAPI WebSocket уместен и подготовка, чтобы быстро понять устройство материала, практические ограничения и типовые точки отказа.


Что мы соберем

Сделаем маленькое FastAPI-приложение:

  • HTTP-страница открывается в браузере;
  • браузер подключается к /ws;
  • сервер принимает сообщения;
  • сервер отправляет уведомления клиенту;
  • несколько клиентов могут получать одно событие.

Это хороший мост между "я понял WebSocket на Python" и "я хочу встроить real-time в API"

Когда FastAPI WebSocket уместен

FastAPI WebSocket хорошо подходит, если:

  • backend уже написан на FastAPI;
  • нужны уведомления в админку;
  • нужно показать статус долгой задачи;
  • хочется отправлять события из Python-кода в браузер;
  • рядом уже есть REST API и авторизация;
  • не хочется поднимать отдельный Node.js real-time сервис.

Если вам нужен только один echo-сервер без FastAPI, можно начать с библиотеки websockets. Если у вас приложение на FastAPI, логичнее использовать встроенную поддержку WebSocket

Подготовка

Создайте проект:

mkdir fastapi-websocket-demo
cd fastapi-websocket-demo
python -m venv .venv

Активируйте окружение

macOS/Linux:

source .venv/bin/activate
.venv\Scripts\Activate.ps1

Установите зависимости:

pip install fastapi "uvicorn[standard]"

Первый WebSocket endpoint

Создайте main.py:

from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse

app = FastAPI()

html = """
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <title>FastAPI WebSocket</title>
  </head>
  <body>
    <h1>FastAPI WebSocket</h1>
    <div id="status">Подключаемся...</div>
    <ul id="messages"></ul>
    <form id="form">
      <input id="input" autocomplete="off" placeholder="Сообщение">
      <button>Отправить</button>
    </form>

    <script>
      const statusEl = document.querySelector('#status');
      const messagesEl = document.querySelector('#messages');
      const form = document.querySelector('#form');
      const input = document.querySelector('#input');

      const socket = new WebSocket('ws://localhost:8000/ws');

      socket.addEventListener('open', () => {
        statusEl.textContent = 'Соединение открыто';
      });

      socket.addEventListener('message', (event) => {
        const li = document.createElement('li');
        li.textContent = event.data;
        messagesEl.append(li);
      });

      socket.addEventListener('close', () => {
        statusEl.textContent = 'Соединение закрыто';
      });

      form.addEventListener('submit', (event) => {
        event.preventDefault();
        const text = input.value.trim();

        if (text && socket.readyState === WebSocket.OPEN) {
          socket.send(text);
          input.value = '';
        }
      });
    </script>
  </body>
</html>
"""

@app.get("/")
async def index():
    return HTMLResponse(html)

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()

    try:
        while True:
            message = await websocket.receive_text()
            await websocket.send_text(f"Сервер получил: {message}")
    except WebSocketDisconnect:
        print("Клиент отключился")

Запуск:

uvicorn main:app --reload

Откройте:

http://localhost:8000

Введите сообщение. Сервер должен вернуть ответ

Что делает accept

В FastAPI WebSocket-соединение нужно принять:

await websocket.accept()

Без этого сервер не подтверждает handshake. Это похоже на явное "да, я принимаю WebSocket-соединение"

Отправляем JSON

Текстовые сообщения хороши для старта, но в приложении удобнее JSON

Изменим серверный цикл:

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()

    try:
        while True:
            data = await websocket.receive_json()

            await websocket.send_json({
                "type": "reply",
                "text": f"Получил событие {data.get('type')}",
                "payload": data,
            })
    except WebSocketDisconnect:
        print("Клиент отключился")

На клиенте:

socket.send(JSON.stringify({
  type: 'lead_created',
  name: text
}));

И при получении:

const data = JSON.parse(event.data);

Так легче расширять протокол: notification, task_finished, lead_created, typing, error

Несколько подключенных клиентов

Для уведомлений часто нужно отправить событие всем открытым вкладкам админки. Сделаем простой manager

from fastapi import FastAPI, WebSocket, WebSocketDisconnect

class ConnectionManager:
    def __init__(self):
        self.active_connections: list[WebSocket] = []

    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)

    def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)

    async def broadcast(self, message: str):
        for connection in self.active_connections:
            await connection.send_text(message)

app = FastAPI()
manager = ConnectionManager()

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await manager.connect(websocket)

    try:
        while True:
            message = await websocket.receive_text()
            await manager.broadcast(f"Новое сообщение: {message}")
    except WebSocketDisconnect:
        manager.disconnect(websocket)

Откройте страницу в двух вкладках и отправьте сообщение из одной. Вторая тоже должна его увидеть

Уведомление из HTTP route

Теперь добавим HTTP endpoint, который рассылает событие:

@app.post("/notify")
async def notify():
    await manager.broadcast("Появилось новое уведомление")
    return {"ok": True}

Проверка:

curl -X POST http://localhost:8000/notify

Браузерные клиенты получат сообщение. Это уже очень похоже на реальную админку: REST-запрос создал событие, WebSocket доставил его открытым клиентам

Что важно для продакшена

Учебный manager хранит подключения в памяти одного процесса. Это нормально для урока, но не для масштабирования

Если процессов несколько, один процесс не знает о клиентах другого. Тогда нужны:

  • Redis pub/sub;
  • message broker;
  • отдельный real-time сервис;
  • sticky sessions, если архитектура это допускает;
  • продуманная схема каналов.

Для первого проекта не нужно начинать с распределенной архитектуры. Но знать ограничение важно

Авторизация

WebSocket тоже должен проверять пользователя. Возможные варианты:

  • cookie с сессией;
  • token в query string;
  • token в первом сообщении;
  • header, если клиент и библиотека это позволяют.

Нельзя оставлять /ws открытым, если через него идут приватные уведомления

Частые ошибки

WebSocketDisconnect

Это не всегда ошибка. Пользователь закрыл вкладку, интернет пропал, сервер перезапустился. Обрабатывайте отключение спокойно

Браузер не подключается к ws://localhost:8000/ws

Проверьте, что uvicorn запущен, путь /ws совпадает, порт 8000 верный

На HTTPS-домене WebSocket не работает

Используйте wss://, а не ws://. И проверьте proxy-настройки

Несколько workers теряют сообщения

In-memory список подключений живет внутри процесса. Для нескольких workers нужен общий слой доставки событий

Swagger не показывает WebSocket как обычный endpoint

Это нормально. WebSocket не является обычным REST route. Проверяйте его браузером, Postman или отдельным клиентом

Ответы на эти вопросы могут быть для вас полезными

FastAPI поддерживает WebSocket из коробки?

Да. В FastAPI есть WebSocket, а практические примеры есть в официальной документации

Можно ли отправлять JSON через FastAPI WebSocket?

Да. Есть методы receive_json и send_json. Для реальных событий это удобнее, чем просто строки

Почему manager из урока не подходит для большого продакшена?

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

Нужно ли ставить библиотеку websockets?

Для запуска через Uvicorn часто достаточно стандартных зависимостей FastAPI/Uvicorn, но в учебных и документационных примерах библиотека websockets может использоваться как клиентская или протокольная зависимость. Следуйте требованиям вашего окружения

Можно ли подключить React/Vue к FastAPI WebSocket?

Да. На frontend используется обычный браузерный WebSocket, а backend остается FastAPI

Что почитать дальше по WebSockets

Если вы собираете тему по шагам, рядом лучше открыть:

Оцените статью
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x