Рассмотрим постановку задачи и её решение.
Задача:
Есть soap-сервис крупного поставщика DHL, построенный на ASP.NET. При обращении к сервису веб-сервер поставщика производит 302-редирект на страницу аутентификации:
После ввода логина и пароля открывается wsdl-описание.
Требуется организовать работу с методами данного веб-сервиса из кода 1С.
Типовые объекты WSОпределение и WSПрокси с данным веб-сервисом работать не могут, поскольку не имеют возможности указания произвольных заголовков HTTP-запроса для передачи значения cookie-аутентификации.
Решение:
План таков:
- Получим страницу аутентификации GET-запросом и сохраним предварительно устанавливаемое в cookie случайное значение. Это значение впоследствии используется для идентификации сеанса.
- POST-запросом проведём отправку данных формы аутентификации. В ответе сервера получим cookie-аутентификации.
- С методами веб-сервиса будем работать с помощью POST-запросов, устанавливая полученные cookie.
- Чтобы упростить сборку требуемых для методов сервиса soap-запросов, воспользуемся XDTO-описанием параметров и результатов методов, полученным от поставщика.
Пример SOAP-запроса и ответа:
Запрос:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetPtsList xmlns="http://tempuri.org/">
<auth>
<Login>string</Login>
<Password>string</Password>
</auth>
<startDate>dateTime</startDate>
<endDate>dateTime</endDate>
</GetPtsList>
</soap:Body>
</soap:Envelope>
Ответ:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetPtsListResponse xmlns="http://tempuri.org/">
<GetPtsListResult>
<PtsItemInfo>
<DealerReceiveDate>dateTime</DealerReceiveDate>
<PickupDate>dateTime</PickupDate>
<ReferenceNumber>string</ReferenceNumber>
<RegistrationDate>dateTime</RegistrationDate>
<PtsNumber>string</PtsNumber>
<VinNumber>string</VinNumber>
<DhlStation>string</DhlStation>
<DestinationDhlStation>string</DestinationDhlStation>
<Dealer>string</Dealer>
<Model>string</Model>
<Gtd>string</Gtd>
<Brand>string</Brand>
<PtsStatus>string</PtsStatus>
<BrandCode>string</BrandCode>
<DealerCode>string</DealerCode>
<Awb>string</Awb>
</PtsItemInfo>
</GetPtsListResult>
</GetPtsListResponse>
</soap:Body>
</soap:Envelope>
Для удобной генерации XML запроса и ответа создадим XDTO-пакет с описанием объектов GetPtsList, auth, GetPtsListResponse, GetPtsListResult, PtsItemInfo с пространством имён http://tempuri.org/ .
Реализация:
1. Получение страницы аутентификации GET-запросом и отправка данных формы (username, password):
Функция АвторизацияНаВебСервере() Экспорт
СтруктураСоединение = Новый Структура("Результат, HTTPСоединение, Заголовки, НастройкиПодключения");
СтруктураСоединение.Результат = Ложь;
// из регистра сведений получаем сервер, url, логин, пароль для подключения
НастройкиПодключения = ПолучитьНастройкиПодключения();
Если НастройкиПодключения.Количество()=0 Тогда
Возврат СтруктураСоединение;
КонецЕсли;
НастройкаПодключения = НастройкиПодключения[0];
АдресСервера = СокрЛП(НастройкаПодключения.ДопПараметр1);
АдресАвторизации = СокрЛП(НастройкаПодключения.ДопПараметр2);
UserName = СокрЛП(НастройкаПодключения.Пользователь);
Password = СокрЛП(НастройкаПодключения.Пароль);
// данные авторизации - значения параметров формы авторизации, их можно подсмотреть с помощью, например, Advanced REST client
ДанныеАвторизации = "&UserName="+UserName+"&Password="+Password+"&RememberMe=true&CustomAuthKey=";
Попытка
Соединение = Новый HTTPСоединение(АдресСервера);
// получим страницу GET-запросом, в Заголовки0 вернутся предварительные cookie
Заголовки0 = Новый Соответствие;
Результат0 = ВыполнитьGETЗапрос(Соединение, АдресАвторизации, Заголовки0);
Если Результат0.Ответ.КодСостояния<>200 Тогда
Возврат СтруктураСоединение;
КонецЕсли;
// отправим POST-запросом данные формы для аутентификации, в Заголовки1 вернутся новые cookie,
// уже со значением успешной аутентификации. Эти новые cookie мы сохраним и будем использовать
// при выполнении методов сервиса
Заголовки1 = Новый Соответствие;
Заголовки1.Вставить("Cookie",Результат0.Ответ.Заголовки["Set-Cookie"]);
Заголовки1.Вставить("Content-Type", "application/x-www-form-urlencoded");
Результат1 = ВыполнитьPOSTЗапрос(Соединение,АдресАвторизации,Заголовки1,ДанныеАвторизации);
Если Результат1.Ответ.КодСостояния<>302 Тогда
Возврат СтруктураСоединение;
КонецЕсли;
СтруктураСоединение.HTTPСоединение = Соединение;
СтруктураСоединение.Заголовки = Новый Соответствие;
// в Результат1.Ответ.Заголовки["Set-Cookie"] находится заветный ключ
СтруктураСоединение.Заголовки.Вставить("Cookie",Результат1.Ответ.Заголовки["Set-Cookie"]);
СтруктураСоединение.Результат = Истина;
СтруктураСоединение.НастройкиПодключения = НастройкаПодключения;
Исключение
ЗаписьЖурналаРегистрации("ИнтеграцияСDHL.АвторизацияНаВебСервере",
УровеньЖурналаРегистрации.Ошибка,
,,ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
Возврат СтруктураСоединение;
КонецФункции
2. Сборка soap-запроса, его выполнение посредством POST-запроса и получение результата в XDTO:
Функция GetMethodName(СтруктураПараметров) Экспорт
ОбъектXDTO = Неопределено;
СтруктураСоединение = АвторизацияНаВебСервере();
Если НЕ СтруктураСоединение.Результат Тогда
Возврат ОбъектXDTO;
КонецЕсли;
ПутьКМетодуСервиса = "urlpart1/Service.asmx?op=GetMethodName";
// предварительно в метаданных опишем XDTO-пакеты для параметров сервиса и для возвращаемых значений.
ТипGetPtsList = ФабрикаXDTO.Тип("http://tempuri.org/","GetPtsList");
Типauth = ФабрикаXDTO.Тип("http://tempuri.org/","auth");
ПараметрXDTO = ФабрикаXDTO.Создать(ТипGetPtsList);
ПараметрXDTO.startDate = СтруктураПараметров.startDate;
ПараметрXDTO.endDate = СтруктураПараметров.endDate;
Параметрauth = ФабрикаXDTO.Создать(Типauth);
Параметрauth.Login = СтруктураСоединение.НастройкиПодключения.Пользователь;
Параметрauth.Password = СтруктураСоединение.НастройкиПодключения.Пароль;
ПараметрXDTO.auth = Параметрauth;
Запись = Новый ЗаписьXML;
Запись.УстановитьСтроку();
// генерируем XML для параметров метода сервиса
ФабрикаXDTO.ЗаписатьXML(Запись,ПараметрXDTO);
СтрXML = Запись.Закрыть();
// для указания нужных пространств имён в xml-запросе и ответе применена быстрая и грубая замена подстрок
СтрXML = СтрЗаменить(СтрXML," xmlns:xs=""http://www.w3.org/2001/XMLSchema"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""","");
SOAPЗапрос = "<?xml version=""1.0"" encoding=""utf-8""?>
|<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">
| <soap:Body>"
+СтрXML+
"</soap:Body>
|</soap:Envelope>";
ФайлЗапроса = ПолучитьИмяВременногоФайла();
ТекстовыйФайл = Новый ТекстовыйДокумент;
ТекстовыйФайл.УстановитьТекст(SOAPЗапрос);
ТекстовыйФайл.Записать(ФайлЗапроса,КодировкаТекста.ANSI);
ФайлРезультата = ПолучитьИмяВременногоФайла();
ЗаголовокНТТР = Новый Соответствие;
ЗаголовокНТТР.Вставить("Cookie",СтруктураСоединение.Заголовки["Cookie"]);
ФайлОтправки = Новый Файл(ФайлЗапроса);
РазмерФайлаОтправки = XMLСтрока(ФайлОтправки.Размер());
ЗаголовокНТТР.Вставить("Content-Type", "text/xml; charset=utf-8");
ЗаголовокНТТР.Вставить("Content-Lenght", РазмерФайлаОтправки);
СтруктураСоединение.HTTPСоединение.ОтправитьДляОбработки(ФайлЗапроса,ПутьКМетодуСервиса,ФайлРезультата,ЗаголовокНТТР);
// Получаем ответ в виде строки
ТекстовыйФайлОтвет = Новый ТекстовыйДокумент;
ТекстовыйФайлОтвет.Прочитать(ФайлРезультата,КодировкаТекста.UTF8);
СтрокаРезультат = ТекстовыйФайлОтвет.ПолучитьТекст();
// постобработка xml:
СтрокаРезультат = СтрЗаменить(СтрокаРезультат,"<soap:Envelope xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema""><soap:Body>","");
СтрокаРезультат = СтрЗаменить(СтрокаРезультат,"</soap:Body></soap:Envelope>","");
СтрокаРезультат = СтрЗаменить(СтрокаРезультат, "xmlns=""http://tempuri.org/""","xmlns=""http://tempuri.org/"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xs=""http://www.w3.org/2001/XMLSchema""");
Попытка
ТипGetPtsListResponse = ФабрикаXDTO.Тип("http://tempuri.org/","GetPtsListResponse");
Чтение = Новый ЧтениеXML;
Чтение.УстановитьСтроку(СтрокаРезультат);
ОбъектXDTO = ФабрикаXDTO.ПрочитатьXML(Чтение,ТипGetPtsListResponse);
Исключение
ЗаписьЖурналаРегистрации("GetMethodName",
УровеньЖурналаРегистрации.Ошибка,
,,ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
// уборка
Попытка
УдалитьФайлы(ФайлЗапроса);
УдалитьФайлы(ФайлРезультата);
Исключение
КонецПопытки;
Возврат ОбъектXDTO;
КонецФункции
Реализация функция GET-запроса и POST-запроса:
// Выполняет прозвольный POST-запрос
Функция ВыполнитьPOSTЗапрос(Соединение, АдресРесурса, ЗаголовкиЗапроса, ДанныеЗапроса) Экспорт
СтруктураРезультат = Новый Структура;
Запрос = Новый HTTPЗапрос(АдресРесурса,ЗаголовкиЗапроса);
Запрос.УстановитьТелоИзСтроки(ДанныеЗапроса);
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
ОтветВВидеСтроки = Ответ.ПолучитьТелоКакСтроку("UTF-8");
СтруктураРезультат.Вставить("Ответ",Ответ);
СтруктураРезультат.Вставить("ТекстОтвета",ОтветВВидеСтроки);
Возврат СтруктураРезультат;
КонецФункции
// Выполняет произвольный GET-запрос
Функция ВыполнитьGETЗапрос(Соединение, АдресРесурса, ЗаголовкиЗапроса) Экспорт
СтруктураРезультат = Новый Структура;
ИмяВыходногоФайлаGET = ПолучитьИмяВременногоФайла("txt");
HTTPЗапросGET = Новый HTTPЗапрос(АдресРесурса,ЗаголовкиЗапроса);
HTTPОтветGET = Соединение.Получить(HTTPЗапросGET,ИмяВыходногоФайлаGET);
КодСостоянияGET = HTTPОтветGET.КодСостояния;
ТекстОтветаGET = HTTPОтветGET.ПолучитьТелоКакСтроку();
ЧтениеФайла = Новый ТекстовыйДокумент;
ЧтениеФайла.Прочитать(ИмяВыходногоФайлаGET);
ТекстОтвета = ЧтениеФайла.ПолучитьТекст();
СтруктураРезультат.Вставить("Ответ",HTTPОтветGET);
СтруктураРезультат.Вставить("ТекстОтвета",ТекстОтвета);
Попытка
УдалитьФайлы(ИмяВыходногоФайлаGET);
Исключение
КонецПопытки;
Возврат СтруктураРезультат;
КонецФункции
В результате получилось использовать методы soap-сервисов поставщика с предварительной аутентфикацией посредством cookie.
Скорее всего, можно сделать более красивой сборку текста xml-запроса, но это уже в следующей статье!