Once you have a web service, you can write clients to invoke that service from any language, mostly with the help of a framework written in to that particular language. When it comes to C, the most popular choice is Apache Axis2/C framework. When you are using Axis2/C to write web service clients, you need to learn about AXIOM which is a easy to use high performing XML model and the service client API which can be used to actually invoke the service. Lets look at the code.

#include <stdio.h>
#include <axiom.h>
#include <axis2_util.h>
#include <axiom_soap.h>
#include <axis2_client.h>

axiom_node_t *build_om_payload_for_helloworld_svc(
    const axutil_env_t * env);

int
main()
{
    const axutil_env_t *env = NULL;
    const axis2_char_t *address = NULL;
    axis2_endpoint_ref_t *endpoint_ref = NULL;
    axis2_options_t *options = NULL;
    const axis2_char_t *client_home = NULL;
    axis2_svc_client_t *svc_client = NULL;
    axiom_node_t *payload = NULL;
    axiom_node_t *ret_node = NULL;

    /* Set up the environment */
    env = axutil_env_create_all("helloworld.log", AXIS2_LOG_LEVEL_TRACE);

    /* Set end point reference of helloworld service */
    address = "http://localhost:9090/axis2/services/helloworld";

    /* Create EPR with given address */
    endpoint_ref = axis2_endpoint_ref_create(env, address);

    /* Setup options */
    options = axis2_options_create(env);
    axis2_options_set_to(options, env, endpoint_ref);
    axis2_options_set_action(options, env,
                             "http://ws.apache.org/axis2/c/samples/helloworldString");

    /* Set up deploy folder. It is from the deploy folder, the configuration is picked up
     * using the axis2.xml file. You need to set the AXIS2C_HOME variable to the axis2/c
     * installed dir.
     */
    client_home = AXIS2_GETENV("AXIS2C_HOME");
    if (!client_home || !strcmp(client_home, ""))
        client_home = "../..";

    /* Create service client */
    svc_client = axis2_svc_client_create(env, client_home);
    if (!svc_client)
    {
        /* reporting the error */
        printf
            ("Error creating service client, Please check AXIS2C_HOME again\\n");
        AXIS2_LOG_ERROR(env->log, AXIS2_LOG_SI,
                        "Stub invoke FAILED: Error code:" " %d :: %s",
                        env->error->error_number,
                        AXIS2_ERROR_GET_MESSAGE(env->error));
        return -1;
    }

    /* Set service client options */
    axis2_svc_client_set_options(svc_client, env, options);

    /* Build the SOAP request message payload using OM API. */
    payload = build_om_payload_for_helloworld_svc(env);

    /* Send request */
    ret_node = axis2_svc_client_send_receive(svc_client, env, payload);

    if (ret_node)
    {
        /* extracting out the content from the response */
        axis2_char_t *om_str = NULL;
        om_str = axiom_node_to_string(ret_node, env);
        if (om_str)
            printf("\\nReceived OM : %s\\n", om_str);
        printf("\\nhelloworld client invoke SUCCESSFUL!\\n");

        AXIS2_FREE(env->allocator, om_str);
        ret_node = NULL;
    }
    else
    {
        AXIS2_LOG_ERROR(env->log, AXIS2_LOG_SI,
                        "Stub invoke FAILED: Error code:" " %d :: %s",
                        env->error->error_number,
                        AXIS2_ERROR_GET_MESSAGE(env->error));
        printf("helloworld client invoke FAILED!\\n");
    }

    /* freeing the allocated memory */
    if (svc_client)
    {
        axis2_svc_client_free(svc_client, env);
        svc_client = NULL;
    }

    if (env)
    {
        axutil_env_free((axutil_env_t *) env);
        env = NULL;
    }

    return 0;
}

Here is the implementation of the “build_om_payload_for_helloworld_svc” function that build the request SOAP message using Axiom/C. Note that axiom_element and axiom_node has one to one association. We use node to to navigate the XML, where as axiom_element to store the data.

