При интеграции 1С:Предприятия со сторонними веб-сервисами (особенно разработанными на языках Java, C# или PHP) разработчики часто сталкиваются со строгими требованиями к структуре XML-пакетов. Типичная проблема возникает, когда метод веб-сервиса содержит необязательные параметры. В схеме WSDL такие элементы помечены свойством minOccurs="0". Это означает, что на уровне XML-документа данные теги могут полностью отсутствовать.
Однако платформа 1С:Предприятие при работе через стандартный объект WSПрокси накладывает жесткое ограничение: сигнатура метода в коде должна строго соответствовать количеству параметров, описанных в WSDL. Если метод принимает 4 параметра, мы обязаны передать в вызов функции ровно 4 аргумента. Попытка опустить необязательный параметр в коде 1С приводит к ошибке компиляции или выполнения: «Недостаточно фактических параметров».
Если же мы передаем в качестве четвертого параметра значение Неопределено, платформа 1С принудительно сериализует его в SOAP-запрос в виде пустого тега (например, <runAs/>) или добавляет атрибут xsi:nil="true". Строгие валидаторы внешних серверов часто не переносят пустые теги или признак nil для строковых типов, возвращая ошибку SERVER_ERROR или выбрасывая внутреннее исключение NullPointerException. Выясним причину такого поведения и разберем по шагам несколько эффективных способов решения этой проблемы.
Проанализируем ситуацию с технической стороны. По умолчанию 1С импортирует WSDL-описание в режиме document/literal wrapped. Это значит, что плоский список параметров метода в коде 1С при генерации запроса автоматически «оборачивается» в единый XML-конверт. Если параметр имеет свойство minOccurs="0", но не помечен как nillable="true" (возможно пустое), передача Неопределено из 1С приводит к некорректной генерации SOAP-сообщения:
Неопределено через плоский список аргументов WSПрокси, возникает неразрешимый конфликт сигнатур.Для решения этой проблемы рассмотрим три практических метода: от локальной корректировки WSDL-схемы до ручного формирования SOAP-конверта.
Это наиболее элегантный способ, который позволяет продолжить использование стандартного объекта WSПрокси и фабрики XDTO, не внося изменений в конфигурацию 1С и не переписывая интеграцию на HTTP-запросы. Суть метода заключается в том, что мы сохраняем оригинальный файл описания WSDL, правим его под нужды 1С и инициализируем подключение из локального источника.
Выполним процедуру по шагам:
.wsdl или .xml.SaveDataObjectRecord. Описание параметров в схеме выглядит следующим образом:
<xsd:complexType name="SaveDataObjectRecord">
<xsd:sequence>
<xsd:element name="DataObjectRecord" type="ns0:dataObjectRecord" form="qualified" minOccurs="0"/>
<xsd:element name="lang" type="xsd:string" form="qualified" minOccurs="0"/>
<xsd:element name="change_version" type="xsd:boolean" form="qualified"/>
<xsd:element name="runAs" type="xsd:string" form="qualified" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
runAs вызывает ошибку на сервере при передаче пустых значений, а сам поставщик сервиса разрешает вызывать метод без него, мы можем полностью удалить описание этого элемента из локальной схемы. Удаляем строку:
<xsd:element name="runAs" type="xsd:string" form="qualified" minOccurs="0"/>
// Получаем путь к локальному файлу WSDL из макета двоичных данных
ПутьКФайлуWSDL = ПолучитьИмяВременногоФайла("wsdl");
МакетWSDL = Справочники.МакетыИнтеграции.ПолучитьМакетWSDL(); // Пример получения макета
МакетWSDL.Записать(ПутьКФайлуWSDL);
// Инициализируем WSОпределения на основе локального скорректированного файла
Определение = Новый WSОпределения(ПутьКФайлуWSDL, Пользователь, Пароль);
// Создаем прокси стандартным образом
Прокси = Новый WSПрокси(Определение, ПространствоИмен, ИмяСервиса, ТочкаПодключения);
Прокси.Пользователь = Пользователь;
Прокси.Пароль = Пароль;
// Теперь 1С ожидает ровно 3 параметра, и вызов завершится успешно:
Параметр_DataObjectRecord = ПолучитьОбъектDataObjectRecord(Прокси);
Параметр_lang = "RU";
Параметр_change_version = Ложь;
ПолученныеДанные = Прокси.SaveDataObjectRecord(Параметр_DataObjectRecord, Параметр_lang, Параметр_change_version);
При таком подходе платформа 1С сформирует SOAP-запрос, содержащий ровно 3 XML-элемента. Сторонний сервер успешно примет и обработает этот пакет, так как отсутствие четвертого параметра полностью укладывается в его внутреннюю бизнес-логику.
Если веб-сервис поставщика часто меняется, или вы не хотите заниматься администрированием локальных файлов WSDL, существует надежный альтернативный вариант. Мы можем полностью отказаться от использования WSПрокси и сформировать тело XML-запроса вручную. Это гарантирует, что в XML-пакете будут присутствовать только те теги и атрибуты, которые мы туда запишем.
Рассмотрим пример реализации такого подхода по шагам:
runAs, оставив только необходимые параметры.ЗаписьXML для динамического формирования тела запроса.HTTPСоединение методом POST.Посмотрим на пример кода, реализующий данную схему работы:
// Шаг 1. Создаем файл для формирования тела запроса
ИмяФайлаЗапроса = ПолучитьИмяВременногоФайла("xml");
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл(ИмяФайлаЗапроса, "UTF-8");
ЗаписьXML.ЗаписатьОбъявлениеXML();
// Шаг 2. Записываем SOAP-конверт вручную
ЗаписьXML.ЗаписатьНачалоЭлемента("soapenv:Envelope");
ЗаписьXML.ЗаписатьАтрибут("xmlns:soapenv", "http://schemas.xmlsoap.org/soap/envelope/");
ЗаписьXML.ЗаписатьАтрибут("xmlns:web", ПространствоИмен);
ЗаписьXML.ЗаписатьНачалоЭлемента("soapenv:Header");
// При необходимости здесь можно разместить заголовки авторизации WS-Security
ЗаписьXML.ЗаписатьКонецЭлемента(); // soapenv:Header
ЗаписьXML.ЗаписатьНачалоЭлемента("soapenv:Body");
ЗаписьXML.ЗаписатьНачалоЭлемента("web:SaveDataObjectRecord");
// Записываем структуру первого комплексного параметра DataObjectRecord
ЗаписьXML.ЗаписатьНачалоЭлемента("web:DataObjectRecord");
// Заполняем свойства объекта DataObjectRecord (в соответствии с требованиями схемы)
ЗаписьXML.ЗаписатьСодержимоеЭлемента("12345", "web:id");
ЗаписьXML.ЗаписатьСодержимоеЭлемента("Тестовая запись", "web:name");
ЗаписьXML.ЗаписатьКонецЭлемента(); // web:DataObjectRecord
// Записываем простые параметры метода
ЗаписьXML.ЗаписатьСодержимоеЭлемента("RU", "web:lang");
ЗаписьXML.ЗаписатьСодержимоеЭлемента("false", "web:change_version");
// Обратите внимание: параметр runAs мы просто игнорируем, тег не создается!
ЗаписьXML.ЗаписатьКонецЭлемента(); // web:SaveDataObjectRecord
ЗаписьXML.ЗаписатьКонецЭлемента(); // soapenv:Body
ЗаписьXML.ЗаписатьКонецЭлемента(); // soapenv:Envelope
ЗаписьXML.Закрыть();
// Шаг 3. Отправляем подготовленный XML-файл через HTTPСоединение
Соединение = Новый HTTPСоединение("srs.gs1ru.org", , Пользователь, Пароль, , , Новый ЗащищенноеСоединениеOpenSSL);
Заголовки = Новый Соответствие;
Заголовки.Вставить("Content-Type", "text/xml; charset=utf-8");
// SOAPAction может быть обязательным заголовком в зависимости от спецификации сервиса
Заголовки.Вставить("SOAPAction", ПространствоИмен + "/SaveDataObjectRecord");
Запрос = Новый HTTPЗапрос("/GS46_Interfaces/GS1RU_Operations", Заголовки);
Запрос.УстановитьИмяФайлаТела(ИмяФайлаЗапроса);
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
// Шаг 4. Анализируем ответ сервера
Если Ответ.КодСостояния = 200 Тогда
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл(Ответ.ПолучитьИмяФайлаТела());
// Читаем и разбираем полученный XML-ответ от сервера
Пока ЧтениеXML.Прочитать() Цикл
// Логика разбора ответа
КонецЦикла;
ЧтениеXML.Закрыть();
Иначе
ВызватьИсключение "Ошибка выполнения SOAP-запроса: " + Ответ.ПолучитьТелоКакСтроку();
КонецЕсли;
// Удаляем временный файл запроса
УдалитьФайлы(ИмяФайлаЗапроса);
Данный подход является максимально надежным и полностью изолирует код 1С от любых ограничений со стороны платформенного парсера WSDL-схем. Вы полностью контролируете структуру XML на выходе.
Если вы используете статическую WS-ссылку, добавленную непосредственно в дерево метаданных конфигурации, вы можете попытаться исправить поведение платформы без изменения программного кода вызова. Для этого проанализируем структуру импортированного XDTO-пакета.
Разберем по шагам порядок настройки свойств элемента схемы:
SaveDataObjectRecord и выберите входящее в него свойство runAs.Истина, измените его на Ложь. Это предотвратит принудительное добавление платформой атрибута xsi:nil="true", который отторгается сервером назначения.0.После изменения данных настроек платформа в ряде версий при передаче аргумента Неопределено сможет корректно исключить тег runAs из финальной XML-сериализации, при этом не ругаясь на количество аргументов при вызове метода прокси-класса.
Если вы хотите сохранить объектный подход работы через WS-прокси, но избежать жестких рамок плоской структуры параметров, можно изменить способ разбора WSDL-описания. Если WSDL-описание составлено так, что имена сообщений запроса не совпадают с именами операций, платформа 1С распознает такую операцию не как набор отдельных параметров, а как метод, принимающий один комплексный параметр (XDTO-объект).
В таком случае вызов метода в коде будет выглядеть следующим образом:
// 1. Получаем тип параметра-обертки из фабрики XDTO
ТипПараметра = Прокси.ФабрикаXDTO.Тип(ПространствоИмен, "SaveDataObjectRecord");
// 2. Создаем сам объект параметров
ОбъектПараметров = Прокси.ФабрикаXDTO.Создать(ТипПараметра);
// 3. Заполняем только нужные свойства
ОбъектПараметров.DataObjectRecord = ПолучитьОбъектDataObjectRecord(Прокси);
ОбъектПараметров.lang = "RU";
ОбъектПараметров.change_version = Ложь;
// Свойство runAs мы оставляем незаполненным.
// Так как в схеме его minOccurs = 0, платформа 1С просто не добавит этот тег в XML при сериализации!
// 4. Вызываем метод, передавая ему ЕДИНСТВЕННЫЙ параметр-объект
ПолученныеДанные = Прокси.SaveDataObjectRecord(ОбъектПараметров);
Этот способ совмещает в себе удобство работы с типизированными объектами 1С и гибкость формирования результирующего XML-пакета без лишних пустых тегов.
Мы детально изучили проблему передачи переменного числа аргументов в методы WS-ссылок 1С и рассмотрели все возможные варианты обхода этого платформенного ограничения. Выберите наиболее подходящий вариант для вашей архитектуры и обеспечьте стабильный обмен данными с внешними информационными системами.