События
Доставка на localhost
Как агент apistend listen приносит события на вашу машину и чем это отличается от поведения боевых сервисов.
На этой странице · 6
Боевые сервисы шлют вебхуки на публичный адрес: обработчик должен быть виден из интернета. На машине разработчика его нет, и обычный путь — поднять туннель, получить временный домен, вписать его в настройки портала, а после перезапуска туннеля вписать заново.
APIStend решает это иначе: события забирает агент, который вы запускаете сами.
npx apistend listen --forward localhost:3000/webhooks
Как это устроено
Соединение всегда инициирует агент. Он поднимает WebSocket к серверу APIStend, и события приходят кадрами в уже открытое соединение. Входящих подключений к вашей машине нет — поэтому схема работает из-за NAT и корпоративного файрвола, и ей не нужны ни публичный адрес, ни проброс портов.
- 1
Агент создаёт сессию
POST /v1/tunnel/sessionsсерверным ключомstend_sk_…. В ответ приходит одноразовый токен подключения со сроком жизни 10 минут и адресws(s)://…/v1/tunnel/connect. - 2
Агент подключается по WebSocket
Токен передаётся заголовком
Authorization: Bearer, подпротокол —apistend.tunnel.v1. Токен гасится при первом использовании: двумя параллельными подключениями одним токеном воспользоваться нельзя. - 3
Сервер отвечает кадром
readyВ нём идентификатор сессии для интерфейса (
tnl-ef80), секрет подписи и число событий, накопившихся за простой. - 4
События идут кадрами
deliveryАгент отвечает
ackсразу по получении иresponse— когда приложение ответило (или не ответило).
Почему это безопасно
В протоколе туннеля нет абсолютных адресов. Сервер присылает только
относительный путь (/bitrix/deal), базу подставляет агент из своего --forward.
Поэтому сервер физически не может заставить агента пойти в чужую сеть, а сам
агент не превращается в открытый прокси.
Адрес пересылки проверяется дважды — в CLI и на сервере — и допускает только
loopback: localhost, 127.0.0.1, ::1 и имена, оканчивающиеся на .localhost.
✗ Адрес пересылки должен быть локальным: получено example.com/hook
Разрешены localhost, 127.0.0.1 и ::1
По той же причине подписка на локальную доставку хранит путь, а не URL:
в форме создания вебхука вы указываете /bitrix/deal, а не полный адрес.
Иначе склейка дала бы http://localhost:3000/webhooks/webhooks/bitrix/deal.
Очередь на время простоя
Если агента нет, доставка остаётся в очереди в состоянии queued. При подключении
сервер досылает накопленное — до 200 доставок за один проход, в порядке
поступления. Сколько их, агент печатает в баннере:
> В очереди 2 события за время простоя, отправляю
Считаются только доставки, которые ждут именно агента: события на публичный адрес отправляет сам сервер, обещать их в этой строке было бы неправдой.
Флаг `--no-replay` очередь не отменяет
Флаг объявлен у команды listen, но влияет он только на строку баннера: сервер
досылает накопленное при подключении в любом случае. Проверено вживую —
с --no-replay события из очереди приходят так же.
Одна сессия на песочницу
Активная сессия у песочницы ровно одна. Второй запущенный apistend listen
вытесняет первый — тот получает код закрытия 4003 и завершается:
✗ Сессия занята другим агентом
Закройте другой apistend listen или используйте другую песочницу
На этот код агент сознательно не переподключается: иначе два открытых терминала бесконечно выбивали бы друг друга.
Сервер пингует агента раз в 20 секунд. Если ответа нет дольше двух интервалов, соединение обрывается принудительно, чтобы в интерфейсе не висело «Подключено» у мёртвого сокета.
Чем это отличается от боя
Локальная доставка — расширение APIStend, а не воспроизведение поведения боевых сервисов. Отличия стоит держать в голове:
Всё, что ниже транспорта, — формат тела, заголовки, правило успеха, таймаут, лестница повторов — остаётся сервисным. Меняется только способ, которым событие доезжает до кода.
Что проверить перед выходом в бой
Обработчик, отлаженный на локальной доставке, в бою окажется на публичном адресе. Проверьте то, чего на localhost не было: доступность извне, TLS и проверку подписи на реальном теле запроса.
Подводные камни
- Фильтр
--eventsмолчит. Отсеянные события агент не пересылает и не отвечает на них серверу: доставка остаётся без ответа и через таймаут сервиса попадает в журнал какno_response. Строка баннера при этом всё равно пишет «События: все». - Локальный получатель без агента копит очередь. Одиночный
triggerпоставится в очередь молча; серия событий в этом случае просто откажется стартовать. - Путь склеивается с
--forward.--forward localhost:3000/webhooksи путь подписки/bitrix/dealдаютhttp://localhost:3000/webhooks/bitrix/deal.