Заголовок Idempotency-Key (IETF-черновик) делает повторённый POST безопасным: сервер хранит первый результат по ключу в пределах окна дедупликации и воспроизводит его для любого повтора, давая отправку exactly-once. Ограничение скорости защищает сервис: сверхлимитные запросы получают 429 Too Many Requests с Retry-After, а заголовки RateLimit объявляют остаток квоты, чтобы клиенты сами себя ограничивали.
Заголовок Idempotency-Key
Отправка сообщения — это POST, который не идемпотентен, однако сети вынуждают клиентов повторять, когда ответ потерян, — создавая классическую двойную отправку. Черновик Idempotency-Key рабочей группы IETF по HTTP-API стандартизирует решение: клиент генерирует уникальный ключ (UUID) на каждую логическую операцию и отправляет его в заголовке запроса Idempotency-Key. Сервер записывает ключ с исходом первого запроса, и если видит тот же ключ снова, он не выполняет второй раз — он возвращает сохранённый результат. Так повтор после тайм-аута гарантированно безвреден: либо первая попытка не долетела (и эта выполняется), либо долетела (и её результат воспроизводится). Ключ принадлежит операции, а не соединению, поэтому клиент переиспользует один и тот же ключ во всех повторах этой одной отправки.
# Generic IETF-draft example (not the BroadNet API, which takes no
# Idempotency-Key header: see /developers/idempotency)
POST /v1/messages HTTP/1.1
Host: api.example
Idempotency-Key: 8f14e45f-ea7b-4c0f-9b2a-1d2c3e4f5a6b
Content-Type: application/json
{ "to": "+15551234567", "text": "..." }
# first call -> 202 Accepted, id 01HZY8Q3M6 (executed, stored)
# same key -> 202 Accepted, id 01HZY8Q3M6 (replayed, not re-sent)Окно дедупликации, воспроизведение ответа и конфликты
Ключ идемпотентности не помнится вечно; сервер держит его в течение окна дедупликации (обычно 24 часа), достаточного, чтобы покрыть любой реалистичный повтор, а затем удаляет его. В пределах этого окна повтор ключа воспроизводит сохранённый код состояния и тело дословно — тот же 202 и тот же message id, — поэтому клиент видит ровно то, что увидел бы, если бы первый ответ пришёл. Два краевых случая требуют явной обработки. Если ключ переиспользуется с другим телом запроса, это ошибка клиента, и сервер возвращает 409 Conflict, а не молча отправляет не то. Если повтор приходит, пока первый запрос ещё в полёте, сервер возвращает конфликт или «в процессе», а не начинает второе выполнение, чтобы конкурентность не могла победить гарантию.
| Ситуация | Поведение сервера | Статус |
|---|---|---|
| Новый ключ | Выполнить, сохранить ключ + результат | Нормальный (напр. 202) |
| Повтор ключа, то же тело | Вернуть сохранённый ответ, не выполнять повторно | Исходный статус |
| Повтор ключа, другое тело | Отклонить — ключ привязан к первой полезной нагрузке | 409 Conflict |
| Повтор, пока первый запрос ещё обрабатывается | Не запускайте второе выполнение | 409 / в процессе |
| Срок действия ключа истёк (за пределами окна) | Обрабатывается как новое | Нормальный |
Ограничение скорости и 429
Разделяемая платформа сообщений защищает себя и других арендаторов, ограничивая, как быстро любой клиент может вызывать, и сигнализирует о нарушении конкретным статусом. RFC 9110 и RFC 6585 определяют 429 Too Many Requests именно для этого: клиент отправил слишком много запросов за данное время. Дисциплинированный сервер сопровождает 429 заголовком Retry-After, сообщающим клиенту, сколько ждать перед новой попыткой (либо задержку в секундах, либо HTTP-дату). Обязанность клиента — соблюсти его: остановиться, подождать указанный интервал, затем возобновить, — а не повторять немедленно, что лишь усугубляет перегрузку. 429 временный и безопасен для повтора, в отличие от 4xx, указывающего на некорректный запрос.
HTTP/1.1 429 Too Many Requests
Retry-After: 2
RateLimit: limit=100, remaining=0, reset=2
RateLimit-Policy: 100;w=1
Content-Type: application/problem+json
{ "type": "https://example/problems/rate-limited",
"title": "Too Many Requests", "status": 429 }Заголовки RateLimit и RateLimit-Policy
Реагировать на 429 постфактум расточительно; черновик IETF по заголовкам RateLimit позволяет клиенту увидеть свой бюджет прежде, чем он его превысит. На обычных ответах сервер объявляет текущую квоту заголовком RateLimit, несущим лимит, оставшийся резерв и секунды до сброса окна, и заголовком RateLimit-Policy, описывающим саму политику (например, 100 запросов на окно в 1 секунду). Клиент, читающий их, может задавать себе темп — замедляясь по мере приближения remaining к нулю — и вовсе не вызывать 429. Это превращает ограничение скорости из карательного отказа постфактум в кооперативный сигнал, что и есть весь смысл стандартизации имён заголовков, чтобы любая клиентская библиотека могла их потреблять.
| Заголовок | Передает | Пример |
|---|---|---|
| 429 Too Many Requests | Статус превышения лимита | строка состояния |
| Retry-After | Сколько времени нужно подождать, прежде чем повторить попытку | Retry-After: 2 |
| RateLimit | лимит / оставшееся / сброс для окна | limit=100, remaining=17, reset=1 |
| RateLimit-Policy | Действующая политика квот | 100;w=1 (100 per 1s) |
Token bucket и как навязываются лимиты
Алгоритм token bucket — обычный механизм за этими заголовками, поскольку он ограничивает устойчивую скорость, допуская короткие всплески. Ведро держит до фиксированного числа токенов и пополняется с постоянной скоростью; каждый запрос забирает один токен, а запрос, нашедший ведро пустым, отклоняется с 429. Размер ведра задаёт всплеск, который клиент может потратить сразу, а скорость пополнения задаёт долгосрочный потолок, так что клиент может отправить быстрый всплеск после простоя, но не может превысить среднее. Значение remaining в RateLimit фактически является счётчиком токенов, а reset — момент, когда ведро пополнится, — что и есть та информация, которая нужна клиенту с заданным темпом. Этот контроль потока на уровне HTTP — REST-аналог размера окна SMPP и ограничения enquire_link на бинарной сессии.
Обратное давление клиента
Клиентская половина хорошего поведения при ограничении скорости — обратное давление: формирование исходящей нагрузки так, чтобы она оставалась в пределах объявленного бюджета, вместо того чтобы выстрелить всё и поглощать 429. На практике это означает клиентский лимитер (token bucket, зеркалящий серверный), чтение заголовков RateLimit для корректировки темпа и, при 429, выдержку на интервал Retry-After с джиттером перед возобновлением. Для массовой отправки это означает дозирование подач под устойчивый потолок, а не их сброс разом, чтобы платформа оставалась здоровой, а собственная задержка клиента — предсказуемой. BroadNet документирует своё конкретное поведение идемпотентности и ограничения скорости для разработчиков.
Часто задаваемые вопросы
Что делает заголовок Idempotency-Key?
Он делает повторённый POST безопасным. Клиент отправляет уникальный ключ на каждую логическую операцию; сервер выполняет первый запрос с этим ключом и сохраняет результат, затем воспроизводит сохранённый результат для любого повтора того же ключа в пределах окна дедупликации. Так повтор после потерянного ответа либо выполняется один раз, либо воспроизводится — никогда не отправляет дважды. Он специфицирован в черновике рабочей группы IETF по HTTP-API.
Как долго сервер помнит ключ идемпотентности?
В течение окна дедупликации — обычно около 24 часов — достаточно долго, чтобы покрыть любой реалистичный повтор, после чего ключ истекает, и переиспользование трактуется как новое. В пределах окна повтор ключа воспроизводит исходный код состояния и тело дословно, а не выполняет заново.
Что произойдёт, если переиспользовать ключ идемпотентности с другим телом?
Сервер отклонит его с 409 Conflict. Ключ привязан к полезной нагрузке первого запроса, поэтому его переиспользование с другим телом трактуется как ошибка клиента, а не молчаливая отправка не того сообщения. Переиспользуйте тот же ключ только для повторов идентичной операции.
Что должен делать клиент при получении 429?
Остановиться и соблюсти заголовок Retry-After — подождать указанные секунды (или до заданной даты), затем возобновить, — а не повторять немедленно, что лишь усугубляет перегрузку. 429 временный и безопасен для повтора после истечения ожидания, в идеале с небольшим джиттером, чтобы многие клиенты не возобновились в унисон.
Для чего нужны заголовки RateLimit и RateLimit-Policy?
Они позволяют клиенту увидеть свою квоту прежде, чем превысить её. RateLimit несёт лимит, оставшийся резерв и время сброса для текущего окна; RateLimit-Policy описывает политику (например, 100 запросов в секунду). Клиент, читающий их, может задавать себе темп и вовсе не вызывать 429. Оба определены в черновике IETF.
Как token bucket соотносится с заголовками RateLimit?
Token bucket — это механизм навязывания: ведро фиксированного размера пополняется с постоянной скоростью, каждый запрос тратит токен, а пустое ведро даёт 429. Значение remaining в RateLimit по сути является текущим счётчиком токенов, а reset — момент пополнения ведра, — так что клиент, читающий эти заголовки, точно знает, сколько всплеска и устойчивой скорости у него осталось.
Технические характеристики источника
На этой странице представлены опубликованные стандарты, на которых они основаны. (Открывается в новой вкладке)