Была задача сделать обмен 1С с электронной торговой площадкой B2B-Center (https://www.b2b-center.ru/). У площадки есть SOAP интерфейс, но передача файлов поддерживается только по протоколу MTOM. Описание этого протокола есть здесь https://www.w3.org/TR/SOAP-attachments и здесь https://www.soapui.org/docs/soap-and-wsdl/attachments.html.
Если говорить кратко, то запрос SOAP представляет из себя HTTP POST запрос, в теле которого расположены данные в формате XML. Данные запрос представляют собой «конверт», содержащий заголовок запроса и тело запроса. Если нужно отправить файл, то все сообщение оформляется multipart, одной частью является SOAP запрос, а файлы прикладываются как остальные части.
Будем считать, что wsdl ссылка у нас успешно импортирована и мы может создавать XDTO объекты для обмена.
Местоположение = "https://demo.b2b-center.ru/market/remote_alternative.html";
URIПространстваИмен = "urn:ws";
ИмяСервиса = "ws";
ИмяТочкиПодключения = "wsPort";
АдресWSDL = Местоположение + "?wsdl";
ИнтернетПрокси = ПолучениеФайловИзИнтернетаКлиентСервер.ПолучитьПрокси(АдресWSDL);
Таймаут = 120;
ЗащищенноеСоединение = Новый ЗащищенноеСоединениеOpenSSL;
WSПрокси = WSСсылки.WS_B2B_Center.СоздатьWSПрокси(URIПространстваИмен, ИмяСервиса, ИмяТочкиПодключения,
ИнтернетПрокси, Таймаут, ЗащищенноеСоединение, Местоположение);
WSПрокси.Пользователь = "ИмяПользователя";
WSПрокси.Пароль = "Пароль";
// отправка запроса без файлов
ТипАвторизация = WSПрокси.ФабрикаXDTO.Тип(URIПространстваИмен, "auth_info");
ТипВнешнийИД = WSПрокси.ФабрикаXDTO.Тип(URIПространстваИмен, "external_id");
Авторизация = WSПрокси.ФабрикаXDTO.Создать(ТипАвторизация);
Авторизация.token = "Билет";
ВнешнийИД = WSПрокси.ФабрикаXDTO.Создать(ТипВнешнийИД);
ВнешнийИД.id = "Идентификатор";
ВнешнийИД.type = 0;
Этап = 0; //Номер этапа, 0 - основной, 1 - первая переторжка, 2 - вторая переторжка и т.д
ВходныеПараметры = Новый Структура;
ВходныеПараметры.Вставить("auth",Авторизация);
ВходныеПараметры.Вставить("auction_id",ИД_Аукциона);
ВходныеПараметры.Вставить("external_id",ВнешнийИД);
ВыходныеПараметры = Новый Структура;
ВыходныеПараметры.Вставить("return","ret_auction_position");
// получим данные по позициям от площадки для связки позиций в 1С и на площадке
ОтветСервера = ОтправитьSOAPЗапрос(WSПрокси,URIПространстваИмен,"getPositions","RemoteAuction_getPositions",ВходныеПараметры,ВыходныеПараметры);
Успешно = (Число(ОтветСервера.status.error_code) = 0);
Если НЕ Успешно Тогда
ВызватьИсключение ОтветСервера.status.error_message;
КонецЕсли;
// отправка запроса с вложенными файлами
СвязанныеФайлы = СвязанныеФайлыВызовСервера.СвязанныеФайлыПоВладельцу(Ссылка);
Для каждого Файл Из СвязанныеФайлы Цикл
Если ТипЗнч(Файл.Файл) <> Тип("СправочникСсылка.Файлы") Тогда
Продолжить;
КонецЕсли;
ДанныеФайла = РаботаСФайламиСлужебныйВызовСервера.ДанныеФайлаДляОткрытия(Файл.Файл, Неопределено, Неопределено, Неопределено, Неопределено);
// отправить файл
// отправляем файлы по одному, чтобы можно было отправлять файлы максимального размера
Хеш = Новый ХешированиеДанных(ХешФункция.MD5);
Хеш.Добавить(ПолучитьИзВременногоХранилища(ДанныеФайла.СсылкаНаДвоичныеДанныеФайла));
ИмяФайла = WSПрокси.ФабрикаXDTO.Создать(ТипИмяФайла);
ИмяФайла.filename = СтрШаблон("%1.%2",ДанныеФайла.Наименование,ДанныеФайла.Расширение);
ИмяФайла.md5 = ПолучитьHexСтрокуИзДвоичныхДанных(Хеш.ХешСумма);
МассивФайлов = Новый Массив;
МассивФайлов.Добавить(ДанныеФайла);
Действие = "uploadDoc";
Метод = "RemoteAuction_uploadDoc";
ВходныеПараметры = Новый Структура;
ВходныеПараметры.Вставить("auth",Авторизация);
ВходныеПараметры.Вставить("auction_id",ИД_Аукциона);
ВходныеПараметры.Вставить("external_id",ВнешнийИД);
ВходныеПараметры.Вставить("type","docs");
ВходныеПараметры.Вставить("append_mode",1);
ВходныеПараметры.Вставить("attachment_name",ИмяФайла);
ВыходныеПараметры = Новый Структура;
ВыходныеПараметры.Вставить("return","ret_status");
ОтветСервера = ОтправитьSOAPЗапросСФайлами(WSПрокси,URIПространстваИмен,Действие,Метод,ВходныеПараметры,МассивФайлов,ВыходныеПараметры);
Успешно = (Число(ОтветСервера.status.error_code) = 0);
Если НЕ Успешно Тогда
ВызватьИсключение ОтветСервера.status.error_message;
КонецЕсли;
КонецЦикла;
/// ----------------------------------------------------------------------------------------------------------
// Работа с SOAP
Функция ОтправитьSOAPЗапрос(WSПрокси,URIПространстваИмен,Действие,Метод,ВходныеПараметры,ВыходныеПараметры)
Результат = Неопределено;
Заголовки = Новый Соответствие;
Заголовки.Вставить("Content-Type", "text/xml;charset=UTF-8");
Заголовки.Вставить("SOAPAction", СтрШаблон("%1#%2",URIПространстваИмен,Действие));
ПространствоИменSOAP = "http://schemas.xmlsoap.org/soap/envelope/";
// формируем конверт
ТелоЗапросаSOAP = СформироватьКонвертSOAP(WSПрокси,URIПространстваИмен,Метод,ВходныеПараметры);
HTTPЗапрос = Новый HTTPЗапрос("/market/remote_alternative.html", Заголовки);
HTTPЗапрос.УстановитьТелоИзСтроки(ТелоЗапросаSOAP, "UTF-8");
ЗащищенноеСоединение = Новый ЗащищенноеСоединениеOpenSSL(, Новый СертификатыУдостоверяющихЦентровОС);
ПроксиСервер = Новый ИнтернетПрокси(Истина);
// https://demo.b2b-center.ru/market/remote_alternative.html
HTTPСоединение = Новый HTTPСоединение("demo.b2b-center.ru",,,,ПроксиСервер,120,ЗащищенноеСоединение);
HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
//
ОтветСервера = HTTPОтвет.ПолучитьТелоКакСтроку();
// Обработка результата
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(ОтветСервера);
Результат = Новый Структура;
Пока ЧтениеXML.Прочитать() Цикл
Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
ИмяЭлемента = СтандартныеПодсистемыСервер.ПреобразоватьСтрокуВДопустимоеНаименованиеКолонки(ЧтениеXML.Имя);
Если ВыходныеПараметры.Свойство(ИмяЭлемента) Тогда
ТипОтвета = WSПрокси.ФабрикаXDTO.Тип(URIПространстваИмен,ВыходныеПараметры[ИмяЭлемента]);
РезультатXDTO = WSПрокси.ФабрикаXDTO.ПрочитатьXML(ЧтениеXML,ТипОтвета);
Если ТипЗнч(РезультатXDTO) = Тип("ОбъектXDTO") Тогда
//Результат = ОбъектXDTOВСтруктуру(РезультатXDTO);
Результат = РезультатXDTO;
Прервать;
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЦикла;
ЧтениеXML.Закрыть();
Возврат Результат;
КонецФункции
Функция ОтправитьSOAPЗапросСФайлами(WSПрокси,URIПространстваИмен,Действие,Метод,ВходныеПараметры,СписокФайлов,ВыходныеПараметры)
Результат = Неопределено;
Разделитель = СтрЗаменить(Строка(Новый УникальныйИдентификатор()), "-", "");
Заголовки = Новый Соответствие;
Заголовки.Вставить("Content-Type", "text/xml;charset=UTF-8");
Заголовки.Вставить("Content-Type",СтрШаблон("multipart/related; type=""text/xml""; boundary=""%1""",Разделитель));
Заголовки.Вставить("SOAPAction", СтрШаблон("%1#%2",URIПространстваИмен,Действие));
// формируем конверт
ТелоЗапросаSOAP = СформироватьКонвертSOAP(WSПрокси,URIПространстваИмен,Метод,ВходныеПараметры);
ПотокТелоЗапроса = Новый ПотокВПамяти();
ЗаписьДанных = Новый ЗаписьДанных(ПотокТелоЗапроса);
ЗаписьДанных.ЗаписатьСтроку(СтрШаблон("--%1",Разделитель));
ЗаписьДанных.ЗаписатьСтроку("Content-Type: text/xml; charset=UTF-8");
ЗаписьДанных.ЗаписатьСтроку("Content-Transfer-Encoding: 8bit");
ЗаписьДанных.ЗаписатьСтроку("");
ЗаписьДанных.ЗаписатьСтроку(ТелоЗапросаSOAP);
// добавляем файлы
Для каждого ДанныеФайла Из СписокФайлов Цикл
ДопустимоеНаименованиеФайла = СтандартныеПодсистемыСервер.ПреобразоватьСтрокуВДопустимоеНаименованиеКолонки(ДанныеФайла.Наименование);
ДопустимоеРасширениеФайла = СтандартныеПодсистемыСервер.ПреобразоватьСтрокуВДопустимоеНаименованиеКолонки(ДанныеФайла.Расширение);
//ДопустимоеНаименованиеФайла = ДанныеФайла.Наименование;
//ДопустимоеРасширениеФайла = ДанныеФайла.Расширение;
ИмяФайла = СтрШаблон("%1.%2",ДопустимоеНаименованиеФайла,ДопустимоеРасширениеФайла);
ЗаписьДанных.ЗаписатьСтроку(СтрШаблон("--%1",Разделитель));
ЗаписьДанных.ЗаписатьСтроку(СтрШаблон("Content-Type: application/octet-stream; name=""%1""",ИмяФайла));
ЗаписьДанных.ЗаписатьСтроку("Content-Transfer-Encoding: binary");
ЗаписьДанных.ЗаписатьСтроку(СтрШаблон("Content-ID: <%1>",ИмяФайла));
ЗаписьДанных.ЗаписатьСтроку(СтрШаблон("Content-Disposition: attachment; name=""%1""; filename=""%1""",ИмяФайла));
ЗаписьДанных.ЗаписатьСтроку("");
ЗаписьДанных.Записать(ПолучитьИзВременногоХранилища(ДанныеФайла.СсылкаНаДвоичныеДанныеФайла));
КонецЦикла;
ЗаписьДанных.Закрыть();
HTTPЗапрос = Новый HTTPЗапрос("/market/remote_alternative.html", Заголовки);
РазмерСообщения = ПотокТелоЗапроса.Размер();
ДвоичныеДанныеТело = ПотокТелоЗапроса.ЗакрытьИПолучитьДвоичныеДанные();
Заголовки.Вставить("Content-Length",XMLСтрока(РазмерСообщения));
HTTPЗапрос.УстановитьТелоИзДвоичныхДанных(ДвоичныеДанныеТело);
ЗащищенноеСоединение = Новый ЗащищенноеСоединениеOpenSSL(, Новый СертификатыУдостоверяющихЦентровОС);
ПроксиСервер = Новый ИнтернетПрокси(Истина);
// https://demo.b2b-center.ru/market/remote_alternative.html
HTTPСоединение = Новый HTTPСоединение("demo.b2b-center.ru",,,,ПроксиСервер,120,ЗащищенноеСоединение);
HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
//
ОтветСервера = HTTPОтвет.ПолучитьТелоКакСтроку();
// Обработка результата
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(ОтветСервера);
Результат = Новый Структура;
Пока ЧтениеXML.Прочитать() Цикл
Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
ИмяЭлемента = СтандартныеПодсистемыСервер.ПреобразоватьСтрокуВДопустимоеНаименованиеКолонки(ЧтениеXML.Имя);
Если ВыходныеПараметры.Свойство(ИмяЭлемента) Тогда
ТипОтвета = WSПрокси.ФабрикаXDTO.Тип(URIПространстваИмен,ВыходныеПараметры[ИмяЭлемента]);
РезультатXDTO = WSПрокси.ФабрикаXDTO.ПрочитатьXML(ЧтениеXML,ТипОтвета);
Если ТипЗнч(РезультатXDTO) = Тип("ОбъектXDTO") Тогда
//Результат = ОбъектXDTOВСтруктуру(РезультатXDTO);
Результат = РезультатXDTO;
Прервать;
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЦикла;
ЧтениеXML.Закрыть();
Возврат Результат;
КонецФункции
Функция СформироватьКонвертSOAP(WSПрокси,URIПространстваИмен,Метод,ВходныеПараметры)
ПространствоИменSOAP = "http://schemas.xmlsoap.org/soap/envelope/";
Пакет = WSПрокси.ФабрикаXDTO.Пакеты.Получить(URIПространстваИмен);
СвойствоXDTOМетод = Пакет.КорневыеСвойства.Получить(Метод);
// формируем конверт
ЗаписьXML = Новый ЗаписьXML;
ПараметрыЗаписиXML = Новый ПараметрыЗаписиXML("UTF-8");
ЗаписьXML.УстановитьСтроку(ПараметрыЗаписиXML);
ЗаписьXML.ЗаписатьОбъявлениеXML();
ЗаписьXML.ЗаписатьНачалоЭлемента("Envelope", ПространствоИменSOAP);
ЗаписьXML.ЗаписатьСоответствиеПространстваИмен("soap", ПространствоИменSOAP);
ЗаписьXML.ЗаписатьСоответствиеПространстваИмен("xs", "http://www.w3.org/2001/XMLSchema");
ЗаписьXML.ЗаписатьСоответствиеПространстваИмен("xsi", "http://www.w3.org/2001/XMLSchema-instance");
ЗаписьXML.ЗаписатьСоответствиеПространстваИмен("urn", URIПространстваИмен);
ЗаписьXML.ЗаписатьНачалоЭлемента("Header", ПространствоИменSOAP);
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьНачалоЭлемента("Body", ПространствоИменSOAP);
ЗаписьXML.ЗаписатьНачалоЭлемента(СвойствоXDTOМетод.ЛокальноеИмя, URIПространстваИмен);
Для каждого Элемент Из ВходныеПараметры Цикл
Если ТипЗнч(Элемент.Значение) <> Тип("ОбъектXDTO") Тогда
Значение = ЗначениеТипаВЗначениеXDTO(Элемент.Значение);
Иначе
Значение = Элемент.Значение;
КонецЕсли;
WSПрокси.ФабрикаXDTO.ЗаписатьXML(ЗаписьXML, Значение, Элемент.Ключ, URIПространстваИмен);
КонецЦикла;
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьКонецЭлемента();
Возврат ЗаписьXML.Закрыть();
КонецФункции