/* build SOAP request message content using OM
           <ns1:greet xmlns:ns1="http://ws.apache.org/axis2/services/helloworld">
                <text>Hello World</text>
           </ns1:greet>
*/
axiom_node_t *
build_om_payload_for_helloworld_svc(
    const axutil_env_t * env)
{
    axiom_node_t *helloworld_om_node = NULL;
    axiom_element_t *helloworld_om_ele = NULL;
    axiom_node_t *text_om_node = NULL;
    axiom_element_t *text_om_ele = NULL;
    axiom_namespace_t *ns1 = NULL;
    axis2_char_t *om_str = NULL;

    ns1 =
       axiom_namespace_create(env, "http://ws.apache.org/axis2/services/helloworld",
                               "ns1");
    helloworld_om_ele =
        axiom_element_create(env, NULL, "greet", ns1, &helloworld_om_node);
    text_om_ele =
        axiom_element_create(env, helloworld_om_node, "text", NULL, &text_om_node);
    axiom_element_set_text(text_om_ele, env, "Hello World!", text_om_node);
    om_str = axiom_node_to_string(helloworld_om_node, env);

    if (om_str)
    {
        printf("\\nSending OM : %s\\n", om_str);
        AXIS2_FREE(env->allocator, om_str);
        om_str = NULL;
    }
    return helloworld_om_node;
}

So lets see how the same thing is done with C++. For C++ we use WSO2 WSF/C++

#include <stdio.h>
#include <WSSOAPClient.h>
#include <OMElement.h>
#include <iostream>
#include <AxisFault.h>
using namespace std;
using namespace wso2wsf;

OMElement build_om_payload_for_helloworld_svc();

int main()
{
    WSSOAPClient * sc = new WSSOAPClient("http://localhost:9090/axis2/services/helloworld");
    sc->initializeClient("helloworld_blocking.log", AXIS2_LOG_LEVEL_TRACE);
    {
        /* generating the payload */
        OMElement * payload = build_om_payload_for_helloworld_svc();

        OMElement * response;
        try
        {
            /* invoking the web service */
            response = sc->request(payload, "http://ws.apache.org/axis2/c/samples/helloworldString");

            /* printing the response */
            if (response)
            {
                cout << endl << "Response: " << response << endl;
            }
        }

        /* handling the fault */
        catch (AxisFault & e)
        {
            if (sc->getLastSOAPFault())
            {
                cout << endl << "Response: " << sc->getLastSOAPFault() << endl;
            }
            else
            {
                cout << endl << "Response: " << e << endl;
            }
        }
        delete payload;
    }
    delete sc;
}

You can see lines of code is reduced a lot. And you can see it from the code to build the request XML as well.

/* building the request soap message
   <ns1:greet xmlns:ns1="http://ws.apache.org/axis2/services/helloworld">
        <text>Hello World</text>
   </ns1:greet>
 */
OMElement build_om_payload_for_helloworld_svc()
{
    OMNamespace * ns = new OMNamespace("http://ws.apache.org/axis2/services/helloworld", "ns1");
    OMElement * payload = new OMElement(NULL,"greet", ns);
    OMElement * child = new OMElement(payload,"text", NULL);
    child->setText("Hello World!");

    return payload;
}

WSF/C++ is build on top of Axis2/C. You can see the WSF/C++ API is designed very carefully to make it easy to use without breaking the flexibility provided in the C API. So C++ developers can straightaway use WSF/C++ to develop their web service consumers. Anyway Axis2/C API still has the power of embedding easily in to scripting languages (Like it is done in WSF/PHP, WSF/Ruby) and probably deploy in legacy systems that doesn’t support C++ compiled binaries. So you have the options to select the most sutiable one for your application.

With WSF/PHP Data Service library, you can convert a SQL query to a Data Service very easily in few steps.

  1. Decide your SQL query first, For the query you may require some input parameters, and you have to decide what should be returned by the query, Say your query is
    $sql_query = "SELECT name, age, email FROM users where country = ?";

    Then the “country” is our input parameter and the name, age, email are our return values.

  2. Define the input Format. For the above query it will be something like,
    $inputFormat = array("country" => "INT");
  3. Define the output format. We are giving the name “Users” for the out most wrapper element and the name “user” for the wrapper element of each user. Here is how we define it,
    $outputFormat = array("resultElement" => "users",
                    "rowElement" => "user",
                    "elements" => array(
                                "name" => "name",
                                "age" => "age",
                                "email" => "email"));

    For this output format, our expected result payload would be like,

    <users>
      <user>
         <name>xxx</name>
         <age>23</age>
         <email>xxx@xxx.xx</email>
      </user>
      <user>
         <name>yyy</name>
         <age>23</age>
         <email>yyy@yyy.yy</email>
      </user>
      ....
    </users>
  4. Define your operation using the sql query, input and output formats.
    $operations = array("getUsersByCountry" => array(
                                  "inputFormat" => $inputFormat,
                                  "sql" => $sql_query,
                                  "outputFormat" => $outputFormat));
  5. Define your database configuration in an array like this,
    $config = array(
        "db" => "mysql", // your db engine
        "username" => "myname", // name & password for the db server
        "password" => "mypasswd",
        "dbname" => "db", // the db
        "dbhost" => "localhost");
  6. Create a DataService instance using the database configuration and the operations we just created. And call DataServices reply method.
    $ds_service = new DataService(array(
                   "config" => $config,
                   "operations" => $operations));
    
    $ds_service->reply();

