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.

Few weeks back I wrote a blog post about Writing RESTful Services in C which explain the use of Axis2/C REST API. Basically when you provide a HTTP Method (GET, POST, PUT or DELETE) and a HTTP URL, it is matched with a given HTTP method and a URL pattern in order to identify the operation and extract out the request parameters. For the example mentioned in the above blog, we can summarize the URL mapping like this.

Operation HTTP Method URL Pattern Example Requests
getSubjects GET subjects GET subjects
getSubjectInfoPerName GET subjects/{name} GET subjects/maths
getStudnets GET students GET students
getStudnetsInfoPerName GET students/{name} GET students/john
getMarksPerSubjectPerStudent GET students/{student}/marks/{subject} GET students/john/marks/maths

You can watch an application with this URL mapping in live, written using WSF/PHP which in fact run Axis2/C algorithms underneath.

Last week I updated this REST mapping algorithm and started a discussion about the changes on Axis2/C Dev list. I thought it would be better explain the idea on by blog too.

What the early algorithm (before my changes) did was, it search each pattern in the order it was declared, and returns when a match is found. Sequential searching for a matching pattern can reduce the performance as the number of operations grows. So my solutions was to keep the url pattern in a multi level (recursive) structure and match the url from one level to another.

Here is the structure of the ‘c struct’. (defined in src/core/util/core_utils.c)

/* internal structure to keep the rest map in a multi level hash */
typedef struct {
    /* the structure will keep as many as following fields */

    /* if the mapped value is directly the operation */
    axis2_op_t *op_desc;

    /* if the mapped value is a constant, this keeps a hash map of
    possible constants => corrosponding map_internal structure */
    axutil_hash_t *consts_map;

    /* if the mapped value is a param, this keeps a hash map of
    possible param_values => corrosponding_map_internal structre */
    axutil_hash_t *params_map;

} axutil_core_utils_map_internal_t;

Here is how it will looks like when the above URL pattern set (shown in the above table) is kept inside this multi-level (recursive) structure.

svc->op_rest_map  (hash)
                |
            "GET:students" --------- axutil_core_utils_map_internal_t (instance)
                |                                            |
                |                                        op_desc (axis2_op_t* for "GET students" op)
                |                                            |
                |                                        consts_map (empty hash)
                |                                            |
                |                                        params_map (hash)
                |                                                         |
                |                                                      "{student_id}" ------------- axutil_core_utils_map_internal_t (instance)
                |                                                                                            |
                |                                                                                op_desc (axis2_op_t* for "GET students/{student_id}" op)
                |                                                                                            |
                |                                                                                parms_map (empty hash)
                |                                                                                            |
                |                                                                                 const_map (hash)
                |                                                                                            |
                |                                                                                        "marks" ------------------- axutil_core_utils_map_internal_t (instance)
                |                                                                                                                            |
                |                                                                                                                    op_desc (NULL)
                |                                                                                                                            |
                |                                                                                                                   consts_map (empty hash)
                |                                                                                                                            |
                |                                                                                                                   params_map (hash)
                |                                                                                                                            |
                |                                                                                                                      "{subject_id}" ----------- axutil_core_utils_map_internal_t (instance)
                |                                                                                                                                                                               |
                |                                                                                                                                       op_desc (axis2_op_t* for "GET students/{student_id}/marks/{subject_id}" op)
                |                                                                                                                                                                               |
                |                                                                                                                                                                 consts_map / params_map (Both NULL)
                |
            "GET:students" --------- axutil_core_utils_map_internal_t (instance)
                                                            |
                                                        op_desc (axis2_op_t* for "GET students" op)
                                                            |
                                                        consts_map (empty hash)
                                                            |
                                                        params_map (hash)
                                                            |
                                                      "{student_id}" ------------- axutil_core_utils_map_internal_t (instance)
                                                                                                          |
                                                                                  op_desc (axis2_op_t* for "GET students/{student_id}" op)
                                                                                                          |
                                                                                             consts_map / params_map (Both NULL)

This structure is build at the time the server initialize the services. (from the “axis2_svc_get_rest_op_list_with_method_and_location” function in src/core/description/svc.c)

