Перейти к содержимому

Вызов внешнего сервиса из кода - таймауты, повторы, журнал

Ходим из кода сайта во внешний сервис так, чтобы его недоступность не роняла оформление заказа и не подвешивала страницу на минуту.

Решение

Отправляем запрос штатным клиентом:

use Bitrix\Main\Web\HttpClient;
use Bitrix\Main\Web\Json; // разбор ответа средствами ядра, а не своим кодом
$http = new HttpClient([
'socketTimeout' => 5, // сколько ждём соединения
'streamTimeout' => 10, // сколько ждём ответа целиком
'waitResponse' => true,
]);
$http->setHeader('Content-Type', 'application/json');
$body = $http->post('https://api.example.com/orders', Json::encode($payload));

Оба таймаута здесь задают явно и осознанно. Значения по умолчанию рассчитаны на дружелюбную сеть, а зависший чужой сервис без них держит процесс PHP до предела времени выполнения и занимает место в пуле.

Разбираем ответ полностью:

$status = $http->getStatus();
if ($status !== 200) {
// тело при ошибке тоже читают: там обычно объяснение отказа
$error = $http->getResult();
}
$data = Json::decode($body ?: '{}'); // разбор может выбросить исключение

Проверяют именно код ответа, а не одно только тело. Пустое тело при коде отказа означает одно, а осмысленный текст ошибки - совсем другое, и разбор без проверки кода превращает отказ в непонятное исключение разбора.

Выносим вызов из запроса посетителя:

// обработчик события кладёт задание, а не ходит в сеть
CAgent::AddAgent('\\Vendor\\Sync::pushOrders();', 'main', 'N', 60);
// оформление заказа не должно зависеть от чужой доступности

Внешний вызов внутри оформления заказа делает чужой сбой своим. Задание, поставленное в очередь, отправляет данные позже и переживает недоступность сервиса без потери заказа.

Пишем в журнал и повторяем с задержкой:

\Bitrix\Main\Diag\Debug::writeToFile(
['status' => $status, 'body' => mb_substr((string)$body, 0, 500)], date('H:i:s'), 'api.log');
// повтор делают с растущей паузой: 1, 5, 30 минут
// бесконечные повторы без паузы кладут и чужой сервис, и свой сайт
// число попыток ограничивают: после него задание помечают неудачным

Запись ответа остаётся единственным способом доказать, чья сторона сломалась. Повторы делают с растущей паузой: частый повтор в цикле добивает сервис, который и так отвечает с трудом.

Ключи доступа держат в настройках модуля или в переменных окружения. Ключ в коде уезжает в систему контроля версий и остаётся там навсегда, даже после смены.

Ответ внешнего сервиса стоит проверять на состав, а не только на код. Пришедший успешный код с пустым или урезанным телом ломает разбор ровно так же, как отказ, и замечают это обычно гораздо позже.

Чужой сервис стоит считать ненадёжным по умолчанию. Отказ, таймаут и неожиданный формат ответа - это не исключительные ситуации, а обычный режим работы любой внешней интеграции.

Типичные проблемы

Страница висит по минуте и отдаёт ошибку.

Вызов внешнего сервиса идёт прямо в запросе посетителя без таймаутов. Зависший чужой ответ держит процесс PHP до предела времени выполнения.

Заказы теряются при недоступности сервиса.

Отправка данных встроена прямо в оформление заказа. Её выносят в отдельное задание, которое переживёт недоступность сервиса.

В логе исключение разбора ответа.

Тело ответа разбирается без проверки его кода. При отказе там приходит текст ошибки, а вовсе не ожидаемый формат.

Внешний сервис заблокировал сайт.

Повторы идут без пауз и без ограничения числа попыток. Частые повторы выглядят с чужой стороны как нападение на сервис.

Непонятно, чья сторона виновата.

Запросы и ответы сервиса никуда не записываются. Без журнала разбор чужого сбоя превращается в спор без доказательств.

Частые вопросы

Какие таймауты ставить?

Секунды на соединение и десяток секунд на ответ. Точные значения берут из договорённостей с сервисом, а не наугад.

Сколько раз повторять запрос?

Три-пять попыток с растущей паузой, дальше - в ручной разбор. Бесконечные повторы вредят обеим сторонам.

Где хранить ключ доступа?

В настройках модуля или переменных окружения. Ключ в коде попадает в историю изменений и живёт там вечно.

Нужно ли шифровать передачу?

Всегда, если уходят данные покупателей. Обмен по незащищённому протоколу с персональными данными недопустим.

Смежное

Первоисточник