При интеграции платформы 1С с внешними SOAP веб-сервисами разработчики нередко сталкиваются с ситуацией, когда инициализация прокси проходит успешно, но при вызове конкретной операции платформа возвращает исключение:
«При вызове веб-сервиса произошла ошибка. Неизвестная ошибка. Несоответствие типов XDTO: Свойство является списковым».
Разберем природу возникновения этой ошибки, выясним, почему встроенный механизм платформы так реагирует на ответ стороннего сервиса, и рассмотрим все практические способы решения данной проблемы.
Проанализируем архитектуру работы платформы 1С с протоколом SOAP. Платформа строго придерживается стандарта WS-I Basic Profile 1.1. Согласно этому профилю, допустимым стилем описания веб-сервисов является Document/Literal (или Document/Wrapped). Использование устаревшего стиля RPC/Encoded платформой официально не поддерживается.
В веб-сервисах со стилем кодирования RPC/Encoded массивы передаются через специальные конструкции наподобие SOAP-ENC:arrayType или soapenc:Array. Встроенная фабрика XDTO 1С при десериализации полученного XML-пакета не может сопоставить эту структуру с жестко типизированной объектной моделью. В результате валидатор ожидает скалярный тип данных, но натыкается на массив элементов, что приводит к фатальному сбою десериализации.
Другая распространенная причина кроется в самом описании схемы XSD внутри WSDL. Если в схеме тип поля оформлен через конструктор <xsd:all> вместо <xsd:sequence>, в нем по спецификации W3C запрещено использование свойств со списковыми значениями (maxOccurs > 1). Если же сервер возвращает несколько одноименных элементов, платформа 1С считает это грубым нарушением схемы.
Рассмотрим самый быстрый и правильный способ решения проблемы, не требующий написания обходных путей. Многие поставщики API (включая дистрибьютора Merlion) поддерживают параллельные точки входа для разных стандартов передачи данных. Обратите внимание на путь в URL веб-сервиса:
/re/ в адресе означает RPC/Encoded — данный формат вызывает ошибку в 1С;/rl/ означает RPC/Literal;/dl/ означает Document/Literal — полностью совместимый с 1С стандарт.Заменим адрес сервиса в коде конфигурации на конечную точку /dl/. Посмотрим на пример рабочего кода подключения:
Пользователь = "Логин";
Пароль = "Пароль";
// Используем адрес с маркером dl вместо re
АдресWSDL = "https://api.merlion.com/dl/mlservice3?wsdl";
URIПространстваИмен = "https://api.merlion.com/dl/mlservice3";
Определения = Новый WSОпределения(АдресWSDL, Пользователь, Пароль);
ВебСервис = Новый WSПрокси(Определения, URIПространстваИмен, "MLService", "MLPort");
ВебСервис.Пользователь = Пользователь;
ВебСервис.Пароль = Пароль;
Результат = ВебСервис.getCatalog("All");
При таком подходе фабрика XDTO корректно считывает типы данных, и результат возвращается в виде стандартных объектов ОбъектXDTO или коллекций СписокXDTO, которые можно обходить стандартным циклом.
Если поставщик сервиса не предоставляет конечную точку в формате Document/Literal, разберем по шагам способ с переопределением описания сервиса на стороне клиента:
<xsd:all> на блок <xsd:sequence>.minOccurs="0" и maxOccurs="unbounded".WSОпределения указываем путь к отредактированному локальному файлу, а в объекте WSПрокси оставляем сетевой адрес реального веб-сервиса.
ПутьКЛокальномуWSDL = "C:\WS\mlservice3_fixed.wsdl";
URIПространстваИмен = "https://api.merlion.com/re/mlservice3";
Определения = Новый WSОпределения(ПутьКЛокальномуWSDL, Пользователь, Пароль);
ВебСервис = Новый WSПрокси(Определения, URIПространстваИмен, "MLService", "MLPort");
ВебСервис.Пользователь = Пользователь;
ВебСервис.Пароль = Пароль;
Результат = ВебСервис.getCatalog("All");
Механизм WSПрокси выполняет строгую проверку входящих и исходящих XML-пакетов, которую невозможно отключить программно. Если WSDL содержит несовместимые конструкции, наиболее надежным способом на чистой платформе 1С является формирование низкоуровневого HTTP-запроса. Проанализируем реализацию такого вызова:
Сервер = "api.merlion.com";
Ресурс = "/re/mlservice3";
Соединение = Новый HTTPСоединение(Сервер, 443, Пользователь, Пароль, , , Новый ЗащищенноеСоединениеOpenSSL());
Тело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:mer=""https://api.merlion.com/re/mlservice3"">
| <soapenv:Header/>
| <soapenv:Body>
| <mer:getCatalog>
| <cat_id xsi:type=""xsd:string"">All</cat_id>
| </mer:getCatalog>
| </soapenv:Body>
|</soapenv:Envelope>";
Запрос = Новый HTTPЗапрос(Ресурс);
Запрос.Заголовки.Вставить("Content-Type", "text/xml; charset=utf-8");
Запрос.Заголовки.Вставить("SOAPAction", "https://api.merlion.com/re/mlservice3#getCatalog");
Запрос.УстановитьТелоИзСтроки(ТелоSOAP, КодировкаТекста.UTF8);
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
Если Ответ.КодСостояния = 200 Тогда
СтрокаОтветаXML = Ответ.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
Чтение = Новый ЧтениеXML;
Чтение.УстановитьСтроку(СтрокаОтветаXML);
DOM = Новый СоздательDOM;
ДокументDOM = DOM.СоздатьДокумент(Чтение);
// Разбираем узлы DOM без участия жестких схем XDTO
КонецЕсли;
Если требуется выгружать большие объемы данных с высокой скоростью десериализации, рассмотрим вариант применения WCF-клиента на платформе .NET, подключаемого к 1С через интерфейс COMОбъект.
Посмотрим на пример инициализации и обработки каталога в 1С с использованием классов .NET:
Врап = Новый COMОбъект("NetObjectToIDispatch45");
ФайлСборки = "C:\APILib\MerlionApi.dll";
MLPortClient = Врап.ПолучитьТипИзСборки("MerlionApi.com.merlion.api.MLService", ФайлСборки);
Клиент = Врап.СоздатьОбъект(MLPortClient);
СетевыеРеквизиты = Врап.СоздатьОбъект("System.Net.NetworkCredential", Логин, Пароль);
URLСервиса = "https://api.merlion.com/dl/mlservice3";
URIОбъект = Врап.СоздатьОбъект("System.Uri", URLСервиса);
Клиент.Credentials = СетевыеРеквизиты.GetCredential(URIОбъект, "Basic");
Клиент.PreAuthenticate = Истина;
Товары = Клиент.getCatalog("N1");
Для Каждого Стр Из Товары Цикл
Сообщить("ИД: " + Стр.ID + ", Наименование: " + Стр.Description);
КонецЦикла;
При передаче исходящих параметров в списковые свойства объектов XDTO помните важное правило: прямое присвоение значения переменной вызовет ошибку «Свойство является списковым». Значения следует добавлять исключительно в коллекцию:
// Ошибочно: ОбъектXDTO.Items = Элемент;
// Правильно:
ОбъектXDTO.Items.Добавить(Элемент);
Также при регулярной работе с API поставщиков учитывайте лимиты обращений к серверу (обычно действует ограничение до 1–2 запросов в секунду). При превышении лимита сервис вернет ошибку. Чтобы исключить аварийное завершение регламентного задания или обработки, обернем вызов в блок Попытка ... Исключение с механизмом паузы и повтора:
Успешно = Ложь;
Попыток = 0;
Пока Не Успешно И Попыток < 5 Цикл
Попытка
Попыток = Попыток + 1;
ДанныеЗаказа = ВебСервис.getOrderLines(НомерЗаказа, "0");
Успешно = Истина;
Исключение
// Делаем паузу перед повторным запросом
Приостановить(2);
КонецПопытки;
КонецЦикла;