As each request hit the service, the request HTTP method and the URL is matched (which we call ‘rest dispatching’) with the above structure using the following algorithm. (defined in the “axis2_rest_disp_find_op” function in src/core/engine/rest_disp.c). Note that here we are extracting out the user REST parameters as well, but it is not shown in here.

  1. The request URL is spitted in to URL components from ‘/’ character. Retrive the instance of axutil_core_utils_map_internal_t  from the svc->rest_map to the varaible ‘mapping_struct’.
  2. Check the existance of URL components, count(URL components) > 0.
  3. If it doesn’t exist any URL components, get the value of mapping_struct->op_desc where the mapping_struct is the current mapping instance of axutil_core_utils_map_internal_t. if the mapping_struct->op_desc is not NULL, we found the operation. If it is NULL just exit returning NULL.
  4. Else If some URL component(s) exist, check the most former URL component in the mapping_struct->const_map hash. If mapping_struct->const_map['former_url_component'] is not NULL, assign the mapping struct->const_map['former_url_component'] value to mapping_struct and follow the step 2 with the remaining URL components. (note that here hash['key'] syntax is used to take the value for the key from the hash ‘hash’. If that returns TRUE, we found the opeartion, if not countine to step5.
  5. if mapping_struct->const_map['former_url_component'] is NULL, match the former url component with each key (which is a URL component pattern) in mapping_struct->param_map hash. (We use the function  “axis2_core_utils_match_url_component_with_pattern” in src/core/util/core_utils.c to map URL component with the URL component pattern). If matching pattern found assign the mapping_struct->param_map['key'] to mapping struct and follow the step 2 with the remaining URL components. If that returns TRUE for some key it will be the matching operation.

Where as the earlier algorithm can be simplified to,

  1. Match the request URL with URL patterns of each operation. This will be like calling the function “axis2_core_utils_match_url_component_with_pattern” (mentioned in step5 of the above algorithm) for the complete URL rather than for a URL component
  2. If the pattern is matched, matching operation is the selected operation for the request.

I approximately calculated the time complexity of both of these algorithm.

Here is the time complexity of the later described algorithm.

Average time complexity of iterating ‘n’ number of operations n/2 = O(n)
Time complexity of matching pattern with a URL with the length ‘p’ (complexity of the ‘axis2_core_utils_match_url_component_with_pattern’ function) O(p^2)
Complete time complexity of the algorithm O(n*p^2)

Time complexity of the formerly described algorithm. (which is currently in the SVN).

Time Complexity of a Hash Search O(1)
Average Number of has searches required. This is the average number of levels in the tree of recursive structures drawn above long(n)/2 = O(log(n))
Time complexity of matching pattern with a URL component with the average length ‘d’, d < p (p = the length of the complete URL) O(d^2)
Number of time pattern matching is required = number of param components in the URL = k, k < p/d (p = the length of the url, d = average length of the URL component)/ k = O(k)
Complete time complexity of the algorithm O(log(n)*d^2*k)

Considering the facts, O(logn) < O(n),d < p and k < p/d we can safely conclude

O(long(n)*d^2*k) < O(n*p^2)  => The newer algorithm has better (low) time complexity.

However the time complexity is valid only it takes high values for the parameters. For low values  the actual time taken by the newer algorithm can have high values, considering the constant overhead of the recursions and the hash search. So in order to judge the performance of the algorithm, we have to run some test cases and measure the actual times. Possibly a task for the weekend :)

October 18th, 2008Write RESTful Services in C

You can write REST as well as SOAP web services using Apache Axis2/C web services framework. There you can make existing Axis2/C web services RESTful just by providing the URL patterns and the HTTP methods to each operation in  the services.xml which act as a simple descriptor for an Axis2/C service.

So if we rewrite the RESTful Demo (Written in PHP) using Axis2/C, the services.xml would be something like following.

<service name="RESTfulSchool">
    <!-- mentioning the service library-->
    <parameter name="ServiceClass" locked="xsd:false">RESTfulSchool</parameter>

    <!-- some description -->
    <description>
        The RESTful School demo
    </description>

    <!-- list of operations -->
    <operation name="getSubjects">
            <parameter name="RESTMethod">GET</parameter>
            <parameter name="RESTLocation">subjects</parameter>
    </operation>
    <operation name="getSubjectInfoPerName">
            <parameter name="RESTMethod">GET</parameter>
            <parameter name="RESTLocation">subjects/{name}</parameter>
    </operation>
    <operation name="getStudents">
            <parameter name="RESTMethod">GET</parameter>
            <parameter name="RESTLocation">students</parameter>
    </operation>
    <operation name="getStudentInfoPerName">
            <parameter name="RESTMethod">GET</parameter>
            <parameter name="RESTLocation">students/{name}</parameter>
    </operation>
    <operation name="getMarksPerSubjectPerStudent">
            <parameter name="RESTMethod">GET</parameter>
            <parameter name="RESTLocation">students/{student}/marks/{subject}</parameter>
    </operation>
</service>

We will check how to write the service logic for a operation like “getMarksPerSubjectPerStudent”.

axiom_node_t *
RESTfulSchool_getMarksPerSubjectPerStudent(
    const axutil_env_t * env,
    axiom_node_t * request_payload)
{
    axiom_node_t *student_node = NULL;
    axiom_node_t *subject_node = NULL;

    /* Extracting out the child nodes from the request */
    student_node = axiom_node_get_first_child(request_payload, env);
    subject_node = axiom_node_get_next_sibling(student_node, env);

    /* now we can write the logic to retrieve the marks
       for the given student and subject and build and
       return the response payload */

    return response_payload;
}

As you can see the variables {student} and {subject} given in the services.xml can be easily accessed from your business logic, so we can build the response accordingly.

This way you can build a RESTful web services easily using C language.


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