Как повторно использовать HTTPСоединение в 1С без проброса через все процедуры

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

При активном взаимодействии с внешними REST- и HTTP-сервисами часто возникает архитектурная задача: необходимо отправить десятки или сотни запросов к API (например, обход пачки измененных заказов). Если на каждую итерацию вызывать конструктор Новый HTTPСоединение, операционная система сталкивается с лавинообразным открытием сетевых сокетов, что ведет к накладным расходам и рискам блокировки. При этом «протаскивать» объект соединения через сигнатуры десятков вспомогательных методов бизнес-логики крайне неудобно. Разберем, как устроена работа сетевого стека платформы 1С и какими способами можно эффективно организовать повторное использование соединений.

Зачем повторно использовать HTTPСоединение: сетевой уровень

Проанализируем механику работы платформы 1С. Конструктор Новый HTTPСоединение(...) выполняет так называемую ленивую инициализацию: физическое TCP-подключение и рукопожатие TLS происходят только в момент вызова одного из методов передачи данных (Получить, ОтправитьДляОбработки и т. д.).

По умолчанию 1С работает по стандарту HTTP/1.1 с включенным механизмом Keep-Alive. Если мы используем один и тот же созданный экземпляр объекта HTTPСоединение, платформа удерживает открытый сокет и отправляет повторные запросы внутри существующей TCP-сессии.

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

Вариант 1 (один экземпляр соединения на цикл):


&НаСервереБезКонтекста
Процедура ТестНаСервере()
    Соединение = Новый HTTPСоединение("localhost", 3000);
    Для Счетчик = 1 По 10 Цикл
        Запрос = Новый HTTPЗапрос("/hello");
        Ответ = Соединение.Получить(Запрос);
    КонецЦикла;
КонецПроцедуры

Проверка сетевых сокетов утилитой мониторинга соединений показывает единственную открытую сессию:


State   Recv-Q  Send-Q  Local Address:Port   Peer Address:Port
ESTAB   0       0       127.0.0.1:39308      127.0.0.1:3000

Вариант 2 (создание нового объекта на каждой итерации):


&НаСервереБезКонтекста
Процедура ТестНаСервере()
    Для Счетчик = 1 По 10 Цикл
        Соединение = Новый HTTPСоединение("localhost", 3000);
        Запрос = Новый HTTPЗапрос("/hello");
        Ответ = Соединение.Получить(Запрос);
    КонецЦикла;
КонецПроцедуры

В этом случае в операционной системе возникает пачка соединений, переходящих в состояние ожидания закрытия:


State      Recv-Q  Send-Q  Local Address:Port   Peer Address:Port
TIME-WAIT  0       0       127.0.0.1:34790      127.0.0.1:3000
TIME-WAIT  0       0       127.0.0.1:34804      127.0.0.1:3000
...
TIME-WAIT  0       0       127.0.0.1:57646      127.0.0.1:3000

При большом потоке запросов создание объектов без переиспользования приводит к эффекту исчерпания диапазона динамических портов (Ephemeral Port Exhaustion), ошибкам сетевого стека ОС и блокировкам со стороны внешнего сервиса за превышение лимита параллельных сессий. Выясним, как архитектурно правильно решить проблему повторного использования в 1С.

Решение 1. Кэширование в общем модуле с повторным использованием

Платформа 1С предоставляет встроенный механизм кэширования возвращаемых значений функций через свойства общих модулей. Мы можем вынести получение соединения в модуль со свойством Повторное использование возвращаемых значений: На время вызова или На время сеанса.

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


Функция ПолучитьHTTPСоединение(ИмяСервера, Порт, ИдентификаторКэша = Неопределено) Экспорт
    Возврат Новый HTTPСоединение(ИмяСервера, Порт);
КонецФункции

При вызове функции из любого места конфигурации 1С вернет уже существующий объект HTTPСоединение, если параметры вызова совпадают. Это позволяет не протаскивать переменную соединения через параметры прикладных процедур создания документов и проведения проверок.

Важные нюансы реализации:

  1. Область кэширования: Рекомендуется использовать режим На время вызова. Режим На время сеанса опасен тем, что при изменении настроек авторизации или адреса сервера кэш не обновится до завершения сеанса или вызова ОбновитьПовторноИспользуемыеЗначения().
  2. Разрыв соединения внешним сервисом: Если сервер закрыл неактивную сессию по таймауту, закэшированный объект выбросит исключение при следующей попытке передачи. Для обхода этой проблемы следует предусмотреть дополнительный параметр сброса кэша.

