This example shows you how to develop and deploy a gSOAP calculator Web service. We will also show you how to develop C and C++ client applications for this service.
We want to expose three operations in our simple calculator Web service: add, sub, and sqrt. We will implement these operations in our service application. An operation is invoked upon a SOAP/XML client request to perform the desired calculation and return the result. To associate a service namespace with our service, we need to choose a namespace prefix, say 'ns', and use this prefix to qualify the function names, ns__add, ns__sub, and ns__sqrt, respectively. This qualification allows the gSOAP RPC compiler to produce SOAP/XML namespace bindings for the service application. This is important, because gSOAP needs this information to produce a complete WSDL document that describes your service. We store the function prototypes in a header file, say 'calc.h':
//gsoap ns service name: calc //gsoap ns service namespace: http://www.mysite.com/calc.wsdl //gsoap ns service location: http://www.mysite.com/calc.cgi //gsoap ns schema namespace: urn:calc int ns__add(double a, double b, double *result); int ns__sub(double a, double b, double *result); int ns__sqrt(double a, double *result); |
You see that we wrote the header file with a set of '//gsoap' directives to bind the 'ns' namespace prefix to a service name (we picked 'calc'), a service WSDL specification namespace 'http://www.mysite.com/calc.wsdl' (this is the location where we want to upload our 'calc.wsdl' WSDL), the endpoint location of the service (in this case we will deploy the service as a CGI application located at 'http://www.mysite.com/calc.cgi'), and a service schema namespace (we picked 'urn:calc').
All parameters of the function prototype are considered input parameters by the gSOAP RPC compiler, except the last which the single output parameter (see the gSOAP documentation for more details and on how to pass multiple output parameters). The 'int' return value is used for error diagnostics.
You can execute the gSOAP RPC compiler on the header file within your favorite IDE for example. From the command line we execute:
soapcpp2 calc.h |
The compiler produces a number of source files for both client and service application development:
soapStub.h |
An annotated copy of calc.h with additional stub/skeleton function declarations |
soapH.h |
Header file definitions of the gSOAP-generated functions. This header file should be included in your application |
soapC.cpp |
Serializers and deserializers for C/C++ data structures for SOAP/XML parameter marshalling |
soapClient.cpp |
Client-side stub routines for SOAP/XML remote procedure calling |
soapServer.cpp |
Server-side skeleton routines for SOAP/XML request handling |
calc.wsdl |
A WSDL document describing your service |
calc.nsmap |
A namespace mapping table to be included in your client/service application |
soapcalcProxy.h |
A client proxy class to invoke the remote service from a C++ application |
calc.add.req.xml |
Sample SOAP/XML 'add' client request message |
calc.add.res.xml |
Sample SOAP/XML 'add' service response message |
calc.sub.req.xml |
Sample SOAP/XML 'sub' client request message |
calc.sub.res.xml |
Sample SOAP/XML 'sub' service response message |
calc.sqrt.req.xml |
Sample SOAP/XML 'sqrt' client request message |
calc.sqrt.res.xml |
Sample SOAP/XML 'sqrt' service response message |
To develop the service application, we don't need 'soapClient.cpp'.
A CGI-based service application is by far the easiest to develop and deploy. The main function simply calls the gSOAP 'soap_serve' request dispatcher, which in turn will call the service functions 'ns__add', 'ns__sub', or 'ns__sqrt' upon an incoming SOAP/XML request message. The entire CGI-based service code is:
#include "soapH.h" /* get the gSOAP-generated definitions */ #include "calc.nsmap" /* get the gSOAP-generated namespace bindings */ #include <math.h> int main() { return soap_serve(soap_new()); /* call the request dispatcher */ } int ns__add(struct soap *soap, double a, double b, double *result) { *result = a + b; return SOAP_OK; } int ns__sub(struct soap *soap, double a, double b, double *result) { result = a - b; return SOAP_OK; } int ns__sqrt(struct soap *soap, double a, double *result); { if (a >= 0) { result = sqrt(a); return SOAP_OK; } else return soap_sender_fault(soap, "Square root of negative value", "I can only take the square root of non-negative values"); } |
Note that we can't take the square root of a negative value, so the 'ns__sqrt' returns a sender fault in that case (the sender is to blame and the error cannot be recovered by the service).
The code below shows you how to code the service as a stand-alone application that can run as a background process to serve requests on a TCP/IP port:
#include "soapH.h" /* get the gSOAP-generated definitions */ #include "calc.nsmap" /* get the gSOAP-generated namespace bindings */ #include <math.h> int main() { int m, s; /* master and slave sockets */ struct soap *soap = soap_new(); if (argc < 2) soap_serve(soap); /* serve as CGI application */ else { m = soap_bind(soap, NULL, atoi(argv[1]), 100); /* bind to the port supplied as command-line argument */ if (m < 0) { soap_print_fault(soap, stderr); exit(-1); } fprintf(stderr, "Socket connection successful: master socket = %d\n", m); for (;;) { s = soap_accept(soap); fprintf(stderr, "Socket connection successful: slave socket = %d\n", s); if (s < 0) { soap_print_fault(soap, stderr); exit(1); } soap_serve(soap); soap_end(soap); } } soap_done(soap); free(soap); return 0; } int ns__add(struct soap *soap, double a, double b, double *result) { *result = a + b; return SOAP_OK; } int ns__sub(struct soap *soap, double a, double b, double *result) { result = a - b; return SOAP_OK; } int ns__sqrt(struct soap *soap, double a, double *result); { if (a >= 0) { result = sqrt(a); return SOAP_OK; } else return soap_sender_fault(soap, "Square root of negative value", "I can only take the square root of non-negative values"); } |
The service can be installed as a CGI application but also as a stand-alone service that listens to a port for requests. This service example does not provide all the important features that you want in a service that runs over the Web. See the gSOAP documentation on HTTP keep-alive support, multi-threading, and timout management to develop a Web-ready service. The example merely shows you that your service can be run as a stand-alone process.
Compile and link your application with 'soapC.cpp', 'soapServer.cpp', and 'stdsoap2.cpp'.
To test your application is quite simple. The generated 'soap.add.req.xml' file contains a SOAP/XML request message:
<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns="urn:calc"> <SOAP-ENV:Body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <ns:add> <a>0.0</a> <b>0.0</b> </ns:add> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
You can edit this file and change the contents with appropriate values if you want. Now simply redirect the contents of this file to your service executable:
calc < soap.add.req.xml |
This produces the SOAP/XML response message (a CGI-based HTTP response):
Status: 200 OK Server: gSOAP/2.2 Content-Type: text/xml; charset=utf-8 Content-Length: 463 Connection: close <?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns="urn:calc"><SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><ns:addResponse><result>0</result></ns:addResponse></SOAP-ENV:Body></SOAP-ENV:Envelope> |
To install your application as a CGI service, move the executable to the appropriate Web server cgi-bin directory and enable the execute permissions. Please be careful not to enable write permissions for your executable!
We all like to minimize this development phase. To ease the debugging of gSOAP services (and client applications), compile the stdsoap2.cpp source code with C/C++ compiler option '-DDEBUG' ('/DDEBUG' in MSVC++). Executing your service or client application will log the activities in three files: SENT.log, RECV.log, and TEST.log, containing the messages sent, received, and trace information, respectively.
Since we already have the header file specification of the service, we don't need to use the WSDL importer to convert the service WSDL into a header file specification. We can simply execute the gSOAP RPC compiler (in case you haven't done this already) on the header file that we wrote in the first step:
soapcpp2 calc.h |
To develop a client application in C++, we can use the generated proxy class defined in 'soapcalcProxy.h' and include it in our client application. The proxy class has 'add', 'sub', and 'sqrt' methods that correspond to the functions specified in the header file. These methods automatically invoke the remote service located at 'http://www.mysite.com/calc.cgi' (as was specified in the header file). Here is a C++ client example:
#include "soapcalcProxy.h" #include "calc.nsmap" int main() { calc c; /* calc object */ double n; /* result value */ if (c.add(2, 3, &n) == SOAP_OK) cout << "2 plus 3 is " << n << endl; else soap_print_fault(c.soap, stderr); /* print error */ return 0; } |
The following code illustrates a ANSI-C client that uses the gSOAP-generated 'soap_call_ns__add' stub function:
#include "soapH.h" #include "calc.nsmap" int main() { struct soap *soap = soap_new(); /* create environment */ double n; /* result value */ if (soap_call_ns__add(soap, NULL, NULL, 2, 3, &n) == SOAP_OK) printf("2 plus 3 is %f\n", n); else soap_print_fault(soap, stderr); /* print error */ soap_end(soap); /* clean up deserialized data */ soap_done(soap); /* detach environment */ free(soap); return 0; } |
The first NULL parameter in the 'soap_call_ns__add' call specifies that we want to use the specified service endpoint 'http://www.mysite.com/calc.cgi'. You can provide an alternative endpoint string parameter when necessary. The second NULL parameter is the SOAPaction string, which we won't be using in this example.