При интеграции 1С со сторонними веб-сервисами (например, сервисами дистрибьюторов) разработчики часто сталкиваются с ситуацией, когда инициализация сервиса проходит успешно, но при вызове конкретной операции платформа выдает исключение:
«Ошибка преобразования данных XDTO: Чтение объекта типа: {...}ArrayOfCatalogResult. Проверка дополнительного свойства: форма: Элемент, имя: item. Структура объекта не соответствует типу».
Проанализируем природу этой ошибки, выясним технические ограничения платформы 1С и разберем практические способы решения задачи исключительно штатными средствами платформы.
Разберем по шагам, что происходит в момент вызова метода через WSПрокси:
/rl/) или специфичную реализацию веб-сервиса (часто на базе PHP). В таких спецификациях возвращаемые массивы упаковываются в контейнеры с элементами item. Платформа 1С при создании прокси-объекта жестко ориентируется на спецификацию Document/Literal (Wrapped). При синтаксическом разборе ответа валидатор XDTO наталкивается на тег item, который не описан в схеме явным образом как допустимое свойство типа, и генерирует исключение.Вызов методов наподобие получения полного каталога через стандартный WSПрокси несет в себе два фундаментальных ограничения:
WSПрокси валидация ответа по внутренней модели XDTO выполняется принудительно. Если структура XML хотя бы на один тег отличается от контракта WSDL, прочитать ответ целиком через прокси невозможно.WSПрокси считывает весь XML-ответ в оперативную память для построения объектной модели. Если метод возвращает полный каталог номенклатуры объемом в десятки или сотни мегабайт, клиентский или серверный процесс 1С аварийно завершится с ошибкой нехватки памяти.Многие современные внешние API поддерживают параллельную работу по разным схемам. Если сервис предоставляет эндпоинт в формате Document/Literal, достаточно скорректировать адрес подключения в коде.
Посмотрим на пример: если в исходном коде использовался URL с сегментом /rl/ (RPC/Literal), заменим его на /dl/ (Document/Literal):
Определения = Новый WSОпределения("https://api.merlion.com/dl/mlservice3?wsdl", Пользователь, Пароль);
ВебСервис = Новый WSПрокси(Определения, "https://api.merlion.com/dl/mlservice3", "MLService", "MLPort");
ВебСервис.Пользователь = Пользователь;
ВебСервис.Пароль = Пароль;
Результат = ВебСервис.getCatalog("All");
В режиме /dl/ описание WSDL и формируемый XML-ответ строго соответствуют стандартам Document/Literal, поэтому десериализация коллекции происходит без ошибок структуры XDTO.
Если изменить точку подключения на стороне провайдера невозможно, скорректируем описание сервиса вручную:
ArrayOfCatalogResult).item с целевым типом элемента каталога и атрибутом maxOccurs="unbounded", а также согласуем директиву elementFormDefault.WSОпределения, передавая путь к локальному исправленному файлу WSDL, но вызывая реальный удаленный веб-сервер:
ПутьКФайлуWSDL = "C:\WSDL\mlservice3_fixed.wsdl";
Определения = Новый WSОпределения(ПутьКФайлуWSDL, Пользователь, Пароль);
ВебСервис = Новый WSПрокси(Определения, "https://api.merlion.com/rl/mlservice3", "MLService", "MLPort");
ВебСервис.Пользователь = Пользователь;
ВебСервис.Пароль = Пароль;
Результат = ВебСервис.getCatalog("All");
Рассмотрим наиболее надежный и масштабируемый подход. Для методов, возвращающих большие каталоги, стандартной практикой является отказ от WSПрокси в пользу прямого выполнения HTTP-запроса через объект HTTPСоединение с последующим потоковым чтением XML через ЧтениеXML.
Этот метод полностью устраняет проблемы со строгой проверкой XDTO и не расходует память на хранение всего дерева элементов.
Разберем реализацию по шагам:
ЧтениеXML, точечно считывая каждый узел номенклатуры с помощью объекта ФабрикаXDTO или СериализаторXDTO.Посмотрим на пример кода:
ИмяСервера = "api.merlion.com";
Ресурс = "/rl/mlservice3";
ЗащищенноеСоединение = Новый ЗащищенноеСоединениеOpenSSL();
Соединение = Новый HTTPСоединение(ИмяСервера, 443, Пользователь, Пароль, , , ЗащищенноеСоединение);
ТекстЗапросаSOAP =
"<soapenv:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" " +
"xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" " +
"xmlns:soapenv=""http://schemas.xmlsoap.org/soap/envelope/"" " +
"xmlns:mls=""https://api.merlion.com/rl/mlservice3"">" +
" <soapenv:Header/>" +
" <soapenv:Body>" +
" <mls:getCatalog soapenv:encodingStyle=""http://schemas.xmlsoap.org/soap/encoding/"">" +
" <cat xsi:type=""xsd:string"">All</cat>" +
" </mls:getCatalog>" +
" </soapenv:Body>" +
"</soapenv:Envelope>";
Заголовки = Новый Соответствие();
Заголовки.Вставить("Content-Type", "text/xml; charset=utf-8");
Заголовки.Вставить("SOAPAction", "");
Запрос = Новый HTTPЗапрос(Ресурс, Заголовки);
Запрос.УстановитьТелоИзСтроки(ТекстЗапросаSOAP, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
ВременныйФайл = ПолучитьИмяВременногоФайла("xml");
Попытка
Ответ = Соединение.ОтправитьДляОбработки(Запрос, ВременныйФайл);
Если Ответ.КодСостояния = 200 Тогда
// Потоковый разбор полученного XML без переполнения памяти
Чтение = Новый ЧтениеXML();
Чтение.ОткрытьФайл(ВременныйФайл);
Пока Чтение.Прочитать() Цикл
Если Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента И Чтение.Имя = "item" Тогда
// Здесь обрабатываем каждый отдельный элемент каталога
// либо считывая вложенные атрибуты, либо десериализуя отдельный узел
КонецЕсли;
КонецЦикла;
Чтение.Закрыть();
Иначе
ВызватьИсключение "Ошибка сервиса. Код ответа: " + Ответ.КодСостояния;
КонецЕсли;
ФиналКода
УдалитьФайлы(ВременныйФайл);
КонецПопытки;
Такой подход гарантирует стабильность обмена данными в 1С независимо от объема загружаемого каталога и некорректностей в WSDL-описаниях внешнего сервиса.