Почему в ботах телеги желательно использовать Webhook вместо Polling

cbe7089e0cd940839dc3f8027a5069f8.png

Привет, коллеги!

Сегодня у нас на повестке дня выбор между двумя технологиями: Polling и Webhook. Почему почему именно Webhook является go-to решением для большинства проектов?

Помните, как в начале 2010-х все разрабы активно юзали Polling? Это был золотой стандарт для многих мессенджер-ботов. Но технологии не стоят на месте. Webhook занял свое место, предлагая свои решения.

Polling

Polling, или опрос, — это процесс, при котором клиентский скрипт периодически отправляет запросы к серверу для проверки наличия новой инфы. В телеге это регулярный запрос к АПИ для получения новых обновлений или сообщений.

При увеличении нагрузки Polling может стать бутылочным горлышком. Такую проблемку можно решить увеличением интервала между запросами в моменты низкой активности и его уменьшение в пиковые часы.

Если бот обрабатывает состояния или сессии пользователей нужно учитывать, что при Polling состояние должно храниться на стороне сервера. Это означает необходимость использования БДшек и т.п

Постоянные запросы к серверу создают дополнительную нагрузку. Необходимо настраивать подходящий баланс, чтобы избежать чрезмерного трафика и потенциального блокирования вашего бота из-за слишком частых запросов. API в телеграме ожидаемо имеет ограничения на количество запросов в секунду. Необходимо убедиться, что бот не превышает эти лимиты.

Согласно документации максимальное количество сообщений, отправляемых ботом, ограничено примерно 30 сообщениями в секунду для обычных сообщений и около 20 сообщений в минуту для групповых чатов.

Если бот достигает этих лимитов, будут вылетать ошибки RetryAfter от API. Постоянные попытки повторной отправки сообщений, игнорируя ошибки API, могут привести к временной блокировке бота.

В python-telegram-bot (с версии 20), существует встроенный механизм BaseRateLimiter, который контролирует количество API запросов, совершаемых ботом за определенный временной интервал

Простейший скрипт Polling бота:

import time
import requests

TOKEN = "токен_бота"
URL = f"https://api.telegram.org/bot{TOKEN}/getUpdates"

def get_updates():
    response = requests.get(URL)
    return response.json()

def main():
    while True:
        updates = get_updates()
        if updates["result"]:
            # Обработка каждого нового сообщения
            for item in updates["result"]:
                print(f"Сообщение от пользователя: {item['message']['text']}")
        time.sleep(2)  # Задержка в 2 секунды между запросами

if __name__ == "__main__":
    main()

Бот каждые две секунды спрашивает у сервера Telegram, не пришло ли новых сообщений. В реальности тут конечно должна быть обработка ошибок, логирование и другие элементы

С Polling было связано множество трудностей. Во-первых, нагрузка на сервера. Каждый бот постоянно что то запрашивал у сервера. Представьте себе тысячи ботов, делающих это каждую секунду. Это было не только неэффективно с точки зрения трафика, но и могло вызывать задержки в доставке сообщений.

Во-вторых каждый раз, когда соединение прерывалось или сервер не отвечал, это могло привести к потере апдейтов. Приходилось тщательно продумывать механизмы повторных попыток и логирования, чтобы минимизировать потери данных.

Со временем, когда телега становилась всё более попсовой, разработчики начали искать более эффективные способы взаимодействия с API. И здесь появляются вебхуки. В отличие от Polling, где бот активно запрашивал данные, Webhook позволяет серверу телеграма самому отправлять обновления боту, как только они появляются.

Webhook

Webhook — это, по сути, колллбек. Это URL, который вы предоставляете телеграму, и по которому телеграм будет отправлять обновления в формате JSON каждый раз, когда вашему боту приходит сообщение или происходит другое событие.

В начале вам нужно настроить Webhook. Это делается путем отправки запроса к Telegram API с указанием URL вашего сервера. Телега будет использовать этот URL для отправки обновлений:

from telegram.ext import Updater, CommandHandler
from telegram import Bot
import logging

# логирование
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
                    level=logging.INFO)
logger = logging.getLogger(__name__)

# функция для обработки команды /start
def start(update, context):
    update.message.reply_text('Привет!')

