gSOAP Calculator Service and Client

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.

Step 1: specify the service operations

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.

Step 2: use the gSOAP RPC compiler

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'.

Step 3: implement your service operations

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.

Step 4: compile your service application

Compile and link your application with 'soapC.cpp', 'soapServer.cpp', and 'stdsoap2.cpp'.

Step 5: deploy your service and try it out

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!

Step 6: debugging

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.

Step 7: develop an example client application

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.

Step 8: have fun!