February 16, 2005

Creating Web Services using Apache Axis-C++

Author: Preston St. Pierre

Most people think of web services, and more specifically SOAP, as being either Java- or .NET-centric.
Although these two technologies are the most commonly used, they are by no means the only ones in existence. Language
bindings exist for Python, PHP and a host of other languages, including C/C++ (from now on referred to as C, for brevity.) This
article will focus on using SOAP in a C environment.

Why would you want to write web services in C when there are alternatives that are better suited to the task?
There are couple of reasons. First, there are very few web services that exist for their own sake, and as
a developer, you are more likely to use them as just another part of a bigger application or project. This means that your language or environment
is dictated by the greater need of the application, not just by the web services aspect of it. The second reason is that there is
an enormous amount of code available as C source or shared libraries, and you may be interested in making some of this available as a web service.

Whatever your reason, let's assume you have some code written in C that needs to be called via SOAP. You can either
use one of the SOAP frameworks available for Java and use JNI to call it, or else you can set up a native C SOAP environment
for your code. The foremost free SOAP solution is the Apache Foundation's Axis. Most people know
the Java version, but there is also a full-featured C version available and this is the one we will be working with.

Unfortunately, installing Axis-C++ can be tricky, especially the integration with the Apache web server. The following step-by-step guide will enable
you to get a production-level installation up and running quickly on GNU/Linux. After installing the server, the rest of the article will describe
the development and deployment of a basic SOAP service.

Installation and Configuration

Step 1: Download the software. For most uses, and certainly for a learning exercise, installing the binary versions is more than
enough. The current stable version of Axis-C++ is 1.4, and the file is available from the
Apache file repository.
The file you should download is axis-c-linux-current-bin.tar.gz. You will also need version 2.2.0 of the
Xerces XML Parser, part of the Apache XML project. The file you need
is probably called xerces-c2_2_0-linux8.0gcc32.tar.gz, but if not, choose the one most appropriate for your operating system and architecture.

For the code generation tools to work, you will also need to install a Java SDK. Any recent version of Sun's J2SDK
will work correctly, though you can try using any other version you may have installed.

Although you can use the standalone server included with Axis, this article will explain how to integrate Axis with the Apache2 webserver.
The installation of Httpd falls outside the scope of this article, so it is assumed that there is already a version installed and working.
Usually the version packaged with your distribution will work fine.

Step 2: Unpack and copy files. First, you'll need to unpack the Axis archive, using the standard tar -xzvf. Move the
unpacked directory to /usr/local and rename it to axiscpp_deploy. The renaming isn't strictly necessary, but all the
configuration files refer to this location, so changing it means modifying more files.

Next, decompress the Xerces parser. You only need one file from this, and it is lib/libxerces-c.so.22.0 inside the directory created
when you unpack the tar file. Copy this file to the /usr/local/axiscpp_deploy/lib, and change to this directory. Create a soft link to
the file using the command


$ ln -s libxerces-c.so.22.0 libxerces-c.so.22

If the directory doesn't contain a file named libaxis_xmlparser.so, or if this file exists but is not a copy of the file
libaxis_xercesc.so.0.0.0, then copy the second file over the first using the command


$ cp libaxis_xercesc.so.0.0.0 libaxis_xmlparser.so

This ensures that Axis will use Xerces as its XML parser. The alternative, Expat, is currently unsupported.

Finally, you need to configure Axis. The configuration files are in the /etc directory of your Axis installation and are all
suffixed with the string _linux. For the time being, simply rename the files by removing the suffix from the filename. The main Axis configuration
is in axiscpp.conf, and the file describing the services deployed by an instance is called server.wsdd.
We'll cover the contents of the latter file in a little more detail later.

With the basic Axis server installation complete, you can test it by trying to run the included standalone server. The commands
for this are the following:


$ cd /usr/local/axiscpp_deploy/bin
$ export AXISCPP_HOME=/usr/local/axiscpp_deploy
$ export AXISCPP_DEPLOY=$AXISCPP_HOME
$ export LD_LIBRARY_PATH=$AXISCPP_DEPLOY/lib
$ ./simple_axis_server 9090


Note that the argument to the server can be any port that is currently not in use. When everything has been installed correctly, the server will not
return and netstat -npl will show your chosen port as accepting connections. If this is not the case, go back and make sure you've followed
every instruction carefully.

