Почему при обращении к Asterisk через HTTP-запрос в 1С возникает ошибка превышения времени ожидания?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При организации интеграции 1С с IP-АТС Asterisk через встроенный веб-интерфейс управления AJAM (Asterisk Java/JavaScript Asterisk Manager) разработчики часто сталкиваются со следующей ситуацией: запрос на получение событий очереди вылетает с системной ошибкой Ошибка работы с Интернет: Превышено время ожидания. При этом периодически запросы отрабатывают успешно и возвращают код состояния 200, но через некоторое время цикл опроса аварийно завершается.

Разберем детально, почему возникает такое поведение, как правильно синхронизировать таймауты между 1С и Asterisk, а также рассмотрим архитектурные правила построения надежного обмена данными.

Анализ механизма работы команды WaitEvent в Asterisk AJAM

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

/asterisk/rawman?action=WaitEvent&Timeout=60

Параметр Timeout=60 в строке запроса сообщает серверу Asterisk следующее: если в телефонии прямо сейчас нет активных событий (входящих звонков, изменения статусов операторов и т. д.), сервер не должен возвращать пустой ответ немедленно. Он переходит в режим ожидания (технология long-polling) и удерживает открытое TCP-соединение в течение 60 секунд. Если за эту минуту событие происходит, Asterisk тут же отдает данные. Если за 60 секунд событий не произошло, сервер просто отправляет штатный пустой ответ и завершает HTTP-сессию.

Теперь обратим внимание на сторону 1С. Объект HTTPСоединение инициализируется через конструктор. Если параметр таймаута не указан явно, платформа 1С использует значение по умолчанию (обычно 30 секунд), либо разработчик указывает свое фиксированное значение.

Когда в платформе 1С таймаут соединения установлен меньше, чем таймаут ожидания на стороне Asterisk (например, 30 секунд в 1С против 60 секунд в URL), возникает конфликт. Если в телефонии за 30 секунд ничего не произошло, Asterisk продолжает удерживать сокет, ожидая свои 60 секунд. Клиент 1С, не дождавшись ответа за положенные ему 30 секунд, принудительно разрывает сетевое соединение и выбрасывает исключение Превышено время ожидания.

Решение проблемы: согласование таймаутов

Рассмотрим по шагам, как исправить эту проблему в программном коде 1С. Главное правило заключается в том, что таймаут клиента 1С в объекте HTTPСоединение всегда должен быть строго больше таймаута, передаваемого в URL Asterisk.

Даже если мы укажем одинаковое время (например, 60 секунд в запросе и 60 секунд в конструкторе 1С), ошибка продолжит периодически появляться. Причина кроется в сетевых задержках (RTT), времени на обработку HTTP-заголовков сервером и накладных расходах операционной системы. Пока Asterisk отсчитает ровно 60 секунд и сформирует пакет, системный таймер в 1С уже успеет зафиксировать превышение интервала.

Обеспечим необходимый запас по времени (не менее 5–15 секунд). Посмотрим на правильный пример инициализации и отправки запроса:


// Задаем время ожидания для Asterisk (в секундах)
ВремяОжиданияAsterisk = 30;

// Таймаут в объекте HTTPСоединение делаем с запасом
ТаймаутСоединения1С = ВремяОжиданияAsterisk + 15; // 45 секунд

Сервер = "192.168.1.100";
Порт = 8088;

// Создаем HTTPСоединение с явным указанием таймаута
Соединение = Новый HTTPСоединение(
    Сервер,
    Порт,
    , // Пользователь
    , // Пароль
    , // Прокси
    ТаймаутСоединения1С
);

ТекстКоманды = СтрШаблон("/asterisk/rawman?action=WaitEvent&Timeout=%1", ВремяОжиданияAsterisk);
Запрос = Новый HTTPЗапрос(ТекстКоманды);

Запрос.Заголовки.Вставить("Cookie", ПараметрыПодключения.Cookie);
Запрос.Заголовки.Вставить("Accept", "*/*");

