Отправка файлов через SOAP. SOAP with attachments, MTOM

Публикация № 1045578

Обмен - Интеграция с WEB

SOAP with Attachments MTOM веб сервисы

36
В продолжение «своеобразной» поддержки 1С протокола SOAP, описанной в https://infostart.ru/public/965259/, опишу еще одну особенность. 1С не поддерживает возможность отправки файлов-вложений. Для решения этой проблемы пришлось самостоятельно писать формирование пакетов SOAP и разбор ответов сервера.

Была задача сделать обмен 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.Закрыть();    
    
КонецФункции

 

36

См. также

Специальные предложения

Комментарии
Избранное Подписка Сортировка: Древо
1. vano-ekt 1160 19.04.19 08:16 Сейчас в теме
2. YPermitin 2119 20.04.19 20:46 Сейчас в теме
(0) Спасибо за полезный материал!
Оставьте свое сообщение