2008/11/08
8 Nov, 2008

Using WSF/C Extended Error Handling Mechanism

  • damitha nanda mangala kumarage
  • senior tech lead - WSO2

Introduction

WSF/C is the foundation for many scripting language-based SOAP stacks, as well as for the WSF/C++ Web services stack for the C++ language.  As the number of users who write modules and applications with these stacks grows, so has the demand for an error handling mechanism.  WSF/C stacks error handling mechanism is extensible for such purposes, and this article will explain how to do exactly that.

Applies To

WSF/C 1.3.0 or later
Environment Linux - Debian Etch/Lenny, Ubuntu, Fedora

 

Table of Contents

WSF/C background

WSF/C is the C language-based web services stack built by WSO2.  WSF/C is based on the Apache Axis2/C Web services stack, with added features and a user-friendly build system.  Freely available under the Apache license, WSF/C provides a complete platform for Web services that integrates many other useful Web services sub-projects such as Apache Sandesha2/C, Apache Savan/C, Rampart/C, and the wsclient command line Web services tool, all with Apache Axis2/C as their core.  This provides an easier user experience for those who need a complete and integrated Web services stack for C language-based Web services projects, with a single robust build system.

There are many scripting language-based and C++ language-based Web services stacks from WSO2 based on WSF/C:WSF/PHP, WSF/Perl, WSF/Python, WSF/Ruby and WSF/C++.

WSF/C Error Handling Mechanism

It is important to first understand the WSF/C environment in which you will handle your errors. In WSF/C, every function is passed a very important structure called axutil_env_t which is defined in axutil_env.h file. This structure contains some other structures needed for the programming environment, including logging, error and memory structures.  Here we are interested only in the error structure, which is defined as:

    typedef struct axutil_error
    {
        /**
         * Memory allocator associated with the error struct.
         * It is this allocator that would be used to allocate memory
         * for the error struct instance in create method.
         */
        axutil_allocator_t *allocator;

        /** Last error number. */
        int error_number;
        /** Last status code. */
        int status_code;
        /**
         * Error message. This could be set to a custom message to be
         * returned, instead of standard errors set in the error messages
         * array by the axutil_error_init function call.
         */
        axis2_char_t *message;
    }
    axutil_error_t;

We will discuss fields of this structure later in this article.

This environment structure is passed as the first or second argument of the function. If the function maintains a state of an object instance, then the environment structure is passed as the second argument. Naturally the instance object is passed as the first argument of the function. If the function is stateless in nature, then the environment structure is passed as the first argument.

The source code files implementing error handling are in <Axis2/C source>/util/src/error.c and <Axis2/C source>/util/inlcude/axutil_error.h.

The following is an example of the most frequent usage of Axis2/C error handling, as shown by the use of two functions called foo and bar.

void *foo(axutil_env_t *env)

{
    ....
    ....
    if(something is wrong)
    {
        AXIS2_ERROR_SET(env->error, AXIS2_ERROR_FOO_ERROR, AXIS2_FAILURE);
        AXIS2_LOG_ERROR(env->log, AXIS2_LOG_SI, axutil_error_get_message(env->error));
        return NULL;
    }
    ....
    ....
    return result;
}

 

axis2_status_t void bar(axutil_env_t *env)
{
    axis2_status_t status = AXIS2_SUCCESS;
    ....
    .....
    result = foo();
    if(!result)
    {
        status = axutil_error_get_status_code(error);
        if(AXIS2_SUCCESS != status)
        {
            return status;
        }
    }
    ....
    ....
}

In the foo function, when something went wrong we set the error with the error code for that particular error, along with the status code.  We do not continue any further with the function, but instead return to the caller.  It should be mentioned that AXIS2_ERROR_SET is not an api function defined in axutil_error.h, but instead it is just a macro defined in <Axis2/C source>/util/include/axutil_utils.h using two api functions of the axutil_error.h namely, axutil_error_set_error_number() and axutil_error_set_status_code().  We will come to the details of these axutil_error api functions later.  To continue with the definitions, status codes are defined as a enum type called axis2_status_codes_t in axutil_error.h.  There are three status codes defined: AXIS2_CRITICAL_FAILURE, AXIS2_FAILURE and AXIS2_SUCCESS. This can be set as the last argument to the AXIS2_ERROR_SET macro. This is set to error->status_code field of the error structure by the function.

    axis2_status_t axutil_error_set_status_code(
        axutil_error_t * error,
        axis2_status_codes_t status_code)

Error codes are defined in axutil_error.h as enum type called axutil_error_codes.  The first value is defined as AXIS2_ERROR_NONE = 0 and the last value is defined as AXIS2_ERROR_LAST.  There are other error codes that could be used to report errors specific to Axis2/C. There is a mechanism to extend this type which will be explained later in this article.

