При интеграции 1С с внешними веб-сервисами разработчики часто сталкиваются с использованием протокола SOAP. Платформа 1С:Предприятие предоставляет для этих целей удобный объект WSПрокси, который позволяет вызывать методы сервиса как обычные функции встроенного языка. Однако этот механизм отлично работает только в простых сценариях. Как только веб-сервис начинает требовать нестандартные структуры данных в заголовках пакета (секция <soapenv:Header>), стандартный WSПрокси пасует.
В этой статье мы подробно разберем, почему возникает эта проблема, как обойти ограничения платформы с помощью низкоуровневых HTTP-запросов, а также как правильно передавать строго типизированные параметры веб-методам и обрабатывать бинарные ответы в формате Base64.
Рассмотрим классическую ошибку, с которой сталкиваются программисты при попытке подключиться к стороннему сервису (например, к информационной системе «Прима-Информ»). Сервер возвращает следующую ошибку десериализации и авторизации:
System.Web.Services.Protocols.SoapException: Server was unable to process request. ---> System.AggregateException: One or more errors occurred. ---> System.Reflection.TargetException: Non-static method requires a target.
at Prima.Core.Repositories.Repository.GetUserId(String username, String password)
Проанализируем ситуацию. Если мы протестируем данный метод через стороннюю утилиту (например, SoapUI), мы увидим, что для успешного выполнения любого запроса сервис ожидает заполнения специального блока в заголовке XML-конверта:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://ws.prima-inform.ru/">
<soapenv:Header>
<ws:AuthHeader>
<ws:Username>Логин</ws:Username>
<ws:Password>Пароль</ws:Password>
<ws:PartnerId>Идентификатор</ws:PartnerId>
</ws:AuthHeader>
</soapenv:Header>
<soapenv:Body>
<ws:GetServiceInfo/>
</soapenv:Body>
</soapenv:Envelope>
Платформа 1С «из коробки» умеет передавать имя пользователя и пароль только в рамках базовой авторизации (HTTP Basic Authentication) при инициализации соединения. Но передать произвольную XML-структуру типа AuthHeader внутрь заголовка SOAP-конверта средствами WSПрокси напрямую невозможно (за исключением случаев, когда эти заголовки жестко и корректно описаны в WSDL-схеме и поддерживаются фабрикой XDTO, что на практике встречается крайне редко).
Вывод очевиден: чтобы обойти ограничения объектной модели работы с веб-сервисами в 1С, нам необходимо отказаться от WSПрокси и перейти на ручное формирование XML-пакета с отправкой через HTTP-соединение.
Переход на HTTP-запросы дает разработчику полный контроль над передаваемым XML-пакетом. Мы можем сформировать абсолютно любой заголовок и тело запроса. Разберем по шагам, как построить надежный механизм взаимодействия.
Для начала подготовим шаблон XML-пакета. Обратите внимание, что мы будем использовать динамическую подстановку параметров авторизации и полезной нагрузки. Создадим и настроим объект защищенного соединения, самого соединения и запроса:
// 1. Настройка параметров подключения
СерверХост = "ws.prima-inform.ru";
ИмяПользователя = "ВашЛогин";
Пароль = "ВашПароль";
// 2. Инициализируем защищенное соединение.
// Важно: не используем привязку к Windows-сертификатам для обеспечения кроссплатформенности кода!
ЗащищенноеСоединение = Новый ЗащищенноеСоединениеOpenSSL();
HTTPСоединение = Новый HTTPСоединение(
СерверХост,
443, // Порт для HTTPS подключения
, // Пользователь для HTTP-авторизации (не используется, так как у нас авторизация в XML)
, // Пароль для HTTP-авторизации
,
60, // Таймаут соединения в секундах
ЗащищенноеСоединение
);
// 3. Установка заголовков HTTP-запроса
Заголовки = Новый Соответствие();
Заголовки.Вставить("Content-Type", "text/xml;charset=UTF-8");
// Заголовок SOAPAction обязателен для спецификации SOAP 1.1. Обратите внимание на точный регистр ключа!
Заголовки.Вставить("SOAPAction", "http://ws.prima-inform.ru/GetServiceInfo");
// Путь к ресурсу (без лишних знаков вопроса на конце)
АдресРесурса = "/PrimaService.asmx";
HTTPЗапрос = Новый HTTPЗапрос(АдресРесурса, Заголовки);
Теперь подготовим сам XML-текст запроса. Нам нужно заполнить структуру AuthHeader актуальными данными:
// 4. Формирование тела SOAP-запроса
СтрокаXML =
"<soapenv:Envelope xmlns:soapenv=""http://schemas.xmlsoap.org/soap/envelope/"" xmlns:ws=""http://ws.prima-inform.ru/"">
| <soapenv:Header>
| <ws:AuthHeader>
| <ws:Username>" + ИмяПользователя + "</ws:Username>
| <ws:Password>" + Пароль + "</ws:Password>
| <ws:PartnerId></ws:PartnerId>
| </ws:AuthHeader>
| </soapenv:Header>
| <soapenv:Body>
| <ws:GetServiceInfo/>
| </soapenv:Body>
|</soapenv:Envelope>";
// Явно указываем кодировку UTF-8 и отключаем использование BOM (Byte Order Mark)
HTTPЗапрос.УстановитьТелоИзСтроки(СтрокаXML, КодировкаТекста.UTF8, ИспользованиеBOM.НеИспользовать);
После подготовки запроса отправляем его на сервер и получаем ответ:
// 5. Отправка запроса и обработка результата
Попытка
РезультатОтправки = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
Если РезультатОтправки.КодСостояния = 200 Тогда
ОтветСервера = РезультатОтправки.ПолучитьТелоКакСтроку();
// Здесь мы получаем чистый XML-ответ от сервиса
ПроверитьИОбработатьОтвет(ОтветСервера);
Иначе
ЗаписьЖурналаРегистрации("ИнтеграцияSOAP", УровеньЖурналаРегистрации.Ошибка,,,
"Ошибка сервера. Код состояния: " + РезультатОтправки.КодСостояния);
КонецЕсли;
Исключение
ЗаписьЖурналаРегистрации("ИнтеграцияSOAP", УровеньЖурналаРегистрации.Ошибка,,,
"Ошибка сетевого подключения: " + ОписаниеОшибки());
КонецПопытки;
Перейдем ко второму важному шагу. Часто методы SOAP-сервисов требуют передачи параметров, которые на стороне бэкенда (например, написанного на C#/.NET) жестко завязаны на перечисления (Enum). Рассмотрим пример вызова метода получения отчета GetFullReport, где передается параметр формата:
<ws:GetFullReport>
<ws:requestId>d2601e0b-ebfb-4c9a-9095-29d53e6a2c5c</ws:requestId>
<ws:format>3</ws:format>
</ws:GetFullReport>
В ответ на такой запрос сервер может вернуть критическую ошибку десериализации:
System.InvalidOperationException: Instance validation error: '3' is not a valid value for ReportFormat.
at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReader1.Read20_ReportFormat(String s)
Выясним причину этой ошибки. Веб-сервер ожидает строго определенные строковые значения, задекларированные в схеме XSDL для этого перечисления. Такими значениями могут выступать: Xml, Json, Pdf, Html, Rtf, Zip.
Важные правила заполнения параметров-перечислений в SOAP-запросах:
3), если в документации прямо не указано соответствие чисел строкам.xml (в нижнем регистре) вызовет такую же ошибку валидации, как и число, поскольку в схеме задекларировано именно значение Xml.<ws:format>"Xml"</ws:format> также является ошибочной. Правильный вариант: <ws:format>Xml</ws:format>.После успешного выполнения метода GetFullReport в XML-ответе сервера возвращается длинная нечитаемая строка символов вида "PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8TWFpb...". Это бинарные данные, закодированные по стандарту Base64.
Рассмотрим, как правильно обработать эти данные в зависимости от того, в каком формате мы запрашивали отчет.
Если данные представляют собой текст, мы можем перевести их в читаемую строку без сохранения на жесткий диск. Для этого воспользуемся возможностями платформы 1С:
// Извлекаем значение Base64-строки из XML-ответа (например, с помощью ЧтенияXML)
СтрокаBase64 = "PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8TWFpb...";
// Преобразуем строку Base64 во внутренний объект ДвоичныеДанные
ДвоичныеДанныеДекод = Base64Значение(СтрокаBase64);
// Получаем чистый текст XML / JSON
ТекстОтчета = ПолучитьСтрокуИзДвоичныхДанных(ДвоичныеДанныеДекод, КодировкаТекста.UTF8);
Внимание! Попытка вызвать метод ПолучитьСтрокуИзДвоичныхДанных() для бинарных файлов (таких как PDF или ZIP-архив) приведет к необратимому искажению структуры файла, и открыть его в дальнейшем будет невозможно. Для бинарных файлов мы должны записывать полученные двоичные данные напрямую в файл на диск:
// Извлекаем значение Base64-строки для PDF-файла
СтрокаBase64_PDF = "JVBERi0xLjQKJ...==";
ДвоичныеДанныеPDF = Base64Значение(СтрокаBase64_PDF);
// Записываем файл на диск во временный каталог или постоянное хранилище
ПутьКФайлу = КаталогВременныхФайлов() + "Report_" + СтрЗаменить(Строка(Новый УникальныйИдентификатор), "-", "") + ".pdf";
ДвоичныеДанныеPDF.Записать(ПутьКФайлу);
// Теперь файл можно открыть или прикрепить к документу в 1С
Для того чтобы разработанный код работал стабильно и не вызывал проблем при обновлении платформы или смене операционной системы сервера 1С, придерживайтесь следующих правил:
Новый СертификатКлиентаWindows() или Новый СертификатыУдостоверяющихЦентровWindows() при инициализации ЗащищенноеСоединениеOpenSSL. При переносе базы 1С на сервер под управлением Linux этот код вызовет фатальный сбой. Конструктор по умолчанию Новый ЗащищенноеСоединениеOpenSSL() автоматически задействует стандартные сертификаты операционной системы, независимо от того, Windows это или Linux.SOAPAction (а не soapaction или Soapaction).HTTPЗапрос.УстановитьТелоИзСтроки() без явного указания параметров кодирует строку с использованием маркера BOM. Некоторые веб-серверы на базе IIS или Apache не могут корректно распарсить входящий XML-пакет, если он начинается с символов BOM. Всегда передавайте параметры КодировкаТекста.UTF8 и ИспользованиеBOM.НеИспользовать явно.Применяя описанный подход с использованием низкоуровневых HTTP-запросов, вы сможете успешно интегрировать 1С с любыми внешними SOAP-сервисами, независимо от сложности их систем авторизации и структуры передаваемых XML-пакетов.