Step 3: Configuring Apache. Now that Axis is working, we can get started on the tricky part, which is hooking Axis up with
a pre-compiled Apache Httpd. This is not as straightforward as it could be because Axis requires a number of environment variables to be set in order to
run correctly, so to use your Axis installation as-is you have to edit your Apache startup scripts. Editing scripts is inconvenient
for a number of reasons, the foremost of which is that it can cause problems when you upgrade your Apache installation. There is an alternative, but
unfortunately, it requires tweaking your machine by modifying your default system configuration. For this reason, we'll look at both ways of getting the
Apache module working properly, and you can choose which method suits your needs best. Remember, you will need to have root privileges for some of the steps
detailed below.

The first way is simply to edit your Apache startup script, probably somewhere in /etc/init.d, and add the following two lines
near the beginning of the script to ensure that all the necessary files can be found:


AXISCPP_DEPLOY=/usr/local/axiscpp_deploy
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$AXISCPP_DEPLOY/lib

The alternative procedure consists of two steps. The first thing to do is add the line


/usr/local/axiscpp_deploy/lib

to your /etc/ld.so.conf file. Run ldconfig, and don't worry about the warnings. This will ensure that Apache can
find all the necessary libraries when it runs. Next, copy the file /usr/local/axiscpp_deploy/etc/axiscpp.conf to /etc and make sure
all the paths inside the file are correct (they should all point to various places inside /usr/local/axiscpp_deploy.)

After carrying out one of the previous steps, copy the file /usr/local/axiscpp_deploy/lib/libaxiscpp_mod2.so to your Apache modules directory and
add the lines


LoadModule axis_module modules/libaxiscpp_mod2.so

<Location /axis>
    SetHandler axis
</Location>


to your Apache configuration file. Note that the path given in the LoadModule line should point to the same directory as the rest of the modules
in your installation.

This will deploy all the SOAP services you wish to export under the /axis directory of your web server. Note
that this assumes a single server instance. If you have a virtual hosts set up, be careful under which host you make the services available. In case you change
the installation host, substitute it's Hostname wherever you see localhost in URLs from now on.

Step 4: Test your installation. With the configuration done, restart your Apache server. Change into the bin directory of
the Axis installation and make sure you still have the three previously mentioned environment variables set. To see if the installation has worked, we're
going to try the bundled calculator application. Run the following command:


$ ./Calculator mul 4 5


This command executes a multiplication operation with operands 4 and 5. If the installation of everything has been successful, the result should look
like:


 Using service at http://localhost/axis/Calculator

Ret:20
20


If the example does not return this, then the type of failure must be examined. If the program refuses to run, then the problem is likely with the
environment variables mentioned above. If there is a problem contacting the Axis server, then the calculator will either fail and tell you the reason was
due to network errors, or else it will just sit there and not do anything. If you installed axis to a virtual host other than localhost, then the calculator
requires an additional argument, the URL of the web service, before any of the others. This will be something like
http://yourhost/axis/Calculator.

If execution returns a correct answer, congratulations! You have a perfectly functional Axis-C++ installation. The next thing to do is to attempt to
write our own web service.

Implementing a Service

The rest of this article focuses on how to create and deploy your very own web service. The sample service will take a string as input
and return the MD5 hash of the given string as output. For this, we'll implement a wrapper to the MD5() function available in the
libssl library, which is part of the OpenSSL project. The service will accept a string in plain text
and will return the string representation of the hexadecimal MD5 hash for the input string. Remember that you might need to install the OpenSSL headers, if
you don't already have them.

SOAP services normally begin their life as a description of what they do. This description is in the form of a Web Services Definition Language (WSDL) file, which
is an XML file that contains information on what the service is called, how to invoke it (URL), what parameters it takes, what it returns and what the types of
all the inputs and outputs are. Because this information is structured, it can be used to generate much of the code needed to deploy and use a web service, and so
it follows that the first step in creating our service is to write the corresponding WSDL description.

Explaining the workings of WSDL lies beyond the scope of this article, but we'll need a description of our service before moving on to the next step.
The simplest possible sample of a WSDL file for the service we're going to implement is as follows:


<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="http://localhost/axis/MD5"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:apachesoap="http://xml.apache.org/xml-soap"
xmlns:local="http://localhost/axis/MD5"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <!-- Message types: define input and output types -->
  <wsdl:message name="GetMD5Request">
    <wsdl:part name="Cleartext" type="xsd:string" />
  </wsdl:message>
  <wsdl:message name="GetMD5Response">
    <wsdl:part name="MD5Hash" type="xsd:string" />
  </wsdl:message>

  <!-- Port type: define a SOAP operations -->
  <wsdl:portType name="MD5Service">
    <wsdl:operation name="GetMD5" parameterOrder="Cleartext">
      <wsdl:input message="local:GetMD5Request" name="GetMD5Request"/>
      <wsdl:output message="local:GetMD5Response" name="GetMD5Response"/>
    </wsdl:operation>
  </wsdl:portType>

  <!-- Binding: Bind a location in this service to an operation -->
  <wsdl:binding name="MD5SoapBinding" type="local:MD5Service">
    <wsdlsoap:binding style="rpc"
    transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="GetMD5">
      <wsdlsoap:operation soapAction="MD5Service#GetMD5"/>
      <wsdl:input name="GetMD5Request">
        <wsdlsoap:body
        encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
        namespace="http://localhost/axis/MD5"
        use="encoded"/>
      </wsdl:input>
      <wsdl:output name="GetMD5Response">
        <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
        namespace="http://localhost/axis/MD5"
        use="encoded"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>

  <!-- Service name: define the name and URL of this service -->
  <wsdl:service name="MD5Service">
    <wsdl:port binding="local:MD5SoapBinding" name="MD5Service">
      <wsdlsoap:address location="http://localhost/axis/MD5Service"/>
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>


This may look complicated, but all it does is define a call with the name GetMD5 which accepts a single input string, called Cleartext,
and returns another string called MD5Hash. This call is available with the name MD5Service#GetMD5 and the entire service is deployed at the URL
http://localhost/axis/MD5. Save this file as MD5.wsdl in a new directory.

Now that the service is defined, we can use some tools provided by Axis to generate most of the code that we're going to need, and this is where
Java comes into play. Assuming it is correctly installed, you will need to have the JAVA_HOME environment variable set to the appropriate
path, and have the Java executable somewhere in your PATH. You'll need to set the Java Classpath using a set of commands like the following:


$ export TMP=$AXISCPP_DEPLOY/lib/axisjava
$ export CLASSPATH=$AXISCPP_DEPLOY/lib/axis/wsdl2ws.jar:\
$TMP/axis-ant.jar:\
$TMP/axis.jar:\
$TMP/commons-discovery.jar:\
$TMP/commons-logging.jar:\
$TMP/jaxrpc.jar:\
$TMP/log4j-1.2.8.jar:\
$TMP/saaj.jar:\
$TMP/wsdl4j.jar:\
$TMP/xml-apis.jar
$ unset TMP


Now, executing the command


$ java org.apache.axis.wsdl.wsdl2ws.WSDL2Ws


should print the WSDL2Ws help blurb. If you get any other output, then go through the above steps again and make sure you have followed each step correctly.

The next step is to generate the server-side stubs for the service. The WSDL2Ws tool is very handy because it generates all the code you need to implement
your web service, leaving you with an empty class where you just fill in the blanks. To generate the stubs, change into the directory that holds the WSDL file
created previously, create a subdirectory called server to hold the generated server-side code and execute the following command:


$ java org.apache.axis.wsdl.wsdl2ws.WSDL2Ws -lc++ -sserver -oserver ./MD5.wsdl


All this command does is generate C++ server-side code from the description in MD5.wsdl and place the resulting code into the
server directory. Once the command has executed, change into the server directory and examine it's contents. The only file
that you need to modify is MD5Service.cpp, which implements the MD5 class. This class will contain the logic of your service.
For now, the only method that you need to implement is MD5::GetMD5(), although for more complex services, you may need to write constructors and
destructors for your class or fill in some of the other generated methods for error handling and the like.

Both the parameter and return types are xsd__string, which is basically a typedef for a char *, so the only code that
has to be written for this example is as follows:


xsd__string MD5Service::GetMD5(xsd__string Value0)
{
        unsigned char * r;
        char * returnValue = (char *)calloc(1, 33);
        r = MD5((unsigned char *)Value0, strlen(Value0), 0);
        snprintf(returnValue, 32, "%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x",
                r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8],
                r[9], r[10], r[11], r[12], r[13], r[14], r[15]);
        return returnValue;
}