Each error code is associated with a corresponding error message which could be obtained by the api function, such as:

    axis2_char_t *axutil_error_get_message(
        const struct axutil_error *error);

The error code to error message mapping is done in util/error.c file within this function:

    axis2_status_t axutil_error_init()

When AXIS2_ERROR_SET macro is called with the error code as shown in the code above, the following function is called.  This passes the error structure and code, such as:

    axis2_status_t axutil_error_set_error_number(
        axutil_error_t * error,
        axutil_error_codes_t error_number)

The result is the error->error_number is set with the passed error code. What axutil_error_get_message() function returns is the error message associated with this error code.

Another method you can use to set error messages from Axis2/C is by using this function:

    axis2_status_t axutil_error_set_error_message(
        axutil_error_t * error,
        axis2_char_t * message)

Use of this is not encouraged and should be used only when you need to add your own detailed error message.

To use the explained functionality in your module/application, you need to include the axutil_error.h and axutil_utils.h.  You also need to link to the libaxutil.so(axutil.dll in windows) library, which can be found in WSF/C binary.  Finally, at the initialization of your module/application, you need to call axutil_error_init() function.

How to Extend

It is very important for someone writing his own module or application to be able to define his own error codes and corresponding error messages.  WSF/C can be extended using pluggable modules, which are packaged in WSF/C. These modules are Addressing, Sandesha2/C, Savan/C and Rampart/C; each is designed for a particular purpose.  Rampart/C is designed to provide Web services security to WSF/C, and it is an implementation of WS Security specification.  Sandesha2/C is an implementation of WS Reliable Messaging specification providing reliability to messages sent or received using WSF/C. You can also write your own modules for WSF/C to add your own functionality. For more about WSF/C modules read Flows, Phases, Handlers and Modules.  Beyond modules, you may also need to specify your own error codes/messages for your own applications.

The following are the steps to achieve this. (Note: For reference purposes I assume your module/application name is Test.)

1. Create files test_error.h and error.c for your application.

2. Define the following enum type:

    typedef enum test_error_codes
    {
        /* No error */
        TEST_ERROR_NONE = USER_ERROR_CODES_START,
        ....
        ....
        ....
        ....
        TEST_ERROR_LAST
    } test_error_codes_t;

 

Between codes TEST_ERROR_NONE and TEST_ERROR_LAST you can define your own error codes. However, the maximum number of error codes you can define for a single module or application is confined to a maximum number of 512 (for comparison, WSF/C core currently has less than 225 error codes). The WSF/C error code USER_ERROR_CODES_START is a value defined in axutil_error.h, in order to facilitate custom error codes by user.  If you are writing a standard WSF/C module, you may want to engage with the developer community to obtain a reserved standard error code range specific to your module.  If you write such a module and already have a reserved error code range for it, you would receive a start value for this range. Let's name this start value TEST_ERROR_CODE_START.   From here, you simply replace USER_ERROR_CODE_START in the above code snippet with the new TEST_ERROR_CODE_START value.

3. In the test_error.h define:

axis2_status_t AXIS2_CALL
test_init();

4. In error.c implement the test_init() function as follows:

    axis2_status_t AXIS2_CALL
    test_init()
    {
        axutil_error_messages[TEST_ERROR_CODE1] = "Message for test error code1";
        axutil_error_messages[TEST_ERROR_CODE2] = "Message for test error code2";
        ....
        ....
        ....
    }

5. In the initializing code for your module or application, add the following:

test_init();

Writing your own error handling implementation is that simple.

Now anywhere in your application code you can use:

AXIS2_ERROR_SET(env->error, TEST_ERROR_CODE_NUMBER, AXIS2_FAILURE);

If you need additional examples of how to do this, see the error handling implementations for Savan/C, Sandesha2/C and other WSF/C modules.

SOAP Faults

The ability to produce SOAP faults from within your server side code is an important requirement. SOAP fault handling could be considered as the remote error handling mechanism defined for SOAP applications. Requirements for this are defined in the SOAP specification, with different requirements for SOAP 1.1 and 1.2 specification.  This example will demonstrate how to respond with a SOAP fault in your server side application whenever you need to inform your clients what went wrong with your server application. The following is the sample code to execute in order to create and send a SOAP fault from within a function of your server application.

    axis2_char_t *fault_code = "Soap specification defined fault code";
    axis2_char_t *fault_sub_code = "Soap specification defined fault sub code";
    axis2_char_t *fault_reason = (axis2_char_t *) axutil_error_get_message(env->error);
    axis2_char_t *fault_detail = "Soap specification defined fault detail";

    axutil_error_set_error_number(env->error, test_error_code);
    test_util_create_fault_envelope(msg_ctx, env, fault_code,
                fault_sub_code, fault_reason, fault_detail);

    AXIS2_ERROR_SET(env->error, test_error_code, AXIS2_FAILURE);
    AXIS2_LOG_ERROR(env->log, AXIS2_LOG_SI, fault_reason);
    return AXIS2_FAILURE;

