Files
talkscore-asr/app/webhook.py
T
Vladimir BryzgalovandClaude Opus 5 e6e10dddd1 Вебхук: сервис сам сообщает о готовой задаче
Опрос статуса заставлял принимающую сторону дёргать сервис каждые
несколько секунд. Теперь при завершении задачи результат уходит POST-ом
на заданный адрес, с подписью HMAC-SHA256 в заголовке и тремя попытками
при неудаче. Адрес задаётся в настройках или параметром запроса.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 00:24:25 +05:00

60 lines
2.8 KiB
Python

"""Отправка готового результата на сторонний адрес.
Опрос статуса работает, но заставляет принимающую сторону дёргать сервис
каждые несколько секунд. Вебхук снимает это: сервис сам постучится, когда
задача готова.
"""
import hashlib
import hmac
import json
import logging
import threading
import time
__all__ = ["sign_payload", "deliver", "deliver_async"]
log = logging.getLogger(__name__)
# Задержки между попытками: сразу, через полминуты, через пять минут.
# Дольше ждать нет смысла - результат лежит в очереди и его можно забрать.
RETRY_DELAYS = (0, 30, 300)
TIMEOUT_SEC = 30
def sign_payload(body: bytes, secret: str) -> str:
"""Подпись тела запроса, чтобы принимающая сторона знала, что это мы."""
return hmac.new(secret.encode("utf-8"), body, hashlib.sha256).hexdigest()
def deliver(url: str, payload: dict, secret: str = "",
delays: tuple = RETRY_DELAYS) -> bool:
"""Отправляет результат, повторяя при неудаче. Возвращает признак успеха."""
import requests
body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
headers = {"Content-Type": "application/json; charset=utf-8"}
if secret:
headers["X-Talkscore-Signature"] = sign_payload(body, secret)
for attempt, delay in enumerate(delays, start=1):
if delay:
time.sleep(delay)
try:
response = requests.post(url, data=body, headers=headers, timeout=TIMEOUT_SEC)
if response.status_code < 300:
log.info("вебхук доставлен по задаче %s", payload.get("job_id"))
return True
log.warning("вебхук: попытка %d, ответ %s", attempt, response.status_code)
except Exception as exc: # noqa: BLE001 - причина неважна, важна повторная попытка
log.warning("вебхук: попытка %d не удалась (%s)", attempt, exc)
log.error("вебхук не доставлен по задаче %s, результат остаётся в очереди",
payload.get("job_id"))
return False
def deliver_async(url: str, payload: dict, secret: str = "") -> None:
"""Отправляет в фоне: воркер не должен ждать чужой сервер."""
thread = threading.Thread(target=deliver, args=(url, payload, secret),
name="webhook", daemon=True)
thread.start()