gSOAP & Web Services

The gSOAP Web Services Toolkit can help you serialize C/C++ data structures in XML with minimal coding effort.


February 01, 2005
URL:http://www.drdobbs.com/cpp/gsoap-web-services/184401909

The popularity of XML as the lingua franca of interoperability in many ways has changed how the software industry develops products and services. Closed proprietary standards for data formats have mostly given way to open standards based on XML. Specification bodies such as OASIS and the W3C continue to release XML-based standards for a variety of old and new application domains. Among those are the web services standards supporting the service-oriented architectures for application development and deployment in networked environments.

A critical part of any web services application is the serialization of application data in XML. But writing XML serializers by hand is not an easy task. Fortunately, tools are available that help you write the necessary code. In this article, I show how the open-source gSOAP Web Services Toolkit [1] can help serialize C/C++ data structures in XML with minimal coding effort. The toolkit includes a source-code generator that does most of the coding for you. The toolkit also includes a WSDL and XML schema analyzer to bind schema types directly to C/C++ data types that are intuitive to use.

The gSOAP Toolkit

Because coding XML serializers by hand is tedious, error prone, and difficult to maintain with emerging standards, the gSOAP toolkit grew out of a need for an effective method to implement XML serialization in C/C++. It did not make sense to develop a fixed set of XML libraries for the perpetually changing XML draft standards. Exploiting reflection mechanisms for serializing data would raise too many portability issues. Consequently, I decided to adopt a compiler-based code-generation approach to achieve a C/C++ programming-language binding for XML. This approach serves two important goals:

Immediately after the public release of SOAP 1.0 in 1999, I designed and implemented (with the help of one of my students) the first prototype of a compiler to generate C/C++ language bindings for SOAP/XML. We dubbed the system "gSOAP" (short for "SOAP generators"). Like many SOAP toolkits at the time, gSOAP only supported the SOAP RPC (remote procedure call) request/response message exchange pattern with the SOAP encoding style for serializing primitive types, structs/classes, arrays, and pointer-based structures in XML. Since then, advances in web services standards have required frequent updates to the system to generate code that meets the requirements for compliance with SOAP 1.1/1.2, SOAP RPC and document/literal style, SOAP with attachments, WSDL 1.1, XML schemas and namespaces, and WS best practices.

Typical Use

Serializing C/C++ data structures in XML with gSOAP is easy. In fact, it does not matter whether you use gSOAP to develop SOAP/ XML web services applications or to generate XML serialization code for other purposes. In practice, gSOAP is mostly used to develop web service applications starting from a service description in WSDL. This works as follows: To bind your application to a WSDL or set of XML schema definitions, you invoke the gSOAP WSDL importer on the WSDL to generate the bindings in C++ or ANSI C. The importer puts the bindings in a C/C++ header file. The gSOAP compiler then takes these bindings and generates XML serializers and service proxies in source-code format; see Figure 1.

Figure 1: The gSOAP WSDL importer produces a service interface definition that is parsed by the gSOAP compiler to generate the proxies and serializers in source-code format.


The intermediate header file specifies the bindings in an intuitive syntax. Primitive XML types such as built-in XSD types and XML schema simpleTypes are mapped to primitive C/C++ data types, and compound XML schema complexTypes are mapped to structs (for C) or classes (for C++). SOAP service operations are mapped to function prototype declarations to implement the service proxies. This intermediate header file is essentially used as an interface definition language with a familiar syntax.

For example, suppose you want to develop a client for the XMethods Temperature Service, which returns the current temperature (in Fahrenheit) for a U.S. zip code region. First you invoke the gSOAP WSDL importer from the command line on the WSDL to generate the bindings:

wsdl2h -o temp.h 
  http://www.xmethods.net/sd/2001/TemperatureService.wsdl

This generates the temp.h file with C++ bindings. To generate C bindings, use the -c option. The generated file is self-documenting and code documentation tools such as Doxygen can produce a set of nicely formatted documents describing the details of the service.

Listing One is the generated temp.h file, where I've removed the nonessential inline documentation. The first thing to notice are the //gsoap comments — directives for the gSOAP compiler. This means that you don't need to worry about the SOAP and XML details such as encoding styles, schema namespaces, and SOAP actions because these are automatically incorporated by the gSOAP compiler in the generated codes to ensure interoperability with the SOAP/XML web service. The service provides a single operation, ns1__getTemp, with one string input parameter zipcode. Because this is a SOAP RPC encoded service, the float output parameter, return_, is defined in a SOAP RPC response struct, ns1__getTempResponse. By convention, all function parameters of the service operation are input parameters except the last, which is a wrapper struct that stores the output parameters returned by the service.

