Как побороть ошибку «Объект не может быть создан повторно» при работе с WinHttp в 1С?

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

При интеграции систем 1С со сторонними веб-сервисами разработчики часто сталкиваются сlegacy-кодом, использующим COM-объекты операционной системы Windows. Одним из наиболее популярных решений в свое время был и остается объект WinHttp.WinHttpRequest.5.1. Однако при его активном использовании в многопоточной среде, фоновых заданиях или при частых вызовах в цикле может возникнуть критическая ошибка: «Объект не может быть создан повторно» (или зависание соединения).

В этой статье мы подробно разберем, почему возникает данная неполадка, как устроена работа с COM-соединениями в среде 1С, исправим сопутствующие архитектурные ошибки в коде и рассмотрим современные стабильные альтернативы.

Выясняем причину: почему блокируется WinHttp?

Рассмотрим стандартный сценарий, представленный в коде. Разработчик при каждом вызове процедуры инициализирует новый COM-объект:

WinHttp = Новый COMОбъект("WinHttp.WinHttpRequest.5.1");

После отправки запроса и получения ответа объект должен быть уничтожен. Но в 1С очистка памяти происходит не мгновенно. Когда мы пишем:

WinHttp = ""; // или WinHttp = Неопределено;

Мы лишь уничтожаем ссылку на этот объект внутри контекста выполнения 1С. Сам же COM-объект в операционной системе Windows остается активным в памяти до тех пор, пока сборщик мусора (Garbage Collector) платформы 1С физически не высвободит память и не закроет дескрипторы соединения. Этот процесс может занимать от нескольких секунд до получаса (особенно в длительных транзакциях или регламентных заданиях).

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

Решение 1. Переиспользование одного объекта WinHttp

Объект WinHttpRequest спроектирован корпорацией Microsoft как многоразовый. Для выполнения серии запросов к одному или разным ресурсам нет необходимости каждый раз создавать его заново через конструктор Новый COMОбъект. Достаточно инициализировать его один раз (например, при начале работы сессии, открытии формы или как переменную модуля), а затем многократно вызывать методы Open() и Send().

Проанализируем правильный шаблон многократного использования объекта:


// Инициализируем объект однократно (например, при начале работы)
Если WinHttp = Неопределено Тогда
    WinHttp = Новый COMОбъект("WinHttp.WinHttpRequest.5.1");
КонецЕсли;

// Выполняем первый запрос
WinHttp.Open("POST", "http://site.ru/api/v1", Ложь);
WinHttp.Send(ДанныеЗапроса1);

// Выполняем второй запрос без пересоздания объекта
WinHttp.Open("POST", "http://site.ru/api/v2", Ложь);
WinHttp.Send(ДанныеЗапроса2);

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

Решение 2. Переход на нативное HTTPСоединение

Использование COM-объектов в среде 1С накладывает жесткие ограничения: код становится несовместимым с серверами под управлением Linux, а разрядность операционной системы (32-бит против 64-бит) может вызывать труднодиагностируемые падения рабочего процесса rphost.

В современных версиях платформы 1С (начиная с 8.3) встроенный объект HTTPСоединение полностью заменяет сторонние библиотеки, поддерживает протоколы HTTPS, TLS 1.2/1.3 и является кроссплатформенным. Перепишем исходный код интеграции на штатные механизмы 1С:


// Настраиваем защищенное соединение (если работаем по HTTPS)
// При необходимости игнорировать ошибки сертификатов передаем параметры SSL
ЗащищенноеСоединение = Новый ЗащищенноеСоединениеOpenSSL(Неопределено, Неопределено);

// Создаем HTTP-соединение. Адрес сервера указывается БЕЗ протокола (http:// или https://)
АдресСервера = "www.bic.infodebit.md";
Соединение = Новый HTTPСоединение(АдресСервера, , , , , 30, ЗащищенноеСоединение);

// Формируем HTTP-запрос
АдресРесурса = "/soap/1_report.php?wsdl";
Запрос = Новый HTTPЗапрос(АдресРесурса);
Запрос.Заголовки.Вставить("Content-Type", "text/xml; charset=utf-8");
Запрос.УстановитьТелоИзСтроки(ТекстXML, КодировкаТекста.UTF8, ИспользованиеМаркераВремени.НеИспользовать);

Попытка
    Ответ = Соединение.ОтправитьДляОбработки(Запрос);
    Если Ответ.КодСостояния = 200 Тогда
        СтрокаОтвета = Ответ.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
        // Вызываем чтение XML напрямую из памяти
        ПрочитатьXMLИзПамяти(СтрокаОтвета);
    Иначе
        ВызватьИсключение "Ошибка сервера. Код состояния: " + Ответ.КодСостояния;
    КонецЕсли;
Исключение
    ЗаписьЖурналаРегистрации("Интеграция", УровеньЖурналаРегистрации.Ошибка, , , ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;

Решение 3. Использование ServerXMLHTTP на стороне сервера

Если по техническим причинам вы обязаны использовать COM-технологии на стороне сервера приложений (например, в регламентных заданиях), то объект WinHttp.WinHttpRequest.5.1 противопоказан к применению. Он разрабатывался Microsoft для клиентских однопоточных сред (клиентские скрипты, браузеры) и не является потокобезопасным.

Для серверных решений используйте MSXML2.ServerXMLHTTP. Этот объект оптимизирован для пула серверных потоков, поддерживает асинхронную отправку запросов и не вызывает конфликтов блокировки портов:


WinHttp = Новый COMОбъект("MSXML2.ServerXMLHTTP.6.0");
WinHttp.open("POST", "http://site.ru/api", Ложь);
WinHttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
WinHttp.send(ТекстXML);

Критическая ошибка работы с временными файлами

Проанализируем еще одну архитектурную ошибку, допущенную в исходном коде темы:


Файл = Новый ЗаписьТекста(КаталогВременныхФайлов() + "tempxml.xml");
Файл.ЗаписатьСтроку(Строка);
Файл.Закрыть();

В чем опасность такого подхода? Если этот код запустят одновременно два пользователя (или параллельно отработают два регламентных задания под одной учетной записью сервера 1С), они попытаются записать данные в один и тот же файл tempxml.xml. Это приведет к ошибке совместного доступа (блокировке файла операционной системой) и аварийному завершению сеанса одного из пользователей.

Устранить эту уязвимость можно двумя путями:

  1. Генерация уникального имени файла: всегда используйте функцию ПолучитьИмяВременногоФайла(). Она создает неповторимый путь в системной директории для каждого вызова.
  2. Чтение XML-строки напрямую из памяти: в 1С нет необходимости сохранять промежуточные данные на жесткий диск, создавая лишнюю нагрузку на дисковую подсистему сервера.

Рассмотрим, как прочитать XML-данные напрямую из полученной текстовой строки без создания файлов на диске:


ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(СтрокаОтвета); // Передаем строку напрямую

// Выполняем обход XML-структуры
Пока ЧтениеXML.Прочитать() Цикл
    // Ваша логика разбора XML-документа
КонецЦикла;
ЧтениеXML.Закрыть();

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

← На главную