getUpdates long polling на PHP для локальной разработки: offset, таймаут и борьба с дублями

На этапе локальной разработки Telegram-ботов настройка Webhook часто превращается в рутину. Вам приходится поднимать туннели через сторонние сервисы (вроде ngrok или LocalTunnel), следить за валидностью SSL-сертификатов, настраивать роутинг на локальном веб-сервере и мириться с сетевыми задержками. Метод getUpdates (long polling) полностью решает эти проблемы. Он позволяет запускать и отлаживать бота прямо на локальной машине без внешнего IP-адреса и сложной инфраструктуры.

Как работает Long Polling и зачем нужны offset и timeout

Обычный опрос (short polling) заставляет скрипт постоянно слать запросы к API: «Есть новые сообщения? А сейчас? А теперь?». Это создает колоссальную паразитную нагрузку на сервер Telegram и на ваш процессор. Long polling решает эту проблему элегантно: клиент отправляет запрос, а сервер Telegram удерживает соединение открытым до тех пор, пока не появится новое событие (update) или не истечет заданное время ожидания.

Для правильной работы getUpdates критически важны два параметра:

  • timeout — время (в секундах), в течение которого Telegram держит соединение открытым, если новых событий нет. Рекомендуемое значение — 30. Большие значения позволяют экономить ресурсы сети и процессора.
  • offset — идентификатор первого апдейта, который вы хотите получить. Если не передавать этот параметр, Telegram будет отдавать вам одни и те же сообщения при каждом запросе в течение 24 часов. Чтобы подтвердить получение событий и удалить их из очереди на сервере Telegram, в следующем запросе нужно передать offset = update_id + 1, где update_id — это ID последнего успешно обработанного события.

Пишем надежный CLI-скрипт на PHP для getUpdates

Для работы с Telegram Bot API на локальной машине нельзя использовать file_get_contents. Этот метод не позволяет гибко управлять таймаутами соединения и плохо обрабатывает сетевые ошибки. Мы будем использовать cURL.

Обратите внимание: таймаут cURL (CURLOPT_TIMEOUT) должен быть обязательно больше, чем таймаут, отправляемый в Telegram (параметр timeout), как минимум на 5 секунд. В противном случае cURL будет обрывать соединение до того, как Telegram успеет ответить, что приведет к бесконечным ошибкам соединения.

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

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

  1. Скрипт запросил пачку из 5 апдейтов через getUpdates.
  2. Успешно обработал первые 3 апдейта.
  3. На 4-м апдейте произошел критический сбой (например, упало соединение с вашей локальной БД, или скрипт превысил лимит памяти).
  4. Скрипт аварийно завершился. Telegram так и не получил запрос со сдвинутым offset.
  5. Вы перезапускаете скрипт. Telegram снова присылает всю пачку из 5 апдейтов. Первые 3 сообщения обрабатываются повторно.

Для предотвращения этой проблемы на уровне архитектуры необходимо вести учет обработанных update_id. Самый простой и быстрый способ — использовать Redis или таблицу в БД (например, SQLite/MySQL на локалке).

Перед обработкой каждого апдейта проверяйте, есть ли его update_id в вашем хранилище. Если есть — пропускайте его и сразу сдвигайте локальный offset. Если нет — обрабатывайте, а затем сохраняйте update_id в базу со статусом «обработано».

Хранить историю всех update_id вечно не нужно — Telegram гарантирует хранение апдейтов не более 24 часов. Достаточно настроить TTL (время жизни) записи в Redis на 25-26 часов.

Когда пора переходить с getUpdates на Webhook

Long polling идеален для локальной разработки, тестирования и работы простых ботов на слабых серверах, где не хочется настраивать веб-сервер. Однако для полноценного продакшена с высокой нагрузкой Webhook практически всегда предпочтительнее по следующим причинам:

  • Параллельная обработка: Веб-сервер (Nginx + PHP-FPM) автоматически масштабирует обработку входящих запросов. При long polling в один поток обработка идет строго последовательно. Если один пользователь вызвал «тяжелый» отчет на 10 секунд, все остальные пользователи будут ждать своей очереди.
  • Реакция в реальном времени: Webhook доставляет событие мгновенно. В long polling всегда есть микрозадержка, связанная с циклом запроса-ответа.
  • Экономия ресурсов: Скрипту не нужно держать постоянное открытое соединение с серверами Telegram. Запрос пришел — PHP отработал за 50 мс, отдал HTTP 200 и завершил процесс.

При переходе на Webhook не забудьте настроить проверку безопасности: используйте параметр secret_token при вызове setWebhook и проверяйте его наличие в заголовке X-Telegram-Bot-Api-Secret-Token каждого входящего POST-запроса от Telegram.

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

Новые статьи — в Telegram

Разбираем, что автоматизировать в бизнесе и как это работает на практике. Без спама.