При интеграции различных информационных систем на платформе 1С:Предприятие разработчики часто сталкиваются с веб-сервисами (SOAP). Одной из самых распространенных и неприятных проблем при импорте или вызове стороннего SOAP-сервиса является ошибка WSDLException (at /html): faultCode=INVALID_WSDL: Expected element '{http://schemas.xmlsoap.org/wsdl/}definitions'. Дополнительно разработчики часто задаются вопросом: обязательно ли создавать и привязывать собственный пакет XDTO для работы веб-сервиса, если параметры и возвращаемые значения представляют собой простые типы данных?
В этой статье мы подробно разберем природу возникновения ошибки WSDLException, выясним, почему веб-сервер возвращает HTML вместо корректного XML-описания, а также докажем, что веб-сервисы в 1С могут полноценно функционировать без использования пользовательских пакетов XDTO. Мы рассмотрим детальные пошаговые сценарии обхода этой проблемы как на стороне сервера, так и на стороне клиента.
Для того чтобы понять суть проблемы, проанализируем механизм работы платформы 1С при создании объекта WSПрокси. Когда в коде вызывается конструктор для получения динамического прокси-класса на основании удаленного WSDL-описания, платформа отправляет GET-запрос по указанному адресу. Она ожидает получить строго валидный XML-документ, корневым элементом которого является <wsdl:definitions>.
Если в ответ на этот запрос веб-сервер (IIS или Apache) присылает стандартную HTML-страницу, XML-парсер платформы 1С моментально выдает ошибку, так как первым тегом в полученном файле оказывается <html>. Код faultCode=INVALID_WSDL прямо говорит о том, что полученный файл не соответствует стандарту WSDL.
Связь этой ошибки с HTTP 500 Internal Server Error очевидна: при возникновении критического сбоя на стороне веб-сервера или в процессе инициализации сеанса 1С сервер формирует HTML-страницу с описанием ошибки 500 и отправляет ее клиенту вместо ожидаемого XML. Рассмотрим наиболее частые причины этого сбоя на стороне сервера:
IUSR или IIS_IUSRS в Windows, либо www-data в Linux), должен иметь полные права на чтение и выполнение из каталога установки платформы 1С (папка bin), а также права на чтение файла публикации default.vrd.ПриНачалеРаботыСистемы или УстановкаПараметровСеанса написан код, который пытается открыть какую-либо форму, вызывает интерактивные диалоги или некорректно инициализирует параметры сеанса без проверки типа клиента, сервер 1С аварийно завершит этот сеанс, выдав ошибку 500.default.vrd не настроен пул соединений). При нехватке лицензий попытка инициализации сеанса провалится.Отвечая на принципиальный вопрос автора темы: да, веб-сервис в 1С может полноценно работать без указания собственного пакета XDTO. Назначение пакета XDTO в свойствах веб-сервиса необходимо только тогда, когда вам требуется передавать сложные структуры данных (например, контрагента с табличной частью адресов, документы со специфической иерархией и т.д.).
Выделим три основных подхода к проектированию веб-сервисов без создания уникальных пакетов XDTO:
string), число (int, decimal), дата (dateTime) или булево (boolean), вы можете использовать предопределенные типы из пространства имен XML Schema (обычно это http://www.w3.org/2001/XMLSchema). Эти типы поддерживаются платформой 1С "из коробки" и не требуют никаких дополнительных настроек.http://v8.1c.ru/8.1/data/core позволяет передавать такие структуры данных, как ValueStorage (ХранилищеЗначений), Structure (Структура), Map (Соответствие) или ValueTable (ТаблицаЗначений).string и возвращаемым значением типа string. Внутри этого строкового параметра передается сериализованный JSON или XML-документ.Проанализируем преимущества и недостатки подхода «Универсальный транспорт»:
ЧтениеJSON, ЗаписьJSON).Если у вас нет прямого доступа к серверу поставщика услуги для исправления ошибки HTTP 500, но настроить интеграцию жизненно необходимо, рассмотрим два обходных пути, которые можно реализовать полностью на стороне клиента.
Этот метод применим, если поставщик сервиса может хотя бы один раз выгрузить корректный файл WSDL-описания (например, через Конфигуратор с помощью команды «Сохранить WSDL в файл») и передать его вам. В этом случае мы уходим от необходимости динамического запроса WSDL с проблемного веб-сервера при каждом создании прокси.
Разберем по шагам, как настроить это решение программно в вашей базе 1С:
C:\WSDL\SupplierService.wsdl).WSОпределения, указав путь к локальному файлу, а не удаленный URL.WSПрокси, переопределив в параметрах адрес подключения на реальный работающий сервер публикации.Рассмотрим пример кода на языке 1С:
// Определяем параметры подключения и пути
ПутьКЛокальномуWSDL = "C:\WSDL\SupplierService.wsdl";
АдресВебСервиса = "http://srv-pub.supplier.ru/trade/ws/Exchange.1cws";
ПространствоИмен = "http://www.supplier.ru/exchange";
ИмяСервиса = "Exchange";
ИмяТочкиПодключения = "ExchangeSoap";
Пользователь = "ExchangeUser";
Пароль = "SecurePassword123";
Попытка
// Инициализируем WSОпределения на основе локального статического файла
Определения = Новый WSОпределения(ПутьКЛокальномуWSDL, Пользователь, Пароль);
// Создаем прокси-объект
Прокси = Новый WSПрокси(Определения, ПространствоИмен, ИмяСервиса, ИмяТочкиПодключения);
// Обязательно переопределяем адрес точки подключения на реальный URL
// (так как в локальном файле может быть прописан неактуальный или внутренний адрес разработчика)
Прокси.ПолучитьТочкуПодключения().Адрес = АдресВебСервиса;
// Передаем параметры авторизации для выполнения запросов
Прокси.Пользователь = Пользователь;
Прокси.Пароль = Пароль;
// Вызываем целевой метод веб-сервиса
ВходнаяСтрока = "Тестовые данные для отправки";
РезультатВыполнения = Прокси.ВыполнитьОперацию(ВходнаяСтрока);
Если РезультатВыполнения Тогда
Сообщить("Запрос успешно обработан веб-сервисом!");
Иначе
Сообщить("Сервер вернул отрицательный результат (Ложь).");
КонецЕсли;
Исключение
ЗаписьЖурналаРегистрации("Интеграция.WS",
УровеньЖурналаРегистрации.Ошибка, , ,
"Ошибка при вызове веб-сервиса через локальный WSDL: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение "Не удалось выполнить интеграционную операцию!";
КонецПопытки;
Если получить файл WSDL не представляется возможным, но вы точно знаете название операции, имена параметров и адрес публикации, вы можете полностью отказаться от использования встроенных механизмов 1С для работы с веб-сервисами (таких как WSОпределения и WSПрокси).
Мы можем собрать тело SOAP-конверта вручную в виде XML-строки и отправить его на сервер с помощью стандартного объекта HTTPСоединение. Этот подход исключает этап разбора схемы WSDL на клиенте.
Рассмотрим подробный пример реализации ручной сборки и отправки SOAP-запроса:
// Настройки подключения к серверу
СерверРесурса = "srv-pub.supplier.ru";
ПортРесурса = 80;
ПутьКПубликации = "/trade/ws/Exchange.1cws";
Пользователь = "ExchangeUser";
Пароль = "SecurePassword123";
// 1. Формируем тело XML-запроса (SOAP Envelope) вручную
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку("UTF-8");
ЗаписьXML.ЗаписатьОбъявлениеXML();
// Открываем конверт SOAP
ЗаписьXML.ЗаписатьНачалоЭлемента("soapenv:Envelope");
ЗаписьXML.ЗаписатьСоответствиеПространстваИмен("soapenv", "http://schemas.xmlsoap.org/soap/envelope/");
ЗаписьXML.ЗаписатьСоответствиеПространстваИмен("exch", "http://www.supplier.ru/exchange");
// Пустой блок заголовков Header (требуется стандартом SOAP)
ЗаписьXML.ЗаписатьНачалоЭлемента("soapenv:Header");
ЗаписьXML.ЗаписатьКонецЭлемента(); // Header
// Блок Body, содержащий имя вызываемого метода и параметры
ЗаписьXML.ЗаписатьНачалоЭлемента("soapenv:Body");
ЗаписьXML.ЗаписатьНачалоЭлемента("exch:ВыполнитьОперацию"); // Имя метода
ЗаписьXML.ЗаписатьНачалоЭлемента("exch:ВходнаяСтрока"); // Имя входного параметра
ЗаписьXML.ЗаписатьТекст("Передаваемое значение строки");
ЗаписьXML.ЗаписатьКонецЭлемента(); // ВходнаяСтрока
ЗаписьXML.ЗаписатьКонецЭлемента(); // ВыполнитьОперацию
ЗаписьXML.ЗаписатьКонецЭлемента(); // Body
ЗаписьXML.ЗаписатьКонецЭлемента(); // Envelope
ТелоЗапросаXML = ЗаписьXML.Закрыть();
// 2. Отправляем сформированный SOAP-конверт через HTTP-соединение
Попытка
HTTPСоединение = Новый HTTPСоединение(СерверРесурса, ПортРесурса, Пользователь, Пароль);
HTTPЗапрос = Новый HTTPЗапрос(ПутьКПубликации);
// Устанавливаем необходимые HTTP-заголовки
HTTPЗапрос.Заголовки.Вставить("Content-Type", "text/xml; charset=utf-8");
// Заголовок SOAPAction обязателен для SOAP 1.1 (может быть пустым или содержать URI метода)
HTTPЗапрос.Заголовки.Вставить("SOAPAction", "http://www.supplier.ru/exchange#Exchange:ВыполнитьОперацию");
// Загружаем тело запроса
HTTPЗапрос.УстановитьТелоИзСтроки(ТелоЗапросаXML, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
// Отправляем POST-запрос
HTTPОтвет = HTTPСоединение.ОтправитьДляОбъекта(HTTPЗапрос);
// Анализируем полученный ответ
Если HTTPОтвет.КодСостояния = 200 Тогда
СтрокаОтветаXML = HTTPОтвет.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
// Читаем полученный XML и извлекаем булево значение
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(СтрокаОтветаXML);
РезультатБулево = Неопределено;
Пока ЧтениеXML.Прочитать() Цикл
// Ищем узел с возвращаемым значением метода
Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента И ЧтениеXML.ЛокальноеИмя = "return" Тогда
ЧтениеXML.Прочитать(); // Переходим к текстовому содержимому узла
Если ЧтениеXML.ТипУзла = ТипУзлаXML.Текст Тогда
ЗначениеТекста = ВРег(СокрЛП(ЧтениеXML.Значение));
РезультатБулево = (ЗначениеТекста = "TRUE" Или ЗначениеТекста = "1" Или ЗначениеТекста = "ИСТИНА");
КонецЕсли;
Прервать;
КонецЕсли;
КонецЦикла;
Если РезультатБулево = Неопределено Тогда
Сообщить("Не удалось извлечь результат из ответа сервера.", СтатусСообщения.Важное);
Иначе
Сообщить("Успешный ручной вызов! Результат выполнения: " + РезультатБулево);
КонецЕсли;
Иначе
Сообщить("Ошибка вызова веб-сервиса! Код состояния сервера: " + HTTPОтвет.КодСостояния, СтатусСообщения.Важное);
Сообщить("Текст ошибки от сервера: " + HTTPОтвет.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8));
КонецЕсли;
Исключение
Сообщить("Сбой сетевого соединения при отправке SOAP-запроса: " + ОписаниеОшибки(), СтатусСообщения.Важное);
КонецПопытки;
Проанализировав все технические аспекты, подведем итог нашей совместной работы по разбору данной проблемы:
WSDLException (at /html) однозначно указывает на то, что веб-сервер при попытке отдать WSDL-схему столкнулся с внутренней ошибкой (HTTP 500) и вернул HTML-страницу со сведениями об ошибке вместо XML. Решать эту проблему необходимо в первую очередь со стороны веб-сервера (проверка прав доступа, проверка разрядности пулов приложений IIS и проверка кода модулей 1С на запуск интерактивных элементов).WSПрокси в пользу формирования прямых POST-запросов (Вариант Б). Это позволит обойти динамическую генерацию схемы WSDL сервером.