def main():
    TOKEN = 'token'
    bot = Bot(TOKEN)

    # Updater и передача токена вашего бота
    updater = Updater(token=TOKEN, use_context=True)

    # диспетчер для регистрации обработчиков
    dp = updater.dispatcher

    # регистрация команды /start
    dp.add_handler(CommandHandler("start", start))

    # настроим вебхук
    updater.start_webhook(listen='0.0.0.0',
                          port=8443,
                          url_path=TOKEN,
                          key='YOUR_PRIVATE.key',
                          cert='YOUR_PUBLIC.pem',
                          webhook_url='https://YOUR.SERVER.IP.ADDRESS:8443/' + TOKEN)

    updater.idle()

if __name__ == '__main__':
    main()

YOUR_PRIVATE.key и YOUR_PUBLIC.pem это путь к ключу SSL, и YOUR.SERVER.IP.ADDRESS адрес сервера.

Для Webhook необходим SSL-сертификат. Telegram требует, чтобы обмен данными был защищен. Телеграм поддерживает ограниченное количество портов для Webhook: 443, 80, 88, 8443.

Для настройки SSL-терминации на веб-сервере нужно установить и настроить Nginx или Apache. На Nginx:

server {
    listen 443 ssl;
    server_name YOUR.SERVER.IP.ADDRESS;

    ssl_certificate /path/to/YOUR_PUBLIC.pem;
    ssl_certificate_key /path/to/YOUR_PRIVATE.key;

    location / {
        proxy_pass http://localhost:8443;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Как только кто-то взаимодействует с ботом (например, отправляет ему сообщение), телеграм делает запрос на юрл вебхук с деталями этого события. Эти данные приходят в виде JSON-объекта, который содержат информацию, к примеру:

{
    "update_id": 123456789,
    "message": {
        "message_id": 111,
        "from": {
            "id": 222,
            "is_bot": false,
            "first_name": "viktor",
            "username": "viktor12313"
        },
        "chat": {
            "id": 333,
            "first_name": "viktor",
            "username": "viktor12313",
            "type": "private"
        },
        "date": 1609459200,
        "text": "ку"
    }
}

Сервер затем обрабатывает этот запрос, парсит полученный JSON и выполняет необходимые действия, например, отвечает на сообщение пользователя.

Следующий шаг — это создание обработчика для входящих сообщений. В коде это обычно реализуется через определение функций с декораторами, указывающими на типы обрабатываемых сообщений (например, текст, изображения и т.д.). В качестве примера, можно использовать библиотеку fastapi вместе с telebot. Создаётся экземпляр класса AsyncTeleBot, затем настраивается webhook и определяются функции для обработки команд (/start, /help) и обычных текстовых сообщений. При получении обновления, бот использует await для асинхронной обработки сообщения и отправляет ответ:

import fastapi
from telebot.async_telebot import AsyncTeleBot
import telebot

API_TOKEN = 'токен'
WEBHOOK_URL = 'вебхук'

bot = AsyncTeleBot(API_TOKEN)
app = fastapi.FastAPI()

@app.post('/')  
async def process_webhook(update: dict):  
    if update:  
        update = telebot.types.Update.de_json(update)  
        await bot.process_new_updates([update])  

@bot.message_handler(commands=['help', 'start'])  
async def send_welcome(message):  
    await bot.reply_to(message, "Бот запущен...")  

@bot.message_handler(func=lambda message: True, content_types=['text'])  
async def echo_message(message):  
    await bot.reply_to(message, message.text)  

Итого в таблице сравнение выглядит так:

Критерий

Polling

Webhook

Время отклика

Относительно медленное

Почти мгновенное

Нагрузка на сервер

Высокая, требует постоянных запросов

Низкая, сервер ждёт уведомлений

Сложность реализации

Простая, легко реализуемая

Средняя, требует настройки HTTPS, SSL

Масштабируемость

Ограничена, увеличение нагрузки на сервер с ростом числа пользователей

Высокая, эффективна при большом количестве пользователей

Использование ресурсов

Активное, постоянный опрос сервера

Пассивное, проще говоря принимай когда пришло

Управление трафиком

Неэффективное, из-за постоянных запросов

Эффективное, обрабатываются только актуальные события

Безопасность

Более уязвим для ддос атак из-за открытых запросов

Более безопасный, использует зашифрованные соединения

Настройка и обслуживание

Простая, не требует дополнительной настройки

Сложнее, требует настройки веб-сервера и SSL-сертификата

Webhook хоть и немного сложнее в настройке, имеет гораздо больше плюсов по сравнению с Polling.

В завершение хочу порекомендовать бесплатный урок на котором коллеги из OTUS разберут практические подходы, которые помогут вам писать чистый, поддерживаемый код на Python. Также разберут основные ошибки и проведем код-ревью проекта.

© Habrahabr.ru