Попытка
    Ответ = Соединение.Получить(Запрос);
    Если Ответ.КодСостояния = 200 Тогда
        // Обработка полученных событий
    Иначе
        // Обработка кодов ошибок сервера (например, 403, 500)
    КонецЕсли;
Исключение
    // Перехват сетевых сбоев и штатных таймаутов
    ИнформацияОбОшибке = ОписаниеОшибки();
    ЗаписьЖурналаРегистрации("Интеграция Asterisk", УровеньЖурналаРегистрации.Предупреждение, , , ИнформацияОбОшибке);
КонецПопытки;

Правила организации непрерывного цикла опроса

Проанализируем сопутствующие проблемы, которые часто возникают при реализации постоянного опроса телефонии в цикле:

  1. Повторное использование объекта HTTPСоединение. Ни в коем случае нельзя инициализировать Новый HTTPСоединение на каждой итерации цикла. При создании объекта открывается новый сокет. Если его постоянно закрывать и открывать заново, операционная система переведет сетевые порты в состояние TIME_WAIT, что быстро приведет к исчерпанию пула доступных клиентских TCP-портов. Создавать соединение следует один раз перед входом в цикл, а внутри цикла только вызывать метод Получить().
  2. Обязательная обработка исключений в цикле. При работе с технологией long-polling истечение таймаута или сброс соединения — это нормальная рабочая ситуация. Если вызов метода Получить() не обернут в блок Попытка ... Исключение, первая же непредвиденная задержка сети приведет к аварийному завершению работы всего алгоритма.
  3. Вынос опроса в фоновое задание. Поскольку метод HTTPСоединение.Получить() является блокирующим и синхронным, поток 1С полностью останавливается на все время ожидания. Запуск такого цикла в пользовательском сеансе намертво заблокирует интерфейс программы. Подобный функционал необходимо реализовывать исключительно в рамках отдельного фонового задания с проверкой флага отмены.
  4. Проверка сессии AJAM. Встроенный веб-сервер Asterisk авторизует запросы через заголовок Cookie с идентификатором сессии менеджера (mansession_id). Сессия имеет ограниченный срок жизни. Если сессия устареет, сервер начнет отвечать ошибками авторизации, поэтому код должен уметь заново авторизоваться и обновлять cookie.
  5. Учет промежуточных серверов (Reverse Proxy). Если веб-сервер Asterisk опубликован через Nginx или Apache, таймаут ожидания бэкенда (например, директива proxy_read_timeout в Nginx) также должен быть больше времени ожидания команды WaitEvent, иначе прокси-сервер сам разорвет соединение с ошибкой 504 Gateway Timeout.

Альтернативные архитектурные подходы

Рассмотрим альтернативы, если технология AJAM не обеспечивает нужной стабильности при высоких нагрузках:

  1. Внешняя компонента с TCP-сокетами. Поскольку в платформе 1С отсутствует штатный объект для работы с чистыми TCP-сокетами, напрямую подключиться к порту Asterisk AMI (порт 5038) без HTTP-обертки нельзя. Использование внешней компоненты (Native API) позволяет держать постоянное сокетное соединение без лишних накладных расходов протокола HTTP.
  2. Внешний сервис-коннектор (микросервис). Создание промежуточного демона (на Node.js, Go или Python), который держит постоянное TCP-соединение с AMI, фильтрует сотни внутренних событий Asterisk и отправляет в 1С только важные события (например, поднятие трубки) через обратный вызов веб-хука — опубликованного в базе 1С HTTPСервиса. Такая схема переводит архитектуру с ресурсоемкой модели опроса (Pull) на событийную модель (Push).
  3. Чтение таблиц CDR/CEL через Внешние источники данных. Если задачи отображать всплывающую карточку в реальном времени нет, а требуется лишь собирать историю вызовов и ссылки на записи разговоров, цикл опроса событий не нужен. Достаточно настроить механизм ВнешниеИсточникиДанных для прямого чтения таблиц базы данных Asterisk (MySQL, PostgreSQL) по расписанию регламентного задания.
← На главную