The code isn't the prettiest, but it does the job. The parameter and return types for numeric values in the WSDL file map to native C++ types such as
int and long, so if your call returns a number, then you don't have to allocate memory for the return value. However, in this
case the memory must be allocated. Also, remember to #include <openssl/md5.h> at the beginning of the file.

A service is contained in a standard .so file, which will then be dynamically loaded by Axis at runtime. The command for compiling the code and generating
the library is:


$ gcc -shared -o libmd5.so *.cpp -lssl -I. -I$AXISCPP_DEPLOY/include -L$AXISCPP_DEPLOY/lib


To deploy the library, you should copy the new file libmd5.so to $AXISCPP_DEPLOY/lib and then modify the Web Service Deployment Definition
(WSDD) file with the information that Axis needs to know in order to deploy your web service.

The file to edit is called $AXISCPP_DEPLOY/etc/server.wsdd. Like almost everything to do with SOAP, it is an XML file, and so is fairly easy to
read and understand. To deploy a service, you need to include the following snippet inside the <deployment> tag:


        <service name="MD5Service" provider="CPP:RPC" description="Simple MD5 Service">
                <parameter name="allowedMethods" value="GetMD5 "/>
                <parameter name="className" value="/usr/local/axiscpp_deploy/lib/libmd5.so" />
        </service>


All this does is tell Axis that it should deploy a service called MD5Service, and accept calls for a method called GetMD5.
This call exists implemented as a function of the same name in the library /usr/local/axiscpp_deploy/lib/libmd5.so. Note that you can install
the library anywhere you want, since the location is an absolute path. We have installed it where it is for convenience. Now, a simple restart of Apache
will deploy the new service.

The next and final step is to test that the service is correctly deployed and working as expected. The easiest way of doing this is by going back to the
directory that holds the original WSDL file and creating some client code using the same tool as before. Again, start by creating a directory called
client to hold the code. Then, run the command:


$ java org.apache.axis.wsdl.wsdl2ws.WSDL2Ws -lc++ -sclient -oclient ./MD5.wsdl


The generated client-side code is even easier to use than the server code, because absolutely everything to do with the service is encapsulated inside a
class and each call contained in the service is implemented as a method of the class. Therefore, the only code that has to be written is a new file that
contains a main() method where an instance of the class is created and the method to test is called. For this example, the following code in
a new file called main.cpp within the client subdirectory will suffice:


#include "MD5Service.hpp"
#include <iostream>

using namespace std;

int main(void) {
        MD5Service srv;
	char * testString = "Test String";
        cout << "MD5 of '" << testString << "' is: " << srv.GetMD5(testString) << endl;
        return 0;
}


As you can see, the generated class implements a single method for each call contained within the WSDL file, and creates it with the same name, to remain
coherent. To compile the test, change into the client subdirectory and execute the command:


$ gcc -o md5test *.cpp -I. -laxiscpp_client -L$AXISCPP_DEPLOY/lib -I$AXISCPP_DEPLOY/include -ldl

Now, all that's left is to execute the test and check the output. A successful test will produce output like this:


$ ./md5test
MD5 of 'Test String' is: bd8ba3c982eaad768602536fb8e1184


And that's it! A working SOAP server and it's corresponding client, all without leaving C/C++. Again, if you have any problems make sure you have followed
all the steps above to the letter.
Conclusion

Unfortunately, setting up and using Axis is not as easy as this article makes it seem. Axis is very picky about how it is set up, and getting even a simple
service like the one above working can require a lot of patience, trial and error, and extensive use of debuggers and packet sniffers to determine the nature
of problems because Axis doesn't return much useful information when something goes wrong. Knowing where to look for problems is a skill that comes with time.

Also, whereas the example presented above has been engineered to work correctly with the available tools as-is, any production-level code must be examined in
detail for correctness. There are still some bugs in the code-generation tools.

However, being able to adapt to new technological trends when management demands it while still being able to use existing know-how can prove invaluable,
especially when time is of the essence. In short, Axis in its C++ version has a limited scope for use, but under the right circumstances, it can save a
lot of time and effort.

Click Here!