При интеграции 1С с внешними системами через внешние компоненты, написанные на C# (.NET Framework), разработчики нередко сталкиваются со странным поведением: код, который отлично работает при запуске из клиентского приложения или формы обработки, начинает выдавать ошибку «Время ожидания истекло» (WebException / Timeout) при запуске в фоновом задании на сервере 1С.
Проанализируем эту ситуацию, выясним глубинную причину блокировки сетевых запросов и рассмотрим эффективные способы решения проблемы как на стороне C#-компоненты, так и средствами платформы 1С:Предприятие.
Рассмотрим характерный сценарий: внешняя компонента выполняет HTTPS-запрос к стороннему REST-сервису внутри цикла для последовательной загрузки массива данных или сообщений. При вызове этой функции из клиентского сеанса 1С всё проходит успешно. Однако при выполнении аналогичного кода на стороне сервера в фоновом задании происходят следующие события:
Разберем, почему один и тот же код ведет себя по-разному на клиенте и в сервере 1С, и почему сбой происходит именно на третьем запросе.
Проанализируем архитектуру работы сетевого стека .NET Framework. Класс HttpWebRequest для управления HTTP/HTTPS-соединениями использует глобальный пул подключений ServicePointManager.
По умолчанию в .NET Framework установлено жесткое ограничение на максимальное количество одновременных активных соединений к одному IP-адресу или хосту: ServicePointManager.DefaultConnectionLimit = 2.
Если в коде компоненты после получения ответа от сервера явно не закрываются объекты HttpWebResponse, Stream и StreamReader, сетевой сокет остаётся занятым в пуле .NET, ожидая автоматической сборки мусора (Garbage Collector):
WebException: Время ожидания истекло.Почему на клиенте код мог работать? На клиентской машине или в интерфейсном потоке паузы между вызовами и работа сборщика мусора успевают очищать незакрытые ресурсы. На сервере 1С (в рабочем процессе rphost) фоновые задания выполняются с высокой интенсивностью, из-за чего сборщик мусора не успевает освобождать сокеты до исчерпания пула.
Самое главное правило при работе с сетевыми потоками в .NET — гарантированное закрытие и очистка сокетов. Если вы используете явный вызов методов закрытия, код компоненты должен закрывать все созданные объекты ответа.
В исходном варианте компоненты ресурсы остаются открытыми:
// Опасный код: ресурсы не освобождаются
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
StreamReader responseReader = new StreamReader(response.GetResponseStream());
message.Строка = responseReader.ReadToEnd();
// Отсутствует закрытие responseReader, response и потока!
Для исправления ошибки необходимо добавить явное закрытие и освобождение всех связанных ресурсов:
// Корректное закрытие ресурсов
responseReader.Close();
responseReader.Dispose();
stream.Close();
stream.Dispose();
response.Close();
Чтобы гарантировать освобождение сокета даже при возникновении ошибки (исключения) в процессе чтения данных, разберем более надежный подход — применение конструкции using:
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
using (Stream stream = response.GetResponseStream())
using (StreamReader responseReader = new StreamReader(stream))
{
message.Строка = responseReader.ReadToEnd();
message.Код = (int)response.StatusCode;
message.КодСообщения = response.Headers["Jmsmessageid"];
}
Конструкция using автоматически вызывает метод Dispose() для каждого объекта при выходе из блока, что мгновенно возвращает сокет в пул и предотвращает зависание на третьем запросе.
Если компонента выполняет интенсивный параллельный или высокочастотный обмен данными, рекомендуется также увеличить базовый лимит соединений в коде инициализации компоненты:
// Увеличиваем лимит параллельных соединений к одному хосту
ServicePointManager.DefaultConnectionLimit = 100;
// Настраиваем время жизни и простоя сокетов
request.ServicePoint.ConnectionLeaseTimeout = 5000; // мс
request.ServicePoint.MaxIdleTime = 5000; // мс
Обратите внимание: Назначение проверки сертификатов через ServicePointManager.ServerCertificateValidationCallback действует глобально на весь процесс. В многопоточной среде рабочего процесса 1С (rphost) это может затронуть другие фоновые задания. Для новых разработок рекомендуется переходить с устаревшего HttpWebRequest на класс HttpClient с настройкой HttpClientHandler.ServerCertificateCustomValidationCallback.
Проанализируем целесообразность использования внешних C#-компонент. Если логика взаимодействия с REST API не содержит специфичных алгоритмов, требующих исключительно .NET, гораздо надежнее реализовать запросы средствами самой платформы 1С.
Платформа 1С:Предприятие содержит встроенные объекты HTTPСоединение, HTTPЗапрос и ЗащищенноеСоединениеOpenSSL, которые автоматически и корректно управляют сетевыми сокетами и пулом подключений.
Рассмотрим пример выполнения аналогичного HTTPS-запроса на языке 1С:
// Настройка защищенного SSL-соединения
ЗащищенноеСоединение = Новый ЗащищенноеСоединениеOpenSSL();
// Создание HTTP-соединения
Соединение = Новый HTTPСоединение(
Сервер,
Порт,
Логин,
Пароль,
,
,
ЗащищенноеСоединение
);
// Формирование и отправка запроса
Заголовки = Новый Соответствие();
Заголовки.Вставить("Content-Type", "application/json");
ПутьКРесурсу = "/v1/peek/" + Ресурс + ".json";
Запрос = Новый HTTPЗапрос(ПутьКРесурсу, Заголовки);
Ответ = Соединение.ВызватьHTTPМетод("GET", Запрос);
КодСостояния = Ответ.КодСостояния;
ТекстОтвета = Ответ.ПолучитьТелоКакСтроку();
Преимущества использования встроенных объектов 1С:
Таким образом, для исправления зависания C#-компоненты на сервере достаточно обернуть работу со всеми сетевыми потоками в блоки using или явно вызывать методы Close() и Dispose().