Here fault_code, fault_sub_code, fault_reason and fault_detail are SOAP specification defined elements which are passed as arguments to the test_util_create_fault_envelope function. It is the responsibility of your application to define this function. An example of an implementation would be:

axis2_status_t AXIS2_CALL
test_util_create_fault_envelope(
    axis2_msg_ctx_t *msg_ctx,
    const axutil_env_t *env,
    axis2_char_t *code,
    axis2_char_t *subcode,
    axis2_char_t *reason,
    axis2_char_t *detail)
{

    axiom_soap_envelope_t *envelope = NULL;
    axiom_soap_body_t *body = NULL;
    axiom_node_t *body_node = NULL;
    axiom_node_t *fault_node = NULL;

    envelope = axiom_soap_envelope_create_default_soap_envelope(env,
        AXIOM_SOAP12);

    body = axiom_soap_envelope_get_body(envelope, env);
    body_node = axiom_soap_body_get_base_node(body, env);

    fault_node = test_util_build_fault_msg(env, code,
        subcode, reason, detail);

    axiom_node_add_child(body_node , env, fault_node);
    axis2_msg_ctx_set_fault_soap_envelope(msg_ctx, env, envelope);

    return AXIS2_SUCCESS;
}
axiom_node_t * AXIS2_CALL
test_util_build_fault_msg(
    const axutil_env_t *env,
    axis2_char_t * code,
    axis2_char_t * subcode,
    axis2_char_t * reason,
    axis2_char_t * detail)
{
    axiom_node_t *fault_node = NULL;
    axiom_element_t *fault_ele = NULL;
    axiom_node_t *code_node = NULL;
    axiom_element_t *code_ele = NULL;
    axiom_node_t *code_value_node = NULL;
    axiom_element_t *code_value_ele = NULL;
    axiom_node_t *sub_code_node = NULL;
    axiom_element_t *sub_code_ele = NULL;
    axiom_node_t *sub_code_value_node = NULL;
    axiom_element_t *sub_code_value_ele = NULL;
    axiom_node_t *reason_node = NULL;
    axiom_element_t *reason_ele = NULL;
    axiom_node_t *reason_text_node = NULL;
    axiom_element_t *reason_text_ele = NULL;
    axiom_node_t *detail_node = NULL;
    axiom_element_t *detail_ele = NULL;

    fault_ele = axiom_element_create(env, NULL, "Fault", NULL, &fault_node);

    code_ele = axiom_element_create(env, fault_node, "Code", NULL, &code_node);
    code_value_ele = axiom_element_create(env, code_node, "Value", NULL, &code_value_node);
    axiom_element_set_text(code_value_ele, env, code, code_value_node);
    sub_code_ele = axiom_element_create(env, code_node, "Subcode", NULL, &sub_code_node);
    sub_code_value_ele = axiom_element_create(env, sub_code_node, "Value", NULL,
            &sub_code_value_node);

    axiom_element_set_text(sub_code_value_ele, env, subcode, sub_code_value_node);
    reason_ele = axiom_element_create(env, fault_node, "Reason", NULL, &reason_node);
    reason_text_ele = axiom_element_create(env, reason_node, "Text", NULL, &reason_text_node);
    axiom_element_set_text(reason_text_ele, env, reason, reason_text_node);
    detail_ele = axiom_element_create(env, fault_node, "Detail", NULL, &detail_node);
    axiom_element_set_text(detail_ele, env, detail, detail_node);

    return fault_node;
}

Alternatively you could use the WSF/C defined function, which is defined in <wsf/c source>/axis2c/axiom/include/axiom_soap_envelope.h

    /**
     * Create the default SOAP fault envelope
     * @param envelope OM SOAP Envelope
     * @param env Environment. MUST NOT be NULL
     *
     * @return Created SOAP fault envelope
     */
    AXIS2_EXTERN axiom_soap_envelope_t *AXIS2_CALL
    axiom_soap_envelope_create_default_soap_fault_envelope(
        const axutil_env_t * env,
        const axis2_char_t * code_value,
        const axis2_char_t * reason_text,
        const int soap_version,
        axutil_array_list_t * sub_codes,
        axiom_node_t * detail_node);

Summary

In this article we examined how to use the WSF/C error handling mechanism to handle errors in your own modules and applications. We further explored how to extend this mechanism to enable writing your own error codes and error messages. We also reviewed how to send SOAP faults from your server applications.  These tools should help you write more robust modules and applications for the WSF/C environment.

Resources

  1. WSO2 WSF/C - WSO2 WSF/C is an Open Source framework for providing and consuming Web services.

Author

Damitha Kumarage, Senior Software Engineer at wso2, committer Apache Software Foundation, damitha at wso2 dot com

 

About Author

  • damitha nanda mangala kumarage
  • senior tech lead
  • wso2