Web Services & C++

Peter shows how to develop SOAP services and clients in C++ using the WASP Server for C++ from Systinet.


December 01, 2003
URL:http://www.drdobbs.com/cpp/web-services-c/184405505

Dec03: Programmer's Toolchest

Developing SOAP services and clients in C++

Peter is an engineer at Systinet. He can be contacted at [email protected].


While there's no shortage of information on how to implement web services using Java, C#, or even Perl, there's little information on how to bring web services to the C++ world—despite the millions of lines of C/C++ code currently in production. In this article, I close this gap by showing how to develop SOAP services and clients in C++ using the WASP Server for C++ from Systinet (the company I work for).

Although a license is required for deployment on multiCPU hardware, WASP is available for a variety of operating systems and compilers as a free download from Systinet. All you need is an operating system and C++ compiler for which WASP binaries are available, and a 1.3 or higher JVM.

Defining Web Services

In SOAP, a "service" loosely corresponds to a C++ "class," and an "operation" to a "method." For instance, a sample service called "Planet" contains a simple operation, getPlanet(). The service's endpoint (that is, URL) is /PlanetService/. The getPlanet() operation takes a single argument—an integer between 1 and 9—and returns the name of the planet that corresponds to that position in order from the Sun. It returns a SOAP fault if the input parameter is out of bounds.

There are two principal components to a web service—the service itself and the Web Services Description Language (WSDL) document that describes it. It is helpful to have the WSDL document in hand when developing services, since you can use the WASP wsdlc utility to autogenerate the client stub code and service skeleton code. However, since WSDL documents can be complicated, it would be nice not to have to create the WSDL manually.

To autogenerate WSDL documents, you have to have a source file that contains enough information to represent a service, and in a format that is simpler than WSDL itself. While WASP for C++ does not have a means of generating a WSDL from a C++ source or object file, you can generate a WSDL from a Java class file. The utility for doing so is Java2WSDL, included in the WASP for C++ Companion Toolkit.

The first thing to do is create a simple Java interface for the service; see Listing One. Save this source as Planet.java and compile it. The compilation directive $ javac Planet.java generates a Java class file from which the WSDL document can be created. To create the WSDL, run Java2WSDL; see Example 1. (Table 1 describes what each flag does.) By default, Java2WSDL creates the file Definitions.wsdl. For consistency, you should rename this file to Planet.wsdl ($ Definitions.wsdl Planet.wsdl).

Generating Source Code

The WSDL document can now be used to generate the bulk of the client and service source code. wsdlc is the WASP utility used to compile a WSDL document into C++ source.

In wsdlc Planet.wsdl Planet, the second argument to wsdlc is a project name, and the wsdlc utility uses this string when naming the source files it creates. The output of this command is a series of C++ source files that help in developing both the client and service. Altogether, the wsdlc utility generates six files: a source and header file for the client stubs, another pair for the service skeletons, and a third pair that represents shared data structures. In this case, the resulting files are the client stubs Planet.cpp and Planet.h, the service skeletons PlanetImpl.cpp and PlanetImpl.h, and the shared structures PlanetStructs.cpp and PlanetStructs.h.

Creating the Service

To create the service, you first declare the service in a distinct header file (Listing Two). This class declaration should be derived from the PlanetImpl skeleton class created by wsdlc and defined in PlanetImpl.h. The WASP_VString type is a WASP class for representing a string in C++. Save this prototype as "PlanetService.h."

Listing Three is the service itself. The code checks the input parameter, which WASP has already serialized from XML to a C++ int type. If the parameter is less than 1 or greater than 9, the code throws an ordinary C++ exception. WASP catches this exception, serializes it into a SOAP fault, and returns it to the client for handling.

The WASP_VString class is a wrapper around a simple char *, primarily offered to allow translation from ASCII to the Unicode encoding used in SOAP, and to speed up serialization. It should be used for all string data types passed between SOAP clients and servers.

Implementing the Server

It only remains to take this service and integrate it with the WASP runtime libraries. Unlike SOAP servers available for more dynamic languages (such as Java), C++ does not allow dynamic deployment of object files to a distinct server process. Therefore, it is necessary to link the service with the WASP server code to create SOAP-enabled executables. It stands to reason that the server code needs to be made aware of the service. Thus, the last bit of server-side source code registers the service with WASP, starts up the WASP environment, and waits forever.

Listing Four is the server code. After including the requisite headers, the code first registers the service with WASP. When expanded, the WASP_FACTORY_DEFINE macro declares a factory function that returns a new instance of the PlanetService class. In the main() body, it creates an array of these function pointers with the help of a few more macros. Under the covers each array element holds a struct containing the name of the service and a pointer to the factory function that instantiates it. In this case, there is just one service, but as more services are created in the future, they can be simply added to this list.

With this infrastructure in place, the code initializes WASP, registers the service factories with the WASP super factory, and starts accepting requests. When implementing this code, remember to wrap the SOAP engine up with some exception handling. Also remember to terminate the WASP server politely with the serverTerminate() call. Save the server code as SOAPServer.cpp and compile and link everything together: $ g++ -o SOAPServer SOAPServer.cpp PlanetService.cpp PlanetImpl.cpp PlanetStructs.cpp -I/usr/local/ waspc/include/usr/local/waspc/lib/libwasp.so.

Configuring the Server

While it is tempting to start the new server at this point, there are a couple of loose threads to resolve. For instance, the server does not know that the Planet.wsdl document is related to the PlanetService service; nor does it know what URL should cause the PlanetService service to be instantiated. Fortunately, the answers to these questions do not require any further coding, but can be set up in a simple XML configuration file like Listing Five.

The configuration file first contains some initial (boilerplate) namespace declarations and server configuration elements. These are followed by the elements that control the service endpoint. Of the components in the <serviceEndpoint> attribute list, the interesting ones are the wsdl and url attributes. The wsdl attribute describes the path to the service's WSDL document relative to the start-up directory of the server, and url specifies the URL of the service relative to the root of the HTTP server.

The use of the <cppa:instance> tag names this endpoint so that it can later be associated with a service instance as known to the server—that is, the object code. This is done with the <serviceInstance> element. The name attribute of the <serviceInstance> element refers back to the ref attribute of the serviceEndpoint/instance element, and the class attribute to the name of the class as specified in the WASP_FACTORY_DEFINE macro of the server. Save this file as config.xml (as dictated by the WASP_Runtime::serverStart() function).

With all the components in place, the server can be started: $ ./SOAPServer &.

Creating the Client

Listing Six, the client code (in which some error handling has been omitted for clarity), begins with a number of boilerplate #includes, the last of which is one of the client header files output by the wsdlc utility. The call to clientInitialize() is a mandatory call that, obviously, initializes and configures the client environment, and should be called before any SOAP-oriented code is executed. It differs from the clientStart() function a few lines further down in that the former performs core functions like initializing threads and factories, while the latter initializes the transport layer, serialization mechanism, and such—all of which you can manipulate. clientStart() requires the name of a configuration file; the default of conf/client.xml, found in the $WASPC_HOME/share/waspc directory, is sufficient.

With the setup out of the way, the code initializes the parameter to be sent to the getPlanet() service and its return value. The input parameter is taken from the command line. The actual call to the service consists of:

Planet PlanetService;

RetPlanet = PlanetService.getPlanet(pos);

The WSDL compiler has created a stub class for every <port> mentioned in the <service> element of the WSDL document. And this class contains methods for every <operation> mentioned in the <binding> element of the WSDL. The code instantiates an instance of this class, which is effectively a local proxy of the remote service, then calls the operation (method). And here is the beauty of web services— the ability to call a service written in an unknown language, hosted on a foreign computer, as if it were local to your own machine.

The returned string is copied to a new variable by the transcode() method before being used. The transcode() method converts UTF encoding to ASCII. Other transcoders are also available, for instance to convert UTF into wide (wchar_t *) characters. It is important to note that transcode() allocates a new buffer for the copy, so it is incumbent on you to release it.

Finally, there is the exception handling code. A WASP_StubFaultException signals that the service is communicating back a SOAP fault, showing nicely how SOAP faults get serialized into C++ exceptions. The second catch of the WASP_Exception catches all other errors, such as not being able to find the server.

Save the client code as client.cpp, and compile/link it (g++ -o client client.cpp Planet.cpp PlanetStructs.cpp -I/usr/local/waspc/include /usr/local/waspc/lib/libwasp.so). Running the client produces the output in Figure 1.

Conclusion

The ability to SOAP-enable new or existing C++ applications has important implications both inside and outside the enterprise. It lets you extend existing services to internal users and partners without having to generate and distribute a number of difficult and incompatible APIs. The ease with which this can be done, and the shallow learning curve of doing so, makes adopting web services much smoother than learning and implementing a complete C# or Java environment.

DDJ

Listing One

interface Planet {
  String getPlanet(int pos);
}

Back to Article

Listing Two

#include "PlanetImpl.h"
class PlanetService : public PlanetImpl {
  public:
    virtual WASP_VString getPlanet(int pos);
};

Back to Article

Listing Three

#include "PlanetService.h"
WASP_VString PlanetService::getPlanet(int pos) {
  if ( (pos < 1) || (pos > 9) ) {
    throw new WASP_Exception("Index out of range. Must be 1 through 9.");
  }
  char * planets[] = {"Mercury", "Venus", "Earth", "Mars", "Jupiter",
                      "Saturn", "Uranus", "Neptune", "Pluto"};
  WASP_VString planet = planets[pos-1];
  return planet;
}

Back to Article

Listing Four

#include <iostream>
#include <waspc/common.h>
#include <waspc/runtime/Runtime.h>
#include <waspc/runtime/SuperFactory.h>
#include "PlanetService.h"

using std::cout;
using std::cerr;
using std::endl;

WASP_FACTORY_DEFINE (PlanetService);

int main (int, char **) {
    WASP_FactoryDefinition serviceFactory[]={
        WASP_FACTORY_ENTRY (PlanetService),
        WASP_FACTORY_END ()
    };
    WASP_Runtime::serverInitialize ();
    WASP_SuperFactory::registerFactory (serviceFactory);

    try {
        cout << "Starting WASP SOAP Server." << endl;
        WASP_Runtime::serverStart ("config.xml",NULL);
    } catch (WASP_Exception *exc) {
        char *trace=GET_TRACE (exc);
        cerr << "Exception during startup: " << exc->getCharMessage() << endl;
        cerr << "Stack trace follows: " << endl << trace << endl;
        delete[] trace;
        delete exc;
    };
    WASP_Runtime::serverTerminate ();
    cout << "WASP Server shutdown" << endl;
    return 0;
}

Back to Article

Listing Five

<?xml version="1.0"?>
<waspc-config
    xmlns:wasp="urn:WaspServer"
    xmlns:cppa="urn:CppAdaptor"
    xmlns:sep="urn:ServiceEndpoint"
    xmlns:svci="urn:ServiceInstanceRepository">
    <wasp:import ref="conf/server.xml"/>
    <!-- Service binding -->
    <sep:serviceEndpoint
        sep:dispatcherRef="DefaultDispatcher"
        sep:adaptorRef="DefaultCppAdaptor"
        sep:wsdl="Planet.wsdl"
        sep:url="/PlanetService/">
        <cppa:instance cppa:ref="planetsref"/>
    </sep:serviceEndpoint>
    <!-- Service instances - implementation classes -->
    <svci:serviceInstance
         svci:class="PlanetService"
         svci:name="planetsref"/>
</waspc-config>

Back to Article

Listing Six

#include <iostream>
#include <waspc/common.h>
#include <waspc/runtime/Runtime.h>
#include <waspc/client/StubFaultException.h>
#include "Planet.h"

using std::cout;
using std::endl;

int main (int argc,char *argv[]) {

  WASP_Runtime::clientInitialize ();

  try {
    WASP_Runtime::clientStart("conf/client.xml", NULL);

    int pos = atoi(argv[1]);
    WASP_VString RetPlanet = "";
    char *PlanetStr;

    Planet PlanetService;
    RetPlanet = PlanetService.getPlanet(pos);

    PlanetStr = RetPlanet.transcode();
    cout << "Planet number " << pos << " is " << PlanetStr << endl;
    delete PlanetStr;
  }
  catch (WASP_StubFaultException *exc) {
    WASP_XMLProtocolFault *fault = exc->getFault();
    if (fault) {
       WASP_VString msg;

       msg << "Fault Received\n";
       msg << "  Fault String: " << fault->getFaultString() << "\n";

       char *transcodedMsg = msg.transcode();
       cout << transcodedMsg;
       delete transcodedMsg;
    }
    delete exc;
  } catch (WASP_Exception *exc) {

    char *trace=GET_TRACE (exc);
    cout << "Exception during call: " << exc->getCharMessage() << endl;
    cout << "Stack trace follows: " << endl << trace << endl;
    delete[] trace;
    delete exc;
  }

  WASP_Runtime::clientTerminate();
  return 0;
}

Back to Article

Dec03: Programmer's Toolchest


Java2WSDL.sh 
  --service-mapping Planet=Planet 
  --class-mapping Planet=http://localhost:6070/PlanetService 
  --no-java-mapping-extension 
  Planet

Example 1: Running Java2WSDL.

Dec03: Programmer's Toolchest


$ ./client 5
Planet number 5 is Jupiter

$ ./client 11
Fault Received
  Fault String: Index out of range. Must be 1 through 9.

Figure 1: Output produced by client.

Dec03: Programmer's Toolchest

Table 1: Flag definitions.

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