Так сложилось, что работа с gSOAP на хабре описана очень слабо. Всего лишь один пост, если быть честным. Но там описано создание веб-сервиса, а как же быть с клиентскими приложениями? Не так давно передо мной встала задача организовать работу с удаленным сервером, использующими SOAP — и я решил написать небольшую статью об этом.
Т.к. предоставить wsdl-файлы, с которыми велась работа, я не могу (NDA и все такое), то я задался поиском сервисов, пригодных для тестирования. Интересными мне показались два:
http://www.webservicex.net/ValidateEmail.asmx?WSDL
http://www.webservicex.net/country.asmx?WSDL
Я так и не нашел, где скачать wsdl-файлы, поэтому скопировал их содержимое и сохранил под именами ValidateEmail.wsdl и country.wsdl
Скачать gSOAP можно тут — http://www.cs.fsu.edu/~engelen/soap.html. По этому же адресу можно и почитать о gSOAP.
Последняя версия на момент написания статьи — 2.8.14
Приступим к работе. В папке gsoapbinwin32 живут две очень важные утилиты. Сначала нас интересует wsdl2h.exe. Узнать о ней побольше можно с помощью справки:
>wsdlh2.exe -h
Запустим ее со следующими параметрами:
wsdl2h.exe -o emailAndCountry.h ValidateEmail.wsdl country.wsdl
Все довольно просто, мы указали лишь имя выходного файла и список wsdl-файлов, с которыми хотим работать.
После этого генерируем непосредственно код классов C++:
soapcpp2.exe -C -dgSoap -j -L -x -I«ADDRESS_TO_GSOAPgsoap-2.8gsoapimport» emailAndCountry.h
Ключ -C говорит, что нужно генерировать только клиентский код.
-dgSoap просит сложить все файла в папку gSoap (ее нужно предварительно создать). Мы не генерируем lib-файлы и не наследуемся от soap-структуры; ключ -x просит не генерировать XML-файлы с примерами сообщений. Указываем адрес до папки с gSOAP и файл, который будем парсить и на основе которого генерируем код.
gSOAP разных версий может генерировать разный код (что логично, в общем-то), причем даже состав файлов будет различаться. Это важно помнить, если вы вдруг захотите генерировать gSOAP-файлs на билд-сервере, а не хранить их в системе контроля версий.
После всех манипуляций в папке gSoap видим много новых файлов. Это плюсовые -h и -cpp файла, а так же countrySoap.nsmap и ValidateEmailSoap.nsmap. Они совпадают, можно сохранить их содержимое в одном (например, namespaces.nsmap), а их удалить. namespaces.nsmap нужно заинклудить в проект. Как правило, это делается в каком-нибудь вспомогательном классе, который будет рабоать с gSOAP. Да, такой класс наверняка будет существовать.
После этого добавляем в папку gSoap stdsoap2.h и stdsoap2.cpp — они инклудятся в soapStub.h
Добавляем всю папку в проект и начинаем работать:)
Смотрим классы soapcountrySoapProxy.cpp и soapValidateEmailSoapProxy.cpp; в методах *_init(soap_mode imode, soap_mode omode) — удаляем namespases (мы же не зря инклудили наш namespaces.nsmap).
Начнем с возможности валидации е-мейла — gSoap/soapValidateEmailSoapProxy.h
Нас интересует метод
virtual int IsValidEmail(_ns1__IsValidEmail *ns1__IsValidEmail, _ns1__IsValidEmailResponse *ns1__IsValidEmailResponse)
Смотрим описание аргументов этой функции:
class SOAP_CMAC _ns1__IsValidEmail
{
public:
std::string *Email; /* optional element of type xsd:string */
struct soap *soap; /* transient *
…./
Поле *Email нам наверняка пригодится.
Создаем объект этого класса и указываем объект, который будем проверять
_ns1__IsValidEmail isValidEmailRequest;
std::string CHECKED_E_MAIL("pisem@sovsem.net");
isValidEmailRequest.Email = &CHECKED_E_MAIL;
Сразу создадим объект, в который будет приходить результат:
_ns1__IsValidEmailResponse isValidEmailResponse;
Мы видим, что результат будет в поле bool IsValidEmailResult.
Отправляем запрос на сервер:
const int gSoapResult = validateEmailProxy.IsValidEmail(&isValidEmailRequest, &isValidEmailResponse);
Сниффером смотрим, что именно мы отправили:
<?xml version="1.0" encoding="UTF-8" ?>
- <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns1="http://www.webservicex.net" xmlns:ns2="http://www.webserviceX.NET">
- <SOAP-ENV:Body>
- <ns1:IsValidEmail>
<ns1:Email>pisem@sovsem.net</ns1:Email>
</ns1:IsValidEmail>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Смотрим ответ — видим, что в поле result находится false. Сниффер это подтверждает.
Пробуем другой адрес — press@fsb.ru
Сразу замечу, что сервер думает довольно-таки долго, но с проверяемым адресом это вряд ли связано.
Но ответ все же приходит, и этот адрес у нас считается невалидным.
Что ж, пробуем заведомо “плохой” адрес — “1354@”
Время выполнения результата — вечность. Работает действительно очень долго.
Примерно через минуту мне ждать надоело, и я решил снова попробовать проверить валидный адрес — adv@thematicmedia.ru
Ок, результат получен — false.
adv@thematicmedia — false
adv@l — false
Попробовал один из своих древних почтовых ящиков — сервер опять задумался. Надолго. Но все-таки ответил — false.
Ну что ж, получается, что проблема лишь в сервере, наш код ответы сервера передает верно. Код работает, но абсолютно бесполезен.
Пробуем второй класс — countrySoapProxy
Количество его методов значительно больше, их можно посмотреть в хидере.
Будем тестировать их по порядку, начиная с GetCountryByCountryCode:
_ns2__GetCountryByCountryCode getCountryByCountryCodeRequest;
std::string COUNTRY_CODE("GB");
getCountryByCountryCodeRequest.CountryCode = &COUNTRY_CODE;
_ns2__GetCountryByCountryCodeResponse getCountryByCountryCodeResponse;
countrySoapProxy countryProxy;
const int gSoapResult = countryProxy.GetCountryByCountryCode(&getCountryByCountryCodeRequest, &getCountryByCountryCodeResponse);
if (gSoapResult != SOAP_OK)
{
std::cout << "FAIL" << std::endl;
return 0;
}
Ответ получен:
<NewDataSet>
<Table>
<countrycode>gb</countrycode>
<name>Great Britain</name>
</Table>
<Table>
<countrycode>gb</countrycode>
<name>Great Britain</name>
</Table>
</NewDataSet>
Я, признаться, ожидал, в ответе будет лишь страна, а не здоровый DataSet, ну да это придирки. Страну с кодом RU этот сервис тоже знает, ура!
Переходим к другому методу:
_ns2__GetISD getISDRequest;
std::string COUNTRY_NAME("Russian Federation");
getISDRequest.CountryName = &COUNTRY_NAME;
_ns2__GetISDResponse getISDResponse;
countrySoapProxy countryProxy;
const int gSoapResult = countryProxy.GetISD(&getISDRequest, &getISDResponse);
И этот метод работает как надо:
<NewDataSet>
<Table>
<code>7</code>
<name>Russian Federation</name>
</Table>
<Table>
<code>7</code>
<name>Russian Federation</name>
</Table>
</NewDataSet>
Что ж, для этого сервиса все работает, как ожидалось, для учебных целей он подходит чуть больше, чем первый. На этом эксперименты можно пока и прекратить.
Такой подход замечательно работает, когда мы собираемся работать только с одним сервисом, который расположен по заданному адресу и не требует авторизации.
Но мы же можем развернуть веб-сервис где угодно и как угодно!
На самом деле, gSoap умеет многое. Так, например, задать логин-пароль можно через soap-структуру. Пример простейшей basic-авторизации:
soap.userid = login;
soap.passwd =password;
Описание soap-структуры находится в stdsoap2.h:
const char *userid; /* HTTP Basic authorization userid */
const char *passwd; /* HTTP Basic authorization passwd */
Изменить адрес, на который будут отправляться запросы, можно двумя способами: задавать его при самом запросе либо задавать при создании SOAP-прокси.
Все это довольно очевидно из самих хидеров:
virtual int GetISD(_ns2__GetISD *ns2__GetISD, _ns2__GetISDResponse *ns2__GetISDResponse) { return this->GetISD(NULL, NULL, ns2__GetISD, ns2__GetISDResponse); }
virtual int GetISD(const char *endpoint, const char *soap_action, _ns2__GetISD *ns2__GetISD, _ns2__GetISDResponse *ns2__GetISDResponse);
В случае второго способа нам надо создавать объект примерно так:
countrySoapProxy countryProxy("http://www.webservicex.net/country.asmx");
Тоже ничего сложного, не так ли? Здесь можно задавать и нормальный IP-адрес с портом.
Еще я не учел, как самому задавать нэймспейсы. Такой подход позволит получать не ns1__IsValidEmail в качестве имен классов, а что-то вроде email__IsValidEmail. Когда классов много, то можно запутаться. Наверное. Это делается тоже без проблем. Создаем файл typeMap.dat c содержимым следующего формата:
myCustomNamespace = «www.webservicex.net»
Т.е. все просто: указываем имя нэймспейса и его адрес.
Вообще, работа с gSoap не составляет особой трудности. Но есть несколько моментов, которые всплывают при работе с ним. Так, например, при задании логина и пароля они могут меняться после выполнения запроса. Т.е. код может выглядеть примерно так:
countrySoapProxy countryProxy("http://www.webservicex.net/country.asmx");
countryProxy.soap->userid = "login";
countryProxy.soap->passwd = "password";
countryProxy.GetISD(&getISDRequest, &getISDResponse);
countryProxy.soap->userid = "login";
countryProxy.soap->passwd = "password";
countryProxy.GetISD(&anotherGetISDRequest, &anotherGetISDResponse);
Выглядит это почему-то сомнительно.
Возможно, я чего-то не учел, но краткий опыт работы с gSOAP оставляет смешанное впечатление: высокая скорость разработки, относительно небольшое количество кода — все это в плюс. Но вот мешанина кода, смесь С и С++ — это минус. Но я допускаю, что это минус не для всех, а плюсов значительно больше. В любом случае, достойных альтернатив gSOAP мне найти не удалось — хотя я искал не очень тщательно, доверившись проверенному в нашей компании решению.
Эта статья — скорее вводная, которая кратко рассказывает о начале работы с gSOAP. Я не привел здесь разных способов авторизации, не изучил вопросы наследования от SOAP-структур (это же для чего-то нужно?). Нераскрытых вопросов много, но краткий экскурс в gSOAP я все же дал, надеюсь. Удачной работы!
Автор: vovochkin