Calculator client & server example
==================================
Overview
--------
This example implements a simple SOAP/XML API client and server in C++.
Web services specification example
----------------------------------
The calculator XML Web service is specified in the [`calc.h`](calc.h) file for the gSOAP soapcpp2 tool to process. This [`calc.h`](calc.h) specification defines five calculator methods add, sub, mul, div, and pow:
//gsoap ns service name: calc Simple calculator service described at https://www.genivia.com/dev.html
//gsoap ns service protocol: SOAP
//gsoap ns service style: rpc
//gsoap ns service encoding: encoded
//gsoap ns service namespace: http://websrv.cs.fsu.edu/~engelen/calc.wsdl
//gsoap ns service location: http://websrv.cs.fsu.edu/~engelen/calcserver.cgi
//gsoap ns schema namespace: urn:calc
//gsoap ns service method: add Sums two values
int ns__add(double a, double b, double &result);
//gsoap ns service method: sub Subtracts two values
int ns__sub(double a, double b, double &result);
//gsoap ns service method: mul Multiplies two values
int ns__mul(double a, double b, double &result);
//gsoap ns service method: div Divides two values
int ns__div(double a, double b, double &result);
//gsoap ns service method: pow Raises a to b
int ns__pow(double a, double b, double &result);
This example [`calc.h`](calc.h) file was manually defined. These special "header files" with Web services specifications can be obtained automatically by running the wsdl2h command on one or more WSDLs of Web services:
[command]
wsdl2h -o calc.h http://www.genivia.com/calc.wsdl
This generates [`calc.h`](calc.h) from the specified WSDL [`calc.wsdl`](calc.wsdl) file.
Before running wsdl2h we recommended to modify [`typemap.dat`](typemap.dat) to bind XML namespace prefixes to namespace URIs that wsdl2h will use. Since this service uses URI "urn:calc" and we want this to be bound to the `ns` prefix we should add the following line to [`typemap.dat`](typemap.dat):
[command]
ns = "urn:calc"
To obtain the namespace URIs from the WSDLs, run wsdl2h on the WSDLs and simply retrieve them from the generated header file (they are in the top part). Then rerun the wsdl2h command after adding the prefix names you want to be associated with namespaces to [`typemap.dat`](typemap.dat).
[![To top](../../images/go-up.png) To top](#)
Some notes on Web services specification syntax
-----------------------------------------------
A Web service specification in a gSOAP "header file" defines the service and data binding interface. The specification uses normal C/C++ syntax with directives and annotations.
* XML namespace qualification of C/C++ types and members is done by C/C++ identifier naming conventions with a double underscore, as in `ns__add` that binds prefix `ns` to `add`. Also a colon can be used as in `ns:add` (the `ns:` is stripped away by soapcpp2).
* `@` is used to declare struct and class members that should be serialized as XML attributes.
* Default values for data members can be assigned with a `=` in the struct or class declaration directly.
* XML element and attribute occurrence constraints are declared of the form `minOccurs:maxOccurs` at the end of a struct or class member declaration.
* XML element repetitions are STL containers or dynamic arrays. A dynamic array is a pair of a size field and an array pointer member, for example `$int size; struct ns__employee_record *manages` is an array of length `size` pointed to by `manages`.
* XML Web service operations are declared as functions where the last parameter is a pointer or reference to the result value. Use a struct or class to return multiple values.
The `//gsoap` directives are used to bind XML namespaces and to define SOAP Web service properties:
//gsoap prefix service name:
//gsoap prefix service style: [rpc|document]
//gsoap prefix service encoding: [literal|encoded]
//gsoap prefix service namespace:
//gsoap prefix service location:
To bind Web service operation properties:
//gsoap prefix service method-style: [rpc|document]
//gsoap prefix service method-encoding: [literal|encoded]
//gsoap prefix service method-action:
//gsoap prefix service method-documentation:
To define type schema properties:
//gsoap prefix schema namespace:
//gsoap prefix schema elementForm: [qualified|unqualified]
//gsoap prefix schema attributeForm: [qualified|unqualified]
//gsoap prefix schema documentation:
//gsoap prefix schema type-documentation:
Here, `prefix` is an XML namespace prefix used in C/C++ names, such as `ns` in `ns__add`.
The Web services specification with the service and data binding interface is processed by soapcpp2 to generate the service and data binding implementation code, see the build steps further below.
[![To top](../../images/go-up.png) To top](#)
Implementing the client application
-----------------------------------
The client application [`calcclient.cpp`](calcclient.cpp) is run at the command line and takes command-line arguments to invoke the calculator Web service to add, subtract, multiply, divide, or raise the power of two numbers:
#include "soapcalcProxy.h"
#include "calc.nsmap"
/* the Web service endpoint URL */
const char server[] = "http://localhost:8080";
int main(int argc, char **argv)
{
if (argc < 4)
{
fprintf(stderr, "Usage: [add|sub|mul|div|pow] num num\n");
exit(1);
}
calcProxy calc(server);
double a, b, result;
a = strtod(argv[2], NULL);
b = strtod(argv[3], NULL);
switch (*argv[1])
{
case 'a':
calc.add(a, b, result);
break;
case 's':
calc.sub(a, b, result);
break;
case 'm':
calc.mul(a, b, result);
break;
case 'd':
calc.div(a, b, result);
break;
case 'p':
calc.pow(a, b, result);
break;
default:
fprintf(stderr, "Unknown command\n");
exit(1);
}
if (calc.soap->error)
calc.soap_stream_fault(std::cerr);
else
std::cout << "result = " << result << std::endl;
calc.destroy(); /* clean up */
return 0;
}
See the [tutorials](https://www.genivia.com/tutorials.html) on how to use thread pools, enable HTTPS, and how to harden stand-alone services.
[![To top](../../images/go-up.png) To top](#)
Build steps for the client application
--------------------------------------
Generate the service and data binding interface for the client side with:
[command]
soapcpp2 -j -r -CL calc.h
where option `-j` generates a C++ proxy class, option `-r` generates a report, and option `-CL` generates the client side only without (the unused) lib files.
This generates several files, including:
* [`soapStub.h`](soapStub.h) a copy of the specification in plain C/C++ header file syntax without annotations.
* [`soapH.h`](soapH.h) declares XML serializers.
* [`soapC.cpp`](soapC.cpp) implements XML serializers.
* [`soapcalcProxy.h`](soapcalcProxy.h) defines the client-side XML services API class `calcProxy`.
* [`soapcalcProxy.cpp`](soapcalcProxy.cpp) implements the client-side XML services API class `calcProxy`.
* [`calc.nsmap`](calc.nsmap) XML namespace binding table to #include
* [`soapReadme.md`](soapReadme.html) service and data binding interface details.
To build the example client application we also need `stdsoap2.h` and `stdsoap2.cpp`:
[command]
c++ -o calcclient calcclient.cpp stdsoap2.cpp soapC.cpp soapcalcProxy.cpp
[![To top](../../images/go-up.png) To top](#)
Implementing a CGI server application
-------------------------------------
The CGI server application [`calcserver.cpp`](calcserver.cpp) implements the service operations and accepts requests on stdin using `soap_serve(soap)` that dispatches the request to one of these five service operations:
#include "soapcalcService.h"
#include "calc.nsmap"
int main()
{
calcService service();
service.serve(); /* serve CGI request */
service.destroy(); /* clean up */
return 0;
}
/* service operation function */
int calcService::add(double a, double b, double &result)
{
result = a + b;
return SOAP_OK;
}
/* service operation function */
int calcService::sub(double a, double b, double &result)
{
result = a - b;
return SOAP_OK;
}
/* service operation function */
int calcService::mul(double a, double b, double &result)
{
result = a * b;
return SOAP_OK;
}
/* service operation function */
int calcService::div(double a, double b, double &result)
{
if (b)
result = a / b;
else
return soap_senderfault("Division by zero", NULL);
return SOAP_OK;
}
/* service operation function */
int calcService::pow(double a, double b, double &result)
{
result = ::pow(a, b);
if (soap_errno == EDOM) /* soap_errno is like errno, but portable */
return soap_senderfault("Power function domain error", NULL);
return SOAP_OK;
}
After building the service application with the build steps discussed further below, install the CGI application in the cgi-bin folder of your Web server.
[![To top](../../images/go-up.png) To top](#)
Implementing a stand-alone server application
---------------------------------------------
This example shows a stand-alone iterative server that accepts incoming requests on a host port. The program is the same as the CGI service except for the service request dispatching over sockets in the `run` method of the service class:
#include "soapcalcService.h"
#include "calc.nsmap"
int port = 8080;
int main()
{
calcService service();
service.soap->send_timeout = service.soap->recv_timeout = 5; // 5 sec socket idle timeout
service.soap->transfer_timeout = 30; // 30 sec message transfer timeout
while (service.run(port))
service.soap_stream_fault(std::cerr);
service.destroy();
return 0;
}
...
/* service operation functions */
...
Note: this server quits whenever accept fails. Best is to add some logic to continue when a short-term connection issue occurred.
To improve performance we should implement a multi-threaded server that handles requests concurrently and will not block other client requests when service operations become time consuming:
#include "soapcalcService.h"
#include "calc.nsmap"
#include "plugin/threads.h"
int port = 8080;
void *process_request(void *arg)
{
calcService *service = (calcService*)arg;
THREAD_DETACH(THREAD_ID);
if (service)
{
service.serve();
service.destroy(); /* clean up */
delete service;
}
return NULL;
}
int main()
{
calcService service(SOAP_IO_KEEPALIVE); /* enable HTTP kee-alive */
service.soap->send_timeout = service.soap->recv_timeout = 5; /* 5 sec socket idle timeout */
service.soap->transfer_timeout = 30; /* 30 sec message transfer timeout */
SOAP_SOCKET m = service.bind(NULL, port), 100); /* master socket */
if (soap_valid_socket(m))
{
while (soap_valid_socket(service.accept()))
{
THREAD_TYPE tid;
void *arg = (void*)service.copy();
/* use updated THREAD_CREATE from plugin/threads.h https://www.genivia.com/files/threads.zip */
if (arg)
while (THREAD_CREATE(&tid, (void*(*)(void*))process_request, arg))
sleep(1);
}
}
service.soap_stream_fault(std::err);
service.destroy(); /* clean up */
return 0;
}
...
/* service operation functions */
...
This server has no limits on how many threads can be spawned, so you may want to keep a mutex-protected counter to not exceed a max. Also, using a pool of threads can improve the overall performance. Best is to use the ISAPI and Apache modules to develop robust and secure services as recommended in the gSOAP user guide.
See the [tutorials](https://www.genivia.com/tutorials.html) on how to use HTTPS and how to harden stand-alone services.
See the [documentation](https://www.genivia.com/docs.html) on how to use ISAPI and Apache modules to develop services.
[![To top](../../images/go-up.png) To top](#)
Build steps for the server application
--------------------------------------
Generate the service and data binding interface for the server side with:
[command]
soapcpp2 -j -r -SL calc.h
where option `-j` generates a C++ service class, option `-r` generates a report, and option `-SL` generates the server side only without (the unused) lib files.
This generates several files, including:
* [`soapStub.h`](soapStub.h) a copy of the specification in plain C/C++ header file syntax without annotations.
* [`soapH.h`](soapH.h) declares XML serializers.
* [`soapC.cpp`](soapC.cpp) implements XML serializers.
* [`soapcalcService.h`](soapcalcService.h) defines the server-side XML services API class `calcService`.
* [`soapcalcService.cpp`](soapcalcService.cpp) implements the server-side XML services API class `calcService`.
* [`calc.nsmap`](calc.nsmap) XML namespace binding table to #include
* [`soapReadme.md`](soapReadme.html) service and data binding interface details.
To build the example server application we also need `stdsoap2.h` and `stdsoap2.cpp`:
[command]
c++ -o calcserver calcserver.cpp stdsoap2.cpp soapC.cpp soapcalcService.cpp
[![To top](../../images/go-up.png) To top](#)
Running the example
-------------------
[command]
./calcserver 8080 &
./calcclient add 2 3
result = 5
[![To top](../../images/go-up.png) To top](#)
Readme report
-------------
See the auto-generated [soapReadme](soapReadme.html) for this example.
[![To top](../../images/go-up.png) To top](#)