Listing One

//gsoap ns1 service name: TemperatureBinding
//gsoap ns1 service port: 
// http://services.xmethods.net:80/soap/servlet/rpcrouter
//gsoap ns1 service namespace: urn:xmethods-Temperature
//gsoap ns1 service method-style: getTemp rpc
//gsoap ns1 service method-encoding: 
// getTemp http://schemas.xmlsoap.org/soap/encoding/
//gsoap ns1 service method-action: getTemp ""
int ns1__getTemp(
   std::string zipcode,
   struct ns1__getTempResponse& );
struct ns1__getTempResponse
{  float return_;
};

Next, you invoke the gSOAP compiler from the command line to generate the proxy and XML serialization routines in source-code format:

soapcpp2 temp.h

The compiler generates a C++ client proxy declared in soapTemperatureBindingProxy.h. The compiler also generates the XML serialization code needed to marshal the input parameter and demarshal the output parameter. Also generated are sample SOAP request and response messages and a namespace mapping table TemperatureBinding.nsmap needed by the gSOAP engine to bind XML namespace prefixes such as ns1 to the namespace URLs of the XML schemas. Listing Two is the client program that prints the temperature reading for a given zip code.

Listing Two

#include "soapTemperatureBindingProxy.h" // get proxy class
#include "TemperatureBinding.nsmap" // get XMLns mapping
main()
{  // create a proxy object
   TemperatureBinding proxy;
   // and an object to store the result
   ns1__getTempResponse result;
   // invoke the ns1__getTemp service operation
   if (proxy.ns1__getTemp("32309", result) == SOAP_OK)
      cout << "Temperature=" << result.return_ << "F" << endl;
}

The name of the TemperatureBinding proxy is inherited from the service name defined by the bindings in Listing One. The proxy has one method, ns1__getTemp. Its signature is declared in the header file specification as a global function prototype (to support C as well as C++). The proxy method takes a zip code, sends the SOAP/XML request message with the zip code to the service, parses the response message from the service, and demarshals the return value into the response struct. The method returns SOAP_OK when successful. To complete the build of the example client program you need to compile and link the generated files soapClient.cpp and soapC.cpp, and the gSOAP runtime engine stdsoap2.cpp that comes with the gSOAP distribution package.

This simple example demonstrated the import of a WSDL and the subsequent steps in the code-generation process to build a fully functional client program. The process is the same for larger real-world web services.

XML Serialization

If you plan on developing your own web services but don't have a WSDL to start with, you can use the gSOAP compiler directly to generate the proxies, serialization code, and a WSDL document to advertise your service. If you have an existing C/C++ program and want to expose it as a web service, you may want to start by defining the program's data types to be serialized in a separate C/C++ header file for processing by the gSOAP compiler, then add the set of function prototypes of the intended web-service operations to the header file. The header file is parsed by the gSOAP compiler to generate the proxies and serializers for the specified data types and a WSDL that bundles the information; see Figure 2. Thus, in contrast to the previous development steps, you omit the WSDL import stage and start with the specification of the operations and data structures in the intermediate header file. The following data types can be declared for serialization:

Figure 2:The header file is parsed by the gSOAP compiler to generate proxies and serializers, and a WSDL that bundles the information.


The gSOAP compiler accepts unions, but they cannot be serialized due to the lack of a union discriminator. Other STL types not listed here and templates with more than one typename parameter cannot be used in the header file specification. To serialize the sequence of values of a dynamic array pointed to by a pointer field in a struct or a class, the runtime size information of the array must be accessible by the gSOAP engine. To provide this information, you simply need to add one or more int __sizeX fields. The field is placed directly preceding the pointer field:

class MyClass
{ public:
   int __sizeOfVals;	// holds the array size
   float *vals;   	// points to an array of floats
   ...
};

STL containers provide a cleaner alternative to the construct shown previously. But when you are coding in C, or if you can't use STL, there is no other alternative.

Transient Types

