These tutorials will help you understand the basics quickly and also covers some advanced topics that you may need later.
First, please read [getting started](dev.html) with gSOAP and check out the [examples.](examples/calc/index.html)
See [advisories](advisory.html) for patches, important updates, and advisories.
See [resources](resources.html) for frequently asked questions.
The full [documentation](docs.html) explains XML data bindings, XML/XPath, XML-RPC, JSON/JSONPath, WS-Security, and more.
Understanding XML SOAP, REST, WSDL, and XML schema {#xml}
---
We recommend to read [getting started](dev.html) with gSOAP first to understand the basics of SOAP, WSDL, XML schema, and XML.
To understand how gSOAP tools are used to develop XML applications and Web services, all you need to know is how to run the tools to auto-generate the C/C++ source code for you. The auto-generated source code is then compiled and linked with the gSOAP library libgsoap (or compiled with the stdsoap2.c/stdsoap2.cpp sources) and combined with your application.
The **wsdl2h** and **soapcpp2** tools simplify the development of applications by auto-coding. Which means that the wsdl2h tool auto-generates the data binding interface file in familiar C/C++ header file syntax:
[command]
wsdl2h [options] -o file.h ... WSDL and XSD files or URLs to WSDLs and XSDs ...
The soapcpp2 tool then takes the generated interface header file and auto-generates the data binding implementation code:
[command]
soapcpp2 [options] file.h
This generates the important source code files with client-side code, server-side code, and XML data binding code that "glues" your C/C++ applications to XML. The auto-generated code makes it easy for you to invoke XML SOAP/REST services. Service deployment can be done with (Fast)CGI, the [gSOAP Apache module](doc/apache/html/index.html) the [gSOAP ISAPI extension](doc/isapi/html/index.html), or as simple stand-alone services. See the next section for details.
Here is a simple example to show the steps needed to build a client application in C using [calc.c](files/calc.c) from the auto-generated [calc.h](files/calc.h):
[command]
wsdl2h -c -o calc.h http://www.genivia.com/calc.wsdl
soapcpp2 -CL calc.h
cc -o calc calc.c soapClient.c soapC.c stdsoap2.c
./calc add 2 3
result = 5
The soap-named source code files are generated by soapcpp2, except for the library code stdsoap2.h and stdsoap2.c with the engine.
You can also generate WSDL and XSD schema files from a given C/C++ header, by running the soapcpp2 command on the header file. This means that you can start developing XML services directly from C/C++ without writing any WSDL and XSD files. For more details, please see the next section.
The wsdl2h and soapcpp2 tools are not only applicable to SOAP, they can also be used to develop [XML REST APIs.](dev.html#client-rest-cpp)
For more example applications in C and C++, from simple to more complex, see our [examples.](examples/calc/index.html)
Learn more about the wsdl2h and soapcpp2 tools in [XML data bindings](doc/databinding/html) and the [gSOAP user guide.](docs.html)
To learn more about our new **testmsgr** tool for Web service and client testing, see [Test Messenger.](doc/testmsgr/html/index.html)
To learn more about our new **domcpp** tool, see [XML DOM API and domcpp.](doc/dom/html/index.html) The domcpp tool produces high-quality, readable and reusable XML DOM source code to parse, search, manipulate, and write XML. The generated code can be readily used in your projects, thereby saving you substantial time and effort.
[![To top](images/go-up.png) To top](#)
SOAP encoding versus document/literal style versus plain XML messaging
---
The gSOAP toolkit supports SOAP 1.1/1.2 encoding, rpc/literal, and document/literal style messaging with XML. Also plain XML messaging is possible. The messaging style is largely automatically determined by the configuration. which is specified in the interface header file generated by wsdl2h.
The document/literal style is specified with a directive in the interface header file generated by wsdl2h:
//gsoap ns service namespace: urn:DB
//gsoap ns service method-protocol: DBupdate SOAP
//gsoap ns service method-style: DBupdate document
int ns__DBupdate(...);
For SOAP RPC style, the interface header file includes the directives:
//gsoap ns service namespace: urn:DB
//gsoap ns service method-protocol: DBupdate SOAP
//gsoap ns service method-style: DBupdate rpc
int ns__DBupdate(...);
In either case, the XML payload has a SOAP envelope, optional SOAP header, and a SOAP body with one element representing the operation with the parameters as subelements:
[xml]
...
By default the document/literal style messaging is used, where the literal XML encoding may be specified with the directive:
//gsoap ns service method-encoding: DBupdate literal
By contrast, the SOAP encoding style must be indicated with a directive, which for the example above would be:
//gsoap ns service method-encoding: DBupdate encoded
In this case the XML payload has a SOAP Body with encoding style attribute, like so:
[xml]
The SOAP encoding style encodes arrays in XML as so-called SOAP arrays with array element type information and array size(s). Multi-referenced objects in the data are serialized with id-ref links. These links are resolved by the deserializer to reconstruct the orignal logical graph structure of the serialized data such that multi-referenced objects are deserialized in memory once. Pointers in the data then point to the deserialized object. For example, the XML payload may contain local references and ids to multi-referenced data, such as the string `Joe` that is referenced twice in this example:
[xml]
-
1234567890
-
Jane
1987654320
-
2345678901
Joe
SOAP 1.2 encoding is cleaner and produces more accurate XML encodings of data graphs by setting the id attribute on the element that is referenced:
[xml]
-
Joe
1234567890
-
Jane
1987654320
-
2345678901
The SOAP 1.2 protocol is specified in general with the import directive, where `soap12.h` is located in gsoap/import:
#import "soap12.h
The SOAP 1.2 protocol can also be specified specifically per service:
//gsoap ns service method-protocol: DBupdate SOAP1.2
A simple REST XML messaging service is specified with:
//gsoap ns service method-protocol: DBupdate REST
**Important**: for both the SOAP 1.1/1.2 literal messaging style and plain XML messaging, you may want to set the `SOAP_XML_TREE` flag to initialize the `soap` context to disable multi-ref serialization with id and (h)ref attributes and ignore id and (h)ref attributes in XML deserialization. This boosts performance significantly and always works with SOAP literal style as per standards (i.e. does not depend on or interpret id-ref). But just to clarify, this flag is not compatible with the SOAP encoding style messaging that requires support for id-ref object serialization to accurately represent data graphs.
You can find more details in our gSOAP [XML data bindings](doc/databinding/html) documentation. This resource is highly rrecommended to understand the gSOAP directives and XML serialization rules implemented for C and C++ with gSOAP.
[![To top](images/go-up.png) To top](#)
How to implement and deploy gSOAP Web services
---
To implement services, use the **wsdl2h** and **soapcpp2** tools to generate the source code for C or C++. You can develop services from scratch without WSDLs by directly using the soapcpp2 tool on an interface header file with your services defined as functions in familiar C/C++ syntax, or you can develop services from a given set of WSDLs and XSDs by invoking wsdl2h:
[command]
wsdl2h [options] -o file.h ... WSDL and XSD files or URLs to WSDLs and XSDs ...
The soapcpp2 tool then takes the generated interface header file and auto-generates the data binding implementation code:
[command]
soapcpp2 [options] file.h
This generates the important source code files with client-side code, server-side code, and XML data binding code that "glues" your C/C++ applications to XML.
Here is a simple example to show the steps needed to build a CGI service application in C using [calccgi.c](files/calccgi.c) from the auto-generated [calc.h](files/calc.h):
[command]
wsdl2h -c -o calc.h http://www.genivia.com/calc.wsdl
soapcpp2 -SL calc.h
cc -o calc.cgi calccgi.c soapClient.c soapC.c stdsoap2.c
The soap-named source code files are generated by soapcpp2, except for the library code stdsoap2.h and stdsoap2.c with the engine.
To deploy gSOAP services, you have several options to integrate the service application in a Web server:
- [CGI](https://en.wikipedia.org/wiki/Common_Gateway_Interface) deployment with a Web server using the **Common Gateway Interface** (CGI) of Web servers such as Apache or IIS is the simplest method, which requires almost no coding with gSOAP. [Fast CGI](https://en.wikipedia.org/wiki/FastCGI) significantly improves the performance of CGI-based services.
- Deploying services as stand-alone **iterative servers** (one thread serving multiple clients iteratively) is a simple option that requires almost no coding. However, iterative servers cannot serve concurrent service requests and will block clients from connecting while other clients are served. Because clients may be repeatedly blocked temporarily or permanently when the bind listen queue (`soap_bind`) is long, resulting in denial of service issues. This is a problem when multiple clients connect or when multiple connections are requested by a single client (such as a Web browser). Iterative servers should only be deployed locally in controlled environments. Furthermore, HTTP keep-alive cannot be used unless at most one client is establishing at most one connection to the service at all times.
- Stand-alone multi-threaded services may be implemented and deployed with **thread spawning** or **thread pooling** techniques. The benefit is better performance compared to the other service deployment methods, especially with HTTP keep-alive enabled, i.e. by initializing the context with `SOAP_IO_KEEPALIVE`. Deploying stand-alone services in uncontrolled high-risk environments is not recommended and you assume all risks which means you should carefully weigh the risks of this scenario. When deciding to develop stand-alone services, you must take important steps to protect your services by [hardening application robustness.](#How_to_harden_your_application's_robustness_with_timeouts_and_error_handlers) To stay safe, periodically check our [advisories](advisory.html) for patches, important updates, and advisories.
- Stand-alone services may be implemented and deployed with **process forking**. The benefit is improved security over multi-threaded services, but with a (small) penalty in performance and more cumbersome/elaborate ways to implement shared state. To update shared state by the forked processes requires allocating shared memory with shmget or mmap. Security and reliability are improved over multi-threaded services by starting processes with de-escalated user privileges using setuid/setgid and by setting timers to kill long-running processes. When managed correctly, a failing process (e.g. a crash) will not likely affect the overall service. When deciding to develop stand-alone services, you must take important steps to protect your services by [hardening application robustness.](#How_to_harden_your_application's_robustness_with_timeouts_and_error_handlers) To stay safe, periodically check our [advisories](advisory.html) for patches, important updates, and advisories.
- gSOAP stand-alone (iterative, multi-threaded, or forked) services can be deployed in Docker containers. Creating a Docker container image of a gSOAP stand-alone service is relatively easy. See the [gSOAP Docker container example](examples/docker/index.html) for details.
- The [gSOAP Apache module](doc/apache/html/index.html) may be used to deploy services in C with the **Apache Web server**. C++ may require more effort to implement with the Apache module, because the way Apache servers are built in C. The benefit of Apache is portability and the range of Web server security and performance configuration options available and to specify the endpoint URL for the service.
- The [gSOAP ISAPI extension](doc/isapi/html/index.html) may be used to deploy services in C and C++ with **Internet Information Services** (IIS) on Windows platforms. The benefit of IIS is the range of Web server security and performance configuration options available and to specify the endpoint URL for the service.
To use the gSOAP Apache module or ISAPI extension please see the referenced documentation for details.
To deploy services with (Fast) CGI or as stand-alone iterative, multi-threaded, or forked processes Web servers, please read on.
[![To top](images/go-up.png) To top](#)
### Deploying CGI services
[CGI](https://en.wikipedia.org/wiki/Common_Gateway_Interface) deployment with a Web server such as Apache or IIS is the simplest method.
The main program of a CGI service calls the auto-generated `soap_serve()` function to process requests:
[codetab.C]
#include "calc.nsmap"
#include "soapH.h" /* generated with soapcpp2 -c calc.h */
int main()
{
struct soap *soap = soap_new();
soap_serve(soap); /* serve request, ignoring failures */
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 are defined here */
...
[codetab.C++]
#include "calc.nsmap"
#include "soapcalcProxy.h" // generated with soapcpp2 -j calc.h
int main()
{
calcService calc(); // service class generated with soapcpp2 -j
calc.serve(); // serve request, ignoring failures
calc.destroy(); // delete deserialized objects and temp data
return 0;
}
...
// service class methods are defined here
...
The example code shown assumes that soapcpp2 was used to generate the server-side source codes from the `calc.h` file in the [C calculator example](examples/calc/index.html) or [C++ calculator example](examples/calc++/index.html)
To compile the C server:
[command]
cc -o calcserver.cgi calcserver.c soapC.c soapServer.c stdsoap2.c
To compile the C++ server:
[command]
c++ -o calcserver.cgi calcserver.cpp soapC.cpp soapcalcService.cpp stdsoap2.cpp
Then place the CGI executable in the designated CGI bin directory of your Web server.
A convenient aspect of CGI is that it exchanges messages over standard input and output. Therefore, you can run the CGI service application from the command line with input and output redirects to test the service:
[command]
./calcserver.cgi < calc.add.req.xml
This displays the service response for the auto-generated sample SOAP/XML message `calc.add.req.xml`.
[Fast CGI](https://en.wikipedia.org/wiki/FastCGI) significantly improves the performance of CGI services.
To use Fast CGI, compile with `-DWITH_FASTCGI`:
[command]
cc -DWITH_FASTCGI -o calcserver.cgi calcserver.c soapC.c soapServer.c stdsoap2.c
And for the C++ server:
[command]
c++ -DWITH_FASTCGI -o calcserver.cgi calcserver.cpp soapC.cpp soapcalcService.cpp stdsoap2.cpp
To use Fast CGI with Apache 2, download and install `libapache2-mod-fcgid`, then create a configuration file `apache.conf` in a directory where you placed your Fast CGI executable `calcserver.cgi`:
[command]
User www-data
Listen 8080
PidFile apache.pid
DocumentRoot .
LoadModule fcgid_module /usr/lib/apache2/modules/mod_fcgid.so
SetHandler fcgid-script
Options +ExecCGI
ErrorLog error.log
This configuration deploys the service on port 8080.
Now run:
[command]
sudo /usr/sbin/apache2 -d . -f apache.conf
Restart the Apache server:
[command]
apachectl -k restart
The service endpoint URL is `http://localhost:8080`.
Make sure that your Fast CGI application (e.g. `calcserver.cgi`) is executable.
To use Fast CGI with IIS, please see the Microsoft windows server FastCGI documentation. For example [Fast CGI with IIS 7.](https://technet.microsoft.com/en-us/library/cc753077.aspx)
[![To top](images/go-up.png) To top](#)
### Deploying stand-alone iterative servers
Iterative servers should only be deployed locally in controlled environments. Iterative servers cannot serve concurrent service requests and will block clients from connecting while other clients are served. Furthermore, HTTP keep-alive cannot be used unless only one client is connecting to the service at all times.
The main program of a stand-alone iterative service calls bind (and listen) to a port, accept, and serves requests over sockets:
[codetab.C]
#include "calc.nsmap"
#include "soapH.h" /* generated with soapcpp2 -c calc.h */
int port = 8080;
int main()
{
SOAP_SOCKET m;
struct soap soap = soap_new();
soap->accept_timeout = 24*60*60; /* quit after 24h of inactivity (optional) */
soap->send_timeout = soap->recv_timeout = 5; /* max send and receive socket inactivity time (sec) */
soap->transfer_timeout = 10; /* max time for send or receive of messages (sec) */
m = soap_bind(soap, NULL, port, 2); /* small backlog queue of 2 to 10 for iterative servers */
if (soap_valid_socket(m))
{
while (soap_valid_socket(soap_accept(soap)))
{
soap_serve(soap); /* serve request, ignoring failures */
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 are defined here
...
[codetab.C++]
#include "calc.nsmap"
#include "soapcalcProxy.h" // generated with soapcpp2 -j calc.h
int port = 8080;
int main()
{
calcService calc(); // service class generated with soapcpp2 -j
calc.soap->accept_timeout = 24*60*60; // quit after 24h of inactivity (optional)
calc.soap->send_timeout = calc.soap->recv_timeout = 5; // max send and receive socket inactivity time (sec)
calc.soap->transfer_timeout = 10; // max time for send or receive of messages (sec)
while (calc.run(port)) // bind, then loop to accept and serve requests
calc.soap_stream_fault(std::cerr);
calc.destroy(); // delete deserialized objects and temp data
return 0;
}
...
// service class methods are defined here
...
Recommended for iterative servers is to use a small backlog queue for `soap_bind` of 2 to 10 pending requests to improve fairness among connecting clients. A smaller value increases fairness and defends against denial of service, but hampers performance because connection requests may be refused when the queue is full. The `backlog` parameter is used with `listen`, which defines the maximum length for the queue of pending connections. If a connection request arrives with the queue full, the client may receive an error with an indication of `ECONNREFUSED` or a connection reset. Alternatively, if the underlying protocol supports retransmission, the request may be ignored so that retries may succeed. See [gSOAP user guide](doc/guide/html/group__group__io.html) `soap_bind` for details.
Also short timeout values should be used, to prevent clients from using connections for too long by terminating unacceptably slow message exchanges that exceed the timeout thresholds. The socket connection idle times `soap->recv_timeout` and `soap->send_timeout` are set to 5 seconds, meaning that an unresponsive client will be cut off after 5 seconds. The `soap->transfer_timeout` is set to just 10 seconds. You should adjust the timeout as needed. The maximum data upload size in bytes is set with `soap->recv_maxlength`. The value of `soap->recv_maxlength` is 2GB by default. To prevent SIGPIPE you should set a handler. See [hardening application robustness.](#How_to_harden_your_application's_robustness_with_timeouts_and_error_handlers)
The number of clients that can be served within a certain time span has a limit because under certain conditions closed sockets may not be immediately reused. This can lead to sockets in `TIME_WAIT` state, see [tuning systems for high connection rates](resources.html#How_do_I_tune_TCP/IP_TIME_WAIT_for_systems_with_high_connection_rates?). Multi-threaded servers with HTTP keep-alive enabled mitigate this limitation, see further below.
The example code shown assumes that soapcpp2 was used to generate the server-side source codes from the `calc.h` file in the [C calculator example](examples/calc/index.html) or [C++ calculator example](examples/calc++/index.html)
To compile and run the C server on port 8080:
[command]
cc -o calcserver calcserver.c soapC.c soapServer.c stdsoap2.c
./calcserver
To compile and run the C++ server on port 8080:
[command]
c++ -o calcserver calcserver.cpp soapC.cpp soapcalcService.cpp stdsoap2.cpp
./calcserver
If port 8080 is in use the server will exit with an error. If port 8080 is reused quickly, the server will also exit unless you set `soap.bind_flags = SO_REUSEADDR` to immediately reuse the port (but be aware of the possible security implications such as port hijacking).
Clients can connect to the service at `http://localhost:8080`. Use the [Test Messenger](doc/testmsgr/html/index.html) to test your service:
[command]
./testmsgr calc.add.req.xml http://localhost:8080
Server response:
---- begin ----
0
---- end ----
The Test Messenger generates randomized SOAP/XML requests with option `-r` given a template XML message to send. To auto-generate the template XML message use soapcpp2 with option `-g`, for example:
[command]
soapcpp2 -g -S -j calc.h
Then use testmsgr with option `-r` and a random seed value, for example `-r1`:
[command]
./testmsgr -r1 calc.add.req.xml http://localhost:8080
Random seed = 1
Server response:
---- begin ----
1.7976931348623157E+308
---- end ----
Use testmsgr option `-c` to continuously test the service until it fails.
See the [Test Messenger documentation](doc/testmsgr/html/index.html) for details on the available options.
[![To top](images/go-up.png) To top](#)
### Deploying stand-alone multi-threaded servers
Stand-alone multi-threaded services can be implemented by thread spawning or thread pooling. The benefit is better performance, compared to the other service deployment methods.
Stand-alone service deployment in uncontrolled high-risk environments is not recommended and you assume all risks which means you should carefully weigh the risks of this scenario. If you decide to develop stand-alone services, then important steps must be taken to protect your services by [hardening application robustness](#How_to_harden_your_application's_robustness_with_timeouts_and_error_handlers). To stay safe, periodically check our [advisories](advisory.html) for patches, important updates, and advisories.
The main program of a stand-alone multi-threaded service calls bind (and listen) to a port, accept, and spawns a new thread for each request:
[codetab.C]
#include "calc.nsmap"
#include "soapH.h" /* generated with soapcpp2 -c calc.h */
#include "plugin/threads.h" /* use gsoap/plugin/threads.h portable threads */
int port = 8080; /* server port */
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->accept_timeout = 24*60*60; /* quit after 24h of inactivity (optional) */
soap->send_timeout = soap->recv_timeout = 5; /* 5 sec socket idle timeout */
soap->transfer_timeout = 10; /* 10 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 or https://www.genivia.com/files/threads.zip */
if (!arg)
soap_force_closesock(soap);
else
while (THREAD_CREATE(&tid, (void*(*)(void*))process_request, arg))
sleep(1); /* thread creation failed, try again in one second */
}
}
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 */
...
[codetab.C++]
#include "calc.nsmap"
#include "soapcalcService.h" /* generated with soapcpp2 -j calc.h */
#include "plugin/threads.h" /* use gsoap/plugin/threads.h portable threads */
int port = 8080; /* server port */
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 keep-alive */
service.soap->accept_timeout = 24*60*60; /* quit after 24h of inactivity (optional) */
service.soap->send_timeout = service.soap->recv_timeout = 5; /* 5 sec socket idle timeout */
service.soap->transfer_timeout = 10; /* 10 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 or https://www.genivia.com/files/threads.zip */
if (!arg)
soap_closesock(soap);
else
while (THREAD_CREATE(&tid, (void*(*)(void*))process_request, arg))
sleep(1); /* thread creation failed, try again in one second */
}
}
service.soap_stream_fault(std::err);
service.destroy(); /* clean up */
return 0;
}
...
/* service operation functions */
...
When `THREAD_CREATE` fails it is retried after waiting one second to permit the system time to release resources. An improved implementation could retry a limited number of times with a shorter duration. When all retries fail, the socket should be closed with `soap_force_closesock(soap)`. Note that we are using the updated `THREAD_CREATE` of gSOAP 2.8.53 and later, which returns zero on success.
The example code shown assumes that soapcpp2 was used to generate the server-side source codes from the `calc.h` file in the [C calculator example](examples/calc/index.html) or [C++ calculator example](examples/calc++/index.html)
The multi-threaded server approach shown spawns as many threads as needed to serve requests. Each request may involve multiple message exchanges with the same client when the client uses HTTP keep-alive. Using HTTP keep-alive improves performance.
Please be aware that a closed socket is not immediately reused when a client closes the socket after the server closes the socket, which can lead to sockets in `TIME_WAIT` state, see [tuning systems for high connection rates](resources.html#How_do_I_tune_TCP/IP_TIME_WAIT_for_systems_with_high_connection_rates?). Remedies may include setting `SO_LINGER` linger time:
soap->accept_flags |= SO_LINGER;
soap->linger_time = 0;
This sets the linger time to zero, but beware of the consequences of the abortive behavior that the server will exhibit!
To compile and run the C server on port 8080:
[command]
cc -o calcserver calcserver.c soapC.c soapServer.c stdsoap2.c
./calcserver
To compile and run the C++ server on port 8080:
[command]
c++ -o calcserver calcserver.cpp soapC.cpp soapcalcService.cpp stdsoap2.cpp
./calcserver
If port 8080 is in use the server will exit with an error. If port 8080 is reused quickly, the server will also exit unless you set `soap.bind_flags = SO_REUSEADDR` to immediately reuse the port (but be aware of the possible security implications such as port hijacking).
Clients can connect to the service at `http://localhost:8080`. Use the [Test Messenger](doc/testmsgr/html/index.html) to test your service:
[command]
./testmsgr calc.add.req.xml http://localhost:8080
Server response:
---- begin ----
0
---- end ----
The Test Messenger generates randomized SOAP/XML requests with option `-r` given a template XML message to send. To auto-generate the template XML message use soapcpp2 with option `-g`, for example:
[command]
soapcpp2 -g -S -j calc.h
Then use testmsgr with option `-r` and a random seed value, for example `-r1`:
[command]
./testmsgr -r1 calc.add.req.xml http://localhost:8080
Random seed = 1
Server response:
---- begin ----
1.7976931348623157E+308
---- end ----
Use testmsgr option `-c` to continuously test the service until it fails.
See the [Test Messenger documentation](doc/testmsgr/html/index.html) for details on the available options.
To limit the number of concurrent threads that can run concurrently is by using the following method. This spawns threads up to a specified maximum of concurrent threads by recycling the contexts of threads that completed. This is done in a round-robin way:
[codetab.C]
#include "calc.nsmap"
#include "soapH.h" /* generated with soapcpp2 -c calc.h */
#include "plugin/threads.h" /* use gsoap/plugin/threads.h portable threads */
#define MAX_THR (100) /* max number of concurrent threads, should not be too low or high */
int port = 8080; /* server port */
int main()
{
struct soap *soap = soap_new1(SOAP_IO_KEEPALIVE); /* new context with HTTP keep-alive enabled */
SOAP_SOCKET m; /* master socket */
THREAD_TYPE tids[MAX_THR]; /* thread pool */
struct soap *soap_thr[MAX_THR]; /* array of thread contexts */
int i;
soap->accept_timeout = 24*60*60; /* quit after 24h of inactivity (optional) */
soap->send_timeout = soap->recv_timeout = 5; /* 5 sec socket idle timeout */
soap->transfer_timeout = 10; /* 10 sec message transfer timeout */
for (i = 0; i < MAX_THR; i++)
soap_thr[i] = NULL;
m = soap_bind(soap, NULL, port, 100);
if (soap_valid_socket(m))
{
while (1)
{
for (i = 0; i < MAX_THR; i++)
{
SOAP_SOCKET s = soap_accept(soap);
if (!soap_valid_socket(s))
goto end; /* error or timed out */
if (!soap_thr[i])
{
/* first time around */
soap_thr[i] = soap_copy(soap);
if (!soap_thr[i])
exit(EXIT_FAILURE); /* could not allocate */
}
else
{
/* recycle soap contexts */
/* optionally, we can cancel the current thread when it is stuck on IO as follows: */
/* soap_close_connection(soap_thr[i]); */ /* requires compiling 2.8.71 or greater with -DWITH_SELF_PIPE */
THREAD_JOIN(tids[i]);
soap_destroy(soap_thr[i]);
soap_end(soap_thr[i]);
soap_copy_stream(soap_thr[i], soap); /* pass on this active connection */
}
while (THREAD_CREATE(&tids[i], (void*(*)(void*))soap_serve, (void*)soap_thr[i]))
sleep(1)
}
}
}
end:
for (i = 0; i < MAX_THR; i++)
{
if (soap_thr[i])
{
THREAD_JOIN(tids[i]);
soap_destroy(soap_thr[i]);
soap_end(soap_thr[i]);
soap_free(soap_thr[i]);
}
}
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 */
...
[codetab.C++]
#include "calc.nsmap"
#include "soapcalcService.h" /* generated with soapcpp2 -j calc.h */
#include "plugin/threads.h" /* use gsoap/plugin/threads.h portable threads */
#define MAX_THR (100) /* max number of concurrent threads, should not be too low or high */
int port = 8080; /* server port */
void *process_request(calcService *service)
{
service->serve();
return NULL;
}
int main()
{
calcService service(SOAP_IO_KEEPALIVE);
SOAP_SOCKET m; /* master socket */
THREAD_TYPE tids[MAX_THR]; /* thread pool */
calcService *service_thr[MAX_THR]; /* array of thread contexts */
int i;
service.soap->accept_timeout = 24*60*60; /* quit after 24h of inactivity (optional) */
service.soap->send_timeout = soap->recv_timeout = 5; /* 5 sec socket idle timeout */
service.soap->transfer_timeout = 10; /* 10 sec message transfer timeout */
for (i = 0; i < MAX_THR; i++)
service_thr[i] = NULL;
m = service.bind(NULL, port, 100);
if (soap_valid_socket(m))
{
while (1)
{
for (i = 0; i < MAX_THR; i++)
{
SOAP_SOCKET s = service.accept();
if (!soap_valid_socket(s))
goto end; /* error or timed out */
if (!service_thr[i])
{
/* first time around */
service_thr[i] = service.copy();
if (!service_thr[i])
exit(EXIT_FAILURE); /* could not allocate */
}
else
{
/* recycle services */
/* optionally, we can cancel the current thread when it is stuck on IO as follows: */
/* soap_close_connection(service_thr[i]->soap); */ /* requires compiling 2.8.71 or greater with -DWITH_SELF_PIPE */
THREAD_JOIN(tids[i]);
service_thr[i]->destroy();
soap_copy_stream(service_thr[i]->soap, service.soap); /* pass on this active connection */
}
while (THREAD_CREATE(&tids[i], (void*(*)(void*))process_request, (void*)service_thr[i]))
sleep(1)
}
}
}
end:
for (i = 0; i < MAX_THR; i++)
{
if (service_thr[i])
{
THREAD_JOIN(tids[i]);
service_thr[i]->destroy();
delete service_thr[i];
}
}
service.destroy();
return 0;
}
...
/* service operation functions */
...
Setting reasonable timeout limits `soap::send_timeout` (socket idle timeout per packet sent), `soap::recv_timeout` (socket idle timeout per packet received), and `soap::transfer_timeout` (transfer timeout per message sent or received) should suffice to avoid the service from "getting stuck" on a connection too long. Cancelling threads that are stuck on connections can be done with `soap_close_connection`, but this could lead to service degredation or even denial of services when lots of connections are made that trigger thread cancellations.
To eliminate the overhead of thread spawning, a pool of worker threads can be started in advance to serve requests. A queue is used to assign jobs (sockets with pending requests) to the workers:
[codetab.C]
#include "calc.nsmap"
#include "soapH.h" /* generated with soapcpp2 -c calc.h */
#include "plugin/threads.h" /* use gsoap/plugin/threads.h portable threads */
#define POOLSIZE (64) /* size of the thread pool */
#define QUEUESIZE (1000) /* max number of waiting jobs in the queue */
static SOAP_SOCKET jobs[QUEUESIZE]; /* job queue with head and tail */
static int head = 0, tail = 0;
static MUTEX_TYPE queue_lock; /* mutex for queue ops critical sections */
static COND_TYPE queue_notempty; /* condition variable when queue is empty */
static COND_TYPE queue_notfull; /* condition variable when queue is full */
/* add job (socket with pending request) to queue */
void enqueue(SOAP_SOCKET s)
{
int next;
MUTEX_LOCK(queue_lock);
next = (tail + 1) % QUEUESIZE;
while (next == head)
COND_WAIT(queue_notfull, queue_lock);
jobs[tail] = s;
tail = next;
COND_SIGNAL(queue_notempty);
MUTEX_UNLOCK(queue_lock);
}
/* remove job (socket with request) from queue */
SOAP_SOCKET dequeue()
{
SOAP_SOCKET s;
MUTEX_LOCK(queue_lock);
while (head == tail)
COND_WAIT(queue_notempty, queue_lock);
s = jobs[head];
head = (head + 1) % QUEUESIZE;
COND_SIGNAL(queue_notfull);
MUTEX_UNLOCK(queue_lock);
return s;
}
/* worker per thread */
void *worker(void *copy)
{
struct soap *tsoap = (struct soap*)copy;
if (!tsoap)
return NULL;
for (;;)
{
tsoap->socket = dequeue();
if (!soap_valid_socket(tsoap->socket))
break;
soap_serve(tsoap);
soap_destroy(tsoap);
soap_end(tsoap);
}
soap_destroy(tsoap);
soap_end(tsoap);
soap_free(tsoap);
return NULL;
}
int main()
{
struct soap *soap = soap_new1(SOAP_IO_KEEPALIVE); /* new context with HTTP keep-alive enabled */
SOAP_SOCKET m; /* master socket */
THREAD_TYPE tids[POOLSIZE]; /* thread pool */
int i;
soap->accept_timeout = 24*60*60; /* quit after 24h of inactivity (optional) */
soap->send_timeout = soap->recv_timeout = 5; /* 5 sec socket idle timeout */
soap->transfer_timeout = 10; /* 10 sec message transfer timeout */
MUTEX_SETUP(queue_lock);
COND_SETUP(queue_notempty);
COND_SETUP(queue_notfull);
/* start workers */
for (i = 0; i < POOLSIZE; i++)
THREAD_CREATE(&tids[i], (void*(*)(void*))worker, (void*)soap_copy(soap));
m = soap_bind(soap, NULL, port, 100);
if (soap_valid_socket(m))
{
SOAP_SOCKET s;
while (soap_valid_socket(s = soap_accept(soap)))
enqueue(s);
}
soap_print_fault(soap, stderr);
/* stop workers */
for (i = 0; i < POOLSIZE; i++)
enqueue(SOAP_INVALID_SOCKET);
for (i = 0; i < POOLSIZE; i++)
THREAD_JOIN(tids[i]);
soap_destroy(soap); /* delete deserialized objects */
soap_end(soap); /* delete heap and temp data */
soap_free(soap); /* we're done with the context */
MUTEX_CLEANUP(queue_lock);
COND_CLEANUP(queue_notempty);
COND_CLEANUP(queue_notfull);
return 0;
}
[codetab.C++]
#include "calc.nsmap"
#include "soapcalcService.h" /* generated with soapcpp2 -j calc.h */
#include "plugin/threads.h" /* use gsoap/plugin/threads.h portable threads */
#define POOLSIZE (64) /* size of the thread pool */
#define QUEUESIZE (1000) /* max number of waiting jobs in the queue */
static SOAP_SOCKET jobs[QUEUESIZE]; /* job queue with head and tail */
static int head = 0, tail = 0;
static MUTEX_TYPE queue_lock; /* mutex for queue ops critical sections */
static COND_TYPE queue_notempty; /* condition variable when queue is empty */
static COND_TYPE queue_notfull; /* condition variable when queue is full */
/* add job (socket with pending request) to queue */
void enqueue(SOAP_SOCKET s)
{
int next;
MUTEX_LOCK(queue_lock);
next = (tail + 1) % QUEUESIZE;
while (next == head)
COND_WAIT(queue_notfull, queue_lock);
jobs[tail] = s;
tail = next;
COND_SIGNAL(queue_notempty);
MUTEX_UNLOCK(queue_lock);
}
/* remove job (socket with request) from queue */
SOAP_SOCKET dequeue()
{
SOAP_SOCKET s;
MUTEX_LOCK(queue_lock);
while (head == tail)
COND_WAIT(queue_notempty, queue_lock);
s = jobs[head];
head = (head + 1) % QUEUESIZE;
COND_SIGNAL(queue_notfull);
MUTEX_UNLOCK(queue_lock);
return s;
}
/* worker per thread */
void *worker(void *copy)
{
calcService *worker = (calcService*)copy;
if (!worker)
return NULL;
for (;;)
{
/* get next job in queue */
worker->soap->socket = dequeue();
if (!soap_valid_socket(worker->soap->socket))
break;
worker->serve();
/* cleanup */
worker->destroy();
}
/* cleanup */
worker->destroy();
delete worker;
return NULL;
}
int main()
{
calcService service(SOAP_IO_KEEPALIVE);
SOAP_SOCKET m; /* master socket */
THREAD_TYPE tids[POOLSIZE]; /* thread pool */
int i;
service.soap->accept_timeout = 24*60*60; /* quit after 24h of inactivity (optional) */
service.soap->send_timeout = service.soap->recv_timeout = 5; /* 5 sec socket idle timeout */
service.soap->transfer_timeout = 10; /* 10 sec message transfer timeout */
MUTEX_SETUP(queue_lock);
COND_SETUP(queue_notempty);
COND_SETUP(queue_notfull);
/* start workers */
for (i = 0; i < POOLSIZE; i++)
THREAD_CREATE(&tids[i], (void*(*)(void*))worker, (void*)service.copy());
m = service.bind(NULL, port, 100);
if (soap_valid_socket(m))
{
SOAP_SOCKET s;
while (soap_valid_socket(s = service.accept()))
enqueue(s);
}
service.soap_stream_fault(std::cerr);
/* stop workers */
for (i = 0; i < POOLSIZE; i++)
enqueue(SOAP_INVALID_SOCKET);
for (i = 0; i < POOLSIZE; i++)
THREAD_JOIN(tids[i]);
/* cleanup */
service.destroy();
MUTEX_CLEANUP(queue_lock);
COND_CLEANUP(queue_notempty);
COND_CLEANUP(queue_notfull);
return 0;
}
Note: on Windows you should use `THREAD_CREATEX` (i.e. `_beginthreadex`) to create a joinable thread.
To prevent clients from using connections for too long, the `soap->transfer_timeout` is set to just 10 seconds. You should adjust the timeout as needed. The socket connection idle times `soap->recv_timeout` and `soap->send_timeout` are set to 5 seconds, meaning that an unresponsive client will be cut off after 5 seconds. The maximum data upload size in bytes is set with `soap->recv_maxlength`. The value of `recv_maxlength` is 2GB by default. Greater values are possible but at your own risk and not recommended in uncontrolled environments. To prevent SIGPIPE you should set a handler. See [hardening application robustness.](#How_to_harden_your_application's_robustness_with_timeouts_and_error_handlers)
More details can be found in the [gSOAP user guide.](doc/guide/html/index.html#mt)
[![To top](images/go-up.png) To top](#)
### Deploying stand-alone forked processes servers
Stand-alone services can be implemented and deployed with process forking. The benefit is improved security over multi-threaded services, but with a (small) penaly in performance and more cumbersome/elaborate ways to implement shared state. When managed correctly, a failing process (e.g. a crash) will not likely affect the overall Web service.
To update shared state by the forked processes requires allocating shared memory with [shmget](https://en.wikipedia.org/wiki/Shared_memory) or with [mmap.](https://en.wikipedia.org/wiki/Mmap)
Security and reliability are improved over multi-threaded services by starting processes with de-escalated user privileges using [setuid or setgid](https://en.wikipedia.org/wiki/Setuid) and by setting timers to kill processes that exceed their time allocated.
The main program of a stand-alone forked processes service calls bind (and listen) to a port, accept, and forks a new process for each request:
[codetab.C]
#include
#include
#include
#include
#include "calc.nsmap"
#include "soapH.h" /* generated with soapcpp2 -c calc.h */
#include "plugin/threads.h" /* use gsoap/plugin/threads.h portable threads */
int port = 8080; /* server port */
int num_proc = 0; /* number of active processes */
int max_proc = 16; /* max number of active concurrent processes */
int main()
{
SOAP_SOCKET m; /* master socket */
struct soap *soap = soap_new1(SOAP_IO_KEEPALIVE); /* new context with HTTP keep-alive enabled */
soap->accept_timeout = 24*60*60; /* quit after 24h of inactivity (optional) */
soap->send_timeout = soap->recv_timeout = 1; /* 1 sec socket idle timeout */
soap->transfer_timeout = 5; /* 5 sec message transfer timeout */
m = soap_bind(soap, NULL, port, 100); /* bind master socket */
if (soap_valid_socket(m))
{
signal(SIGCHLD, sigchld_handle); /* catch child process termination */
while (soap_valid_socket(soap_accept(soap)))
{
pid_t pid;
while (num_proc > max_proc) /* while too many active processes, wait to let them terminate */
sleep(1);
while ((pid = fork()) == -1) /* fork or retry after sleeping one second */
sleep(1);
if (pid == 0) /* child process */
{
soap->master = SOAP_INVALID_SOCKET; /* do not close master socket when done */
set_privileges(); /* set child process privileges */
set_timer(); /* set child process max running time */
soap_serve(soap);
soap_destroy(soap);
soap_end(soap);
soap_free(soap);
exit(EXIT_SUCCESS);
}
else /* parent process */
{
++num_proc;
soap_force_closesock(soap); /* parent should close its socket */
fprintf(stderr, "PID %d started ... ", pid); /* for debugging only */
}
}
}
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 */
...
[codetab.C++]
#include
#include
#include
#include
#include "calc.nsmap"
#include "soapcalcService.h" /* generated with soapcpp2 -j calc.h */
#include "plugin/threads.h" /* use gsoap/plugin/threads.h portable threads */
int port = 8080; /* server port */
int num_proc = 0; /* number of active processes */
int max_proc = 32; /* max number of active processes */
int main()
{
calcService service(SOAP_IO_KEEPALIVE); /* enable HTTP keep-alive */
service.soap->accept_timeout = 24*60*60; /* quit after 24h of inactivity (optional) */
service.soap->send_timeout = service.soap->recv_timeout = 1; /* 1 sec socket idle timeout */
service.soap->transfer_timeout = 5; /* 5 sec message transfer timeout */
SOAP_SOCKET m = service.bind(NULL, port, 100); /* bind master socket */
if (soap_valid_socket(m))
{
signal(SIGCHLD, sigchld_handle); /* catch child process termination */
while (soap_valid_socket(service.accept()))
{
pid_t pid;
while (num_proc > max_proc) /* too many active processes, wait to let them terminate */
sleep(1);
while ((pid = fork()) == -1) /* fork or retry after sleeping for one second */
sleep(1);
if (pid == 0) /* child process */
{
service.soap->master = SOAP_INVALID_SOCKET; /* do not close master socket when done */
set_privileges(); /* set child process privileges */
set_timer(); /* set child process max running time */
service.serve();
service.destroy();
exit(EXIT_SUCCESS);
}
else /* parent process */
{
++num_proc;
service.soap_force_close_socket(); /* parent should close its socket */
fprintf(stderr, "PID %d started ... ", pid); /* for debugging only */
}
}
}
service.soap_stream_fault(std::err);
service.destroy(); /* clean up */
return 0;
}
...
/* service operation functions */
...
To prevent a clients from using connections for too long, the `soap->transfer_timeout` is set to just 5 seconds. You should adjust the timeout as needed. The socket connection idle times `soap->recv_timeout` and `soap->send_timeout` are set to 1 second, meaning that an unresponsive client will be cut off after 1 second. The maximum data upload size in bytes is set with `soap->recv_maxlength`. The value of `recv_maxlength` is 2GB by default. To prevent SIGPIPE you should set a handler. See [hardening application robustness.](#How_to_harden_your_application's_robustness_with_timeouts_and_error_handlers)
By default, the parent process ignores the termination of a child process. But this can lead to many processes in `` state. Therefore, we want to set a `SIGCHLD` handler to check and wait for child processes to terminate:
void sigchld_handle(int x)
{
for (;;)
{
int wstat;
pid_t pid = waitpid(-1, &wstat, WNOHANG);
if (pid == 0)
return;
if (pid == -1)
return;
/* figure out what happened, here we print the results for debugging only */
if (WIFEXITED(wstat))
fprintf(stderr, "PID %d returned %d\n", pid, WEXITSTATUS(wstat));
else if (WIFSIGNALED(wstat))
fprintf(stderr, "PID %d signaled %d%s\n", pid, WTERMSIG(wstat), WCOREDUMP(wstat) ? "dumped core" : "");
else if (WIFSTOPPED(wstat))
fprintf(stderr, "PID %d stopped with signal %d\n", pid, WSTOPSIG(wstat));
--num_proc;
}
}
When running the server program with escalated privileges (e.g. root), you should drop the privileges of the child process with setuid or setgid. The child's privileges should be minimized by the [principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege) to still function without privileges that it does not require. A recommended approach is explained in [Secure Programming Cookbook for C and C++](https://www.safaribooksonline.com/library/view/secure-programming-cookbook/0596003943/ch01s03.html). The source code to drop privileges permanently is replicated here:
void set_privileges()
{
gid_t newgid = getgid(), oldgid = getegid();
uid_t newuid = getuid(), olduid = geteuid();
if (!olduid)
setgroups(1, &newgid);
if (newgid != oldgid)
{
#if !defined(linux)
setegid(newgid);
if (setgid(newgid) == -1)
abort();
#else
if (setregid(newgid, newgid) == -1)
abort();
#endif
}
if (newuid != olduid)
{
#if !defined(linux)
seteuid(newuid);
if (setuid(newuid) == -1)
abort();
#else
if (setreuid(newuid, newuid) == -1)
abort();
#endif
}
}
To allocate a maximum running time to a child process can be done by starting a `ITIMER_REAL` timer. The timer triggers a `SIGALRM` signal when it runs out:
void set_timer()
{
/* configure the timer to expire after 5 seconds */
struct itimerval timer;
timer.it_value.tv_sec = 5;
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = 0;
signal(SIGALRM, sigalrm_handle);
setitimer(ITIMER_REAL, &timer, NULL);
}
The `SIGALRM` handler exits the child process when it exceeded its allocated running time:
void sigalrm_handle(int x)
{
exit(EXIT_SUCCESS);
}
You can also use `setitimer` with the `ITIMER_VIRTUAL` timer, which runs only when the process is executing. This timer triggers a `SIGVTALRM` when it runs out. When using this timer set `signal(SIGVTALRM, sigalrm_handle)`.
Use the [Test Messenger](doc/testmsgr/html/index.html) to test your forked processes server.
[![To top](images/go-up.png) To top](#)
How to chain multiple C++ service classes to accept requests on one server port
---
When building C++ services from multiple service definitions (i.e. multiple WSDLs) while using soapcpp2 with options `-j` (or `-i`), you will need to chain the generated service classes. Chaining the service classes allows the server to accept requests on one listening server port and dispatch the request to one of the service classes for processing. Chaining only applies to options `-j` and `-i`, because these options generate a service class for each service definition. As we explained in the previous section, a service class has a `serve` method that serves a request but fails when the request is not intended for this class. Basically, we want to detect this failure and try another service class to serve the pending request, and so on.
We recommend soapcpp2 option `-j` to generate service classes. Assume three service classes `FirstService`, `SecondService` and `ThirdService` are generated with soapcpp2. To implement a CGI server to serve three types of request requires the following code that uses the service class `dispatch` methods (instead of the `serve` method):
#include "soapFirstService.h"
#include "soapSecondService.h"
#include "soapThirdService.h"
/* A dummy global namespace table, to keep the linker happy */
struct Namespace namespaces[] = { {NULL} };
int main()
{
struct soap *soap = soap_new();
FirstService first(soap);
SecondService second(soap);
ThirdService third(soap);
/* serve over stdin/out, CGI style */
if (soap_begin_serve(soap))
soap_stream_fault(soap, std::cerr);
else
{
if (first.dispatch() == SOAP_NO_METHOD)
if (second.dispatch() == SOAP_NO_METHOD)
third.dispatch();
if (soap->error)
soap_send_fault(soap);
}
soap_destroy(soap);
soap_end(soap);
soap_free(soap);
return 0;
}
...
/* service operation functions for FirstService, SecondService, and ThirdService */
...
Note that all services share the same `soap` context, which makes it easy to chain the service classes. The important part is the `soap_begin_serve(soap)` call that processes the SOAP envelope, header, and body until the service operation can be dispatched to one of the three services.
When using soapcpp2 with option `-i` the code is different, because each service class has its own context instead of a shared `soap` context as in the previous example:
#include "soapFirstService.h"
#include "soapSecondService.h"
#include "soapThirdService.h"
/* A dummy global namespace table, to keep the linker happy */
struct Namespace namespaces[] = { {NULL} };
int main()
{
FirstService first;
SecondService second;
ThirdService third;
/* serve over stdin/out, CGI style */
if (soap_begin_serve(&first))
quote.soap_stream_fault(std::cerr);
else
{
if (first.dispatch() == SOAP_NO_METHOD)
{
soap_copy_stream(&second, &first);
soap_free_stream(&first);
if (second.dispatch() == SOAP_NO_METHOD)
{
soap_copy_stream(&third, &second);
soap_free_stream(&second);
if (third.dispatch())
soap_send_fault(&third);
}
else if (second.error)
soap_send_fault(&second);
}
else if (first.error)
soap_send_fault(&first);
}
first.destroy();
second.destroy();
third.destroy();
return 0;
Of course, you do not need to use CGI to deploy services. This approach works with the gSOAP Apache module, the gSOAP ISAPI extension and with stand-alone services by replacing the `serve` method call.
[![To top](images/go-up.png) To top](#)
How to make stand-alone services serve HTTP GET requests
---
To make stand-alone services return SOAP/XML, JSON, or other data on HTTP GET requests is simple. Just define a `http_get_handler` function to set the `fget` callback of the `soap` context. This callback can check the HTTP path and return data for the GET request:
soap->fget = http_get_handler;
where your `http_get_handler` function is a callback invoked by the service to process HTTP GET requests. For example, use the following code to return the content of a WSDL file `service.wsdl` when the HTTP GET path has a query `?wsdl`:
int http_get_handler(struct soap *soap)
{
FILE *fd;
char *s = strchr(soap->path, '?'); // soap->path has the URL path (soap->endpoint has the full URL)
if (!s || strcmp(s, "?wsdl"))
return SOAP_GET_METHOD; // GET method not supported
fd = fopen("service.wsdl", "rb"); // open WSDL file to copy as a response
if (!fd)
return 404; // return HTTP 404 not found
soap->http_content = "text/xml; charset=utf-8"; // HTTP header with text/xml content
// send http header 200 OK and the XML response
if (soap_response(soap, SOAP_FILE))
{
fclose(fd);
return soap_closesock(soap);
}
for (;;)
{
size_t r = fread(soap->tmpbuf, 1, sizeof(soap->tmpbuf), fd);
if (!r || soap_send_raw(soap, soap->tmpbuf, r))
break;
}
fclose(fd);
soap_end_send(soap);
return SOAP_OK;
}
This example returns the content of file `service.wsdl` located in the current directory, upon receiving a URL with a `?wsdl` query string. It may be tempting to return files referenced in the `soap->path` string, but **do not use the path to simply return files referenced in the url** without making sure that the directories and files opened do not pose security risks. Strip all `/` from the file names to open, or at least strip `..` to prevent snooping around (i.e. directory traversals) with `../../` from the directory root.
Note that our webserver example and documentation explicitly state the following warning since 2003 when implementing HTTP GET (and other) handlers:
int http_GET_handler(struct soap *soap)
{
...
/* Use soap->path (from request URL) to determine request: */
if (options[OPTION_v].selected)
fprintf(stderr, "HTTP GET Request '%s' to host '%s' path '%s'\n", soap->endpoint, soap->host, soap->path);
/* we don't like requests to snoop around in dirs, so we must reject request that
have paths with a '/' or a '\'. You must at least check for .. to avoid request
from snooping around in higher dirs!!! */
if (strchr(soap->path + 1, '/') || strchr(soap->path + 1, '\\'))
return 403; /* HTTP forbidden */
URL query string keys and values can be parsed with the HTTP GET plugin's query functions (the HTTP GET plugin is located in `gsoap/plugin/httpget.h` and `gsoap/plugin/httpget.c`):
#include "plugin/httpget.h"
int http_get_handler(struct soap *soap)
{
char *s = soap_query(soap);
while (s)
{
char *key = soap_query_key(soap, &s);
char *val = soap_query_val(soap, &s);
... // use key and val (can be NULL)
}
...
}
You can return serialized XML for any serializable type `SomeSerializableDataType` as follows:
int http_get_handler(struct soap *soap)
{
... // see the code examples above to check URL path
SomeSerializableDataType data;
soap_default_SomeSerializableDataType(soap, data);
... // set the data value here
soap->http_content = "text/xml; charset=utf-8";
// send http header 200 OK and the XML response
if (soap_response(soap, SOAP_FILE))
|| soap_put_SomeSerializableDataType(soap, &data, "data", "")
|| soap_end_send(soap))
return soap_closesock(soap);
return SOAP_OK;
}
where `"data"` is the tag name of the XML document root element, which may be namespace qualified e.g. `"ns:data"` if `ns` is a XML namespace prefix used with the data type's name. Serializable types and XML namespace prefixes are declared in the interface header file for soapcpp2.
To return a SOAP response we create a SOAP envelope with a header and body as follows:
int http_get_handler(struct soap *soap)
{
... // see the code examples above to check URL path
SomeSerializableDataType data;
soap_default_SomeSerializableDataType(soap, data);
... // set the data value here
soap_set_local_namespaces(soap); // to detect SOAP 1.1 or 1.2
if (soap->version == 1)
soap->http_content = "text/xml; charset=utf-8"; // SOAP 1.1
else
soap->http_content = "application/soap+xml; charset=utf-8"; // SOAP 1.2
// send http header 200 OK and the SOAP response
if (soap_response(soap, SOAP_FILE))
|| soap_envelope_begin_out(soap)
|| soap_putheader(soap)
|| soap_body_begin_out(soap)
|| soap_put_SomeSerializableDataType(soap, &data, "data", "")
|| soap_body_end_out(soap)
|| soap_envelope_end_out(soap)
|| soap_end_send(soap))
return soap_closesock(soap);
return SOAP_OK;
}
Likewise, to return a JSON response:
int http_get_handler(struct soap *soap)
{
... // see the code examples above to check URL path
struct value data;
... // set the data value here
soap->http_content = "application/json; charset=utf-8";
// send http header 200 OK and the JSON response
if (soap_response(soap, SOAP_FILE)
|| json_send(soap, &data);
|| soap_end_send(soap))
return soap_closesock(soap);
return SOAP_OK;
}
To return a HTTP error code with a message body:
int http_get_handler(struct soap *soap)
{
... // see the code examples above to check URL path etc
soap->http_content = "text/html; charset=utf-8";
// send http header 404 Not Found with HTML message
if (soap_response(soap, SOAP_FILE + 404)
|| soap_send(soap, "The resource you are looking for was not found")
|| soap_end_send(soap))
return soap_closesock(soap);
return SOAP_OK;
}
In case you need to pass application data to the `http_get_handler` function, for example to manage state, you can set the `void *user` member variable of the `soap` context to point to the data you want to access in your callback function.
See the JSON tutorial below or the [XML-RPC/JSON and jsoncpp](doc/xml-rpc-json/html/index.html) documentation for details on how to use JSON with gSOAP.
[![To top](images/go-up.png) To top](#)
How to make stand-alone services serve HTTP POST, PUT, PATCH and DELETE requests
---
To make stand-alone services serve HTTP POST, PUT, PATCH and DELETE requests is simple to implement with the gSOAP HTTP POST plugin. The plugin makes it possible to serve generic POST, PUT, PATCH and DELETE requests at the same time SOAP requests are served, simply by calling `soap_serve()` to do both. This means that registering this plugin does not affect SOAP services that you already implemented. The HTTP POST plugin can combined with the `fget` callback (or with the HTTP GET plugin) as described in the previous section to implement REST services. You can also use this approach to deploy JSON REST services on the same port as SOAP services.
The HTTP POST plugin captures POST requests that have a content type matching a content type entry specified in a table of POST, PUT, PATCH and DELETE handlers. Each entry in this table specifies a content type of a POST request with the function to invoke that will handle it. We can also specify generic POST, PUT, PATCH and DELETE request handlers in this table:
struct http_post_handlers my_handlers[] = {
{ "application/json", json_handler },
{ "application/json;*", json_handler },
{ "POST", generic_POST_handler },
{ "PUT", generic_PUT_handler },
{ "PATCH", generic_PATCH_handler },
{ "DELETE", generic_DELETE_handler },
{ NULL }
};
Note that `*` can be used as a wildcard, in this case we use `*` to also capture `"application/json; charset=utf-8"` content type variations.
To register the plugin:
#include "plugin/httppost.h"
struct soap *ctx = soap_new();
soap_register_plugin_arg(ctx, http_post, my_handlers);
To combine SOAP and REST services on the same port, simply call `soap_serve()`. However, when doing so, you will need to build the gSOAP JSON API with C++ namespaces or using an approach as described in the [XML-RPC/JSON and jsoncpp](doc/xml-rpc-json/html/index.html) documentation.
If you're not combining SOAP services with the handlers, then there is no `soap_serve()` function to call which would have been generated by soapcpp2. Instead you'll have to write your own `soap_serve()` service loop that just calls `soap_begin_serve()` to run the plugin handlers:
int soap_serve(struct soap *soap)
{
soap->keep_alive = soap->max_keep_alive + 1;
do
{
if (soap->keep_alive > 0 && soap->max_keep_alive > 0)
soap->keep_alive--;
if (soap_begin_serve(soap))
{
if (soap->error >= SOAP_STOP) // request was handled by plugin handler
continue; // so continue the keep-alive loop
return soap->error;
}
soap->error = 400; // we aren't serving anything else
return soap_send_fault(soap); // so we report an error
} while (soap->keep_alive);
return SOAP_OK;
}
The handlers in the example `my_handlers` table given above are used by the plugin to dispatch the request. If a non-SOAP or non-POST request is made, the handler in the table that matches the HTTP content type of the request is invoked. Also generic POST, PUT, PATCH and DELETE handlers can be optionally specified.
A handler in the `my_handlers` table is a function that takes the context and returns `SOAP_OK` or an error code. Here is an example `json_handler` in C:
int json_handler(struct soap *ctx)
{
struct value *request = new_value(ctx);
struct value *response = new_value(ctx);
if (json_recv(ctx, request) || soap_end_recv(ctx))
return json_send_fault(ctx); // return a JSON-formatted fault
... // use the 'request' value
... // set the 'response' value
// set http content type
ctx->http_content = "application/json; charset=utf-8";
// send http header 200 OK and the JSON response
if (soap_response(ctx, SOAP_FILE)
|| json_send(ctx, response)
|| soap_end_send(ctx))
soap_closesock(ctx);
return SOAP_OK;
}
Note that we use `json_send_fault()` instead of `soap_send_fault()` when an internal error occurred, since we want the error to be reported in JSON format as per Google JSON Style Guide. For application-specific errors, we can use `json_send_error()` as follows:
if (some application error occurred)
json_send_error(ctx, 400, "Error message", "Error details");
else
... // send the response
You can specify an HTTP error code such as 400 in this case, which means that a HTTP 400 Bad Request is returned to the client with a JSON error:
[command]
Status: 400 Bad Request
Server: gSOAP/2.8
X-Frame-Options: SAMEORIGIN
Content-Type: application/json; charset=utf-8
Content-Length: 53
Connection: close
{"error":{"code":400,"Error message":"Error Details"}}
A `generic_POST_handler` should be similar to the above. The HTTP content type of the request is stored in the `http_content` string variable of the context.
A `generic_PUT_handler` and `generic_PATCH_handler` should not return a response message but should call `soap_send_empty_response()` instead. Likewise, these handlers should return a HTTP status code instead of calling `json_send_error()` since the HTTP body should be empty:
int generic_PUT_handler(struct soap *ctx)
{
struct value *request = new_value(ctx);
if (!ctx->http_content || soap_tag_cmp(ctx->http_content, "application/json*"))
{
return 400; // HTTP Bad Request
}
if (json_recv(ctx, request) || soap_end_recv(ctx))
return 400; // HTTP Bad Request
... // use the 'request' value
// send http header 200 OK and empty http body
return soap_send_empty_response(ctx);
}
The `soap_tag_cmp()` function is similar to `strcmp()` but it is case insensitive and supports `*` (any number of characters) and `-` (any single character) wildcards.
[![To top](images/go-up.png) To top](#)
How to use JSON and JSONPath with gSOAP {#json}
---
While gSOAP is primarily designed for XML, the gSOAP Web services engine and auto-coding tools are more than sufficiently powerful to develop other protocol frameworks such as JSON and XML-RPC.
The `gsoap/samples/xml-rpc-json` directory in the gSOAP download package includes the JSON API for C and C++. Several JSON and XML-RPC examples are included.
We also offer extensive documentation on [XML-RPC/JSON and jsoncpp](doc/xml-rpc-json/html/index.html) in addition to this tutorial.
A new code generation tool **jsoncpp** is included (gSOAP 2.8.26 and later). The jsoncpp tool produces high-quality, readable and reusable source code. The generated code can be readily used in your projects to populate JSON data and retrieve data, thereby saving you substantial time and effort. You may not have to write any C or C++ code to manipulate JSON data with your application's code base when taking full advantage of the jsoncpp auto-coding tool.
The gSOAP JSON C++ and C APIs are intuitive to use. You simply declare variables that contain JSON-based values. The values can be set by parsing JSON from streams, from files, from text buffers, or by directly assigning them. You can write values back to streams in JSON format:
[codetab.C]
#include "json.h"
// create a JSON value managed by context 'ctx':
struct value *v = new_value(ctx);
// create a JSON "book" object with title, author, and price
*string_of(value_at(value_at(v, "book"), "title")) = "Moby Dick";
*string_of(value_at(value_at(v, "book"), "author")) = "Herman Melville";
*double_of(value_at(value_at(v, "book"), "price")) = 22.99;
// write JSON object to stdout (or to any other open fd):
ctx->sendfd = 1; // stdout fd
json_write(ctx, v);
if (ctx->error)
... // handle IO error
// parse JSON data from stdin into 'v' managed by the context:
ctx->recvfd = 0; // stdin fd
json_read(ctx, v);
if (ctx->error)
... // handle IO error (error info stored in 'v')
[codetab.C++]
#include "json.h"
// create a JSON value managed by context 'ctx':
value v(ctx);
// create a JSON "book" object with title, author, and price
v["book"]["title"] = "Moby Dick";
v["book"]["author"] = "Herman Melville";
v["book"]["price"] = 22.99;
// write JSON object to cout:
std::cout << v;
if (ctx->error)
... // handle IO error
// parse JSON data from cin into 'v' managed by the context:
std::cin >> v;
if (ctx->error)
... // handle IO error (error info stored in 'v')
The JSON object created by this code is:
[json]
{
"book": {
"title": "Moby Dick",
"author": "Herman Melville",
"price": 22.99
}
}
Memory management is automatic. You just need to set up a context and delete it later, when JSON values are no longer used:
[codetab.C]
#include "json.h"
struct soap *ctx = soap_new1(SOAP_C_UTFSTRING|SOAP_XML_INDENT); // new context
...
struct value *v = new_value(ctx);
...
...
soap_end(ctx); // delete values and temp data
soap_free(ctx); // free context
[codetab.C++]
#include "json.h"
struct soap *ctx = soap_new1(SOAP_C_UTFSTRING|SOAP_XML_INDENT); // new context
...
value v(ctx);
...
soap_destroy(ctx); // delete values
soap_end(ctx); // delete temp data
soap_free(ctx); // free context
If memory conservation is critical then we suggest to intermittently delete all values by calling `soap_destroy` and `soap_end` without deleting the context. If certain values must be kept then deep copy them into another managing context with `soap_dup_value` (with some conditions, see our full JSON documentation).
The JSON values that you can assign are bool, int, double, string, array, and object:
[codetab.C]
struct value *v = new_value(ctx);
*int_of(v) = 12345LL; // 64 bit int or narrower ints
*double_of(v) = 12.34; // double float or float
*string_of(v) = "abc"; // string (may contain UTF-8)
*string_of(v) = soap_wchar2s(ctx, L"xyz");
// wide string (converted to UTF-8)
*bool_of(v) = 0; // Boolean 0 is false
*bool_of(v) = 1; // Boolean 1 is true
// create an array [24, 99.99, "abc"]:
*int_of(nth_value(v, 0)) = 24;
*double_of(nth_value(v, 1)) = 99.99;
*string_of(nth_value(v, 2)) = "abc";
// create a struct (JSON object) {"name": "gsoap", "major": 2.8, "©": 2015}:
*string_of(value_at(v, "name")) = "gsoap";
*double_of(value_at(v, "major")) = 2.8;
*int_of(value_atw(v, L"©")) = 2015; // wide string tags are OK
[codetab.C++]
value v(ctx);
v = 12345LL; // 64 bit int or narrower ints
v = 12.34; // double float or float
v = "abc"; // string (may contain UTF-8)
v = string("abc"); // std::string (may contain UTF-8)
v = L"xyz"; // wide string (converted to UTF-8)
v = wstring(L"xyz"); // std::wstring (converted to UTF-8)
v = false; // Boolean
// create an array [24, 99.99, "abc"]:
v[0] = 24;
v[1] = 99.99;
v[2] = "abc";
// create a struct (JSON object) {"name": "gsoap", "major": 2.8, "©": 2015}:
v["name"] = "gsoap";
v["major"] = 2.8;
v[L"©"] = 2015; // wide string tags are OK
Some special values for dateTime and base64 are also supported, which are not discussed here. See the full [XML-RPC/JSON and jsoncpp](doc/xml-rpc-json/html/index.html) documentation.
To convert a JSON value to bool, int, double, or string in C++, simply cast the value to one of these types, e.g. with `static_cast(v)` to get the double float value when `v.is_double()` is true.
For C only: to convert a JSON value to bool, int, double, or string, simply use one of the `bool_of`, `int_of`, `double_of`, and `string_of` functions. These always return a non-NULL pointer to a value. The internal value is converted to the target type when needed.
For C++ only: you can use the `std::copy` algorithm to copy a container into a JSON array and vice versa, for example:
int myints[] = { 1, 2, 3 };
v.size(3); // make value v a conformant array
std::copy(myints, myints + 3, v.begin()); // copy ints into array
Before casting a value or accessing an array element or object field, you may want to check the type of the value first and also obtain the array size and check the presence of fields in an object:
[codetab.C]
is_null(v) // nonzero if v is not set (JSON null)
is_bool(v) // nonzero if v is a Boolean "true" or "false" value
is_true(v) // nonzero if v is Boolean "true"
is_false(v) // nonzero if v is Boolean "false"
is_int(v) // nonzero if v is a 32 or a 64 bit int
is_double(v) // nonzero if v is a 64 bit double floating point
is_string(v) // nonzero if v is a string or wide string
is_array(v) // nonzero if v is an array
is_struct(v) // nonzero if v is a JSON object
nth_at(const char*) // returns index >= 0 if v has the named field
nth_atw(const wchar_t*) // returns index >= 0 if v has the named field
has_size(v) // returns array or struct size of v or 0
set_size(v, int) // (re)set array size of v
[codetab.C++]
v.is_null() // true if v is not set (JSON null)
v.is_bool() // true if v is a Boolean "true" or "false" value
v.is_true() // true if v is Boolean "true"
v.is_false() // true if v is Boolean "false"
v.is_int() // true if v is a 32 or a 64 bit int
v.is_double() // true if v is a 64 bit double floating point
v.is_string() // true if v is a string or wide string
v.is_array() // true if v is an array
v.is_struct() // true if v is a JSON object
v.has(int) // true if index is within v's array bounds
v.has(const char*) // true if v has the named field
v.has(const wchar_t*) // true if v has the named field
v.empty() // true if v is an empty array or struct
v.size() // returns array or struct size or 0
v.size(int) // (re)set array size
As we pointed out earlier, the context manages the memory of the values that you create and parse from streams. The context also manages IO and socket connections for JSON RPC/REST services over HTTP and HTTPS:
[codetab.C]
struct soap *ctx = soap_new1(SOAP_C_UTFSTRING|SOAP_XML_INDENT);
struct value *v = new_value(ctx);
json_call(ctx, "https://api.github.com/orgs/Genivia/repos", NULL, v);
if (ctx->error)
... // handle IO error
ctx->sendfd = 1; // stdout (or open() a file)
json_write(ctx, v); // display JSON response of the REST GET
...
soap_end(ctx); // delete values and temp data
soap_free(ctx); // free context
[codetab.C++]
struct soap *ctx = soap_new1(SOAP_C_UTFSTRING|SOAP_XML_INDENT);
value v(ctx);
json_call(ctx, "https://api.github.com/orgs/Genivia/repos", NULL, &v);
if (ctx->error)
... // handle IO error
std::cout << v; // display JSON response for the REST GET
...
soap_destroy(ctx); // delete values
soap_end(ctx); // delete temp data
soap_free(ctx); // free context
As you can see, a `json_call` takes a context, an endpoint URL (with URL query string parameters as needed), and optional `in` and `out` values to send and receive, respectively. To use the JSON REST POST method, pass both `in` and `out` values to the `json_call`. For the GET method, pass a NULL to `in`. For the PUT method, pass a NULL to `out`. For the DELETE method, pass both NULL to `in` and `out`.
To compile your JSON RPC/REST C++ code with HTTPS enabled:
[command]
cd gsoap/samples/xml-rpc-json
soapcpp2 -CSL -Ecd xml-rpc.h
c++ -DWITH_OPENSSL -I../.. -o myapp myapp.cpp json.cpp xml-rpc.cpp \
soapC.cpp ../../stdsoap2.cpp -lssl -lcrypto
To compile your JSON RPC/REST C code with HTTPS enabled:
[command]
cd gsoap/samples/xml-rpc-json
soapcpp2 -c -CSL -Ecd xml-rpc.h
cc -DWITH_OPENSSL -I../.. -o myapp myapp.c json.c xml-rpc.c \
soapC.c ../../stdsoap2.c -lssl -lcrypto
A new code generation tool called "jsoncpp" is bundled with 2.8.27 (and later). You can use the gSOAP jsoncpp auto-coding tool to generate high-quality readable source code that uses the gSOAP JSON C or C++ API to populate JSON data and execute JSONPath queries on JSON data. You can embed C/C++ code in the JSONPath query and specify options to channel the JSONPath results to output, or collect results in a JSON array, or to specify your own C/C++ code to execute on the results.
Let's use jsoncpp to generate code to retrieve the project names of a GitHub repository specified by its URL:
[command]
jsoncpp -m -p'$[:].name'
This command generates the following C++ code for JSONPath `$[:].name`:
#include "json.h"
struct Namespace namespaces[] = {{NULL,NULL,NULL,NULL}};
int main(int argc, char **argv)
{ /* Generated by jsoncpp -m -p$[:].name */
struct soap *ctx = soap_new1(SOAP_C_UTFSTRING | SOAP_XML_INDENT);
ctx->double_format = "%lG";
value x(ctx);
std::cin >> x;
if (x.soap->error)
exit(EXIT_FAILURE); // error parsing JSON
// JSONPath: $[:].name
#define QUERY_YIELD(v) std::cout << v << std::endl
if (x.is_array())
{
int j, k = x.size()-1;
for (j = 0; j <= k; j += 1)
{
value& r = x[j];
if (r.has("name"))
{
QUERY_YIELD(r["name"]);
}
}
}
soap_destroy(ctx);
soap_end(ctx);
soap_free(ctx);
return 0;
}
By replacing the `std::cin >> x` JSON parsing in the above by a JSON REST call, you can run this code as a stand-alone command-line application that takes the URL of the repository and prints the project names by executing the JSONPath query:
json_call(ctx, argv[1], NULL, &x);
if (ctx->error)
exit(EXIT_FAILURE);
The new jsoncpp command-line tool is located in `gsoap/samples/xml-rpc-json` in the gSOAP download package. It is built together with the examples, but if you need to (re)build it then execute:
[command]
soapcpp2 -CSL -Ecd xml-rpc.h
c++ -I../.. -o jsoncpp jsoncpp.cpp json.cpp xml-rpc.cpp \
soapC.cpp ../../stdsoap2.cpp
The jsoncpp tool has several options to control the generated content, which we will not discuss here further.
Finally, you can use arithmetic operators and comparisons on JSON numerical and string values in C++. You can also concatenate arrays and JSON objects:
#include
std::stringstream ss("[0,1,2]");
// parse JSON array into 'x'
value x(ctx);
ss >> x; // x = [0,1,2]
x[3] = x[1] + x[2]; // x = [0,1,2,3]
value y(ctx);
y[0] = "abc"; // y = ["abc"]
y[1] = y[0] + 123; // y = ["abc","abc123"]
std::cout << x + y; // [0,1,2,3] + ["abc","abc123"] is [0,1,2,3,"abc","abc123"]
This requires gSOAP 2.8.28 or later. Arithmetic and concatenation operations incur memory overhead due to temporaries, type conversions (when applicable), and managed heap storage, so use them when memory usage is not critical.
To learn more about JSON, JSONPath, the jsoncpp command, and how to compile the JSON API files together with other gSOAP XML data binding files, see the full [XML-RPC/JSON and jsoncpp](doc/xml-rpc-json/html/index.html) documentation.
[![To top](images/go-up.png) To top](#)
How to connect through HTTP proxies and use HTTP bearer or basic/digest authentication, NTLM authentication and WS-Security authentication {#auth}
---
Suppose we have a C++ client application that uses a client proxy `calcProxy` object to talk to the server, as was discussed in [getting started.](dev.html)
On Windows you can also use the [WinInet plugin](doc/wininet/html/index.html) to automate proxy settings, authentication, and other connection settings with the Internet Options of the control panel on Windows.
### HTTP bearer token authentication
For gSOAP versions prior to 2.8.71, to authenticate with bearer tokens set the `http_extra_header` string value of the `soap` context to the string "Authorization: Bearer token" where "token" is the bearer token value:
#include "calc.nsmap"
#include "soapcalcProxy.h"
calcProxy calc("server-endpoint-URL");
double sum;
calc.soap->http_extra_header = "Authorization: Bearer mF_9.B5f-4.1JqM";
if (calc.add(1.23, 4.56, sum) == SOAP_OK)
std::cout << "Sum = " << sum << std::endl;
else
calc.soap_stream_fault(std::cerr);
calc.destroy();
Since gSOAP 2.8.71 you can simply set the `bearer` string value of the `soap` context to the token value as follows:
soap->bearer = "mF_9.B5f-4.1JqM";
[![To top](images/go-up.png) To top](#)
### HTTP basic and digest authentication
Basic authentication is not recommended, unless you are communicating securely with the server over HTTPS. At the client side simply set the `userid` and `passwd` strings of the `soap` context for basic authentication:
#include "calc.nsmap"
#include "soapcalcProxy.h"
calcProxy calc("server-endpoint-URL");
double sum;
calc.soap->userid = "user-id";
calc.soap->passwd = "user-pw";
if (calc.add(1.23, 4.56, sum) == SOAP_OK)
std::cout << "Sum = " << sum << std::endl;
else
calc.soap_stream_fault(std::cerr);
calc.destroy();
The `authrealm` string is set when making an unauthorized call and the server responds with a WWW-Authenticate header:
if (calc.add(1.23, 4.56, sum) == 401) // unauthorized
{
if (calc.soap->authrealm)
std::cout << "Authentication required to access " << calc.soap->authrealm << std::endl;
calc.soap->userid = "user-id";
calc.soap->passwd = "user-pw";
if (calc.add(1.23, 4.56, sum) == SOAP_OK)
std::cout << "Sum = " << sum << std::endl;
...
}
The same mechanism works in C by setting the `soap` context as shown above.
A server implementation for basic authentication checks the authentication credentials in the service operation as follows:
int calc::add(double a, double b, double *result)
{
if (soap->authrealm && soap->userid && soap->passwd)
{
if (!strcmp(soap->authrealm, "server-domain") && !strcmp(soap->userid, "user-id") && !strcmp(soap->passwd, "user-pw"))
{
*result = a + b;
return SOAP_OK;
}
}
soap->authrealm = "server-domain"; // set the realm of the WWW-Authenticate header
return 401; // unauthorized
}
When the client must HTTP authenticate to the server with digest authentication, use [the gSOAP httpda plugin](doc/httpda/html/httpda.html) as follows:
#include "calc.nsmap"
#include "soapcalcProxy.h"
#include "httpda.h"
calcProxy calc("server-endpoint-URL");
double sum;
soap_register_plugin(calc.soap, http_da);
if (calc.add(1.23, 4.56, sum) == 401) // HTTP 401 Unauthorized
{
struct http_da_info authinfo; // store HTTP basic/digest auth info
http_da_save(calc.soap, &authinfo, calc.soap->authrealm, "user-id", "user-pw");
if (calc.add(1.23, 4.56, sum) == SOAP_OK) // should be OK now
...
http_da_restore(calc.soap, &authinfo);
if (calc.add(4.56, 7.89, sum) == SOAP_OK) // should be OK
...
http_da_release(calc.soap, &authinfo); // free auth info
}
calc.destroy();
The same mechanism works in C by setting the `soap` context as shown above. The `soap->authrealm` is set by the server with the fixed name of the realm that the client has to authenticate to. The digest authentication state is maintained in `authinfo` and has to be restored for each new call, otherwise the call will be rejected again with an HTTP 401 error.
To compile, include the `httpda` plugin and compile the gSOAP portable mutex and threads library `threads.c` and `md5evp.c` (`smdevp.c` for gSOAP 2.8.29 and later that replaces `md5evp.c`) and the OpenSSL library as follows:
[command]
c++ -Iplugin -o calcclient calcclient.cpp stdsoap2.cpp \
plugin/httpda.c plugin/md5evp.c plugin/threads.c \
soapC.cpp soapcalcProxy.cpp -lssl -lcrypto
When using MSVC++ to compile C++ applications, please rename the `.c` files listed above to `.cpp` files. Otherwise compilation and link errors may occur.
A server implementation for digest authentication registers the plugin and checks the authentication credentials in the service operation as follows:
int calc::add(double a, double b, double *result)
{
if (soap->authrealm && soap->userid)
{
passwd = ... // use userid and authrealm to find passwd here
if (!strcmp(soap->authrealm, authrealm) && !strcmp(soap->userid, "user-id"))
{
if (!http_da_verify_post(soap, "user-wd")) // HTTP POST DA verification successful
{
*result = a + b;
return SOAP_OK;
}
}
}
soap->authrealm = "server-domain"; // set the realm to return to client
return 401; // unauthorized
}
All OpenSSL versions prior to OpenSSL 1.1.0 require mutex locks to support multi-threaded applications. The OpenSSL-specific functions for the set up and clean up of locks can be downloaded as [`thread_setup.c`](files/thread_setup.c) to add to your code. These set up and cleanup functions are not required with OpenSSL 1.1.0 or greater.
[![To top](images/go-up.png) To top](#)
### HTTP Proxy authentication
Proxy authentication works the same way, but with HTTP error 407 and `http_da_proxy_X` plugin functions:
if (calc.add(1.23, 4.56, sum) == 407) // HTTP 407 Proxy Authentication Required
{
struct http_da_info authproxy; // store HTTP basic/digest auth info
http_da_proxy_save(calc.soap, &authproxy, "proxy-authrealm", "proxy-id", "proxy-pw");
if (calc.add(1.23, 4.56, sum) == SOAP_OK) // should be OK now
...
http_da_proxy_restore(calc.soap, &authproxy);
if (calc.add(4.56, 7.89, sum) == SOAP_OK) // should be OK
...
http_da_proxy_release(calc.soap, &authproxy); // free auth info
}
calc.destroy();
See the build steps in the previous section to compile your code.
All OpenSSL versions prior to OpenSSL 1.1.0 require mutex locks to support multi-threaded applications. The OpenSSL-specific functions for the set up and clean up of locks can be downloaded as [`thread_setup.c`](files/thread_setup.c) to add to your code. These set up and cleanup functions are not required with OpenSSL 1.1.0 or greater.
Proxy authentication with only basic authentication (when supported by the proxy) is much simpler and does not require the `httpda` plugin or OpenSSL:
calc.soap->proxy_host = "proxy-domain-or-IP";
calc.soap->proxy_port = 3128;
calc.soap->proxy_userid = "proxy-id";
calc.soap->proxy_passwd = "proxy-pw";
if (calc.add(1.23, 4.56, sum) == SOAP_OK) // should be OK
...
[![To top](images/go-up.png) To top](#)
### NTLM authentication
On non-windows systems, HTTP NTLM authentication is enabled at the client-side by installing `libntlm` from .
NTLM authentication is enabled in the gSOAP engine by compiling all project source code with the compile-time flag `-DWITH_NTLM`. This configures the gSOAP engine and related source code with NTLM. The client code below illustrates NTLM authentication against a HTTP server with this option enabled:
if (calc.add(1.23, 4.56, sum) == 401) // HTTP 401 Unauthorized
{
if (calc.soap->authrealm)
std::cout << "Authentication required to access " << calc.soap->authrealm << std::endl;
calc.soap->userid = "user-id";
calc.soap->passwd = "user-pw";
if (calc.add(1.23, 4.56, sum) == SOAP_OK) // should be OK now
...
}
The client code below illustrates NTLM authentication against a proxy:
if (calc.add(1.23, 4.56, sum) == 407) // HTTP 407 Proxy Authentication Required
{
if (calc.soap->authrealm)
std::cout << "Authentication required to access " << calc.soap->authrealm << std::endl;
calc.soap->proxy_host = "ntlm-proxy-domain-or-IP";
calc.soap->proxy_port = 80;
calc.soap->proxy_userid = "proxy-id";
calc.soap->proxy_passwd = "proxy-pw";
if (calc.add(1.23, 4.56, sum) == SOAP_OK) // should be OK now
...
}
To avoid the overhead of the initial call that fails, when we know for sure that we have to authenticate anyway, use the following trick:
calc.soap->ntlm_challenge = ""; // pretend we've been challenged
calc.soap->userid = "user-id"; // set proxy_userid
calc.soap->passwd = "user-pw"; // set proxy_passwd
calc.soap->authrealm = "authrealm"; // the realm of the service
if (calc.add(1.23, 4.56, sum) == SOAP_OK) // should be OK
...
To compile the client, link the code against the [libntlm library](http://www.nongnu.org/libntlm/):
[command]
c++ -DWITH_NTLM -o calcclient calcclient.cpp soapC.cpp \
soapcalcProxy.cpp stdsoap2.cpp -lntlm
[![To top](images/go-up.png) To top](#)
### WS-Security authentication
The wsse-lite plugin is a simplified wsse plugin to support WS-Security authentication over HTTPS only. The plugin requires HTTPS to ensure that credentials are encrypted. You can use the full range of WS-Security capabilities, including digest authentication over clear HTTP, with the wsse plugin.
The wsse-lite API is located in the gSOAP download package:
- `gsoap/plugin/wsseapi-lite.h` include this file.
- `gsoap/plugin/wsseapi-lite.c` compile and link this file (C and C++).
- compile all sources with `-DWITH_OPENSSL` to enable HTTPS.
- if you have zlib installed, compile all sources also with `-DWITH_GZIP`.
- link with `-lssl -lcrypto -lz -gsoapssl++` (or `-lgsoapssl` for C, or compile `stdsoap2.c[pp]`).
The interface header file for soapcpp2 should import `wsse.h` (or the older 2002 version `wsse2.h`):
#import "wsse.h"
The wsdl2h tool adds the necessary imports to the generated header file if the WSDL declares the use of WS-Security. If not, you may have to add this import manually before running soapcpp2.
To run soapcpp2 use soapcpp2 with option `-Ipath/gsoap/import` to specify the path to the location of wsse.h in the gSOAP distribution.
To add a user name token with clear text password on the client side, use:
#include "wsseapi-lite.h"
soap_wsse_add_UsernameTokenText(soap, NULL, "username", "password");
It is strongly recommended to use `soap_wsse_add_UsernameTokenText` only in combination with HTTPS encrypted transmission or not at all because the credentials are send in the clear without HTTPS.
Passwords are verified on the receiving side with `soap_wsse_verify_Password`, for example in a service operation `ns__myMethod`:
int ns__myMethod(struct soap *soap, ...)
{
const char *username = soap_wsse_get_Username(soap);
const char *password;
if (!username)
return soap->error; // no username: return FailedAuthentication (from soap_wsse_get_Username)
password = ...; // lookup password of username
if (soap_wsse_verify_Password(soap, password))
{
int err = soap->error;
soap_wsse_delete_Security(soap); // remove old security headers
return err; // password verification failed: return FailedAuthentication
}
return SOAP_OK;
}
The `soap_wsse_get_Username` functions sets the wsse:FailedAuthentication fault upon failure. It is common for the API functions functions to return `SOAP_OK` or a wsse fault that should be passed to the sender by returning `soap->error` from service operations.
See also [WS-Security Lite.](doc/wsse-lite/html/wsse.html)
With plain HTTP (not HTTPS) you should use password digests instead of clear text passwords! To do so, you will need the **full WS-Security API and wsse plugin** that requires OpenSSL installed to compute digests.
- `gsoap/plugin/wsseapi.h` include this file.
- `gsoap/plugin/wsseapi.c` compile and link this file (C and C++).
- `gsoap/plugin/smdevp.c` compile and link this file (C and C++).
- `gsoap/plugin/mecevp.c` compile and link this file (C and C++).
- `gsoap/plugin/threads.h` include this file for portable threads (C and C++).
- `gsoap/plugin/threads.c` required for Windows to support portable threads (C and C++).
- compile all sources with `-DWITH_OPENSSL -DWITH_DOM`.
- if you have zlib installed, compile all sources also with `-DWITH_GZIP`.
- link with `-lssl -lcrypto -lz -gsoapssl++` (or `-lgsoapssl` for C, or compile `stdsoap2.c[pp]` and `dom.c[pp]`).
For example, to compile a client `myapp` application:
[command]
c++ -Iplugin -DWITH_OPENSSL -DWITH_DOM -DWITH_GZIP -o myapp myapp,cpp \
soapC.cpp soapClient.cpp stdsoap2.cpp dom.cpp plugin/wsseapi.c \
plugin/smdevp.c plugin/mevevp.c plugin/threads.c -lssl -lcrypto -lz
When using MSVC++ to compile C++ applications, please rename the `.c` files listed above to `.cpp` files. Otherwise compilation and link errors may occur.
All OpenSSL versions prior to OpenSSL 1.1.0 require mutex locks to support multi-threaded applications. The OpenSSL-specific functions for the set up and clean up of locks can be downloaded as [`thread_setup.c`](files/thread_setup.c) to add to your code. These set up and cleanup functions are not required with OpenSSL 1.1.0 or greater.
To add a user name token with digest password on the client side, use:
#include "wsseapi.h"
soap_wsse_add_UsernameTokenDigest(soap, NULL, "username", "password");
The digest password verification on the receiving side uses the same code as shown above for `ns_myMethod`, so the verification applies to both text password authentication and digest password authentication.
For WS-Security with signatures and encryption, you should register the wsse plugin with an optional security token handler:
#include "wsseapi.h"
soap_register_plugin(soap, soap_wsse, (void*)token_handler);
You do not need to do this for digest authentication, but WS-Security signatures and encryption requires it, see [WS-Security](doc/wsse/html/wsse.html) for details.
[![To top](images/go-up.png) To top](#)
How to retry connections with exponential backoff
---
When a client connects to a service it may receive a 400 or 500 HTTP response code in rare cases when something goes wrong with the request. The SOAP protocol offers insights into the error by returning a SOAP Fault. The SOAP Fault may indicate a server-side fault (SOAP 1.1 `SOAP-ENV:Sender` or SOAP 1.2 `SOAP-ENV:Receiver` fault returned as error code `soap->error == SOAP_CLI_FAULT`) a client-side fault (SOAP 1.1 `SOAP-ENV:Client` or SOAP 1.2 `SOAP-ENV:Sender` fault returned as error code `soap->error == SOAP_SVR_FAULT`). Non-SOAP 400 and 500 errors are returned as plain HTTP error codes and stored in `soap->error`.
Retrying to send the message when a client-side fault `SOAP_CLI_FAULT` occurred makes no sense. However, if a server-side fault `SOAP_SVR_FAULT` occurred or an HTTP 500 error occurred, it may be worthwhile to retry the request. The followup request may succeed when the original failed. However, we do not want to flood the server with requests. It is important not to simply loop repeatedly making requests as the looping behavior can overload the network and the server.
A common approach is to retry with increasing delays between attempts, usually with an exponentially increasing delay known as [Exponential Backoff.](https://en.wikipedia.org/wiki/Exponential_backoff)
To implement exponential backoff two parameters are needed: the initial delay and the multiplicative factor to increase the delay. We also want to terminate the retries when the delay exceeds a threshold. For example, to connect to the calculator service with exponential backoff:
[codetab.C]
#include // usleep
float delay;
float delay_factor = 2; // delay doubles for each retry
float max_delay = 120; // 2 minutes max delay
struct soap *soap = soap_new();
double sum;
delay = 0.010; // 10 ms initial delay
while (soap_call_ns__add(soap, "server-endpoint-URL", NULL, 1.23, 4.56, sum))
{
if (delay <= max_delay &&
(soap->error == SOAP_SVR_FAULT || (soap->error >= 500 && soap->error < 599)))
{
usleep((useconds_t)(1000 * delay));
delay *= delay_factor;
}
else
{
soap_print_fault(soap, stderr);
break;
}
}
if (soap->error == SOAP_OK)
{
... // OK: success!
}
[codetab.C++]
#include // usleep
float delay;
float delay_factor = 2; // delay doubles for each retry
float max_delay = 120; // 2 minutes max delay
calcProxy calc("server-endpoint-URL");
double sum;
delay = 0.010; // 10 ms initial delay
while (calc.add(1.23, 4.56, sum))
{
if (delay <= max_delay &&
(calc.soap->error == SOAP_SVR_FAULT || (calc.soap->error >= 500 && calc.soap->error < 599)))
{
usleep((useconds_t)(1000 * delay));
delay *= delay_factor;
}
else
{
calc.soap_stream_fault(std::cerr);
break;
}
}
if (calc.soap->error == SOAP_OK)
{
... // OK: success!
}
[![To top](images/go-up.png) To top](#)
How to handle HTTP redirects
---
A server may respond with a HTTP 301, 302, 303, or a 307 error code to redirect the client to another endpoint URL. The semantics between these error codes differ slightly, but overall there is no point in retrying the connection to the current endpoint. The client should use the endpoint URL that is specified with the 300 HTTP error code, to connect.
When responding to HTTP error code to redirect, there are a few things we must do:
- For security reasons, if the current endpoint URL uses HTTPS transport security then the redirected endpoint URL must also used HTTP transport security.
- Infinite cyclic redirects must be detected by the client, for example by limiting the number of redirects.
- Retry the connection with the specified endpoint URL but only if the endpoint URL is an http/s address.
For example, to connect to an example service while supporting 301, 303 and 307 redirects:
[codetab.C]
int redirects;
int max_redirects = 10;
const char *endpoint;
struct soap *soap = soap_new();
double sum;
redirects = 0;
endpoint = "server-endpoint-URL";
while (!strncmp(endpoint, "http", 4) && soap_call_ns__add(soap, endpoint, NULL, 1.23, 4.56, sum))
{
if (redirects <= max_redirects &&
(soap->error == 301 || soap->error == 303 || soap->error == 307) &&
!strncmp(endpoint, soap->endpoint, 6)) // both http or both https
{
redirects++;
endpoint = soap_strdup(soap, soap->endpoint);
}
else
{
soap_print_fault(soap, stderr);
break;
}
}
if (soap->error == SOAP_OK)
{
... // OK: success!
}
[codetab.C++]
int redirects;
int max_redirects = 10;
const char *endpoint;
calcProxy calc;
double sum;
redirects = 0;
endpoint = "server-endpoint-URL";
while (!strncmp(endpoint, "http", 4) && calc.add(endpoint, NULL, 1.23, 4.56, sum))
{
if (redirects <= max_redirects &&
(calc.soap->error == 301 || calc.soap->error == 303 || calc.soap->error == 307) &&
!strncmp(endpoint, calc.soap->endpoint, 6)) // both http or both https
{
redirects++;
endpoint = soap_strdup(calc.soap, calc.soap->endpoint);
}
else
{
calc.soap_stream_fault(std::cerr);
break;
}
}
if (calc.soap->error == SOAP_OK)
{
... // OK: success!
}
Note: HTTP 302 and 303 error codes are not expected responses to POST requests. In fact, clients should not follow these redirects automatically for POST/PUT/DELETE requests.
[![To top](images/go-up.png) To top](#)
How to enable HTTP access control (CORS) headers
---
HTTP access control (CORS) is supported at the client and server sides.
CORS is automatic at the server side. The server internally calls the `int fopt(struct soap*)` callback (built-in as `int http_200(struct soap*)` in stdsoap2.c and stdsoap2.cpp) to serve the OPTION method CORS requests. The built-in callback returns an HTTP 200 OK with CORS headers. The callback can be changed to point to your own server-side OPTIONS logic if needed. The default value of the CORS `Access-Control-Allow-Origin` is `*`. This default value can be changed by:
struct soap *soap = soap_new(); // initialize context etc.
...
soap->cors_allow = "http://example.com"; // set Access-Control-Allow-Origin header value to "http://example.com"
...
soap_serve(soap); // serve requests, returning header Access-Control-Allow-Origin: "http://example.com"
which means the the resource owners wish to restrict access to the resource to be only from `http://example.com`.
At the client side CORS requires the OPTIONS method with CORS headers. Use the following code to send OPTIONS with CORS headers to a server:
const char *server = "server-endpoint-URL";
soap->origin = "http://example.com";
soap->cors_method = "POST"; // request method is POST
soap->cors_header = "..."; // list of comma-separated request headers (may be omitted)
if (soap_connect_command(soap, SOAP_OPTIONS, server, NULL)
|| soap_end_send(soap)
|| soap_recv_empty_response(soap))
{
soap_print_fault(soap, stderr); // an error occurred
}
else if (soap_call_ns__myMethod(soap, server, NULL, ...)) // SOAP call with POST method
{
soap_print_fault(soap, stderr); // an error occurred
}
else
{
...
}
See also [Cross-origin resource sharing](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) on Wikipedia and the MDN article [HTTP access control (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS) for details about CORS.
[![To top](images/go-up.png) To top](#)
How to add a custom HTTP header
---
To add a custom HTTP header set the `http_extra_header` of the context:
soap->http_extra_header = "name: value(s)";
At the client side set the header just before making the call. At the server side, set the header just before returning from a service operation.
The `http_extra_header` string will be set to NULL each time it was used, to prevent it from bleeding into other calls.
Tip: to create multiple custom headers, separate each with a `\r\n` in the string assigned to `http_extra_header`.
[![To top](images/go-up.png) To top](#)
How to use cURL with gSOAP clients
---
To use cURL with gSOAP clients, register the [CURL plugin](https://www.genivia.com/doc/curl/html/index.html) for gSOAP using `soap_register_plugin(soap, soap_curl)`. Add `curl_global_init(CURL_GLOBAL_ALL)` to the start of your program and add `curl_global_cleanup()` to the end of your program:
#include "plugin/curlapi.h"
struct soap *soap = soap_new();
curl_global_init(CURL_GLOBAL_ALL);
soap_register_plugin(soap, soap_curl);
... // application logic goes here
soap_free(soap);
curl_global_cleanup();
To use your own cURL handle:
#include "plugin/curlapi.h"
CURL *curl
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
soap_register_plugin_arg(soap, soap_curl, curl);
... // application logic goes here
soap_free(soap);
curl_easy_cleanup(curl);
curl_global_cleanup();
Compile your code together with `plugin/curlapi.c` located in the gSOAP package.
See the [cURL example](examples/curl/index.html) and the [CURL plugin](doc/curl/html/index.html) documentation for details.
[![To top](images/go-up.png) To top](#)
How to use HTTPS TLS/SSL with clients and stand-alone gSOAP servers {#tls}
---
You must either have [OpenSSL](http://www.openssl.org), [GNUTLS](http://www.gnutls.org), or [WolfSSL](https://www.wolfssl.com) installed to use TLS/SSL for HTTPS clients and stand-alone servers. The gSOAP software supports OpenSSL 3.0 to the latest 3.3 and also the older OpenSSL 1.1. Earlier OpenSSL versions are supported but not recommended.
### HTTPS client
Suppose we have a client application. For example the client proxy `calcProxy` object discussed in [getting started.](dev.html) Change the code as follows to enable the client to communicate with TLS/SSL over HTTPS:
[codetab.C]
#include "calc.nsmap"
#include "soapH.h" /* generated with soapcpp2 -c calc.h */
struct soap *soap = soap_new();
double sum;
soap_ssl_init(); /* init SSL (just need to do this once in an application) */
/* soap_ssl_no_init(); */ /* or prevent init OpenSSL when already initialized elsewhere in an application */
if (soap_ssl_client_context(soap,
SOAP_SSL_DEFAULT,
NULL, /* no keyfile */
NULL, /* no keyfile password */
"cacerts.pem", /* trusted certificates (or use self-signed cacert.pem) */
NULL, /* no capath to trusted certificates */
NULL /* no random data to seed randomness */
))
{
soap_print_fault(soap, stderr);
exit(EXIT_FAILURE);
}
if (soap_call_ns__add(soap, "https-server-endpoint-URL", NULL, 1.23, 4.56, sum) == SOAP_OK)
...
[codetab.C++]
#include "calc.nsmap"
#include "soapcalcProxy.h" // generated with soapcpp2 -j calc.h
calcProxy calc("https-server-endpoint-URL");
double sum;
soap_ssl_init(); // init SSL (just need to do this once in an application)
// soap_ssl_no_init(); // or prevent init OpenSSL when already initialized elsewhere in an application
if (soap_ssl_client_context(calc.soap,
SOAP_SSL_DEFAULT,
NULL, // no keyfile
NULL, // no keyfile password
"cacerts.pem", // trusted certificates (or use self-signed cacert.pem)
NULL, // no capath to trusted certificates
NULL // no random data to seed randomness
))
{
calc.soap_stream_fault(std::cerr);
exit(EXIT_FAILURE);
}
if (calc.add(1.23, 4.56, sum) == SOAP_OK)
...
To authenticate the server as specified with `SOAP_SSL_DEFAULT`, `cacerts.pem` is used that contains trusted certificates of CAs. This file is included with the gSOAP software and has [downloadable updates.](files/cacerts.zip) In fact, without the `soap_ssl_init()` and `soap_ssl_client_context()` calls the engine will still connect to HTTPS services and the messages will be encrypted, but the server is not authenticated by the client.
To simplify the client TLS/SSL context setup, download [`ssl_setup.c`](files/ssl_setup.zip) and use `soap_ssl_client_setup()` which offers the following features:
- automatically uses certificates stored in Unix/Linux common locations,
- automatically uses Windows system certificate store,
- when `-DWITH_WININET` is defined, uses the gSOAP WinInet plugin with WinInet system certificate store,
- when `-DWITH_CURL` is defined, uses the gSOAP CURL plugin with CURL certificate store.
The example given here does not authenticate the client to the server. To authenticate clients to servers, please see the [gSOAP user guide](docs.html) for guidance and check out the `gsoap/samples/ssl` example in the gSOAP download package.
To compile the C client, enable OpenSSL with `-DWITH_OPENSSL`:
[command]
cc -DWITH_OPENSSL -o calcclient calcclient.c soapC.c \
soapClient.c stdsoap2.c -lssl -lcrypto
or enable GNUTLS with `-DWITH_GNUTLS` instead of OpenSSL:
[command]
cc -DWITH_GNUTLS -o calcclient calcclient.c soapC.c \
soapClient.c stdsoap2.c -lgnutls
or enable WolfSSL with `-DWITH_WOLFSSL` instead of OpenSSL:
[command]
cc -DWITH_WOLFSSL -o calcclient calcclient.c soapC.c \
soapClient.c stdsoap2.c -lwolfssl
To compile the C++ client, enable OpenSSL with `-DWITH_OPENSSL`:
[command]
c++ -DWITH_OPENSSL -o calcclient calcclient.cpp soapC.cpp \
soapcalcProxy.cpp stdsoap2.cpp -lssl -lcrypto
or enable GNUTLS with `-DWITH_GNUTLS` instead of OpenSSL:
[command]
c++ -DWITH_GNUTLS -o calcclient calcclient.cpp soapC.cpp \
soapcalcProxy.cpp stdsoap2.cpp -lgnutls
or enable WolfSSL with `-DWITH_WOLFSSL` instead of OpenSSL:
[command]
c++ -DWITH_WOLFSSL -o calcclient calcclient.cpp soapC.cpp \
soapcalcProxy.cpp stdsoap2.cpp -lwolfssl
All OpenSSL versions prior to OpenSSL 1.1.0 require mutex locks to support multi-threaded applications, see below for more details.
For OpenSSL options to specify SSL/TLS connection settings, to enable CRLs and to customize certificate validation, please see the subsections further below.
[![To top](images/go-up.png) To top](#)
### HTTPS clients in a multi-threaded environment
In addition to the source code shown in the previous section, all OpenSSL versions prior to 1.1.0 require locks to support multi-threading. To do so we recommend to use the gSOAP portable threads in `plugin/threads.h` and `plugin/threads.c` located in the gSOAP package. Then add OpenSSL locks as follows:
#include "plugin/threads.h"
soap_ssl_init(); // start your program by initializing SSL and setting up locks
// soap_ssl_no_init(); // or prevent init OpenSSL when already initialized elsewhere in an application
if (CRYPTO_thread_setup()) // OpenSSL thread mutex setup
... // error
... // code goes here
CRYPTO_thread_cleanup(); // OpenSSL thread mutex cleanup
exit(EXIT_SUCCESS); // exit program
The set up and clean up should be done only once in your application. The OpenSSL-specific code for these set up and clean up functions is not shown here. Get it from [`thread_setup.c`](files/thread_setup.c) to add to your code.
OpenSSL 1.1.0 and greater does not require these locks to be set up. If you are not sure which version of OpenSSL you may be using with your multi-threaded application, then set up the locks as shown.
GNUTLS and WolfSSL do not require these locks to be set up.
[![To top](images/go-up.png) To top](#)
### HTTPS stand-alone server
Suppose we have a stand-alone application. For example the server class `calcService` object discussed in [getting started.](dev.html) We change the code by adding a call to `soap_ssl_accept(soap)` (in C) or `calc.ssl_accept()` (in C++) as follows. This enables the server to communicate with TLS/SSL over HTTPS by binding a port and accepting TLS/SSL connections to serve:
[codetab.C]
#include "calc.nsmap"
#include "soapH.h" /* generated with soapcpp2 -c calc.h */
int main()
{
struct soap *soap = soap_new();
soap->accept_timeout = 24*60*60; /* quit after 24h of inactivity (optional) */
soap->send_timeout = soap->recv_timeout = 5; /* max send and receive socket inactivity time (sec) */
soap->transfer_timeout = 10; /* max time for send or receive of messages (sec) */
soap_ssl_init(); /* init SSL (just need to do this once in an application) */
/* soap_ssl_no_init(); */ /* or prevent init OpenSSL when already initialized elsewhere in an application */
if (soap_ssl_server_context(soap,
SOAP_SSL_DEFAULT,
"server.pem", /* server keyfile (cert+key) */
"password", /* password to read the private key in the keyfile */
NULL, /* no cert to authenticate clients */
NULL, /* no capath to trusted certificates */
NULL, /* DH/RSA: use 2048 bit RSA (default with NULL) */
NULL, /* no random data to seed randomness */
NULL /* no SSL session cache */
))
{
soap_print_fault(soap, stderr);
exit(EXIT_FAILURE);
}
if (soap_valid_socket(soap_bind(soap, NULL, 8080, 2))) /* small backlog queue of 2 to 10 for iterative servers */
{
while (soap_valid_socket(soap_accept(soap)))
{
if (soap_ssl_accept(soap) || soap_serve(soap))
soap_print_fault(soap, stderr);
soap_destroy(soap); // free deserialized data
soap_end(soap); // free deserialized data
}
}
soap_print_fault(soap, stderr);
soap_destroy(soap);
soap_end(soap);
soap_free(soap);
return 0;
}
[codetab.C++]
#include "calc.nsmap"
#include "soapcalcService.h" // generated with soapcpp2 -j calc.h
int main()
{
calcService calc;
calc.soap->accept_timeout = 24*60*60; // quit after 24h of inactivity (optional)
calc.soap->send_timeout = calc.soap->recv_timeout = 10; // max send and receive socket inactivity time (sec)
calc.soap->transfer_timeout = 10; // max time for send or receive of messages (sec)
soap_ssl_init(); // init SSL (just need to do this once in an application)
// soap_ssl_no_init(); // or prevent init OpenSSL when already initialized elsewhere in an application
if (soap_ssl_server_context(calc.soap,
SOAP_SSL_DEFAULT,
"server.pem", // server keyfile (cert+key)
"password", // password to read the private key in the keyfile
NULL, // no cert to authenticate clients
NULL, // no capath to trusted certificates
NULL, // DH/RSA: use 2048 bit RSA (default with NULL)
NULL, // no random data to seed randomness
NULL // no SSL session cache
))
{
calc.soap_stream_fault(std::cerr);
exit(EXIT_FAILURE);
}
if (soap_valid_socket(calc.bind(NULL, 8080, 2))) /* small backlog queue of 2 to 10 for iterative servers */
{
while (soap_valid_socket(calc.accept()))
{
if (calc.ssl_accept()
|| calc.serve())
calc.soap_stream_fault(std::cerr);
// free deserialized data
calc.destroy();
}
}
calc.soap_stream_fault(std::cerr);
// clean up
calc.destroy();
return 0;
}
For C++, newer versions of gSOAP include a `ssl_run()` method with the server object, so the above conditional loop can be rewritten to:
while (calc.ssl_run(8080) != SOAP_OK)
calc.soap_stream_fault(std::cerr);
To compile the C server, enable OpenSSL with `-DWITH_OPENSSL`:
[command]
cc -DWITH_OPENSSL -o calcserver calcserver.c soapC.c \
soapServer.c stdsoap2.c -lssl -lcrypto
or enable GNUTLS with `-DWITH_GNUTLS` instead of OpenSSL:
[command]
cc -DWITH_GNUTLS -o calcserver calcserver.c soapC.c \
soapServer.c stdsoap2.c -lgnutls
or enable WolfSSL with `-DWITH_WOLFSSL` instead of OpenSSL:
[command]
cc -DWITH_WOLFSSL -o calcserver calcserver.c soapC.c \
soapServer.c stdsoap2.c -lwolfssl
To compile the C++ server, enable OpenSSL with `-DWITH_OPENSSL`:
[command]
c++ -DWITH_OPENSSL -o calcserver calcserver.cpp soapC.cpp \
soapcalcService.cpp stdsoap2.cpp -lssl -lcrypto
or enable GNUTLS with `-DWITH_GNUTLS` instead of OpenSSL:
[command]
c++ -DWITH_GNUTLS -o calcserver calcserver.cpp soapC.cpp \
soapcalcService.cpp stdsoap2.cpp -lgnutls
or enable WolfSSL with `-DWITH_WOLFSSL` instead of OpenSSL:
[command]
c++ -DWITH_WOLFSSL -o calcserver calcserver.cpp soapC.cpp \
soapcalcService.cpp stdsoap2.cpp -lwolfssl
All OpenSSL versions prior to OpenSSL 1.1.0 require mutex locks to support multi-threaded applications. The OpenSSL-specific functions for the set up and clean up of locks can be downloaded as [`thread_setup.c`](files/thread_setup.c) to add to your code. These set up and cleanup functions are not required with OpenSSL 1.1.0 or greater.
[![To top](images/go-up.png) To top](#)
### HTTP TLS/SSL options
The `SOAP_SSL_DEFAULT` flag used with `soap_ssl_client_context()` and `soap_ssl_server_context()` enables TLS v1.0, v1.1, and v1.2. It also requires server authentication to clients by means of the certificate included with the `server.pem` parameter of `soap_ssl_server_context()`.
Additional client-side and server-side options to set the TLS/SSL context are:
- `SOAP_SSL_NO_AUTHENTICATION` disables peer authentication
- `SOAP_SSL_REQUIRE_SERVER_AUTHENTICATION` requires servers to authenticate to the client (default)
- `SOAP_SSL_REQUIRE_CLIENT_AUTHENTICATION` requires clients to authenticate to the server
- `SOAP_SSL_SKIP_HOST_CHECK` disables checking of the common name of the host in certificate
- `SOAP_SSL_ALLOW_EXPIRED_CERTIFICATE` disables checking of the expiration date of the certificate and omit CRL checks
- `SOAP_SSL_NO_DEFAULT_CA_PATH` disables `default_verify_paths` (OpenSSL)
- `SOAP_TLSv1` enables TLS v1.0/1.1/1.2 (default)
- `SOAP_SSLv3_TLSv1` enables SSL v3 and TLS v1.0/1.1/1.2
- `SOAP_SSLv3` restricts SSL to SSL v3 only
- `SOAP_TLSv1_0` restricts TLS to TLS v1.0 only
- `SOAP_TLSv1_1` restricts TLS to TLS v1.1 only
- `SOAP_TLSv1_2` restricts TLS to TLS v1.2 only
Multiple options can be combined with `|` as in `SOAP_SSL_SKIP_HOST_CHECK | SOAP_SSL_ALLOW_EXPIRED_CERTIFICATE`, except for the TLS/SSL version restrictions of which only one of these options can be used.
The DH/RSA server-side parameter of `soap_ssl_server_context` specifies a Diffie-Hellman (DH) key. When `NULL`, an ephemeral RSA key is generated. If this string parameter is the name of a file, say `"dh2048.pem"`, then the file is accessed to obtain the DH key from that PEM file. If the string parameter is a number, say `"4096"`, then an ephemeral DH key of that bit size will be generated. When the DH/RSA parameter is`NULL`, RSA keys of 2048 bits will be used (number of bits is defined by the `SOAP_SSL_RSA_BITS` macro).
To simplify the client TLS/SSL context setup, download [`ssl_setup.c`](files/ssl_setup.zip) and use `soap_ssl_client_setup()` which offers the following features:
- automatically uses certificates stored in Unix/Linux common locations,
- automatically uses Windows system certificate store,
- when `-DWITH_WININET` is defined, uses the gSOAP WinInet plugin with WinInet system certificate store,
- when `-DWITH_CURL` is defined, uses the gSOAP CURL plugin with CURL certificate store.
[![To top](images/go-up.png) To top](#)
### RSA and DSA cipher lists and suites
You can specify a cipher list to use with TLSv1.2 and below with `SSL_CTX_set_cipher_list(soap->ctx, "...")` where `soap->ctx` is the SSL context created by `soap_ssl_client_context()` or by `soap_ssl_server_context()`. Likewise, use `SSL_CTX_set_ciphersuites(soap->ctx, "...")` to configure the available TLSv1.3 ciphersuites.
We refer to the OpenSSL documentation and manual pages of `SSL_CTX_set_cipher_list` and `SSL_CTX_set_ciphersuites` for details on the latest cipher lists and suites available to use.
[![To top](images/go-up.png) To top](#)
### CRL checking and rejection
CRL checking requires OpenSSL 0.9.8 or higher, or GNUTLS, or WolfSSL (only when enabled during the WolfSSL library build).
To enable CRL checking, use `soap_ssl_crl` as follows:
if (soap_ssl_crl(soap, "crl.pem")) // load CRLs from file in PEM format
... // error loading CRLs
This verifies certificates against the revocation list.
Use an empty string argument with `soap_ssl_crl()` to enable checking of CRLs that are provided by CAs and embedded in certificates:
soap_ssl_crl(soap, "")
If you enable a CRL on a context with OpenSSL, any certificate whose CA does not have a CRL will be rejected. Setting a verification callback can work around these issues.
[![To top](images/go-up.png) To top](#)
### OpenSSL verification callback
To set a certificate verification callback for OpenSSL, define a callback function as follows. The function returns `ok = 1` to attempt to override the error, for example when a self-signed certificate is found and/or no CRLs are available:
soap->fsslverify = verify_callback;
int ssl_verify_callback(int ok, X509_STORE_CTX *store)
{
if (!ok)
{
int err = X509_STORE_CTX_get_error(store);
switch (err)
{
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
case X509_V_ERR_UNABLE_TO_GET_CRL:
X509_STORE_CTX_set_error(store, X509_V_OK);
ok = 1;
break;
}
}
return ok;
}
The callback can also be used to report issues with certificates, such as self-signed certificates, and possibly accept them anyway if the application's security policy permits.
[![To top](images/go-up.png) To top](#)
### HTTPS multi-threaded stand-alone server
To spawn threads, change the server code in `main()` as follows:
[codetab.C]
#include "calc.nsmap"
#include "soapH.h" /* generated with soapcpp2 -c calc.h */
#include "plugin/threads.h"
int main()
{
soap_ssl_init();
if (CRYPTO_thread_setup()) /* OpenSSL thread mutex setup */
exit(EXIT_FAILURE);
... /* C++ service object instantiation goes here */
... /* soap_ssl_server_context() initialization goes here */
if (soap_valid_socket(soap_bind(soap, NULL, 8080, 100)))
{
while (soap_valid_socket(soap_accept(soap)))
{
THREAD_TYPE tid;
void *arg = (void*)soap_copy(soap);
/* use updated THREAD_CREATE from plugin/threads.h or https://www.genivia.com/files/threads.zip */
if (!arg)
soap_force_closesock(soap);
else
while (THREAD_CREATE(&tid, (void*(*)(void*))process_request, arg))
sleep(1); /* thread creation failed, try again in one second */
}
}
... /* finalizations go here */
CRYPTO_thread_cleanup(); /* OpenSSL thread mutex cleanup */
return 0;
}
void *process_request(void *arg)
{
struct soap *soap = (struct soap*)arg;
THREAD_DETACH(THREAD_ID);
if (soap)
{
if (soap_ssl_accept(soap) == SOAP_OK)
soap_serve(soap);
soap_destroy(soap);
soap_end(soap);
soap_free(soap);
}
return NULL;
}
[codetab.C++]
#include "calc.nsmap"
#include "soapcalcService.h" // generated with soapcpp2 -j calc.h
#include "plugin/threads.h"
int main()
{
soap_ssl_init();
if (CRYPTO_thread_setup()) // OpenSSL thread mutex setup
exit(EXIT_FAILURE);
... // C++ service object instantiation goes here
... // soap_ssl_server_context() initialization goes here
if (soap_valid_socket(calc.bind(NULL, 8080, 100)))
{
while (soap_valid_socket(calc.accept()))
{
THREAD_TYPE tid;
void *arg = (void*)calc.copy();
/* use updated THREAD_CREATE from plugin/threads.h or https://www.genivia.com/files/threads.zip */
if (!arg)
soap_closesock(soap);
else
while (THREAD_CREATE(&tid, (void*(*)(void*))process_request, arg))
sleep(1); /* thread creation failed, try again in one second */
}
}
... // finalizations go here
CRYPTO_thread_cleanup(); // OpenSSL thread mutex setup
return 0;
}
void *process_request(void *arg)
{
calcService *calc = (calcService*)arg;
THREAD_DETACH(THREAD_ID);
if (calc)
{
if (calc.ssl_accept() == SOAP_OK) // SSL handshake
calc.serve();
calc.destroy();
delete calc;
}
return NULL;
}
As shown in the code above, all OpenSSL versions prior to 1.1.0 require locks to support multi-threading. To do so we recommend to use the gSOAP portable threads in `plugin/threads.h` and `plugin/threads.c` located in the gSOAP package. Then add OpenSSL locks as follows:
#include "plugin/threads.h"
soap_ssl_init(); // start your program by initializing OpenSSL and setting up locks:
// soap_ssl_no_init(); // or prevent init OpenSSL when already initialized elsewhere in an application
if (CRYPTO_thread_setup()) // OpenSSL thread mutex setup
... // error
... // code goes here
CRYPTO_thread_cleanup(); // OpenSSL thread mutex cleanup
exit(EXIT_SUCCESS); // exit program
The set up and clean up should be done only once in your application. The OpenSSL-specific code for these set up and clean up functions is not shown here. Get it from [`thread_setup.c`](files/thread_setup.c) to add to your code.
OpenSSL 1.1.0 and greater does not require these locks to be set up. If you are not sure which version of OpenSSL you may be using with your multi-threaded application, then set up the locks as shown.
GNUTLS and WolfSSL do not require these locks to be set up.
[![To top](images/go-up.png) To top](#)
How to enable FIPS 140-2 with OpenSSL
---
The following hints apply to older OpenSSL 1.1 prior to OpenSSL 3.0. Always use the appropriate OpenSSL-recommended FIPS API functions. We take no responsibility for omissions in this section, because FIPS compliance applies to an application as a whole, not to a development tool and runtime library, such as gSOAP.
To enable FIPS 140-2 mode with OpenSSL, we use [`FIPS_mode_set()`](https://wiki.openssl.org/index.php/FIPS_mode_set()) as follows to enable FIPS mode:
#include "plugin/threads.h"
soap_ssl_init(); // start your program by initializing OpenSSL and setting up locks:
// soap_ssl_no_init(); // or prevent init OpenSSL when already initialized elsewhere in an application
if (CRYPTO_thread_setup()) // OpenSSL thread mutex setup (not needed with OpenSSL 1.1.0 and greater)
... // error
if (FIPS_mode_set(1) != 1) // enter FIPS 140-2 mode
{
err = ERR_get_error();
... // error
}
... // code goes here
if (FIPS_mode_set(0) != 1) // exit FIPS 140-2 mode
{
err = ERR_get_error();
... // error
}
CRYPTO_thread_cleanup(); // OpenSSL thread mutex cleanup (not needed with OpenSSL 1.1.0 and greater)
exit(EXIT_SUCCESS); // exit program
The gSOAP runtime library, the generated code, and your program **must have been built with the FIPS Object Module, and the FIPS Object Module must have been acquired, built, and installed in accordance with the OpenSSL Security Policy**.
`FIPS_mode_set()` can fail for a number of reasons, and many of the error codes are discussed in detail in the OpenSSL FIPS Object Module User Guide 2.0. One common code is `CRYPTO_R_FIPS_MODE_NOT_SUPPORTED` (0xf06d065). The particular error code indicates the application was likely linked against an OpenSSL library without validated cryptography. That is, a FIPS Capable Library was **not** used during application linking. See [`FIPS_mode_set()`](https://wiki.openssl.org/index.php/FIPS_mode_set()) for more details.
OpenSSL 1.1.0 and greater does not require locks to be set up. If you are not sure which version of OpenSSL you may be using with your multi-threaded application, then set up the locks as shown.
[![To top](images/go-up.png) To top](#)
How to create self-signed certificates with OpenSSL and gSOAP {#cert}
---
To generate a self-signed root certificate to sign client and server certificates, first create a new private directory, say `CA` for "Certificate Authority" in your home directory to store private keys and certificates. Next, copy [`openssl.cnf`](files/openssl.cnf.txt), [`root.sh`](files/root.sh.txt), and [`cert.sh`](files/cert.sh.txt) to this directory. These files are also included in the gSOAP download package under `gsoap/samples/ssl`.
Edit `openssl.cnf` and go to the `[req_distinguished_name]` section to add or change the following items:
[command]
[ req_distinguished_name ]
countryName_default = US
stateOrProvinceName_default = Your-State
localityName_default = Your-City
0.organizationName_default = Your-Company-Name
emailAddress_default = your-email@address
If you are going to use these settings often, we suggest to add the following line to your `.cshrc` or `.tcshrc` when you're using csh or tcsh:
[command]
setenv OPENSSL_CONF $HOME/CA/openssl.cnf
or add the folling line to `.bashrc` when you're using bash:
[command]
export OPENSSL_CONF=$HOME/CA/openssl.cnf
To generate the root CA, execute `root.sh`:
[command]
./root.sh
* This utility creates a self-signed private root CA and public certificate:
* root.pem - private root CA (keep this secret)
* cacert.pem - public CA certificate (shared)
* Distribute the cacert.pem to clients to authenticate your server
* Use root.pem to sign certificates using the cert.sh utility
*
* Enter a (new) secret pass phrase when requested
* Enter it again when prompted to self-sign the CA root certificate
* You also need the pass phrase later to sign the client and server key files
* Enter your company name as the Common Name (e.g. genivia.com) when requested
* The root CA will expire after three years (1095 days)
Generating a 1024 bit RSA private key
..............................................+++++
.+++++
writing new private key to 'rootkey.pem'
Enter PEM pass phrase: XXXXXXXXXX
Verifying - Enter PEM pass phrase: XXXXXXXXXX
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [US]: XX
State or Province Name (full name) [Your-State]: XX
Locality Name (eg, city) [Your-City]: XXXXXXXXXX
Organization Name (eg, company) [Your-Company-Name]: XXXXXXXXXX
Organizational Unit Name (eg, section): XXXXXXXXXX
Common Name (eg, your name or host name): XXXXXXXXXX
Email Address [your-email@address]: XXXXXXXXXX
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
Signature ok
subject= /C=...
Getting Private key
Enter pass phrase for rootkey.pem: XXXXXXXXXX
subject= /C=...
issuer= /C=...
notBefore=Nov 15 19:07:55 2018 GMT
notAfter=Nov 14 19:07:55 2021 GMT
When prompted, choose a PEM pass phrase to protect the CA's private key that you are about to generate. After entering your info, enter the pass phrase again to self-sign the root CA. You will also need the CA's PEM pass phrase later when you sign certificates with the `cert.sh` script.
Now you have a new `root.pem` with the CA's private key. Save the generated `root.pem` keyfile and the CA's PEM pass phrase in a safe place. Do not distribute the `root.pem`! The generated `cacert.pem` certificate of the CA can be distributed and used by peers (web browsers and other client applications) to authenticate all server certificates that are signed with it.
The `root.pem` and `cacert.pem` are valid for three years (1095 days as set in `openssl.cnf`).
Next, generate the `server.pem` keyfile and sign it with the root CA by executing `cert.sh server`:
[command]
./cert.sh server
* This utility create certificate server.pem signed by the root CA:
* servercert.pem - public key (CA-signed certificate to be shared)
* serverkey.pem - private key (keep this secret)
* server.pem - private key bundled with certificates (keep secret)
* Distribute the CA root cacert.pem (and/or servercert.pem when needed)
* Before using this utility, create a root CA root.pem using root.sh
* Keep server.pem key file secret: store locally with client/server app
*
* Enter a secret pass phrase when requested
* The pass phrase is used to access server.pem in your application
* Enter the server's host name as the Common Name when requested
* Enter the root CA pass phrase (Getting CA Private Key) to sign the key file
* The key file will expire after three years or when the root CA expires
Generating a 1024 bit RSA private key
...................+++++
....................+++++
writing new private key to 'serverkey.pem'
Enter PEM pass phrase: XXXXXXXXXX
Verifying - Enter PEM pass phrase: XXXXXXXXXX
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [US]: XX
State or Province Name (full name) [Your-State]: XX
Locality Name (eg, city) [Your-City]: XXXXXXXXXX
Organization Name (eg, company) [Your-Company-Name]: XXXXXXXXXX
Organizational Unit Name (eg, section): XXXXXXXXXX
Common Name (eg, your name or host name): XXXXXXXXXX
Email Address [your-email@address]: XXXXXXXXXX
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
Signature ok
subject=/C=...
Getting CA Private Key
Enter pass phrase for root.pem: XXXXXXXXXX
subject= /C=...
issuer= /C=...
notBefore=Nov 15 19:29:23 2018 GMT
notAfter=Nov 14 19:29:23 2021 GMT
When prompted, choose a PEM pass phrase to protect the server's private key that you are about to generate. The server's PEM pass phrase is used to lock the private key of the server that is stored in the `server.pem` keyfile and will therefore be needed by your server application to unlock the private key from this file. Enter your info and for the common name enter the server's host name or simply `localhost` when testing your servers on your local machine. Enter the root CA pass phrase when prompted to sign the server certificate.
Repeat this procedure for the client if the client must authenticate to a server:
[command]
./cert.sh client
We strongly recommend to use a fresh PEM pass phrase to protect the client private key.
The `server.pem` and `client.pem` keys are valid for one year. Do not distribute them, because they hold the private key. The private key files are used locally by the TLS/SSL application. Only distribute the CA certificate `cacert.pem` which is needed by peers to authenticate servers.
Server applications should use `server.pem` with `soap_ssl_server_context()`:
if (soap_ssl_server_context(soap,
SOAP_SSL_DEFAULT,
"server.pem", /* server keyfile (cert+key) */
"XXXXXXXXXX", /* password to read the private key stored in keyfile */
NULL, /* no certificate to authenticate clients */
NULL, /* no capath to trusted certificates */
NULL, /* DH/RSA: use 2048 bit RSA (default with NULL) */
NULL, /* no random data to seed randomness */
"myservice" /* a named SSL session cache for improved performance */
))
{
soap_print_fault(soap, stderr);
exit(EXIT_FAILURE);
}
Client applications should use `cacert.pem` to authenticate the server:
if (soap_ssl_client_context(soap,
SOAP_SSL_DEFAULT,
NULL, /* no keyfile */
NULL, /* no keyfile password */
"cacert.pem", /* self-signed certificate cacert.pem */
NULL, /* no capath to trusted certificates */
NULL /* no random data to seed randomness */
))
{
soap_print_fault(soap, stderr);
exit(EXIT_FAILURE);
}
To disable server authentication use `SOAP_SSL_REQUIRE_SERVER_AUTHENTICATION` and use NULL for the certificate file. Client applications may also use `client.pem` as the key file with `soap_ssl_client_context()`, but this is only needed if the client must authenticate to the server. This assumes that the client and server are tightly coupled and must mutually trust each other.
The `server.pem` and `client.pem` files actually hold both the private key and certificate.
To print the contents of a PEM file:
[command]
openssl x509 -text -in file.pem
To generate parameters for DH (Diffie Hellman) key exchange with OpenSSL, use:
[command]
openssl dhparam -out dh2048.pem -2 2048
To summarize, the files you need are:
[command]
openssl.cnf
root.sh
cert.sh
Files generated:
[command]
cacert.pem CA certificate for distribution and authentication
root.pem CA private key and certificate to sign client/server key files (do not distribute!)
rootkey.pem CA private key (do not distribute!)
rootreq.pem CA self-sign request
root.srl serial number
server.pem server private key and certificate (do not distribute!)
servercert.pem server certificate, signed by root CA, for distribution
serverkey.pem server private key (do not distribute!)
serverreq.pem sign request
client.pem client private key and certificate (do not distribute!)
clientcert.pem client certificate signed by root CA (public)
clientkey.pem client private key (do not distribute!)
clientreq.pem sign request
Files bundled with the gSOAP software and [downloadable updates:](files/cacerts.zip)
[command]
cacerts.pem trusted certificates of common CAs
cacerts.h header file for cacerts.c: declares soap_ssl_client_cacerts()
cacerts.c trusted certificates of common CAs hardcoded, no cacerts.pem required
[![To top](images/go-up.png) To top](#)
How to convert certificates in PEM format to CER format for MS Windows {#pem}
---
To convert certificate `cacert.pem` to CER format:
[command]
openssl x509 -in cacert.pem -outform der -out cacert.cer
Install the certificate on MS Windows by opening it and then select "Install Certificate". Client applications running on MS Windows can now connect to the server. The server authenticates to the client by means of the certificate.
[![To top](images/go-up.png) To top](#)
How to create self-signed certificates with GNUTLS {#gnutls}
---
Use the [GNUTLS `certtool`](http://www.gnutls.org/manual/html_node/certtool-Invocation.html#certtool-Invocation) command (or the `gnutls-certtool` command) to create keys and certificates as follows.
First, generate a private key (for a client or server):
[command]
certtool --generate-privkey --outfile privkey.pem
Make sure to use GNUTLS `certtool`, sometimes renamed to `gnutls-certtool` to avoid confusion with the system `certtool` command.
As of gSOAP 2.8.64 and greater, you must encrypt the private key with certtool (like with OpenSSL), otherwise the key will not be loaded by the gSOAP engine.
Then self-sign the certificate:
[command]
certtool --generate-self-signed --load-privkey privkey.pem --outfile cert.pem
When prompted, the following options are recommended for the certificate:
- The certificate MUST NOT be marked as a CA certificate.
- The DNS name MUST match the FQDN that clients will use to access the server. Use the server domain name here. One name suffices.
- The certificate MUST be marked as usable for encryption.
- The certificate MUST be marked as usable for a TLS server (or client when appropriate).
The `client.pem` and `server.pem` keyfiles used in the `soap_ssl_client_context()` and `soap_ssl_server_context()` gSOAP API functions are a combination of the private key and the certificate:
[command]
cat privkey.pem cert.pem > server.pem
The client can use `cert.pem` to authenticate the server. The private key file and server.pem are for the server only and SHOULD NEVER be shared.
Note that the `server.pem` file generated above is NOT encrypted with a password, so the password parameter of `soap_ssl_server_context()` is not used. Neither is the `capath` parameter used for the fact that GNUTLS does not search for loadable certificates.
The PEM files produced by GNUTLS can be used with OpenSSL.
The PEM key files created with OpenSSL (such as `server.pem` and `client.pem`) CANNOT be used with GNUTLS, because they contain encrypted private keys that GNUTLS cannot decrypt to read ("SSL/TLS error: Can't read key file").
You can also use the GNUTLS `certtool` to create a Certificate Authority (CA) to sign client and server certificates.
[command]
certtool --generate-privkey --outfile cakey.pem
Then self-sign the CA certificate:
[command]
certtool --generate-self-signed --load-privkey cakey.pem --outfile cacert.pem
When prompted, the following options are recommended for the CA certificate:
- The CA certificate SHOULD be marked to belong to an authority.
- The CA certificate MUST be marked as a CA certificate.
- The CA certificate MUST be usable for signing other certificates.
- The CA certificate MUST be usable for signing Certificate Revocation Lists (CRLs).
Now create a server key and use the CA to sign the server's certificate:
[command]
certtool --generate-privkey --outfile serverkey.pem
certtool --generate-request --load-privkey serverkey.pem --outfile server.csr
certtool --generate-certificate --load-request server.csr --load-ca-certificate cacert.pem --load-ca-privkey cakey.pem --outfile servercert.pem
Use the recommended options discussed earlier for creating the certificate.
The `client.pem` and `server.pem` keyfiles used in the `soap_ssl_client_context()` and `soap_ssl_server_context()` gSOAP API functions is a combination of the private key and the certificate:
[command]
cat serverkey.pem servercert.pem > server.pem
The procedure above can be repeated to create a key and signed certificate for clients and other servers. All clients and servers can be authenticated with the CA certificate `cacert.pem`. That is, `cacert.pem` is to be used by all peers that require the other party to authenticate (e.g. the client uses cacert.pem CA cert to authenticate the server, who uses the `server.pem` keyfile).
To generate parameters for DH (Diffie Hellman) key exchange with GNUTLS, use:
[command]
certtool --generate-dh-params --bits 2048 --outfile dh2048.pem
Files generated:
[command]
cacert.pem CA certificate for distribution, for authentication
cakey.pem CA private key (do not distribute!)
server.pem server key with private key and certificate (do not distribute!)
servercert.pem server certificate, signed by root CA, for distribution
serverkey.pem private key (do not distribute!)
server.csr sign request
client.pem client key with private key and certificate (do not distribute!)
clientcert.pem client certificate signed by root CA (public)
clientkey.pem private key (do not distribute!)
client.csr sign request
Files bundled with the gSOAP software and [downloadable updates:](files/cacerts.zip)
[command]
cacerts.pem trusted certificates of common CAs
cacerts.h header file for cacerts.c: declares soap_ssl_client_cacerts()
cacerts.c trusted certificates of common CAs hardcoded, no cacerts.pem required
[![To top](images/go-up.png) To top](#)
How to harden your application's robustness with timeouts and error handlers
---
To harden your application, add timeouts to avoid your client application from blocking on a socket connection with an unresponsive server:
[codetab.C]
struct soap *soap = soap_new();
soap->connect_timeout = 10; /* connecting to server times out after 10s of trying */
soap->connect_retry = 1; /* retry connecting once more when the first attemtp fails (gSOAP 2.8.87 or greater) */
soap->send_timeout = 5; /* send times out after 5s socket inactivity */
soap->recv_timeout = 5; /* receive times out after 5s socket inactivity */
soap->transfer_timeout = 10; /* message send or receive times out after 10s */
[codetab.C++]
clientProxy proxy; // client proxy class generated with soapcpp2 -j
proxy.soap->connect_timeout = 10; // connecting to server times out after 10s of trying
proxy.soap->connect_retry = 1; // retry connecting once more when the first attemtp fails (gSOAP 2.8.87 or greater)
proxy.soap->send_timeout = 5; // send times out after 5s socket inactivity
proxy.soap->recv_timeout = 5; // receive times out after 5s socket inactivity
proxy.soap->transfer_timeout = 10; // message send or receive times out after 10s
The `connect_timeout` limits the time it can take to connect to the server, with the time specified in seconds. Use a negative value to specify micro seconds. Linux operating systems do not honor connection timeouts, and take up to 20 seconds to connect.
The `send_timeout` and `recv_timeout` limit the socket send and receive delays that are caused by high latencies or low bandwidth. These timeouts are independent of the length of messages sent or received. Use a negative value to specify micro seconds.
The `transfer_timeout` limits the total time a message send and a message receive operation can take, specified in seconds. This value should be used in combination with `send_timeout` and `recv_timeout` to enable accurate timeout control. The send, receive, and transfer timeouts combined prevent unreasonable resource usage.
In addition, the `recv_maxlength` value (when nonzero) limits the length of messages that can be received in bytes in total in decompressed form (message length includes HTTP headers and HTTP chunk size fields). The value of `recv_maxlength` is 2GB by default. Greater values are possible but at your own risk and not recommended in uncontrolled environments. It is strongly recommended to deploy services in uncontrolled environments with the [Apache module](https://www.genivia.com/doc/apache/html/index.html) and [ISAPI extension](https://www.genivia.com/doc/isapi/html/index.html), see the gSOAP [tutorial](https://www.genivia.com/tutorials.html) and [documentation.](https://www.genivia.com/docs.html)
On Unix/Linux systems, socket communications and other events may cause [UNIX signals](https://en.wikipedia.org/wiki/Unix_signal). Signals should be handled. When a client suddenly disconnects the server receives an RST that triggers a SIGPIPE signal. When the SIGPIPE is not handled, the application terminates. A SIGPIPE can be prevented using the following choice of non-portable socket flags of the `soap` context:
[codetab.C]
soap->connect_flags = SO_NOSIGPIPE; // some systems use this to disable SIGPIPE
soap->socket_flags = MSG_NOSIGNAL; // others want this to disable SIGPIPE
[codetab.C++]
proxy.soap->connect_flags = SO_NOSIGPIPE; // some systems use this to disable SIGPIPE
proxy.soap->socket_flags = MSG_NOSIGNAL; // others want this to disable SIGPIPE
Similarly, on the server side you can set `accept_flags` to `SO_NOSIGPIPE` when supported, to suppress SIGPIPE. Also on the server side, you can optionally set the `accept_timeout` to specify a limit on the time to accept a new request from a client. Use a negative value to specify micro seconds.
Best is to add a signal handler, which is portable on Unix and Linux systems:
signal(SIGPIPE, sigpipe_handler);
where the `sigpipe_handler` is a function that catches the signal code `x`:
void sigpipe_handler(int x) { }
The handler can perform some useful task, such as clean up, or just return to ignore the signal and permit the gSOAP engine continue. Either way, the gSOAP engine is designed for easy memory cleanup after being interrupted by using `soap_destroy(soap)` and `soap_end(soap)`, after which the `soap` context can be reused.
Other signals to handle are:
SIGALRM // alarm triggered by a timer (could be useful to implement timeouts)
SIGINT // interrupt with ctrl-c
SIGFPE // floating point hardware may trigger signals (does not occur in gSOAP code)
C++ exceptions are never raised when memory allocations fail in the gSOAP code unless `SOAP_NOTHROW` is defined as a macro to permit `new` to throw exceptions. Out of memory errors are returned as `soap->error == SOAP_EOM`
To limit the size and content of XML parsed, set one or more of the following parameters of the context:
[codetab.C]
struct soap *soap = soap_new1(SOAP_XML_STRICT);
soap->maxlevel = 10; /* maximum nesting level depth of XML elements and JSON objects */
soap->maxlength = 1000; /* maximum length of strings, hex, and base64 data */
soap->maxoccurs = 100; /* maximum XML and JSON array and container lengths */
[codetab.C++]
clientProxy proxy(SOAP_XML_STRICT); // client proxy class generated with soapcpp2 -j
proxy.soap->maxlevel = 10; // maximum of nesting levels of XML elements and JSON objects
proxy.soap->maxlength = 1000; // maximum length of strings, hex, and base64 data
proxy.soap->maxoccurs = 100; // maximum XML and JSON array and container lengths
The limits shown here are examples. Make sure to set reasonable limits that are not overly restrictive, resulting in normal messages to fail validation. Note that strict XML validation in gSOAP with the `SOAP_XML_STRICT` context flag already ensures that XML schema restrictions are obeyed. The context parameters shown here ensure that unrestricted XML schema components are restricted or when `SOAP_XML_STRICT` is not used. These limits also apply to JSON content when the gSOAP JSON API is used.
As recommended by the gSOAP user guide, services are best deployed with the [gSOAP Apache module](doc/apache/html/index.html) or with the [gSOAP ISAPI extension](doc/isapi/html/index.html) to ensure robustness and security of services.
Avoid using the `WITH_COOKIES` compile-time flag to implement state with HTTP cookies in stand-alone servers, i.e. when the gSOAP Apache module and ISAPI extension is not used. Cookies may require a fair amount of processing overhead and are not needed to implement stateful services, which is typically performed with session IDs in XML/JSON messages or via the URL. HTTP cookies are disabled by default and optionally enabled with the compile-time flag `-DWITH_COOKIES`.
[![To top](images/go-up.png) To top](#)
How to set and get SOAP Headers
---
The wsdl2h-generated .h interface file may define a `struct SOAP_ENV__Header` with one or more SOAP headers used in SOAP messages. Also check the `#import` in the wsdl2-generated .h interface when these files define this structure. This structure is "mutable" and may be defined multiple times. Each definition adds more members that are collected in the final `SOAP_ENV__Header` structure. For example:
mutable struct SOAP_ENV__Header
{
mustUnderstand char* ns__ID;
};
and
mutable struct SOAP_ENV__Header
{
mustUnderstand unsigned int *ns__Timeout;
};
are merged together. The `mutable` qualifier allows merging with soapcpp2 such that all distinct members are added together in the final structure generated by soapcpp2.
The `mustUnderstand` qualifier is used by the engine and the XML validator to produce a `SOAP-ENV:mustUnderstand="1"` (SOAP 1.1) or a `SOAP-ENV:mustUnderstand="true"` (SOAP 1.2) attribute on the specific SOAP header element. This means that a receiver cannot ignore the SOAP header and should fault if it does not recognize this header, when present. This is an important feature of SOAP that makes SOAP header processing reliable.
Note that SOAP header members are qualified with an example `ns` prefix. SOAP headers should be qualified to produce a valid SOAP Header:
[xml]
abc123
...
After running soapcpp2 on the .h interface file, you get a `soapStub.h` file that has a valid C/C++ definition of the merged structures:
struct SOAP_ENV__Header
{
char* ns__ID;
unsigned int *ns__Timeout;
};
At the client side, before each call is made, you must set the SOAP Header structure. C++ proxy classes include header-setting methods and you can use `soap_noheader()` to skip or remove the header. In C, if no SOAP Header should be sent to the server, then set `soap->header = NULL`. To populate the header in C, use the gSOAP-generated allocation functions to allocate header data in managed memory to be collected and deallocated automatically later. [![To top](images/go-up.png) To top](#)
At the server side, the service operation receives headers in `soap->header` when non-NULL. Before returning from the service operation, you must set the SOAP Header structure or remove it.
Several WS-* plugings such as WS-Addressing and WS-ReliableMessaging include API functions to assist with populating the SOAP Headers for protocol conformance.
### Client-side headers
To set the SOAP headers on the client side you will need to allocate the SOAP headers and set one or more of them. If SOAP headers are used, you must set headers before each call or remove the header before the call with `soap->header = NULL`.
After the call, to get the SOAP headers just access the non-NULL `soap->header` members. The following example shows the source code in C (soapcpp2 option `-c`) and C++ (soapcpp2 option `-j`) to set and get SOAP headers:
[codetab.C]
#include "soapH.h" /* this also #includes "soapStub.h" */
/* a new context */
struct soap *soap = soap_new();
/* allocate a SOAP_ENV__Header with default member values */
soap->header = soap_new_SOAP_ENV__Header(soap, -1); /* -1 = one object (not an array) */
if (soap->header == NULL)
return EXIT_FAILURE; /* out of memory */
/* set the two headers */
soap->header->ns__ID = soap_strdup(soap, "abc123");
soap->header->ns__Timeout = soap_new_unsignedInt(soap, -1);
if (soap->header->ns__Timeout == NULL)
return EXIT_FAILURE; /* out of memory */
*soap->header->ns__Timeout = 999;
/* make the call */
if (soap_call_ns__method(soap, ...))
{
soap_print_fault(soap, stderr);
}
else
{
/* get the SOAP headers */
char *response_ID = soap->header->ns__ID;
unsigned int *response_Timeout = soap->header->ns__Timeout;
if (response_ID)
printf("%s\n" response_id);
if (response_Timeout)
printf("%u\n" *response_Timeout);
/* clear the SOAP headers */
soap->header = NULL;
}
/* clean up */
soap_destroy(soap);
soap_end(soap);
soap_free(soap);
[codetab.C++]
#include "soapProxy.h" /* this also #includes "soapStub.h" */
/* add a useful template to construct primitive values on the managed heap */
template T * soap_make(struct soap *soap, T val) throw (std::bad_alloc)
{
T *p = (T*)soap_malloc(soap, sizeof(T));
if (p == NULL)
throw std::bad_alloc;
*p = val;
return p;
}
Proxy proxy;
/* allocate a SOAP_ENV__Header and set the two headers */
proxy.soap_header(soap_strdup(proxy.soap, "abc123"), soap_make(999));
/* make the call */
if (proxy.method(...))
{
soap_stream_fault(soap, std::cerr);
}
else
{
/* get the SOAP headers */
char *response_ID = soap->header->ns__ID;
unsigned int *response_Timeout = soap->header->ns__Timeout;
if (response_ID)
std::cout << response_id << std::endl;
if (response_Timeout)
std::cout << response_Timeout << std::endl;
/* clear the SOAP headers */
proxy.soap_noheader();
}
/* clean up */
proxy.destroy();
The wsdl2h-generated .h interface file documents which specific SOAP headers are expected to be used with a service operation. So you should only set and get SOAP headers that are specific to the service operation invoked.
[![To top](images/go-up.png) To top](#)
### Server-side headers
To set and get server-side SOAP headers is similar to the code shown above for the client side, but with the change that the service operation function should get the SOAP headers received and then set new headers before returning or remove them with `soap->header = NULL`. **When service operations are not required to return SOAP headers then it is very important that service operations reset the SOAP headers (to NULL) before returning:**
[codetab.C]
int ns__method(struct soap *soap, ...)
{
...
soap->header = NULL;
return SOAP_OK;
}
[codetab.C++]
int Service::method(...)
{
...
this->soap_noheader();
return SOAP_OK;
}
Otherwise, the input SOAP headers are copied to the output SOAP headers.
Likewise, to set new headers clear the old ones first in C:
[codetab.C]
int ns__method(struct soap *soap, ...)
{
...
soap->header = soap_new_SOAP_ENV__Header(soap, -1); /* -1 = one object (not an array) */
if (soap->header == NULL)
return soap_receiver_fault(soap, "out of memory", NULL);
... /* now set the soap->header->xyz values */
return SOAP_OK;
}
[codetab.C++]
int Service::method(...)
{
...
this->soap_header(xyz, NULL, ...); /* sets new soap->header values */
if (soap->header == NULL)
return soap_receiver_fault(soap, "out of memory", NULL);
...
return SOAP_OK;
}
We added some out-of-memory checking just in case.
The wsdl2h-generated .h interface file documents which specific SOAP headers are expected to be used with a service operation. So you should only set and get the SOAP headers that are specific to the service operation implemented.
[![To top](images/go-up.png) To top](#)
How to set and get SOAP Faults
---
### Client-side faults
At the client side a SOAP Fault is automatically handled by the gSOAP stack. Error codes are returned by remote calls. When an error occurred you can print the fault message. If the fault is related to invalid XML you can also print the location of the error:
[codetab.C]
/* make the call */
if (soap_call_ns__method(soap, ...))
{
/* an error has occurred */
int err;
char *buf[1024];
const char *detail;
int err = soap->error; /* get error code, same as soap_call_ns__method() return */
soap_sprint_fault(soap, buf, sizeof(buf)); /* get human-readable fault message */
soap_print_fault(soap, stderr); /* print human-readable fault message */
soap_print_fault_location(soap, stderr); /* print location in XML of the error */
detail = soap_fault_detail(soap); /* get detail XML string or NULL */
...
}
else
{
printf("OK!\n");
}
[codetab.C++]
/* make the call */
if (proxy.method(...))
{
/* an error has occurred */
int err;
char *buf[1024];
const char *detail;
int err = proxy.soap->error; /* get error code, same as proxy.method() return */
proxy.soap_sprint_fault(buf, sizeof(buf)); /* get human-readable fault message */
proxy.soap_stream_fault(std::cerr); /* print human-readable fault message */
proxy.soap_stream_fault_location(std::cerr); /* print location in XML of the error */
detail = soap_fault_detail(proxy.soap); /* get detail XML string or NULL */
...
}
else
{
std::cout << "OK!" << std::endl;
}
When the service returns custom faults, then the wsdl2h-generated .h interface file defines a `struct SOAP_ENV__Detail` with one or more SOAP fault details used in SOAP Fault messages. This structure is accessible as `soap->fault->detail` (SOAP 1.1) and `soap->fault->SOAP_ENV__Detail` (SOAP 1.2). When the WSDL uses WS-Addressing or other WS-* protocols, then check the documentation on these protocols. If in doubt, check the `#import` in the wsdl2-generated .h interface for the files that define this structure. This structure is "mutable" and may be defined multiple times. Each definition adds more members that are collected in the final `SOAP_ENV__Detail` structure. For example:
mutable struct SOAP_ENV__Detail
{
char* ns__ID;
};
It is possible to include additional `SOAP_ENV__Detail` structures in the .h interface file to define custom faults.
After running soapcpp2 on the .h interface file, you get a `soapStub.h` file that has a valid C/C++ definition of the merged structures.
If one or more of these application-specific SOAP fault details are defined then you can retrieve them at the client side by accessing the non-NULL `soap->fault->detail` (SOAP 1.1) or `soap->fault->SOAP_ENV__Detail` (SOAP 1.2):
[codetab.C]
/* make the call */
if (soap_call_ns__method(soap, ...))
{
/* get SOAP 1.1 detail or SOAP 1.2 detail */
SOAP_ENV__Detail *detail = soap->fault->detail ? soap->fault->detail : soap->fault->SOAP_ENV__Detail;
if (detail && detail->ns__ID)
printf("%s\n", detail->ns__ID);
}
else
{
printf("OK!\n");
}
[codetab.C++]
/* make the call */
if (proxy.method(...))
{
/* get SOAP 1.1 detail or SOAP 1.2 detail */
SOAP_ENV__Detail *detail = proxy.soap->fault->detail ? proxy.soap->fault->detail : proxy.soap->fault->SOAP_ENV__Detail;
if (detail && detail->ns__ID)
std::cout << detail->ns__ID << std::endl;
}
else
{
std::cout << "OK!" << std::endl;
}
[![To top](images/go-up.png) To top](#)
### Server-side faults
To set SOAP faults at the server side that are just HTTP error codes or SOAP faults that are strings is very simple:
[codetab.C]
int ns__method(struct soap *soap, ...)
{
...
soap->header = NULL;
if (not_authorized)
return 401; /* respond with HTTP 401 Unauthorized */
if (sender_did_something_wrong)
return soap_sender_fault(soap, "It is your fault", "some XML-formatted details or NULL");
if (something_internal_went_wrong)
return soap_receiver_fault(soap, "It is my fault", "some XML-formatted details or NULL");
return SOAP_OK; /* HTTP 200 OK */
}
[codetab.C++]
int Service::method(...)
{
...
this->soap_noheader();
if (not_authorized)
return 401; /* respond with HTTP 401 Unauthorized */
if (sender_did_something_wrong)
return this->soap_senderfault("It is your fault", "some XML-formatted details or NULL");
if (something_internal_went_wrong)
return this->soap_receiverfault("It is my fault", "some XML-formatted details or NULL");
return SOAP_OK; /* HTTP 200 OK */
}
When the wsdl2h-generated .h interface file defines a `struct SOAP_ENV__Detail` with one or more SOAP fault details used in SOAP Fault messages, as was shown in the client-side example above that has a `ns__ID` fault detail, then you can set one or more of these fault details as follows:
[codetab.C]
int ns__method(struct soap *soap, ...)
{
...
soap->header = NULL;
if (something_internal_went_wrong)
{
soap_receiver_fault(soap, "It is my fault", NULL);
if (soap->fault) /* we did not ran out of memory */
{
soap->fault->detail = soap_new_SOAP_ENV__Detail(soap, -1); /* -1 = one object (not an array) */
if (soap->fault->detail) /* we did not ran out of memory */
{
soap_default_SOAP_ENV__Detail(soap, soap->fault->detail);
soap->fault->detail->ns__ID = soap_strdup(soap, "abc123");
}
}
return soap->error;
}
return SOAP_OK; /* HTTP 200 OK */
}
[codetab.C++]
int Service::method(...)
{
...
...
this->soap_noheader();
if (something_internal_went_wrong)
{
this->soap_receiverfault("It is my fault", NULL);
if (soap->fault) /* we did not ran out of memory */
{
soap->fault->detail = soap_new_SOAP_ENV__Detail(soap);
if (soap->fault->detail) /* we did not ran out of memory */
{
soap->fault->detail->ns__ID = soap_strdup(soap, "abc123");
}
}
return soap->error;
}
return SOAP_OK; /* HTTP 200 OK */
}
The above is for SOAP 1.1 fault details only. To set SOAP 1.2 fault details use `soap->fault->SOAP_ENV__Detail` throughout.
The wsdl2h-generated .h interface file documents which specific SOAP fault details are expected to be returned by a service operation. So you should only set the SOAP faults that are specific to the service operation implemented.
[![To top](images/go-up.png) To top](#)
How to create new SOAP service operations from XSDs by wrapping XML request and response elements
---
When you do not have a WSDL that defines web services but you do have a set of XML schemas (XSD files, such as the TR-069 schemas), you can simply use wsdl2h to convert XML schemas to a .h interface binding file for soapcpp2. After that you can create any number of new service operations by defining them in a new .h file for soapcpp2 to process.
More specifically, create a new .h interface file that #imports the wsdl2h-generated .h file. You define your service operations in this new file. Each new service operation that you define should wrap the XML element with the XML request message and the XML element with the XML response message in a new function with a name that has two leading underscores followed by a namespace prefix.
For example, let's assume you have a `types.xsd` file that has XML request and response messages defined. First, run wsdl2h on this file:
[command]
wsdl2h -o types.h types.xsd
If you have more than one XSD file then simply add these to the command line as well. This command generates a `types.h` interface file. Let's assume this file defines the following XML types, among many others, that are XML root elements (as indicated by the leading underscore):
class _ns__Upload
{
std::string name;
xsd__base64Binary data;
};
class _ns__UploadResponse
{
unsigned int ID;
};
We can wrap the `_ns__Upload` and `_ns__UploadResponse` objects in a server operation function that we define in a new file `service.h`:
#import "types.h"
//gsoap ns service name: myservice
//gsoap ns service style: document
//gsoap ns service encoding: literal
int __ns__Upload(
_ns__Upload *ns__Upload,
_ns__UploadResponse *ns__UploadResponse);
where:
- `#import "types.h"` was generated by wsdl2h and is imported here since it defines the objects we need.
- The `//gsoap ns service` directives declares our service name and that the service uses SOAP document/literal style. An alternative style is `rpc` and `encoded` encoding. To use `encoded` you must make sure that XSD files define XML elements that conform to the SOAP encoding standard. This basically means no XML attributes are permitted, no schema choice compositor, and no particle with maxOccurs>1 (use a SOAP array instead).
- The `int __ns__Upload()` function has two leading underscores that makes it a wrapper of one or more request objects and one final response object.
- We reused the `ns` prefix from the `types.h` file but that is not absolutely necessary. You can use a new prefix to define service operations. Note that prefixes generated by wsdl2h are sequenced as ns1, ns2, etc. We recommend to bind XML namespace URIs meaningful prefixes in the `typemap.dat` file that is used by wsdl2h.
To generate the client and/or server source code with interface binding implementation:
[command]
soapcpp2 service.h
This generates the client-side and server-side functionality to invoke the `__ns__Upload` service. See e.g. [Getting Started](dev.html) on how to implement clients and services.
[![To top](images/go-up.png) To top](#)