Посмотрим на практический шаблон отказоустойчивой обертки:


Функция ВыполнитьЗапрос(Запрос, ИмяСервера, Порт, ИдентификаторКэша = Неопределено) Экспорт
    Если ИдентификаторКэша = Неопределено Тогда
        ИдентификаторКэша = Новый УникальныйИдентификатор;
    КонецЕсли;

    Соединение = ИнтеграцияПовтИсп.ПолучитьHTTPСоединение(ИмяСервера, Порт, ИдентификаторКэша);
    Попытка
        Возврат Соединение.Получить(Запрос);
    Исключение
        // В случае разрыва сессии генерируем новый ключ для создания свежего соединения
        ИдентификаторКэша = Новый УникальныйИдентификатор;
        Соединение = ИнтеграцияПовтИсп.ПолучитьHTTPСоединение(ИмяСервера, Порт, ИдентификаторКэша);
        Возврат Соединение.Получить(Запрос);
    КонецПопытки;
КонецФункции

Решение 2. Инкапсуляция через объект внешнего API (Обработка)

Второй классический подход объектно-ориентированного проектирования — создание класса-клиента на базе внешней или встроенной обработки. В модуле объекта объявляются внутренние переменные, сохраняющие состояние на протяжении жизни экземпляра.

Разберем по шагам создание такого API-клиента:

  1. В конфигураторе создаем обработку ВнешнееАПИКлиент.
  2. В модуле объекта объявляем переменную для хранения соединения и методы бизнес-логики:

Перем мСоединение;

Процедура Инициализировать(ИмяСервера, Порт) Экспорт
    мСоединение = Новый HTTPСоединение(ИмяСервера, Порт);
КонецПроцедуры

Функция ПолучитьЗаказ(ИдентификаторЗаказа) Экспорт
    Запрос = Новый HTTPЗапрос("/api/orders/" + ИдентификаторЗаказа);
    Ответ = мСоединение.Получить(Запрос);
    Возврат Ответ.ПолучитьТелоКакСтроку();
КонецФункции

Функция ПолучитьОплату(ИдентификаторОплаты) Экспорт
    Запрос = Новый HTTPЗапрос("/api/payments/" + ИдентификаторОплаты);
    Ответ = мСоединение.Получить(Запрос);
    Возврат Ответ.ПолучитьТелоКакСтроку();
КонецФункции

В вызывающем коде мы оперируем готовым клиентом API:


КлиентAPI = Обработки.ВнешнееАПИКлиент.Создать();
КлиентAPI.Инициализировать("api.service.com", 443);

Для Каждого ИдЗаказа Из МассивИдентификаторов Цикл
    ДанныеЗаказа = КлиентAPI.ПолучитьЗаказ(ИдЗаказа);
    // Обработка данных и запись в базу
КонецЦикла;

Такой подход обеспечивает изоляцию низкоуровневых параметров подключения (токены, заголовки, порты) внутри модуля объекта и гарантирует, что все обращения выполняются через один открытый сокет.

Ограничения и особенности платформы

При проектировании высоконагруженных интеграций необходимо учитывать платформенные особенности 1С:

  1. Изоляция сеансов: Ни один модуль с повторным использованием или глобальная переменная не могут разделять сокеты между разными клиентскими или фоновыми сеансами. Каждое регламентное или фоновое задание всегда стартует в отдельном сеансе со своим собственным сетевым стеком.
  2. Сброс сессий сетевым оборудованием: Межсетевые экраны и NAT-шлюзы могут молча сбрасывать неактивные соединения без отправки пакета TCP RST. При использовании постоянных подключений всегда оборачивайте сетевые вызовы в конструкцию Попытка ... Исключение с логикой повторной отправки (retry).
  3. DNS Pinning: Объект HTTPСоединение фиксирует IP-адрес хоста при первом обращении. Если удаленный сервис использует частую смену IP по механизму балансировки Round-Robin DNS, слишком долго живущий объект может приводить к ошибкам доставки пакетов до момента его пересоздания.
← На главную