The keywords extern and volatile are reserved and have a special meaning. Entire type declarations or specific struct fields and class members qualified as extern are considered transient and won't be serialized. As mentioned earlier, public data members of a class are always serialized, unless you explicitly indicate otherwise. In some cases, you may want to prevent the serialization of a public data member without changing its access permissions. To do this, you need to qualify it as extern. For example:

class MyClass
{ public:
   extern int secret; // secret is public, but don't serialize!
   ...
};

Entire nonserializable data types can be declared extern. This enables the gSOAP compiler to parse the header file without complaining about undefined types. For example, to include a public iostream data member in a class, you declare the iostream type extern without further details (that is, the details of its definition are external and of no concern to gSOAP):

extern class iostream;
class MyClass
{  ...
   iostream stream; // has transient type; not serialized 
   ...
};

If you hadn't declared the iostream type extern, then gSOAP couldn't compile MyClass without generating compile errors.

Serializing Nonserializable Types

When a particular data type is defined elsewhere (such as in a library's header file) and its definition cannot be moved into the gSOAP header without duplicating it, you need to qualify it as volatile. The volatile qualification of a data type ensures that the type is not redefined in the gSOAP compiler's output. The gSOAP compiler generates new header files soapStub.h and soapH.h in which all of the data types defined in the gSOAP header file specification are copied, except types qualified as volatile. If you wouldn't qualify a predefined data type volatile, compiling the generated source code would result in a redefinition error.

In fact, the volatile qualifier lets you serialize predefined types that would otherwise not be serializable (that is, should otherwise be declared extern). For example, suppose you have a class with a public struct tm member, as in:

class MyClass
{ public:
   struct tm created; // oops, how to serialize?
   ...
};

The tm struct is declared in <time.h> and its definition may vary from one platform to another. To serialize the member, you define volatile struct tm in the header file with a selection of fields you want serialized:

volatile struct tm
{ int tm_sec;	// seconds (0 - 60)
  int tm_min;	// minutes (0 - 59)
  int tm_hour;	// hours (0 - 23)
  int tm_mday;	// day of month (1 - 31)
  int tm_mon;	// month of year (0 - 11)
  int tm_year;	// year - 1900
  int tm_wday;	// day of week (Sunday = 0)
  int tm_yday;	// day of year (0 - 365)
  int tm_isdst;	// is summer time in effect?
  char* tm_zone;	// abbreviation of timezone name
  long tm_gmtoff;	// offset from UTC in seconds
};

Another use of volatile can be found for classes. Classes are augmented with virtual serialization methods by the gSOAP compiler (they're redefined with additional methods in the generated soapStub.h file). While this approach provides a powerful mechanism to serialize polymorphic objects, there may be situations in which it is undesirable to change a class definition. The role of the volatile qualifier is to prevent class redefinition and to keep the original definition intact.

Developing a Web Service

With this basic understanding of XML serialization with gSOAP, take a closer look at developing a web service application with gSOAP. The goal here is to implement an object collision-detection service that takes a collection of geometric objects and determines how many collide with each other on a 2D plane.

Of course, you can implement a collision-detection algorithm locally, but for the sake of exposition, assume you want to expose a collision detector as a service. This example illustrates the use of primitive types, class hierarchies, containers, and operations to send and receive the objects. To keep the system open ended, I define the objects in an extensible class hierarchy starting with the simplest form, a point in 2D space declared in point.h in Listing Three. A Point object has a serializable x- y-coordinate pair and a virtual radius method to describe the total size of the object in terms of a simple circular shape. The virtual distance method measures the geometric distance from another object's center point. Because the gSOAP compiler does not parse method code, all method implementations must be provided in a separate source-code file point.cpp in Listing Four.

Listing Three

// file: point.h
class Point
{ public:
   float x, y;
   Point();
   Point(float, float);
   virtual float radius() const;
   virtual float distance(const Point&) const;
};

Listing Four

// file: point.cpp
#include "soapH.h"
Point::Point() : x(0.0), y(0.0) {}
Point::Point(float x, float y) : x(x), y(y) {}
// the radius of a point is always 0.0
float Point::radius() const
{ return 0.0; }
// compute the distance to another point
float Point::distance(const Point& p) const
{ return sqrt((x-p.x)*(x-p.x)+(y-p.y)*(y-p.y)); }

You also need a container to hold the objects. Since Point is the base class of the hierarchy, you will use an STL vector of pointers to Point to store and serialize geometric objects. I've added a collisions method to compute the number of collisions among objects in the container and an empty method. Listing Five is the declaration of the Objects class with the method's source code in Listing Six.

Listing Five

// file: objects.h 
#import "point.h"  // import the point definitions
#import "stl.h"    // need STL (stl.h is part of gSOAP's package)
class Objects
{ public:
   std::vector<Point*> object;
   Objects();
   int collisions() const;
   bool empty() const;
};

Listing Six

// file: objects.cpp 
#include "soapH.h"
Objects::Objects() : object() {}
// compute the number of collisions
int Objects::collisions() const
{  int hits = 0;
   for (std::vector<Point*>::const_iterator i = object.begin(); i !=
     object.end(); ++i)
   for (std::vector<Point*>::const_iterator j = i; j != object.end();
     ++j)
      if (*i != NULL && *j != NULL && *i != *j
         && (*i)->distance(**j) <= (*i)->radius() + (*j)->radius())
            hits++;
   return hits;
}
// empty collection?
bool empty() const
{ return object.empty(); }

Objects.h imports the point.h and predefined stl.h definitions. The #import directive imports a file into the current header file. Any occurrences of #include and #define are deferred until C/C++ source code compilation after code generation with the gSOAP compiler. Listing Seven shows the service specification. The service operation cws__detect_collisions takes a collection of objects as input and sets the integer hits parameter. The return value of an operation is always int (SOAP_OK or error code). The cws__ prefix binds the C-style operation to the name and its endpoint port via the //gsoap cws service annotations.

Listing Seven

// file: service.h
#import "objects.h"  // import the object collection
// define a name for the 'cws' service
//gsoap cws service name: CollisionService
// define the endpoint location of the service
//gsoap cws service port: http://localhost:8080
// define the 'cws' service operation
int cws__detect_collisions(Objects objects, int& hits);

Listing Eight is the complete source code for the implementation of the service. The service runs as a standalone sequential web service accepting SOAP requests at port 8080. The soap_serve function is generated by gSOAP. It parses the SOAP/XML request and dispatches the request to the service operation cws__detect_collisions. The service can be multithreaded and incorporate SSL encryption and/or Zlib compression by adding a few lines of code. (The details are available in the gSOAP documentation. Or, you can use the CGI, Fast-CGI, Apache mod, and IIS interfaces.) gSOAP supports document/literal style by default. If you're not familiar with the differences in encoding styles, don't worry because gSOAP generates a WSDL CollisionService.wsdl from this specification with the necessary details.

Listing Eight

// file: service.cpp
#include "soapH.h"
#include "CollisionService.nsmap"
main()
{  // set up a gSOAP runtime context
   struct soap *soap = soap_new();
   // bind to port 8080 (service endpoint)
   soap_bind(soap, NULL, 8080, 100);
   // server loop
   for (;;)
   {  // accept request
      soap_accept(soap);
      if (soap->error != SOAP_OK)
      {  soap_print_fault(soap, stderr);
         break;
      }
      // dispatch the client request
      if (soap_serve(soap) != SOAP_OK)
         soap_print_fault(soap, stderr);
     // remove class instances
     soap_destroy(soap);
     // clean up
     soap_end(soap);
   }
   // detach gSOAP context
   soap_done(soap);
   // dealloc gSOAP context
   free(soap);
}
// service operation
int cws__detect_collisions(struct soap *soap, Objects objects, int& hits)
{  if (objects.empty())
   { char *msg = soap_malloc(soap, 256);
   sprintf(msg, "Empty collection from IP=%d.%d.%d.%d", 
      (int)(soap->ip>>24)&0xFF, (int)(soap->ip>>16)&0xFF, 
        (int)(soap->ip>>8)&0xFF, (int)soap->ip&0xFF);
     return soap_sender_fault(soap, msg, NULL);
   }
   hits = objects.collisions();
   return SOAP_OK;
}

Dressing It Up

To illustrate extensibility based on inheritance, add another type of object to the collision service. Changing the code requires recompilation of the service program. It also affects the WSDL, because a new WSDL is generated with the new type. The new type of object appears as a schema extension of the base Point type. The neat thing about XML schema extensibility is that client programs developed with the old WSDL will still talk to the new service.

Listing Nine shows the new Circle type derived from Point. >Listing Ten shows the method code for Circle. You override the radius method to return the actual radius of the circle. The data member r is public so it can be serialized. To include the new circle object in the collection, import circle.h in service.h to make its definition available to the gSOAP compiler.

Listing Nine

// file: circle.h
#import "point.h"
class Circle: public Point
{ public:
   float r;
   virtual float radius() const;
   Circle();
   Circle(float,float,float);
};

Listing Ten

// file: circle.cpp
#include "soapH.h"
Circle::Circle() : Point(), r(1.0) {}
Circle::Circle(float x, float y, float r) : Point(x, y), r(r) {}
float Circle::radius() const
{ return r; }

Developing a gSOAP Client

Listing Eleven is a C++ client program for the service. The client is directly developed from the service.h header file specification of the CollisionDetection web service. I could have used the generated WSDL and run it through the gSOAP WSDL importer. However, the WSDL notation does not preserve the method definitions of the Point and Circle classes, because only the serializable members are part of the generated WSDL schema type definitions.

Listing Eleven

// file: client.cpp
#include "soapCollisionServiceProxy.h"
#include "CollisionService.nsmap"
main()
{  // create a proxy
   CollisionService proxy;
   // create two points and a circle   
   Point p, q(1.0, 1.0);
   Circle c(1.0, 1.0, 2.0);
   // create a collection with the objects
   Objects collection;
   collection.objects.push_back(&p);
   collection.objects.push_back(&q);
   collection.objects.push_back(&c);
   // compute the hits remotely and print the count
   int hits;
   if (proxy.cws__detect_collisions(collection, hits) == SOAP_OK)
      cout << "Hits=" << hits << endl;
   else
      soap_print_fault(proxy.soap, stderr);
}

The client prints a fault message upon failure. The failure can be due to connection issues or application-related issues such as an empty collection, which the service explicitly prohibits by returning a sender-side fault (see Listing Eight).

Saving and Retrieving XML Data

Suppose you want to retrieve a collection of geometric objects from a file or stream instead of hard coding it in the client program (Listing Eleven). You do this by using the XML serializers directly. For every nontransient type declared in the intermediate header file specification, gSOAP generates a serializer and deserializer. Let X be the name of a primitive type, enum, struct, or typedef, then the serializers and deserializers for type X are:

For a class X, gSOAP generates serialization and deserialization methods:

The soap_serialize functions and methods ensure that pointer-based object graphs are serialized in a format that preserves the logical graph structure. Coreferenced data is serialized with XML id-refs. This enables the serialization of arbitrary object graphs without loss of structural integrity. The soap_serialize function or method is called before soap_out. The soap_out function or method emits the data in XML. The soap_in function or method parses XML and returns a pointer to the newly instantiated data. Both functions/methods accept an XML tag name and an optional xsi type value referring to the qualified XML schema typename.

To read a collection of objects from a file, add this code to the client program:

proxy.soap->is = new ifstream("data.xml"); 
         // bind gSOAP runtime to an istream
if (proxy.soap->is == NULL
|| soap_begin_recv(proxy.soap) != SOAP_OK // start reading
|| collection.soap_in(proxy.soap, "objects", NULL) == NULL
|| soap_end_recv(proxy.soap) != SOAP_OK) // stop reading
   exit(1);
proxy.soap->is->close();
delete proxy.soap->is;

The proxy object is managed by a gSOAP runtime context, proxy.soap. The context is used to parse and populate the collection from a file and to manage its memory allocation and deallocation.

Memory Management

Memory management is crucial. The gSOAP runtime context manages the lifetime of an object from allocation and instantiation to destruction. Objects managed by a proxy's runtime context are automatically deallocated when the proxy object is destroyed. At the server side, the destruction of objects and temporary data is explicitly performed with the soap_destroy and soap_end calls; see Listing Eight. The soap_malloc function allocates a temporary string in the service operation, which is deallocated by soap_end after message serialization.

Conclusion

The gSOAP Web Services Toolkit serializes almost any type of C/C++ data directly to and from XML. This feature of the C/C++ language binding to XML, combined with the powerful gSOAP WSDL importer, lets you rapidly develop and deploy web services in C or C++.

References

[1] http://gsoap2.sourceforge.net/.


Robert van Engelen is an associate professor in the Department of Computer Science at Florida State University. He is also president of Genivia Inc., a company dedicated to the research and development of XML products and services. He can be contacted at [email protected] or at [email protected].

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.