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 -c -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: <WSDLserviceName> <documentationText> //gsoap prefix service style: [rpc|document] //gsoap prefix service encoding: [literal|encoded] //gsoap prefix service namespace: <WSDLnamespaceURI> //gsoap prefix service location: <WSDLserviceAddressLocationURI> To bind Web service operation properties: //gsoap prefix service method-style: <methodName> [rpc|document] //gsoap prefix service method-encoding: <methodName> [literal|encoded] //gsoap prefix service method-action: <methodName> <actionString> //gsoap prefix service method-documentation: <methodName> <documentation> To define type schema properties: //gsoap prefix schema namespace: <schemaNamespaceURI> //gsoap prefix schema elementForm: [qualified|unqualified] //gsoap prefix schema attributeForm: [qualified|unqualified] //gsoap prefix schema documentation: <documentationText> //gsoap prefix schema type-documentation: <typeName> <documentationText> 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.c`](calcclient.c) 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 floating point numbers: #include "soapH.h" #include "calc.nsmap" /* the Web service endpoint URL */ const char server[] = "http://websrv.cs.fsu.edu/~engelen/calcserver.cgi"; int main(int argc, char **argv) { struct soap *soap = soap_new1(SOAP_XML_INDENT); /* new context */ double a, b, result; if (argc < 4) { fprintf(stderr, "Usage: [add|sub|mul|div|pow] num num\n"); exit(1); } a = strtod(argv[2], NULL); b = strtod(argv[3], NULL); switch (*argv[1]) { case 'a': soap_call_ns__add(soap, server, "", a, b, &result); break; case 's': soap_call_ns__sub(soap, server, "", a, b, &result); break; case 'm': soap_call_ns__mul(soap, server, "", a, b, &result); break; case 'd': soap_call_ns__div(soap, server, "", a, b, &result); break; case 'p': soap_call_ns__pow(soap, server, "", a, b, &result); break; default: fprintf(stderr, "Unknown command\n"); exit(1); } if (soap->error) soap_print_fault(soap, stderr); else printf("result = %g\n", result); soap_destroy(soap); /* delete deserialized objects */ soap_end(soap); /* delete heap and temp data */ soap_free(soap); /* we're done with the context */ return 0; } See the [tutorials](https://www.genivia.com/tutorials.html) on how to use HTTP proxies and HTTPS. [![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 -c -r -CL calc.h where option `-c` generates C code, 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.c`](soapC.c) implements XML serializers. * [`soapClient.c`](soapClient.c) implements the client-side XML services API. * [`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.c`: [command] cc -o calcclient calcclient.c stdsoap2.c soapC.c soapClient.c [![To top](../../images/go-up.png) To top](#) Implementing a CGI server application ------------------------------------- The CGI server application [`calcserver.c`](calcserver.c) 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 "soapH.h" #include "calc.nsmap" int main() { struct soap *soap = soap_new(); /* new context */ soap_serve(soap); /* serve CGI request */ soap_destroy(soap); /* delete deserialized objects */ soap_end(soap); /* delete heap and temp data */ soap_free(soap); /* we're done with the context */ return 0; } /* service operation function implementation */ int ns__add(struct soap *soap, double a, double b, double *result) { *result = a + b; return SOAP_OK; } /* service operation function implementation */ int ns__sub(struct soap *soap, double a, double b, double *result) { *result = a - b; return SOAP_OK; } /* service operation function implementation */ int ns__mul(struct soap *soap, double a, double b, double *result) { *result = a * b; return SOAP_OK; } /* service operation function implementation */ int ns__div(struct soap *soap, double a, double b, double *result) { if (b) *result = a / b; else return soap_sender_fault(soap, "Division by zero", NULL); return SOAP_OK; } /* service operation function implementation */ int ns__pow(struct soap *soap, double a, double b, double *result) { *result = pow(a, b); if (soap_errno == EDOM) /* soap_errno is like errno, but portable */ return soap_sender_fault(soap, "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 a loop: #include "soapH.h" #include "calc.nsmap" int port = 8080; int main() { SOAP_SOCKET m; /* master socket */ struct soap soap = soap_new(); /* new context */ soap->send_timeout = soap->recv_timeout = 5; /* 5 sec socket idle timeout */ soap->transfer_timeout = 30; /* 30 sec message transfer timeout */ m = soap_bind(soap, NULL, port), 1); /* backlog=1 for iterative servers, multi-threaded services should use 100 */ if (soap_valid_socket(m)) { while (soap_valid_socket(soap_accept(soap))) { soap_serve(soap); /* serve request */ soap_destroy(soap); /* delete deserialized objects */ soap_end(soap); /* delete heap and temp data */ } } soap_print_fault(soap, stderr); soap_destroy(soap); /* delete deserialized objects */ soap_end(soap); /* delete heap and temp data */ soap_free(soap); /* we're done with the context */ 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 "soapH.h" #include "calc.nsmap" #include "plugin/threads.h" int port = 8080; void *process_request(void *arg) { struct soap *soap = (struct soap*)arg; THREAD_DETACH(THREAD_ID); if (soap) { soap_serve(soap); soap_destroy(soap); soap_end(soap); soap_free(soap); } return NULL; } int main() { SOAP_SOCKET m; /* master socket */ struct soap *soap = soap_new1(SOAP_IO_KEEPALIVE); /* new context with HTTP keep-alive enabled */ soap->send_timeout = soap->recv_timeout = 5; /* 5 sec socket idle timeout */ soap->transfer_timeout = 30; /* 30 sec message transfer timeout */ m = soap_bind(soap, NULL, port), 100); if (soap_valid_socket(m)) { while (soap_valid_socket(soap_accept(soap))) { THREAD_TYPE tid; void *arg = (void*)soap_copy(soap); /* 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); } } soap_print_fault(soap, stderr); soap_destroy(soap); /* delete deserialized objects */ soap_end(soap); /* delete heap and temp data */ soap_free(soap); /* we're done with the context */ 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 thread pools, enable 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 -c -r -SL calc.h where option `-c` generates C code, 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.c`](soapC.c) implements XML serializers. * [`soapServer.c`](soapServer.c) implements the server-side XML services API. * [`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.c`: [command] cc -o calcserver calcserver.c stdsoap2.c soapC.c soapServer.c [![To top](../../images/go-up.png) To top](#) Running the example ------------------- [command] ./calcclient add 2 3 result = 5 To run the example against a stand-alone server on port 8080, change `server[] = "http://localhost:8080"`. [![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](#)