При организации интеграции 1С с IP-АТС Asterisk через встроенный веб-интерфейс управления AJAM (Asterisk Java/JavaScript Asterisk Manager) разработчики часто сталкиваются со следующей ситуацией: запрос на получение событий очереди вылетает с системной ошибкой Ошибка работы с Интернет: Превышено время ожидания. При этом периодически запросы отрабатывают успешно и возвращают код состояния 200, но через некоторое время цикл опроса аварийно завершается.
Разберем детально, почему возникает такое поведение, как правильно синхронизировать таймауты между 1С и Asterisk, а также рассмотрим архитектурные правила построения надежного обмена данными.
Выясним причину возникновения ошибки на уровне взаимодействия протоколов. В тексте запроса разработчик указывает команду следующего вида:
/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", УровеньЖурналаРегистрации.Предупреждение, , , ИнформацияОбОшибке);
КонецПопытки;
Проанализируем сопутствующие проблемы, которые часто возникают при реализации постоянного опроса телефонии в цикле:
HTTPСоединение. Ни в коем случае нельзя инициализировать Новый HTTPСоединение на каждой итерации цикла. При создании объекта открывается новый сокет. Если его постоянно закрывать и открывать заново, операционная система переведет сетевые порты в состояние TIME_WAIT, что быстро приведет к исчерпанию пула доступных клиентских TCP-портов. Создавать соединение следует один раз перед входом в цикл, а внутри цикла только вызывать метод Получить().Получить() не обернут в блок Попытка ... Исключение, первая же непредвиденная задержка сети приведет к аварийному завершению работы всего алгоритма.HTTPСоединение.Получить() является блокирующим и синхронным, поток 1С полностью останавливается на все время ожидания. Запуск такого цикла в пользовательском сеансе намертво заблокирует интерфейс программы. Подобный функционал необходимо реализовывать исключительно в рамках отдельного фонового задания с проверкой флага отмены.Cookie с идентификатором сессии менеджера (mansession_id). Сессия имеет ограниченный срок жизни. Если сессия устареет, сервер начнет отвечать ошибками авторизации, поэтому код должен уметь заново авторизоваться и обновлять cookie.proxy_read_timeout в Nginx) также должен быть больше времени ожидания команды WaitEvent, иначе прокси-сервер сам разорвет соединение с ошибкой 504 Gateway Timeout.Рассмотрим альтернативы, если технология AJAM не обеспечивает нужной стабильности при высоких нагрузках:
HTTPСервиса. Такая схема переводит архитектуру с ресурсоемкой модели опроса (Pull) на событийную модель (Push).ВнешниеИсточникиДанных для прямого чтения таблиц базы данных Asterisk (MySQL, PostgreSQL) по расписанию регламентного задания.