That is it. You just exposed your query as a web service. The PHP script URL will be the endpoint URL for the web service.

With PHP DataServices it is just a matter of putting a little configuration php file to make your database available as a web service. I only needed few minutes to make a simple web service from my blog after figuring out my wordpress database structure, http://wpbits.wordpress.com/2007/08/08/a-look-inside-the-wordpress-database/. In this guide, I m exposing the title, date and the content of each of my blog for my service, But you can extend this more the way you prefer.

  1. Download and install WSF/PHP 1.3.2 and PHP Data Services Library. If WSF/PHP 2.0 released by the time you are reading this, (it is to be released in this week), you only need to install WSF/PHP, since the DataServices library is packed with WSF/PHP from 2.0.
  2. Drop the following file (Say WordpressService.php) in to any of your web server document directories.
    <?php
    
    // Make sure you put the DataService.php in your include path
    require_once("wso2/DataServices/DataService.php");
    
    // database configuraitons
    // you have to set your database configurations in here..
    // These entries can be copy and past from the wp-config.php in your wordpress installation
    
    $config = array(
    "db" => "mysql",
    "username"=>DB_USER,
    "password"=> DB_PASSWORD,
    "dbname"=>DB_NAME,
    "dbhost"=>DB_HOST);
    // output format, plese check the API from http://wso2.org/wiki/display/wsfphp/API+for+Data+Services+Revised
    $outputFormat = array("resultElement" => "Posts",
    "rowElement" => "post",
    "elements" => array( "title" => "post_title",
    "content" => "post_content",
    "date" => "post_date"));
    
    // sql statment to execute, note that I assume the table prefix is wp_ (so the table name is wp_posts)
    // just check $table_prefix variable in the wp-config.php of your wordpress installation
    $sql="select post_title, post_content, post_date from wp_posts where post_status='published'";
    
    // operations is consist of inputFormat (optional), outputFormat(required), sql(sql), input_mapping(optional)
    $operations = array("getPosts"=>array("outputFormat"=>$outputFormat, "sql"=>$sql));
    $my_data_service = new DataService(array("config"=>$config,"operations"=>$operations));
    
    $my_data_service->reply();
    ?>
  3. It is all. Just access the above file from a web browser, you see your service is hosted. Here is the endpoint for my service. http://ws.dimuthu.org/blog/WordpressService.php. Since I m using WSF/PHP latest svn, I m able to retrieve the wsdl automatically from http://ws.dimuthu.org/blog/WordpressService.php?wsdl. Please wait for WSF/PHP 2.0 release for ?wsdl feature.
  4. To verify whether your service deployed correctly, you may need to write a simple test client. Yea I too wrote one. Since I have the wsdl generated, I just needed to generate the code for the client from the wsdl using wsdl2php tool. There is an online version of the tool in the wsf/php demo site. Here is the generated code for my client, http://labs.wso2.org/wsf/php/wsdl2phptool.php?wsdl_url=http%3A%2F%2Fws.dimuthu.org%2Fblog%2FWordpressService.php%3Fwsdl. I added the following code to the TODO section in handling response,
        if(is_array($response->post)) {
            foreach($response->post as $post) {
    
                echo "<h2>".$post->title . " - ".$post->date."</h2>";
                echo "<p>";
                echo $post->content;
                echo "</p>";
    
                echo "<hr/>";
            }
        }

    Check my Web Service client for the above service here, http://ws.dimuthu.org/blog/WordpressClient.php.

Now I can call for my blog from any web service enabled platform.


© 2007 Dimuthu’s Blog | iKon Wordpress Theme by Windows Vista Administration | Powered by Wordpress