Как в 1С отправить SOAP-запрос с кастомными XML-заголовками авторизации и обработать ответ?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При интеграции 1С с внешними веб-сервисами разработчики часто сталкиваются с использованием протокола SOAP. Платформа 1С:Предприятие предоставляет для этих целей удобный объект WSПрокси, который позволяет вызывать методы сервиса как обычные функции встроенного языка. Однако этот механизм отлично работает только в простых сценариях. Как только веб-сервис начинает требовать нестандартные структуры данных в заголовках пакета (секция <soapenv:Header>), стандартный WSПрокси пасует.

В этой статье мы подробно разберем, почему возникает эта проблема, как обойти ограничения платформы с помощью низкоуровневых HTTP-запросов, а также как правильно передавать строго типизированные параметры веб-методам и обрабатывать бинарные ответы в формате Base64.

В чем заключается проблема WSПрокси?

Рассмотрим классическую ошибку, с которой сталкиваются программисты при попытке подключиться к стороннему сервису (например, к информационной системе «Прима-Информ»). Сервер возвращает следующую ошибку десериализации и авторизации:


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Соединения и 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-запросах:

  1. Ни в коем случае нельзя передавать числовые индексы (например, 3), если в документации прямо не указано соответствие чисел строкам.
  2. Нельзя путать регистр символов. Значение xml (в нижнем регистре) вызовет такую же ошибку валидации, как и число, поскольку в схеме задекларировано именно значение Xml.
  3. В XML-тегах значения передаются напрямую, без кавычек. Конструкция вида <ws:format>"Xml"</ws:format> также является ошибочной. Правильный вариант: <ws:format>Xml</ws:format>.

Декодирование двоичных данных (Base64) из ответа веб-сервиса

После успешного выполнения метода GetFullReport в XML-ответе сервера возвращается длинная нечитаемая строка символов вида "PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8TWFpb...". Это бинарные данные, закодированные по стандарту Base64.

Рассмотрим, как правильно обработать эти данные в зависимости от того, в каком формате мы запрашивали отчет.

Вариант А: Если запрошен текстовый отчет (Xml или Json)

Если данные представляют собой текст, мы можем перевести их в читаемую строку без сохранения на жесткий диск. Для этого воспользуемся возможностями платформы 1С:


// Извлекаем значение Base64-строки из XML-ответа (например, с помощью ЧтенияXML)
СтрокаBase64 = "PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8TWFpb..."; 

// Преобразуем строку Base64 во внутренний объект ДвоичныеДанные
ДвоичныеДанныеДекод = Base64Значение(СтрокаBase64);

// Получаем чистый текст XML / JSON
ТекстОтчета = ПолучитьСтрокуИзДвоичныхДанных(ДвоичныеДанныеДекод, КодировкаТекста.UTF8);

Вариант Б: Если запрошен бинарный отчет (Pdf, Zip, Rtf)

Внимание! Попытка вызвать метод ПолучитьСтрокуИзДвоичныхДанных() для бинарных файлов (таких как PDF или ZIP-архив) приведет к необратимому искажению структуры файла, и открыть его в дальнейшем будет невозможно. Для бинарных файлов мы должны записывать полученные двоичные данные напрямую в файл на диск:


// Извлекаем значение Base64-строки для PDF-файла
СтрокаBase64_PDF = "JVBERi0xLjQKJ...=="; 

ДвоичныеДанныеPDF = Base64Значение(СтрокаBase64_PDF);

// Записываем файл на диск во временный каталог или постоянное хранилище
ПутьКФайлу = КаталогВременныхФайлов() + "Report_" + СтрЗаменить(Строка(Новый УникальныйИдентификатор), "-", "") + ".pdf";
ДвоичныеДанныеPDF.Записать(ПутьКФайлу);

// Теперь файл можно открыть или прикрепить к документу в 1С

Рекомендации по оптимизации кода интеграции

Для того чтобы разработанный код работал стабильно и не вызывал проблем при обновлении платформы или смене операционной системы сервера 1С, придерживайтесь следующих правил:

Применяя описанный подход с использованием низкоуровневых HTTP-запросов, вы сможете успешно интегрировать 1С с любыми внешними SOAP-сервисами, независимо от сложности их систем авторизации и структуры передаваемых XML-пакетов.

← На главную