Надёжный long polling
Результат
Вы получите входящее сообщение без busy loop, увидите повторную доставку
неподтверждённого update и подтвердите только обработанное событие с помощью
offset.
Что понадобится
- пройденный быстрый старт;
- запущенный gateway профиля
telegram-v1-core-preview-4на127.0.0.1:8081; - открытый диалог пользователя с ботом в клиенте Скрепы;
- Python 3,
curlиjq.
export SKREPA_BOT_API='http://127.0.0.1:8081'
export SKREPA_BOT_TOKEN='ваш-token-из-BotFather'
set -o pipefail
Если раньше был настроен webhook, верните runtime в polling mode:
curl --fail-with-body -sS -X POST \
"$SKREPA_BOT_API/bot${SKREPA_BOT_TOKEN}/deleteWebhook" | jq .
1. Создайте безопасный polling-сценарий
Пример запрашивает ровно один update. Благодаря этому offset никогда не
подтвердит соседнее событие, которое приложение ещё не обработало. Сохраните
как polling_demo.py:
import json
import os
import urllib.error
import urllib.request
BASE_URL = os.environ["SKREPA_BOT_API"].rstrip("/")
TOKEN = os.environ["SKREPA_BOT_TOKEN"]
UPDATES_URL = f"{BASE_URL}/bot{TOKEN}/getUpdates"
def get_updates(payload: dict) -> dict:
request = urllib.request.Request(
UPDATES_URL,
data=json.dumps(payload).encode(),
headers={"content-type": "application/json"},
method="POST",
)
try:
with urllib.request.urlopen(request, timeout=55) as response:
result = json.load(response)
except urllib.error.HTTPError as error:
body = error.read().decode(errors="replace")
raise SystemExit(f"getUpdates вернул HTTP {error.code}: {body}") from error
except urllib.error.URLError as error:
raise SystemExit(f"gateway недоступен: {error.reason}") from error
if result.get("ok") is not True or not isinstance(result.get("result"), list):
raise SystemExit(f"неожиданный ответ gateway: {result!r}")
return result
while True:
response = get_updates(
{"limit": 1, "timeout": 30, "allowed_updates": ["message"]}
)
if not response["result"]:
print("За 30 секунд сообщение не пришло; продолжаю ждать.")
continue
update = response["result"][0]
if "message" not in update:
kinds = sorted(set(update) - {"update_id"})
raise SystemExit(
"В очереди уже лежит update другого типа "
f"({', '.join(kinds) or 'unknown'}). Обработайте его отдельным "
"consumer либо осознанно очистите тестовую очередь; offset не изменён."
)
break
update_id = update["update_id"]
chat_id = update["message"]["chat"]["id"]
print(f"Получен update_id={update_id}, chat_id={chat_id}")
repeated = get_updates({"limit": 1, "timeout": 0})
if not repeated["result"] or repeated["result"][0]["update_id"] != update_id:
raise SystemExit("неподтверждённый update не был доставлен повторно")
print("Повторная доставка подтверждена")
# Здесь находится успешно завершившийся бизнес-эффект приложения.
print(f"Обработано сообщение: {update['message'].get('text', '')}")
next_offset = update_id + 1 # Python хранит целые числа с произвольной точностью.
acknowledged = get_updates(
{"offset": next_offset, "limit": 1, "timeout": 0}
)
print(f"Подтверждено offset={next_offset}: {acknowledged}")
2. Дождитесь сообщения и подтвердите его
Запустите пример, затем отправьте боту текст из клиента Скрепы:
python3 polling_demo.py
Если очередь пуста, соединение ожидает до 30 секунд без busy loop. Скрипт
покажет один и тот же update_id дважды, выполнит учебный бизнес-эффект и лишь
после этого передаст offset = update_id + 1.
offset подтверждает все updates с меньшим идентификатором. В рабочем
consumer сохраняйте новый offset только после надёжной фиксации бизнес-эффекта.
Как понять, что всё получилось
- long poll дождался сообщения без частых запросов;
- повторный запрос без
offsetснова увидел тот жеupdate_id; - запрос с
offset = update_id + 1удалил обработанный update из выдачи.
Типичные ошибки
| Симптом | Что проверить |
|---|---|
HTTP 409 | активен webhook или уже выполняется второй long poll этого runtime |
| скрипт продолжает ждать | long poll завершился по timeout; отправьте сообщение нужному боту |
| в очереди update другого типа | обработайте его подходящим consumer; для одноразового тестового бота очередь можно сознательно очистить через deleteWebhook с drop_pending_updates=true |
| события пропали раньше обработки | приложение передало слишком большой offset; подтверждайте лишь успешно обработанные updates |
| после перезапуска приходит старое событие | оно оставалось неподтверждённым; обработчик должен быть идемпотентным по update_id |
Что гарантирует gateway
- update доставляется как минимум один раз до подтверждения через
offset; - pending queue и выбранный
allowed_updatesпереживают штатный перезапуск; - опущенный
allowed_updatesсохраняет последний выбор, а для свежего runtime пустой список является начальным значением и включает все обычные события профиля, кроме реакции; - новый фильтр действует на будущие входящие события и не меняет уже накопленную очередь;
- накопленное событие возвращается немедленно, пустая очередь ожидает не более
timeoutсекунд; допустимый диапазон — 0–50 секунд; - один runtime допускает один ожидающий polling-запрос, конкурентный запрос
получает HTTP
409; - отзыв token проверяется во время ожидания и не позволяет старому соединению получить следующий update.