Из-за временных ограничений на территории РФ наблюдаются проблемы с оплатой. Если платёж не проходит, оставьте запрос в службу поддержки.Служба поддержки работает 24/7 — мы всегда на связи по вопросам хостинга и серверов.Открыт прием заявок на аренду выделенных серверов и размещение оборудования в дата-центре.Напоминаем: рекомендуем включить резервное копирование для дополнительной защиты данных.Доступна новая линейка VPS/VDS с NVMe-дисками и увеличенной производительностью.Технические работы на части серверов завершены. Все сервисы работают в штатном режиме.
Статья5 мин чтенияПросмотры3

429 Retry-After: секунды или дата — проверяем паузу клиента API

Практический разбор двух форм Retry-After для HTTP 429: парсим секунды и HTTP-дату, ограничиваем ожидание и проверяем шесть граничных случаев на Python 3.12.

Комментарии 0

Таймер, сетевой кабель и янтарная сигнальная лампа рядом с посылкой
В этой статье

Если API ответил 429 и прислал Retry-After, клиенту нужно распознать две допустимые формы: целое число секунд или HTTP-дату. Число задаёт относительную паузу, а из даты вычисляют неотрицательную разницу со временем ответа. Некорректное значение лучше не угадывать: применить заранее заданную резервную политику и записать причину.

Что именно обещает Retry-After

Код 429 означает, что клиент отправил слишком много запросов за некоторый интервал. Ответ может содержать Retry-After с подсказкой, сколько ждать; это описано в RFC 6585.

Синтаксис поля задаёт RFC 9110: либо HTTP-date, либо delay-seconds — неотрицательное десятичное число секунд. Базовая грамматика RFC 5234 определяет DIGIT как ASCII-цифры 0–9; метод isdecimal() без isascii() шире этого правила. Поэтому 120 и Sun, 11 Oct 2026 05:02:00 GMT требуют разных веток разбора.

На маленьком примере проверим только вычисление паузы. Это учебная модель клиента, а не тест конкретного API 1С-Битрикс. Среда: CPython 3.12.14, стандартная библиотека, UTC; дата проверки — 14 октября 2026 года.

Минимальная функция

У функции три входа: значение поля, время ответа и локальный предел ожидания. Предел 300 секунд выбран для учебного примера; для рабочего клиента его определяют отдельно.

from datetime import datetime from email import utils def retry_after_seconds(value: str, response_date: datetime, max_wait: int = 300) -> int: value = value.strip() if value.isascii() and value.isdecimal(): delay = int(value) else: retry_at = utils.parsedate_to_datetime(value) if retry_at.tzinfo is None: raise ValueError("Retry-After date must include a timezone") delay = max(0, int((retry_at - response_date).total_seconds())) return min(delay, max_wait)

Функцию parsedate_to_datetime() предоставляет стандартная библиотека Python. При успешном разборе она возвращает datetime, а для некорректных значений даты выбрасывает ValueError. Проверка tzinfo не позволяет молча принять дату без часового пояса. Подробности синтаксиса есть в документации Python.

Почему нужен response_date

Абсолютная дата должна превратиться в длительность. Если ответ содержит поле Date, удобно считать от него: оба значения сформированы серверной стороной, поэтому возможное расхождение между часами клиента и сервера меньше влияет на расчёт. Это инженерная мера, а не отдельное правило Retry-After. Без Date явно используют локальное время получения ответа и учитывают возможное расхождение часов клиента и сервера.

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

Воспроизводимая проверка

Фиксируем время ответа и шесть случаев: обе формы, определённые стандартом, прошедшую дату, чрезмерную задержку, ошибочную строку и десятичные Unicode-цифры вне ASCII.

from datetime import datetime, timezone REFERENCE = datetime(2026, 10, 11, 5, 0, tzinfo=timezone.utc) CASES = [ ("120", 120), ("Sun, 11 Oct 2026 05:02:00 GMT", 120), ("Sun, 11 Oct 2026 04:59:00 GMT", 0), ("86400", 300), ] for raw, expected in CASES: actual = retry_after_seconds(raw, REFERENCE) assert actual == expected, (raw, actual, expected) print(f"{raw!r} -> {actual} s") try: retry_after_seconds("two minutes", REFERENCE) except ValueError as exc: print(f"'two minutes' -> {type(exc).__name__}") else: raise AssertionError("invalid value was accepted") unicode_digits = chr(0x0661) + chr(0x0662) + chr(0x0660) try: retry_after_seconds(unicode_digits, REFERENCE) except ValueError as exc: print(f"'U+0661 U+0662 U+0660' -> {type(exc).__name__}") else: raise AssertionError("Unicode digits outside ASCII were accepted")

Запуск завершился с кодом 0. Наблюдаемый вывод:

'120' -> 120 s 'Sun, 11 Oct 2026 05:02:00 GMT' -> 120 s 'Sun, 11 Oct 2026 04:59:00 GMT' -> 0 s '86400' -> 300 s 'two minutes' -> ValueError 'U+0661 U+0662 U+0660' -> ValueError

Вход

Ожидание

Результат

Что подтверждает

120

120 с

120 с

Относительная задержка

HTTP-дата +120 с

120 с

120 с

Абсолютная дата

HTTP-дата −60 с

0 с

0 с

Нет отрицательной паузы

86400 при max_wait=300

300 с

300 с

Локальный предел

two minutes

Ошибка

ValueError

Недопустимое значение отклонено

U+0661 U+0662 U+0660

Ошибка

ValueError

Unicode-цифры вне ASCII отклонены

Что делать с пустым или неправильным заголовком

  • Не подставлять ноль: немедленный повтор может продлить ограничение.

  • Не превращать произвольную строку в большое число: ошибку разбора наблюдают отдельно.

  • Применить документированную резервную задержку с jitter и общий предел попыток.

  • Сохранить статус, Retry-After и номер попытки без токенов и персональных данных.

  • Остановить повтор, если операция не защищена от дублей.

Тут есть подвох: корректная пауза не делает POST безопасным. После ожидания соединение снова может оборваться уже после обработки запроса сервером. Для создания заказа нужен идемпотентный ключ или другой механизм дедупликации.

Отдельный проверяемый пример разобран в статье «Повторный webhook не должен создать второй заказ». Retry-After отвечает за момент следующей попытки, а идемпотентность — за её последствия.

Границы решения

  • Пример не отправляет реальные запросы и не измеряет лимиты конкретного сервиса.

  • Retry-After может отсутствовать: тогда действует собственная заранее описанная политика клиента.

  • Максимум 300 секунд выбран только для теста; протокол такого потолка не задаёт.

  • Нескольким воркерам нужен общий ограничитель или координация, иначе они проснутся одновременно.

  • Пауза должна быть прерываемой при остановке задания.

Критерий приёмки клиента

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

Обсуждение 0

Делись опытом и задавай вопросы. Комментарии без ссылок появляются после проверки редактором.

Пока никто не написал. Начни обсуждение.