При интеграции устаревших систем на платформе 1С:Предприятие 7.7 с современными интернет-магазинами, CRM и веб-сервисами разработчики часто сталкиваются с крайне неприятной проблемой. При попытке отправить большой объем данных (например, пакет из нескольких тысяч товаров с ценами и остатками) методом POST через HTTP-соединение, платформа 1С 7.7 мгновенно и без каких-либо предупреждений закрывается (вылетает). При этом небольшие пакеты данных (до 500-1000 позиций) отправляются абсолютно корректно.
В этой статье мы подробно разберем, почему возникает эта ошибка, проанализируем внутренние механизмы платформы и рассмотрим самые эффективные способы обхода данного ограничения с готовыми примерами кода.
Многие разработчики ошибочно полагают, что проблема кроется в ограничениях принимающей стороны (веб-сервера) или в лимитах самого COM-объекта Microsoft.XMLHTTP (или WinHttp.WinHttpRequest.5.1). Однако это не так. Причина кроется в механизме взаимодействия встроенного языка 1С 7.7 с COM-объектами.
При передаче строкового параметра из 1С 7.7 во внешний COM-объект происходит процесс маршалинга — преобразования внутренней строковой переменной 1С в стандартный тип строки Automation — BSTR. Платформа 1С 7.7 проектировалась очень давно, и ее менеджер памяти имеет внутренние ограничения на объем непрерывного блока памяти, выделяемого под передачу текстовых параметров в библиотеки COM.
Эмпирический лимит: опытным путем установлено, что при попытке передать строковый параметр размером более 385–500 КБ напрямую в метод COM-объекта (например, в метод Send()), происходит переполнение буфера платформы. Программа закрывается аварийно по ошибке нарушения прав доступа к памяти (Access Violation, код исключения 0xc0000005), чаще всего ссылаясь на сбойные модули BASIC.DLL или bkend.dll.
Самое очевидное и простое решение — это разбиение всего массива отправляемых данных на более мелкие порции. Если 4000 товаров приводят к падению, мы можем отправлять их пачками по 500–1000 штук в цикле.
Рассмотрим пример реализации такого алгоритма на встроенном языке 1С 7.7:
// Пример деления выгрузки на пакеты
Процедура ОтправитьДанныеПакетами()
ВыгружаемыеТовары = СоздатьОбъект("СписокЗначений");
// ... здесь происходит заполнение списка товаров, например, 4000 позиций ...
РазмерПакета = 500;
ВсегоТоваров = ВыгружаемыеТовары.РазмерСписка();
ТекущийИндекс = 1;
Пока ТекущийИндекс <= ВсегоТоваров Цикл
СтрокаПакета = "";
Для Шаг = 1 По РазмерПакета Цикл
Если ТекущийИндекс > ВсегоТоваров Тогда
Прервать;
КонецЕсли;
Товар = ВыгружаемыеТовары.ПолучитьЗначение(ТекущийИндекс);
// Формируем строковое представление (например, строку JSON или XML-узел)
СтрокаПакета = СтрокаПакета + Товар + ";";
ТекущийИндекс = ТекущийИндекс + 1;
КонецЦикла;
// Отправляем сформированный пакет
ОтправитьОдинПакет(СтрокаПакета);
КонецЦикла;
КонецПроцедуры
Преимущества: простота реализации, не требует использования сторонних библиотек.
Недостатки: существенно увеличивается общее время обмена из-за накладных расходов на установку множества HTTP-соединений; принимающий веб-сервис должен уметь корректно обрабатывать частичные (порционные) данные.
Если формат вашего обмена — XML, то проблему лимита строк можно элегантно решить, полностью отказавшись от работы со строковыми переменными в 1С при отправке. Объект XMLHTTP умеет принимать в метод Send() не только строки, но и ссылки на интерфейсы других COM-объектов, например, дерево XML-документа.
При передаче COM-объекта вместо строки платформа 1С 7.7 передает лишь указатель на интерфейс IDispatch. Конвертация огромных строк в тип BSTR силами 1С не производится, и передача данных любого объема проходит абсолютно стабильно.
Разберем по шагам, как это реализовать:
Процедура ОтправитьЧерезDOM()
// Создаем объект XML-документа
ДомДокумент = СоздатьОбъект("Msxml2.DOMDocument.6.0");
// Создаем корневой элемент
КорневойУзел = ДомДокумент.createElement("root");
ДомДокумент.appendChild(КорневойУзел);
// Наполняем документ данными в цикле
Для Номер = 1 По 4000 Цикл
УзелТовара = ДомДокумент.createElement("product");
УзелТовара.setAttribute("id", Номер);
УзелТовара.setAttribute("name", "Товар " + Номер);
КорневойУзел.appendChild(УзелТовара);
КонецЦикла;
// Создаем объект HTTP-запроса
WinHttp = СоздатьОбъект("Msxml2.ServerXMLHTTP.6.0");
WinHttp.Open("POST", "http://testtesttest.com/api/update_products", 0);
WinHttp.SetRequestHeader("Content-Type", "text/xml; charset=utf-8");
// КРИТИЧЕСКИ ВАЖНО: передаем сам объект DOM-документа, а не его XML-строку!
// Платформа 1С не упадет, даже если XML весит десятки мегабайт.
WinHttp.Send(ДомДокумент);
Сообщить("Статус ответа: " + WinHttp.Status);
КонецПроцедуры
Преимущества: максимальная надежность, полное отсутствие риска падения платформы из-за утечки памяти или переполнения буфера BSTR, корректная работа с кодировкой UTF-8 "из коробки".
Недостатки: данный подход работает исключительно для XML-формата. Если принимающий сервер требует JSON, напрямую передать DOM-объект не получится.
Что делать, если передаваемые данные должны быть отправлены в формате JSON или в виде бинарного файла, размер которого превышает критические 500 КБ? Здесь на помощь приходит объект ADODB.Stream.
Мы можем сформировать нужный нам JSON-файл на диске компьютера (например, в каталоге временных файлов), а затем прочитать его в двоичный поток ADODB.Stream и передать этот поток напрямую в метод Send().
Проанализируем пример кода:
Процедура ОтправитьБольшойJSON()
ПутьКФайлу = КаталогВременныхФайлов() + "payload.json";
// 1. Формируем JSON-файл на диске стандартным объектом Текст
ТекстФайл = СоздатьОбъект("Текст");
// Допустим, здесь очень длинная строка JSON, собранная ранее
ТекстФайл.ДобавитьСтроку(БольшаяСтрокаJSON);
ТекстФайл.Записать(ПутьКФайлу);
// 2. Считываем файл в двоичный поток ADODB.Stream
Stream = СоздатьОбъект("ADODB.Stream");
Stream.Type = 1; // Указываем тип данных: adTypeBinary (двоичный)
Stream.Open();
Stream.LoadFromFile(ПутьКФайлу);
// 3. Выполняем отправку потока без создания длинных строк в памяти 1С
WinHttp = СоздатьОбъект("Msxml2.ServerXMLHTTP.6.0");
WinHttp.Open("POST", "http://testtesttest.com/api/update_products", 0);
WinHttp.SetRequestHeader("Content-Type", "application/json; charset=utf-8");
// Передаем объект потока. Платформа успешно обработает этот вызов
WinHttp.Send(Stream);
Stream.Close();
// Удаляем временный файл за собой
ФС.УдалитьФайл(ПутьКФайлу);
Сообщить("Ответ сервера: " + WinHttp.ResponseText);
КонецПроцедуры
Преимущества: универсальный метод для отправки текстовых файлов (JSON, XML, CSV) и любых бинарных файлов (изображения, архивы) неограниченного размера без риска падения программы.
Недостатки: необходимость выполнения операций записи и чтения с жесткого диска (файловый кэш), что может незначительно снизить производительность на медленных дисковых подсистемах.
Еще один оригинальный и очень эффективный способ — перенос выполнения HTTP-запроса целиком во внешнюю среду выполнения сценариев Windows Script Host (VBScript/JScript). Среда выполнения скриптов работает в своем собственном адресном пространстве памяти и лишена каких-либо ограничений платформы 1С 7.7 на маршалинг длинных строк.
Посмотрим, как выглядит архитектура такого решения:
Функция ОтправитьЧерезСкрипт(АдресРесурса, ДанныеЗапроса)
Попытка
WinHttp = СоздатьОбъект("Msxml2.ServerXMLHTTP.6.0");
Скрипт = СоздатьОбъект("MSScriptControl.ScriptControl");
Скрипт.Language = "VBScript";
// Описываем функцию отправки на VBScript, которая примет наши COM-объекты
ТекстСкрипта =
"Function SendData(HttpObj, URL, Payload)
| HttpObj.Open ""POST"", URL, False
| HttpObj.setRequestHeader ""Content-Type"", ""application/x-www-form-urlencoded; charset=utf-8""
| HttpObj.Send Payload
| SendData = HttpObj.responseText
|End Function";
Скрипт.AddCode(ТекстСкрипта);
// Вызываем функцию скрипта, передавая туда ссылку на объект HTTP и данные
Результат = Скрипт.Run("SendData", WinHttp, АдресРесурса, ДанныеЗапроса);
Возврат Результат;
Исключение
Предупреждение("Ошибка при отправке: " + ОписаниеОшибки());
Возврат "";
КонецПопытки;
КонецФункции
Преимущества: отправка больших строк происходит напрямую из памяти скрипта. Исключается необходимость создания временных файлов на диске. Полностью обходит баг платформы 1С 7.7.
Недостатки: требует установленной в системе библиотеки msscript.ocx, которая на современных 64-битных версиях Windows может требовать дополнительной регистрации в реестре.
Для крупных проектов интеграции отличной альтернативой является использование консольной утилиты cUrl. Это решение раз и навсегда избавляет разработчика не только от проблем с памятью в 1С 7.7, но и решает еще одну критически важную проблему — поддержку современных протоколов безопасности шифрования.
Важное примечание: старые встроенные COM-объекты Windows (такие как Microsoft.XMLHTTP) используют системные библиотеки, которые на старых ОС не поддерживают современные защищенные протоколы TLS 1.2 и TLS 1.3. Из-за этого вызовы к современным сайтам могут завершаться ошибками рукопожатия SSL (Handshake Error). Утилита cUrl всегда содержит актуальные библиотеки шифрования.
Алгоритм работы через cUrl выглядит следующим образом:
curl.exe с необходимыми ключами командной строки через объект WshShell.Пример программного вызова внешнего cUrl:
Процедура ОтправитьЧерезCurl()
ФайлДанных = КаталогВременныхФайлов() + "request.json";
ФайлОтвета = КаталогВременныхФайлов() + "response.json";
// Записываем данные в файл
ТекстФайл = СоздатьОбъект("Текст");
ТекстФайл.ДобавитьСтроку(БольшаяСтрокаJSON);
ТекстФайл.Записать(ФайлДанных);
// Формируем команду для curl.exe
// Ключ -d @ указывает утилите прочитать тело запроса из файла
Команда = "curl.exe -X POST -H ""Content-Type: application/json"" -d @" + ФайлДанных + " http://testtesttest.com/api/update_products -o " + ФайлОтвета;
// Запуск через Windows Script Host Shell
WshShell = СоздатьОбъект("Wscript.Shell");
// Параметр 0 скрывает черное консольное окно, параметр 1 заставляет 1С ожидать завершения работы curl
WshShell.Run(Команда, 0, 1);
// Читаем ответ сервера
Ответ = СоздатьОбъект("Текст");
Ответ.Открыть(ФайлОтвета);
Если Ответ.КоличествоСтрок() > 0 Тогда
Сообщить("Ответ успешно получен и сохранен!");
КонецЕсли;
// Удаляем временные файлы
ФС.УдалитьФайл(ФайлДанных);
ФС.УдалитьФайл(ФайлОтвета);
КонецПроцедуры
Преимущества: 100% стабильность работы, поддержка любых объемов данных, поддержка TLS 1.2 и TLS 1.3, возможность тонкой настройки таймаутов и заголовков запроса.
Недостатки: необходимость наличия исполняемого файла curl.exe на рабочем компьютере или сервере, где выполняется обработка.
Мы проанализировали причины нестабильной работы платформы 1С 7.7 при интенсивном веб-обмене и рассмотрели эффективные архитектурные решения. Чтобы выбрать наиболее подходящий вариант для вашей задачи, воспользуйтесь следующими рекомендациями: