C/C++ Users Journal May 2004

Web Services & Existing Server Applications

Extending the reach of existing systems

By Peter Lacey

There is an argument to be made that using web services as a portable, Internet-enabled Remote Procedure Call mechanism is a naïve approach. However, web- services toolkits are geared towards just that. Furthermore, as a mechanism for creating a universal API, web services have proven themselves. It is far better — and easier — to SOAP-enable existing server applications than it is to create C/C++ APIs (for Windows, Solaris, Linux, and so on), Java APIs, or .NET assemblies.

In this article, I'll examine how to add web services to existing server applications that already present a network interface to the outside world. In this case, the existing application is written in C, while the SOAP framework is Systinet's WASP (developed in C++). Admittedly, the application is only a single-threaded, socket-based server that accepts a connection and immediately returns the current date and time. Still, the underlying principles are the same as existing servers that are more useful and robust.

WASP is available as a free download from Systinet (http://www.systinet.com/), although a license is required for deployment on multiCPU hardware. The UNIX/Linux binaries are getting a bit stale (for instance, it's hard to find a compatible version of GCC), but Systinet does make the source code available for a fee. The source generally compiles on any UNIX-like OS using GCC or the OS's native compiler. The application I present here, for instance, was created on Mac OS X 10.3.2 using GCC 3.3, though OS X is not officially supported.

Ground Rules

The main() function in Listing 1 (server.c) does little more than establish some signal handlers and call the startSocketServer() function, which never returns. The signal handlers catch SIGINT and SIGTERM and direct program flow to cleanExit(), which does nothing much at this point (though it should). The socket-handling code in SocketServer (Listings 2 and 3) is equally simple. Of interest is that the calls to accept() and write() are in an infinite loop, so that the server can handle one request after another, though only one at a time. The server's "business logic" consists of the call to getCurrentDateTime() in TimeUtils (Listings 4 and 5) and the formatting call that follows. This functionality is intentionally forced. The intent is that getCurrentDateTime() represents existing functionality that is useful to all clients, and that the call to strftime() represents functionality needed for a particular type of client. When the SOAP functionality is added, it reuses the getCurrentDateTime() function, but formats the results differently before returning to the caller. Only that particular getCurrentDateTime() uses the reentrant version of localtime(), acknowledging that this is a multithreaded application. The server can be built using the makefile in Listing 6. I won't bother writing a client because you can test this server using Telnet:

$ ./server &
Starting socket server on port 6069

$ telnet localhost 6069
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Feb 23, 2004 19:31:37
Connection closed by foreign host.

Generating the Source Code

Systinet's WASP server for C++ assumes that all development begins with a WSDL document. A WASP utility, wsdlc, compiles the WSDL into C++ code — skeletons, stubs, and data structures. The first step in the process is to create a WSDL document. While you can use whichever method you like, I elected to create a Java interface file and use Systinet's java2wsdl utility to create the WSDL document. Here's the interface I used, along with my use of java2wsdl.

public interface DateServer {
   java.util.Date getCurrentDateTimeWS();
}

This file must be saved as DateServer.java. When converted to a WSDL document, the default is to name the base service the same as the class, DateServer, and to create an operation called getCurrentDateTimeWS (the WS suffix is arbitrary) that takes no inputs and returns an XML Schema dateTime type. Compile it like this:

$ javac DateServer.java

Then convert it to a WSDL document:

$ java2wsdl 
  --class-mapping DateServer=http://localhost:6070/DateTimeService
  --no-java-mapping-extension 
  DateServer

The --class-mapping flag instructs WASP regarding which URL to use in the WSDL file's <service> section. I specified port 6070 because that is WASP C++'s default. The --no-java-mapping-extension keeps WASP from annotating the WSDL document with hints meaningful to Systinet's Java SOAP stack. DateServer is, of course, the compiled Java interface. By default the WSDL file is called "Definitions.wsdl."

With the WSDL file in hand, you can generate the needed source.

$ wsdlc Definitions.wsdl DateTime

wsdlc is in the WASPC_HOME/bin directory, taking as arguments a minimum of the WSDL filename and a string to use when naming the output files. In this case, the resulting files are the client stubs DateTime.cpp and DateTime.h, server skeletons DateTimeImpl.cpp and DateTimeImple.h, and shared data DateTimeStructs.cpp and DateTimeStructs.h (available at http://www.cuj.com/code/).

Creating the Service

To create the service, you need only write an ordinary C++ class file that extends the service declared in DateTimeImpl.h, called DateServerImpl (remember, the WSDL file says the service is called "DateServer") that has a single public virtual method getCurrentDateTimeWS(). Listings 7 and 8 are the header and class definition.

While straightforward, the class is notable in a number of ways. First, there's nothing very "SOAPy" about it. All of the XML serialization/deserialization, transport connectivity, and SOAP processing are taken care of by WASP. Second, the use of a pointer to a WASP_DateTime type as the return value is one of many WASP types that are used when serializing between XML and C++. Finally, the call to getCurrentDateTime() represents the existing business logic. The empty constructor for WASP_DateTime() defaults to the current date and time.

Creating the Server

With the service complete, you can create the server itself. Typically, a WASP server behaves much like this socket server — once started it blocks forever while servicing requests. Your challenge is to get the WASP server and the socket server to coexist in the same executable.

Fortunately, Systinet has made available a nonblocking version of its server. Using it requires only a few simple changes to the typical way of doing things:

Listings 9 and 10 present the SOAP server. The function startSOAPServer() is called from main(). The code includes three mandatory WASP header files, and the header file for the service just created. To register the service with WASP, it uses the WASP_FACTORY_DEFINE macro to create the definitions of the service as a class factory. In the body of startSOAPServer(), it populates an array of these factories with the help of a few more macros. In this case, there is just one service, but as more services are created, they can simply be added to this list.

Next, WASP initializes all of the WASP infrastructure with the initialize() call, and registers the array of service factories with what's called the "WASP super-factory" via the registerFactory() call. Finally, load() is called to retrieve the (as yet unmentioned) configuration file, and start() is called to begin accepting requests.

Mixing C and C++

The load() and start() functions are wrapped in exception-handling code. You can treat this as boilerplate, but witness how a C++ exception is caught and changed into a simple return value for the use of the C code that calls the exception. I have also implemented a stopSOAPServer() function. Typically, WASP takes care of shutting down cleanly, but in nonblocking mode, this is your responsibility.

Also, because I am linking C and C++ code, the function declarations are wrapped up with a check for whether a C++ preprocessor is working on it; if so, a call to extern "C" is inserted to keep name mangling from occurring. This same functionality had to be added to TimeUtils.h so that it could be called from the C-based socket code and C++ SOAP code (Listing 11).

Configuring the Server

The configuration file that load() reads in can be used to configure many aspects of a server. Generally, the defaults are acceptable. However, for each service you create, you need to specify in the configuration file which URL is mapped to which service, which class implements the service, and the WSDL code to return when asked.

The configuration file (see Listing 12) initially contains some (boilerplate) namespace declarations and server configuration elements. These are followed by the service endpoint configuration. Of the components in the <serviceEndpoint> attribute list, the important ones are the wsdl and url attributes. The wsdl attribute describes the path to the service's WSDL document relative to the location of the configuration file, and the url attribute specifies the URL of the service relative to the root of the HTTP server. The wsdl attribute is preceded by the cppa namespace, not the sep namespace.

The <cppa:instance> tag names this service endpoint configuration so that it can later be associated with a service instance, <svci:serviceInstance>. The class attribute is the name of the real C++ service class; in this case, DateTimeService.

Updating Client and Server

With the SOAP server in place, you can update the main() function to start it up (Listing 13). I also create a client to exercise the SOAP server (Listing 14). The client is straightforward even if you aren't familiar with WASP. After the SOAP infrastructure is initialized via the clientstart() call, the client instantiates a local proxy of the remote DateServer. It then sets up a pointer to a WASP_DateTime to hold the return value of the operation. Finally, it calls the remote operation just as if it were local to the client. Of course, under the covers, WASP is creating SOAP messages, transporting them back and forth, and serializing and deserializing the data.

A peculiarity of WASP is that string data is kept in UTF format. To get a char * reference to it, you must call the WASP_String transcode() method. Transcode() allocates a buffer that you must remove. Of course, you must also dispose of the WASP_DateTime when you're done with it, too.

You can build the entire system with the makefile (available at http://www.cuj.com/code/). The original C code is still using GCC as the compiler, but the WASP code uses g++. The server now uses g++ as the linker, not GCC. Finally, the dylib extension on the WASP libraries is the way OS X names shared object libraries.

Running the server and the client results in the following:

$ ./server &
[1] 302
Starting WASP Server on port 6070
Starting socket server on port 6069

$ telnet localhost 6069
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Feb 25, 2004 19:13:39
Connection closed by foreign host.

$ ./client
The current date & time is 
  2004-02-25T19:13:43-05:00

$ kill 302
Shutting down ...

SOAP Server shutdown

Conclusion

If you are an enterprise developer with existing servers written in C or C++, you can extend the reach of those servers by adding web services and doing a little code refactoring. This technique should be equally compelling if you are an ISV, since you can now set aside all of the client-side APIs you have created and offer a single server-side interface to your customers.


Peter Lacey is an independent contractor doing web-services development for financial organizations. He has held engineering positions at Systinet, Cisco Systems, and Netscape Communications. Peter can be contacted at placey@wanderingbarque.com.