Writing a SOAP client in C ++ using gSOAP

It so happened that working with gSOAP on the hub is described very poorly. Just one post , to be honest. But it describes the creation of a web service, but what about client applications? Not so long ago, I faced the task of organizing work with a remote server using SOAP - and I decided to write a short article about it.
Because I can’t provide the wsdl files with which I was working (NDA and all that), then I started looking for services suitable for testing. Two seemed interesting to me:

http://www.webservicex.net/ValidateEmail.asmx?WSDL
http://www.webservicex.net/country.asmx?WSDL

I still could not find where to download the wsdl files, so I copied them contents and saved under the names ValidateEmail.wsdl and country.wsdl
You can download gSOAP here - http://www.cs.fsu.edu/~engelen/soap.html . You can read about gSOAP at the same address.

The latest version at the time of writing is 2.8.14.

Let's get started. Two very important utilities live in the gsoap \ bin \ win32 \ folder. First, we are interested in wsdl2h.exe. You can find out more about it using the help:
> wsdlh2.exe -h
Run it with the following parameters:
wsdl2h.exe -o emailAndCountry.h ValidateEmail.wsdl country.wsdl
It's quite simple, we just specified the output file name and a list of wsdl files we want to work with.
After that, we directly generate the C ++ class code:
soapcpp2.exe -C -dgSoap -j -L -x -I "ADDRESS_TO_GSOAP \ gsoap-2.8 \ gsoap \ import" emailAndCountry.h
The -C switch says that only client code needs to be generated.
-dgSoap asks to put all the files in the gSoap folder (you need to create it first). We do not generate lib files and do not inherit from the soap structure; the -x switch asks you not to generate XML files with sample messages. We indicate the address to the folder with gSOAP and the file that we will parse and based on which we generate the code.
gSOAP of different versions can generate different code (which is logical, in general), and even the composition of the files will vary. This is important to remember if you suddenly want to generate gSOAP files on a build server, rather than storing them in a version control system.
After all the manipulations in the gSoap folder, we see many new files. These are plus -h and -cpp files, as well as countrySoap.nsmap and ValidateEmailSoap.nsmap. They are the same, you can save their contents in one (for example, namespaces.nsmap), and delete them. namespaces.nsmap needs to be included in the project. Typically, this is done in some kind of helper class that will work with gSOAP. Yes, such a class will certainly exist.

After that, add stdsoap2.h and stdsoap2.cpp to the gSoap folder - they will be included in soapStub.h

Add the entire folder to the project and start working :)
We look at the classes soapcountrySoapProxy.cpp and soapValidateEmailSoapProxy.cpp; in * _init methods (soap_mode imode, soap_mode omode) - delete namespases (we knowingly included our namespaces.nsmap).

Let's start with the ability to validate the email - gSoap / soapValidateEmailSoapProxy.h
We are interested in the
virtual int IsValidEmail method (_ns1__IsValidEmail * ns1__IsValidEmail, _ns1__IsValidEmailResponse * ns1__IsValidEmail's

description of the function EmailResponse
class SOAP_CMAC _ns1__IsValidEmail
{
public:
	std::string *Email;	/* optional element of type xsd:string */
	struct soap *soap;	/* transient *
…./


The * Email field will probably come in handy.

We create an object of this class and specify the object that we will check

	_ns1__IsValidEmail isValidEmailRequest;
	std::string CHECKED_E_MAIL("pisem@sovsem.net");
	isValidEmailRequest.Email = &CHECKED_E_MAIL;


Immediately create an object in which the result will come:
_ns1__IsValidEmailResponse isValidEmailResponse;

We see that the result will be in the bool IsValidEmailResult field.

We send a request to the server:
const int gSoapResult = validateEmailProxy.IsValidEmail(&isValidEmailRequest, &isValidEmailResponse);


We look at the sniffer exactly what we sent:

- 
- 
- pisem@sovsem.net


We look at the answer - we see that the result field is false. Sniffer confirms this.

We try another address - press@fsb.ru.

I ’ll immediately notice that the server thinks for quite some time, but this is hardly connected with the address being verified.
But the answer nevertheless comes, and this address is considered invalid for us.
Well, we are trying the deliberately “bad” address - “1354 @”

The lead time is eternity. It really works for a very long time.
After about a minute, I was tired of waiting, and I decided to try again to check the valid address - adv@thematicmedia.ru
Ok, the result was false.
adv @ thematicmedia - false
adv @ l - false
I tried one of my ancient mailboxes - the server thought again. For a long time. But still he answered - false.

Well, it turns out that the problem is only in the server, our code sends server responses correctly. The code works, but is completely useless.

We try the second class - countrySoapProxy The
number of its methods is much larger, you can see them in the header.
We will test them in order, starting with 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;
	}


Answer received:
gbGreat Britain
gbGreat Britain


I admit, I expected that the answer would be only a country, not a healthy DataSet, but it's nit-picking. This service also knows the country with the RU code, cheers!

Let's move on to another method:

	_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);


And this method works as it should:

7Russian Federation
7Russian Federation


Well, everything works for this service, as expected, for training purposes, it suits a little more than the first. On this experiment can be stopped for now.

This approach works great when we are going to work with only one service, which is located at a given address and does not require authorization.
But we can deploy a web service anywhere and anytime!
In fact, gSoap can do a lot. So, for example, you can set the login password through the soap structure. An example of the simplest basic authorization:
soap.userid = login;
soap.passwd =password;


The description of the soap structure is in stdsoap2.h:
  const char *userid;		/* HTTP Basic authorization userid */
  const char *passwd;		/* HTTP Basic authorization passwd */


There are two ways to change the address to which requests will be sent: set it at the request itself or set at creation of a SOAP proxy.
All this is pretty obvious from the readers themselves:

	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);


In the case of the second method, we need to create an object like this:

	countrySoapProxy countryProxy("http://www.webservicex.net/country.asmx");


Nothing too complicated, right? Here you can set the normal IP address with the port.

I also did not take into account how to set namespace myself. This approach will allow you to receive not ns1__IsValidEmail as class names, but something like email__IsValidEmail. When there are many classes, you can get confused. Probably. This is also done without problems. Create a file typeMap.dat with the contents of the following format:
myCustomNamespace = " www.webservicex.net "
That is everything is simple: specify the namespace and its address.

In general, working with gSoap is not particularly difficult. But there are a few points that pop up when working with it. So, for example, when specifying a username and password, they can change after the request. Those. the code might look something like this:

	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);


It looks somehow doubtful.
Maybe I didn’t take into account something, but a brief experience with gSOAP leaves a mixed impression: high development speed, relatively small amount of code - all this is a plus. But here's a jumble of code, a mixture of C and C ++ is a minus. But I admit that this is not a minus for everyone, but much more pluses. In any case, I could not find any worthy alternatives to gSOAP - although I did not search very carefully, trusting in a solution tested in our company.
This article is more of an introductory article that briefly talks about getting started with gSOAP. I did not give different authorization methods here, I did not study the issues of inheritance from SOAP structures (is this necessary for something?). There are many unsolved questions, but I nevertheless gave a brief excursion into gSOAP, I hope. Have a good work!

Also popular now: