Вебсокеты на FastAPI: Реализация простого чата с комнатами за 20 минут
- воскресенье, 23 февраля 2025 г. в 00:00:05
В ближайшее время я планирую опубликовать большую статью на Хабре, где подробно разберу разработку анонимного чата в формате Telegram MiniApp. Мы создадим сервис для общения тет-а-тет, который будет работать внутри Telegram и обеспечивать мгновенный обмен сообщениями.
Технологический стек проекта:
FastAPI + WebSocket – для реального времени и обмена сообщениями
Redis – для быстрого поиска и соединения собеседников
PostgreSQL – для хранения сообщений и информации о пользователях
Vue 3 + Pinia – для удобного и отзывчивого интерфейса
Telegram Mini Apps API – чтобы встроить чат прямо в Telegram
Кроме технического разбора, в статье я также затрону вопрос монетизации. Одним из самых эффективных способов монетизации Telegram MiniApp является реклама. Это позволяет оставить сервис бесплатным для пользователей, а разработчику — зарабатывать на популярности проекта.
В качестве рекламной платформы буду рассматривать RichAds – они предлагают отличные инструменты для интеграции рекламы в Telegram MiniApps, что делает их логичным выбором для такого проекта.
Но прежде чем погружаться в разработку MiniApp, давайте разберёмся с основами. В этой статье мы познакомимся с веб-сокетами, узнаем, как они работают, и научимся реализовывать их на FastAPI.
Это лишь первая часть большого проекта, на который я буду ссылаться в будущем материале, чтобы сделать его более структурированным и удобным для чтения.
Итак, что такое WebSocket и как он поможет нам в разработке чата? Давайте разбираться.
Представьте себе телефонный звонок: вы набираете номер (устанавливаете соединение) один раз, и после этого можете свободно разговаривать, не набирая номер заново для каждой фразы. WebSocket работает похожим образом – это технология, которая создает постоянный "канал связи" между браузером пользователя и сервером, позволяя им обмениваться сообщениями в реальном времени.
Давайте разберемся с терминами:
Клиент – это то, чем пользуется человек: веб-страница в браузере, приложение на телефоне или компьютере
Сервер – это удаленный компьютер, который хранит и обрабатывает данные нашего приложения
Постоянное соединение: как телефонный звонок – установили связь один раз и общаетесь
Быстрая работа: не тратится время на повторное соединение
Двусторонняя связь: и клиент, и сервер могут начать общение первыми
Экономия ресурсов: служебная информация передается только в начале соединения
Представьте, что вы отправляете письма:
Пишете сообщение и отправляете его на сервер
Сервер сохраняет письмо
Другие участники чата должны постоянно проверять "почтовый ящик" (делать запросы к серверу)
Только после проверки они увидят новое сообщение
Даже если проверка происходит автоматически (например, через AJAX), всё равно приходится постоянно "заглядывать в почтовый ящик", что создает лишнюю нагрузку.
Недостатки:
Сервер устает от постоянных проверок
Сообщения доходят с задержкой
Тратится много интернет-трафика
Теперь представьте групповой звонок:
Все участники подключаются к общему разговору
Когда кто-то говорит, все сразу это слышат
Не нужно постоянно проверять, есть ли новые сообщения
Общение происходит мгновенно
Преимущества:
Сообщения приходят моментально
Сервер меньше нагружен
Экономится интернет-трафик
Настоящее общение в реальном времени
Такой подход отлично работает не только в чатах, но и в онлайн-играх, совместных редакторах документов и везде, где нужна мгновенная реакция на действия пользователей.
Дальше мы создадим простой чат на FastAPI, чтобы увидеть, как это работает на практике. Для простоты примера мы сделаем и серверную, и клиентскую части в одном приложении, хотя в реальном чате "Тет-а-Тет" интерфейс будет отдельным приложением на VueJS3.
Сегодня мы создадим полноценное FullStack-приложение – групповой чат, в котором пользователи смогут:
Создавать комнаты для общения
Подключаться к существующим комнатам
Обмениваться сообщениями в реальном времени
Все участники, находящиеся в одной комнате, мгновенно будут получать новые сообщения без необходимости обновлять страницу.
Для реализации проекта мы будем использовать современные и удобные инструменты:
Python + FastAPI + WebSockets – для серверной части и организации реального времени
JavaScript – для установки соединений по веб-сокетам и динамики на странице
TailwindCSS – для быстрой и удобной стилизации интерфейса
HTML + Jinja2 – для рендеринга страниц с данными
Amvera Cloud – для быстрого и простого деплоя
1. Серверная часть (Backend)
Разработка класса для управления веб-сокетами
Создание эндпоинта для обработки подключений через WebSocket
Реализация маршрутов (эндпоинтов) для рендеринга HTML-страниц
2. Клиентская часть (Frontend)
Разработка двух HTML-страниц
Добавление JavaScript-логики для подключения к WebSocket
Реализация динамического обновления интерфейса
3. Деплой проекта
Групповой чат бессмыслен без доступа извне, поэтому в завершение мы развернём проект в интернете.
Для деплоя будем использовать Amvera Cloud – сервис, который позволяет развернуть FastAPI-приложение буквально за пару минут. Плюсы этого решения:
Автоматическое HTTPS-сертификаты
Бесплатное доменное имя
Поддержка вебхуков (например, для интеграции с Telegram-ботами)
Таким образом, по итогам работы у нас получится готовый, развернутый и рабочий групповой чат, который можно использовать, тестировать и расширять.
Начнем с подготовки проекта. Напоминаю, что писать мы будем на Python фреймворке FastAPI, который отлично подходит для реализации WebSocket благодаря своей асинхронной природе и простому API.
Открываем IDE и создаем новый проект
Создаем файл requirements.txt и заполняем его следующим образом:
fastapi==0.115.8 # Сам веб-фреймворк
websockets==15.0 # Библиотека для работы с WebSocket
uvicorn==0.34.0 # ASGI сервер для запуска приложения
jinja2==3.1.5 # Шаблонизатор для рендеринга HTML
Устанавливаем библиотеки:
pip install -r requirements.txt
Подготовим структуру проекта:
my_chat_project/
├── requirements.txt # Файл зависимостей проекта
├── app/ # Главная директория приложения
│ ├── templates/ # Директория с HTML-шаблонами
│ │ ├── home.html # Шаблон домашней страницы
│ │ └── index.html # Основной шаблон приложения
│ ├── static/ # Директория со статическими файлами (JS, CSS)
│ │ └── index.js # JavaScript для index.html
│ ├── api/ # Директория с API-роутами
│ │ ├── router_page.py # Роуты для страниц (HTML)
│ │ └── router_socket.py # Роуты для WebSocket-соединений
│ └── main.py # Основной файл приложения (запуск)
Основную логику серверной части нашего приложения мы опишем в файле router_socket.py. Этот файл будет отвечать за управление WebSocket-соединениями и передачу сообщений между пользователями.
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
from typing import Dict
FastAPI — используется для создания веб-приложения.
WebSocket, WebSocketDisconnect — классы для работы с WebSocket-соединениями. Первый позволяет устанавливать соединения, второй — обрабатывать их разрыв.
Dict — тип данных для удобной аннотации структуры хранения подключений пользователей.
FastAPI использует маршрутизаторы (APIRouter) для организации эндпоинтов в логические блоки. В данном случае, создадим маршрутизатор с префиксом /ws/chat:
router = APIRouter(prefix="/ws/chat")
Этот маршрут будет обрабатывать все WebSocket-запросы, связанные с чатом.
Для удобного управления соединениями создадим класс ConnectionManager. Он будет отвечать за подключение и отключение пользователей, а также за рассылку сообщений.
Полный код класса:
class ConnectionManager:
def __init__(self):
# Хранение активных соединений в виде {room_id: {user_id: WebSocket}}
self.active_connections: Dict[int, Dict[int, WebSocket]] = {}
async def connect(self, websocket: WebSocket, room_id: int, user_id: int):
"""
Устанавливает соединение с пользователем.
websocket.accept() — подтверждает подключение.
"""
await websocket.accept()
if room_id not in self.active_connections:
self.active_connections[room_id] = {}
self.active_connections[room_id][user_id] = websocket
def disconnect(self, room_id: int, user_id: int):
"""
Закрывает соединение и удаляет его из списка активных подключений.
Если в комнате больше нет пользователей, удаляет комнату.
"""
if room_id in self.active_connections and user_id in self.active_connections[room_id]:
del self.active_connections[room_id][user_id]
if not self.active_connections[room_id]:
del self.active_connections[room_id]
async def broadcast(self, message: str, room_id: int, sender_id: int):
"""
Рассылает сообщение всем пользователям в комнате.
"""
if room_id in self.active_connections:
for user_id, connection in self.active_connections[room_id].items():
message_with_class = {
"text": message,
"is_self": user_id == sender_id
}
await connection.send_json(message_with_class)
Конструктор класса
self.active_connections — словарь, который хранит активные соединения, сгруппированные по комнатам (room_id).
В каждой комнате (room_id) подключенные пользователи хранятся в виде {user_id: WebSocket}.
connect
Принимает WebSocket-соединение, идентификатор комнаты (room_id) и пользователя (user_id).
Подтверждает соединение (websocket.accept()).
Добавляет WebSocket в self.active_connections.
disconnect
Удаляет WebSocket пользователя из self.active_connections.
Если в комнате не осталось пользователей, удаляет комнату.
broadcast
Отправляет сообщение всем пользователям в комнате.
Дополнительно добавляет флаг is_self, чтобы клиент мог визуально выделять свои сообщения.
Создадим экземпляр класса ConnectionManager, который будем использовать в дальнейшем:
manager = ConnectionManager()
Теперь создадим WebSocket-эндпоинт, который будет управлять подключениями пользователей и передачей сообщений в чате.
@router.websocket("/{room_id}/{user_id}")
async def websocket_endpoint(websocket: WebSocket, room_id: int, user_id: int, username: str):
await manager.connect(websocket, room_id, user_id)
await manager.broadcast(f"{username} (ID: {user_id}) присоединился к чату.", room_id, user_id)
try:
while True:
data = await websocket.receive_text()
await manager.broadcast(f"{username} (ID: {user_id}): {data}", room_id, user_id)
except WebSocketDisconnect:
manager.disconnect(room_id, user_id)
await manager.broadcast(f"{username} (ID: {user_id}) покинул чат.", room_id, user_id)
Маршрут
Эндпоинт принимает три параметра из URL: room_id, user_id, username.
Каждый пользователь подключается по URL /ws/chat/{room_id}/{user_id}.
Подключение
await manager.connect(...) — добавляет пользователя в список активных соединений.
await manager.broadcast(...) — уведомляет всех пользователей комнаты о новом участнике.
Прием и передача сообщений
Бесконечный цикл (while True) слушает входящие сообщения через websocket.receive_text().
После получения сообщения оно рассылается всем пользователям комнаты через manager.broadcast(...).
Отключение
Если соединение прерывается (WebSocketDisconnect), вызывается manager.disconnect(...).
Отправляется сообщение в чат о выходе пользователя.
В этом разделе мы создали серверную часть чата на WebSocket с использованием FastAPI. Мы:
Разобрались с импортами и маршрутизаторами.
Создали ConnectionManager для управления соединениями.
Написали WebSocket-эндпоинт для приема и рассылки сообщений.
В следующем разделе разберем, как подключить клиентскую часть и протестировать WebSocket-соединения.
Клиентская часть условно будет состоять из двух основных этапов:
Описание эндпоинтов для рендеринга HTML-страниц
Написание самой фронтенд-части: HTML + CSS + JS
В этом разделе мы опишем эндпоинты, которые будут обслуживать HTML-страницы. Реализуем их в файле app/router_page.py.
Сначала импортируем необходимые модули:
from fastapi import APIRouter, Request, Form
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse
import random
Разберёмся, зачем нам каждый импорт:
APIRouter – позволяет организовать маршрутизацию в приложении.
Request – объект запроса, который передаётся в шаблон при рендеринге.
Form – используется для обработки данных, переданных через HTML-форму.
Jinja2Templates – нужен для работы с HTML-шаблонами.
HTMLResponse – указывает, что эндпоинт возвращает HTML-страницу.
Random - для генерации случайного ID пользователя
Перед тем как описывать эндпоинты, создадим объект templates, который укажет, где хранятся HTML-шаблоны, и инициализируем роутер:
templates = Jinja2Templates(directory='app/templates')
router = APIRouter()
Теперь создадим два эндпоинта. Первый будет отвечать за рендеринг главной страницы, а второй — за страницу комнаты (нашего группового чата).
@router.get("/", response_class=HTMLResponse)
async def home_page(request: Request):
return templates.TemplateResponse("home.html", {"request": request})
Этот эндпоинт возвращает главную страницу home.html. Она будет содержать форму для входа в чат, которую разберём позже.
@router.post("/join_chat", response_class=HTMLResponse)
async def join_chat(request: Request, username: str = Form(...), room_id: int = Form(...)):
# Простая генерация user_id
user_id = random.randint(100, 100000)
return templates.TemplateResponse("index.html",
{"request": request,
"room_id": room_id,
"username": username,
"user_id": user_id}
)
Этот эндпоинт выполняет несколько задач:
Получает username и room_id из формы (с помощью Form(...)).
Генерирует случайный идентификатор пользователя в диапазоне от 100 до 100000
Возвращает HTML-страницу index.html с переданными в шаблон параметрами:
room_id – ID комнаты, куда заходит пользователь.
username – имя пользователя.
user_id – сгенерированный идентификатор пользователя.
В FastAPI существует несколько способов передавать данные в эндпоинт. В данном случае Form(...) указывает, что параметры username и room_id передаются через HTML-форму методом POST. Это удобно для обработки данных, введённых пользователем на веб-странице.
Если бы мы передавали данные в URL (как параметры запроса), нам пришлось бы использовать Query(...), а если бы передавали JSON – Body(...).
На этом этапе у нас готовы эндпоинты для работы с HTML-страницами. В следующем разделе мы реализуем фронтенд: создадим HTML-шаблоны, стили и подключим WebSocket.
Теперь, когда у нас есть серверная часть и эндпоинты для рендеринга страниц, перейдём к созданию пользовательского интерфейса. Наша клиентская часть будет включать:
HTML – для структуры страниц,
CSS (TailwindCSS) – для стилизации,
JavaScript – для обработки событий и работы с WebSocket.
Мы создадим два основных HTML-файла:
home.html – главная страница с формой входа в чат.
index.html – страница чата, где будет происходить общение в реальном времени.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Вход в чат</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 flex flex-col items-center justify-center min-h-screen p-4">
<h1 class="text-3xl font-bold mb-6">Добро пожаловать в чат</h1>
<form action="/join_chat" method="post" class="bg-white p-6 rounded-lg shadow-md w-full max-w-md">
<label class="block text-gray-700">Введите ваше имя:</label>
<input type="text" name="username" required
class="w-full p-2 border border-gray-300 rounded-lg mt-2 focus:ring-2 focus:ring-blue-500">
<label class="block text-gray-700 mt-4">Введите ID комнаты:</label>
<input type="number" name="room_id" required min="1"
class="w-full p-2 border border-gray-300 rounded-lg mt-2 focus:ring-2 focus:ring-blue-500">
<button type="submit" class="w-full bg-blue-500 text-white px-4 py-2 rounded-lg mt-4 hover:bg-blue-600">Войти в
чат
</button>
</form>
</body>
</html>
Этот шаблон делает следующее:
Выводит заголовок с приветствием.
Показывает форму входа, состоящую из двух полей:
Имя пользователя (username) – обычное текстовое поле.
ID комнаты (room_id) – поле для ввода числового идентификатора.
После нажатия на кнопку "Войти в чат", данные отправляются методом POST на сервер (/join_chat).
Почему просто ввод ID комнаты?
В боевых проектах можно было бы сделать выбор комнаты из списка, но в нашем случае проще дать пользователю возможность самому ввести ID. Если комната существует – он подключится, если нет – создаст новую.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Чат на WebSocket - Комната {{ room_id }}</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 flex flex-col items-center p-4">
<h1 class="text-2xl font-bold mb-4">Чат на WebSocket - Комната {{ room_id }}</h1>
<!-- Скрытый элемент для хранения данных комнаты -->
<div id="room-data"
data-room-id="{{ room_id }}"
data-username="{{ username }}"
data-user-id="{{ user_id }}"
class="hidden">
</div>
<!-- Область сообщений -->
<div id="messages"
class="w-full max-w-lg h-96 overflow-y-auto border border-gray-300 bg-white p-4 rounded-lg shadow-md">
</div>
<!-- Поле ввода и кнопка -->
<div class="flex mt-4 w-full max-w-lg">
<input id="messageInput"
type="text"
placeholder="Введите сообщение"
class="flex-1 p-2 border border-gray-300 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-blue-500"/>
<button onclick="sendMessage()"
class="bg-blue-500 text-white px-4 py-2 rounded-r-lg hover:bg-blue-600">Отправить
</button>
</div>
<script src="/static/index.js"></script>
</body>
</html>
Этот шаблон создаёт интерфейс чата:
Выводит заголовок с номером комнаты ({{ room_id }}).
Скрытый блок #room-data
Хранит данные, переданные сервером (room_id, username, user_id).
Сделан скрытым с помощью class="hidden".
В будущем JavaScript будет извлекать эти данные.
Область сообщений (#messages) – здесь будут появляться сообщения пользователей.
Поле ввода и кнопка "Отправить"
Поле (#messageInput) для ввода текста.
Кнопка onclick="sendMessage()" отправляет сообщение.
Подключение index.js
В файле /static/index.js будет описана логика взаимодействия с WebSocket.
Зачем скрытый блок #room-data?
Передавать серверные переменные напрямую в JS-код не всегда удобно. С Jinja2 их можно вывести в HTML и затем прочитать их в JavaScript.
Мы используем TailwindCSS, который подключается через CDN:
<script src="https://cdn.tailwindcss.com"></script>
Это позволяет нам писать компактный и гибкий CSS-код прямо в HTML, например:
<body class="bg-gray-100 flex flex-col items-center p-4">
Почему TailwindCSS?
Избавляет от необходимости писать кастомные CSS-файлы.
Позволяет быстро стилизовать элементы.
Хорошо подходит для прототипирования.
Пользователь заходит на главную страницу сайта (/).
Вводит имя и ID комнаты.
Нажимает "Войти в чат" – запрос отправляется на /join_chat.
Сервер возвращает index.html с переданными в него параметрами.
Страница загружается, а JS-код начинает работу с WebSocket.
Мы подготовили интерфейс, но пока чат не умеет отправлять сообщения. В следующем разделе реализуем JavaScript-логику, которая позволит подключаться к WebSocket, отправлять и получать сообщения.
Теперь, когда у нас есть интерфейс, пришло время добавить логику для обмена сообщениями в реальном времени. Мы будем использовать WebSocket для связи между клиентом и сервером.
Хочу, чтобы у вас сложилось правильное понимание: реализация WebSocket, а особенно чатов, на стороне бэкенда — не такая уж сложная задача.
Для нас, бэкенд-разработчиков, достаточно написать несколько десятков строк кода, чтобы создать WebSocket-соединение, обрабатывать подключение клиентов и пересылать сообщения всем участникам чата, комнаты или, например, онлайн-игры.
Но если кому и приходится действительно попотеть, так это фронтенд-разработчикам.
Почему?
Помимо визуальной составляющей интерфейса, им нужно правильно установить соединение с сервером.
Обрабатывать события WebSocket: подключение, отключение, получение и отправку сообщений.
Организовать удобное и динамическое отображение сообщений в режиме реального времени.
Конечно, эта работа не сверхсложная, но основные трудности при работе с WebSocket чаще возникают именно на фронтенде.
Далее я покажу на простом примере, как реализовать WebSocket-соединение на JavaScript и как с ним работать на клиентской стороне.
Дальнейший код описываем в файле app/static/index.js.
Вначале необходимо получить данные о пользователе и комнате. Так как мы передавали их через Jinja2 в скрытом HTML-блоке (#room-data), извлечём их с помощью JavaScript:
// Получаем данные из скрытого элемента
const roomData = document.getElementById("room-data");
const roomId = roomData.getAttribute("data-room-id");
const username = roomData.getAttribute("data-username");
const userId = roomData.getAttribute("data-user-id");
Эти данные понадобятся нам для установки WebSocket-соединения.
Создадим соединение с сервером:
const ws = new WebSocket(`ws://localhost:8000/ws/chat/${roomId}/${userId}?username=${username}`);
На этапе деплоя на сервис Amvera Cloud нам предстоит заменить эту ссылку на боевой https домен.
Что происходит здесь?
Мы создаём WebSocket-соединение по адресу ws://localhost:8000/ws/chat/.
В URL передаём ID комнаты (roomId), ID пользователя (userId) и имя пользователя (username).
Сервер использует эти данные, чтобы идентифицировать подключение.
Дополнительно, мы добавляем обработчики событий, чтобы отслеживать состояние соединения:
ws.onopen = () => {
console.log("Соединение установлено");
};
ws.onclose = () => {
console.log("Соединение закрыто");
};
Когда сервер отправляет сообщение, срабатывает обработчик onmessage. Разбираем JSON-данные и добавляем их в область чата:
ws.onmessage = (event) => {
const messages = document.getElementById("messages");
const messageData = JSON.parse(event.data);
const message = document.createElement("div");
// Определяем стили в зависимости от отправителя
if (messageData.is_self) {
message.className = "p-2 my-1 bg-blue-500 text-white rounded-md self-end max-w-xs ml-auto";
} else {
message.className = "p-2 my-1 bg-gray-200 text-black rounded-md self-start max-w-xs";
}
message.textContent = messageData.text;
messages.appendChild(message);
messages.scrollTop = messages.scrollHeight; // Автопрокрутка вниз
};
Разбор кода:
JSON.parse(event.data) – преобразует JSON-строку в объект.
Если is_self == true, значит сообщение отправил текущий пользователь, и оно отображается справа (синим цветом).
Иначе – сообщение от другого пользователя, и оно отображается слева (серым цветом).
Автопрокрутка (messages.scrollTop = messages.scrollHeight) – чтобы новые сообщения всегда были видны.
Создадим функцию для отправки сообщений:
function sendMessage() {
const input = document.getElementById("messageInput");
if (input.value.trim()) {
ws.send(input.value);
input.value = '';
}
}
Как это работает?
Берём текст из поля ввода (#messageInput).
Если сообщение не пустое, отправляем его через ws.send().
Очищаем поле ввода.
Дополнительно, чтобы отправлять сообщения по Enter, добавляем обработчик события:
document.getElementById("messageInput").addEventListener("keypress", (e) => {
if (e.key === "Enter") {
sendMessage();
}
});
Теперь пользователь может нажимать Enter, и сообщение будет отправляться автоматически.
Подключаемся к WebSocket-серверу при загрузке страницы.
Ждём входящие сообщения и отображаем их в окне чата.
При отправке сообщения пользователь вводит текст и нажимает кнопку или Enter.
Сервер передаёт сообщение всем участникам комнаты.
Чат обновляется в реальном времени без перезагрузки страницы.
Благодаря WebSocket и небольшому количеству JavaScript-кода у нас получился полноценный чат, работающий в режиме реального времени.
Теперь нам остается настроить main-файл (файл запуска) и можно начинать тестировать наше веб-приложение.
На этом этапе нам осталось выполнить финальные настройки и запустить наше приложение.
Работать будем с файлом app/main.py, где реализуем три ключевые задачи:
Инициализируем FastAPI-приложение
Добавим обработку статических файлов (например, JS, CSS)
Зарегистрируем все маршруты (роуты)
Добавляем нужные импорты и описываем базовую конфигурацию:
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from app.api.router_page import router as router_page
from app.api.router_socket import router as router_socket
app = FastAPI()
# Подключаем папку со статическими файлами
app.mount('/static', StaticFiles(directory='app/static'), 'static')
# Регистрируем маршруты
app.include_router(router_socket)
app.include_router(router_page)
Так как наше веб-приложение само отвечает за рендеринг HTML-страниц, CORS-настройки нам не требуются.
Для старта используем uvicorn — это легковесный ASGI-сервер, который мы установили ранее.
Базовая команда:
uvicorn main:app
По умолчанию сервер поднимется на http://127.0.0.1:8000.
Если нужно настроить параметры запуска, добавляем флаги:
--host 0.0.0.0 – делает сервер доступным из внешней сети
--port 8005 – указывает конкретный порт
--reload – включает автоперезапуск при изменении кода (полезно в разработке)
Пример: Запуск на порту 8005 с автообновлением кода
uvicorn main:app --port 8005 --reload
После успешного запуска сервер готов к работе, и мы можем подключаться к чату! 🎉
Ниже представлен небольшой скринкаст, демонстрирующий работу приложения (3 пользователя в одной комнате):
Всё это замечательно, но общаться в чате с самим собой не так уж увлекательно. Было бы здорово сделать этот чат доступным для всех желающих. И в этом нам поможет сервис Amvera Cloud.
Я выбрал этот сервис, потому что он отличается простотой развертывания приложений. Вы сами можете убедиться в этом. Чтобы развернуть приложение, похожее на наше, достаточно выполнить всего несколько простых шагов:
Зарегистрироваться на сервисе.
Нажать на кнопку «Создать проект».
Загрузить файлы проекта на сервис. Это можно сделать как через интерфейс на сайте, так и с помощью команд Git.
На сайте заполнить необходимые конфигурации (например, выбрать язык программирования для проекта или указать команду для его запуска).
Прикрепить к проекту бесплатное https-доменное имя или зарегистрировать собственное.
Процесс деплоя занимает всего пару минут. Кроме того, каждый новый пользователь получает бонус в размере 111 рублей на основной баланс. Выбор сервиса очевиден.
Выполним деплой.
Регистрируемся на сайте Amvera Cloud, если регистрации ещё не было
Кликаем на "Приложения"
Затем выбираем "Создать приложение" и далее следуем пошаговой инструкции.
Далее, чтобы активировать бесплатный https-домен, нам необходимо перейти в созданный проект, затем перейти на вкладку "Домены" и активировать бесплатный домен, как показано ниже:
Теперь нам нужно внести некоторые изменения в файл static/index.js. В частности, мы должны заменить строку подключения к веб-сокетам.
Было:
const ws = new WebSocket(`ws://localhost:8000/ws/chat/${roomId}/${userId}?username=${username}`);
Стало:
const ws = new WebSocket(`wss://easysocketfastap-yakvenalex.amvera.io/ws/chat/${roomId}/${userId}username=${username}`);
Обратите внимание, что мы не только заменили ссылку на доменное имя, но и исправили формат с ws на wss. Будьте внимательны!
Теперь осталось перезаписать файл index.js в Amvera и пересобрать проект.
Работающий код можно посмотреть тут: https://easysocketfastap-yakvenalex.amvera.io. Полный исходный код проекта, как и прочий эксклюзивный контент, который я не публикую на Хабре вы найдете в моем бесплатном телеграмм канале "Легкий путь в Python".
К слову, я разобрал проект, о котором сегодня шла речь, в своём получасовом видео, доступном на YouTube и RuTube.
Сегодняшний материал – это методическая основа, которая подготовит вас к разбору более сложного и масштабного проекта: Telegram-бота с MiniApp для анонимного чата "Тет-а-Тет". В следующей статье мы детально разберём этот проект, а знания, полученные здесь, помогут вам легче его освоить.
Я старался сделать материал максимально доступным и понятным, чтобы даже сложные темы стали проще. Если эта статья была вам полезна, не забудьте поддержать её лайком или комментарием – это мотивирует меня готовить ещё больше интересного контента.
А если хотите ещё больше практики и полезных разборов, подписывайтесь на мой Telegram-канал «Легкий путь в Python» – там уже почти 3000 участников, и мы регулярно обсуждаем интересные темы по разработке.
На этом пока всё. До встречи в следующих материалах!