GT 4.0 C WS Core : Developer's Guide

1. Introduction

The C WS-Core developer's guide provides information related to writing and running web services and WSRF-enabled services in C. It includes tutorials walking the developer through creation of services, and clients to interact with services. It includes scenarios for possible configurations that the developer may want. It also provides references to APIs and their documentation.

2. Before you begin

2.1. Feature summary

Binding Generation:

  • Binding Generation directly from WSDL schemas

    • ANSI-C stubs and skeletons
    • Non-blocking client stubs for writing event-driven code
    • EPR (EndpointReference) encapsulation
    • WSRF enabled client stubs and services
  • HTTP/1.1 Support
  • Embeddable Service API
  • Standalone service container
  • WSRF-enabled services

Deprecated Features

  • Dynamic Deployment (WSDD) using AxisC++ was included in an early pre-release but is no longer supported.

2.2. Tested platforms

Tested Platforms for C WS Core

  • IA32/Linux/gcc32
  • IA64/Linux/gcc64
  • x86_64/Linux/gcc64
  • SPARC/Solaris 9/vendorcc32
  • PowerPC/AIX 5.2/vendorcc32
  • Mac/OS X/gcc32

2.3. Backward compatibility summary

Protocol changes since GT version 3.2

  • SOAP messages conform to WSRF schemas instead of previous OGSI/OGSA schemas.
  • WS-Addressing has been added to the list of supported standards, as defined by the WS-Resource Framework.
  • HTTP/1.1 with 'chunked' transfer encoding is used by default.

API changes since GT version 3.2

  • The 3.2 cbindings API is obsolete, with no overlap to the new API. Bindings APIs are now generated directly from WSDL.
  • The underlying XML/SOAP messaging framework is also new, based on the libxml2 pull parser API.

Schema changes since GT version 3.2

  • Schemas are completely new. The WS C Core implements the OASIS WSRF and WSN working drafts specifications (with minor fixes to the 1.2-draft-01 published schemas and with the March 2004 version of the WS-Addressing specification.)

2.4. Technology dependencies

C WS Core depends on the following GT components:

  • C Common Libraries
  • Pre-WS Authentication and Authorization (GSI)
  • Globus XIO (used by C WS core for efficient HTTP and TCP transport)

C WS Core depends on the following 3rd party software:

  • Libxml2 (used by C WS Core for SOAP XML parsing and WSDL parsing)
  • OpenSSL (used by C WS Core for Security)
  • JavaScript (used by C WS Core as a template language to generate the C bindings from WSDL schemas)

2.5. Security considerations

C WS-Core supports secure transport (https) and secure message (just X509 signing, not encryption).

2.5.1. Secure Transport

With secure transport, the entire container must be run over an https transport. This is done by default for the C container. If the user does not want security in the container, or wants to use secure message instead of secure transport, they should use the -nosec argument to globus-wsc-container.

For clients, the secure transport is enabled if the contact URI contains the 'https' scheme instead of 'http', so the client doesn't have to enable or disable it explicitly.

3. Architecture and design overview

4. Public interface

The semantics and syntax of the APIs and WSDL for the component, along with descriptions of domain-specific structured interface data, can be found in the GT 4.0 Component Guide to Public Interfaces: C WS Core.

5. Usage scenarios

Here we provide some scenarios for using C WS-Core that aren't described in the tutorials.

5.1. Using Wildcards

Both clients and services may need to create or parse instances of xsd_any or xsd_anyType types. This is necessary when the XML schema defines a type that includes the xsd_any or xsd_anyType as a type for one of its elements, such as:

  <xsd:complexType name="TemporalType">
    <xsd:sequence>
      <xsd:any minOccurs="1" maxOccurs="1" processContents="lax" />
    </xsd:sequence>
  </xsd:complexType>

  <xsd:element name="Temporal" type="tns:TemporalType"/>

The content of an instance of TemporalType is not restricted by the schema definition, and so must be handled specially at runtime. For serialization and deserialization of wildcard elements, a special global variable of type globus_xsd_type_info_t is associated with each type that can be set on the wildcard. For example, if a user wanted an instance of TemporalType to contain an instance of an xsd:dateTime, the any field must be filled in properly. The following bit of C code does this:

time_t current;
TemporalType temp;
xsd_QName * element;
xsd_dateTime * time; /* this is just a struct tm */

...

result = TemporalType_init_contents(&temp);
/* check result */

temp.any.any_info = &xsd_dateTime_info;

result = xsd_dateTime_init(&time);
/* check result */

current = time(NULL);

/* get the current time */
result = xsd_dateTime_copy_contents(
    time,
    (xsd_dateTime *)localtime(&current));
/* check result */

temp.any.value = (void *)time;

result  = xsd_QName_init(&element);
/* check result */

element->Namespace = globus_libc_strdup("http://temporal.com");
element->local = globus_libc_strdup("Time");

temp.any.element = element;

/* now we can serialize it */

result = TemporalType_serialize(
    &Temporal_qname,
    temp,
    handle,
    0);
/* check result */

This serializes the TemporalType to the contain the current timestamp. The resulting serialized elements would look like this:

<time:Temporal xmlns:time="http://temporal.com">
<time:Time>Mon Apr 17 10:14:22 CDT 2005</time:Time>
</time:Temporal>

If we want to serialize it to a string of the current day of the week, we would do this:

time_t current;
TemporalType temp;
xsd_QName * element;
xsd_string * day; /* this is just a pointer to char * */

...

result = TemporalType_init_contents(&temp);
/* check result */

temp.any.any_info = &xsd_string_info;

result = xsd_string_init_cstr(&day, "Monday");
/* check result */

temp.any.value = (void *)day;

result  = xsd_QName_init(&element);
/* check result */

element->Namespace = globus_libc_strdup("http://temporal.com");
element->local = globus_libc_strdup("Day");

temp.any.element = element;

/* now we can serialize it */

result = TemporalType_serialize(
    &Temporal_qname,
    temp,
    handle,
    0);
/* check result */

This allows us to serialize the temporal time element as the day of the week. The resulting serialized elements for this code would look like this:

<time:Temporal xmlns:time="http://temporal.com">
<time:Day>Monday</time:Day>
</time:Temporal>

So this allows us to inject types into wildcard elements at runtime, and demonstrates how to serialize those wildcards.

For deserialization of wildcard types, a registry is used to lookup the actual type of the element based on QName or the xsi:type attribute. The registry contains key/value pairs of QName to globus_xsd_type_info_t structures. These structures contain the appropriate information about deserializing the type.

5.2. Using Asynchronous client stubs

A client may wish to perform many invocations of resource property requests to different services (or the same service) at once, without waiting for the response from one request before starting a second request. The asynchronous client stubs generated for each operation allow the client to do this. The example code below shows the implementation of the callback that gets called once the response from a resource property has been received for the CounterService.


typedef struct
{
    globus_cond_t cond;
    globus_mutex_t mutex;
} counter_monitor;

void
get_rp_counter_value_callback(
    CounterService_client_handle_t      handle,
    void *                              user_args,
    globus_result_t                     result,
    const wsrp_GetResourcePropertyResponseType *
                                        GetResourcePropertyResponse,
    CounterPortType_GetResourceProperty_fault_t
                                        fault_type,
    const xsd_any *                     fault)
{
    counter_monitor_t * monitor = (user_args);
    xsd_int * rp_value;

    if(GetResourcePropertyResponse->any.elements[0].any_info !=
       (&Value_rp_info))
    {
        /* error - expected Value as the first (and only) resource
         * property
         */
    }

    rp_value = (xsd_int *)GetResourcePropertyResponse->any.elements[0].value;

    globus_mutex_lock(&monitor->mutex);
    monitor->value = *rp_value;
    monitor->done = 1;
    globus_cond_signal(&monitor->cond);
    globus_mutex_unlock(&monitor->mutex);
}

...

counter_monitor_t * monitor;

monitor = globus_malloc(sizeof(counter_monitor_t));
/* check OOM */

globus_cond_init(&monitor->cond, NULL);
globus_mutex_init(&monitor->mutex, NULL);
monitor->done = 0;
monitor->value = 0;

result = CounterPortType_GetResourceProperty_epr_register(
    client_handle,
    createCounterResponse->EndpointReference,
    &Value_rp_qname,
    get_rp_counter_value_callback,
    prop_monitor);
if(result != GLOBUS_SUCCESS)
{
   ...
}

/* do other processing */

globus_mutex_lock(&monitor->mutex);
while(!monitor->done)
{
    globus_cond_wait(&monitor->cond, &monitor->mutex);

    /* do other processing */
}
globus_mutex_unlock(&monitor->mutex);

This allows us to do other processing while the GetResourceProperty operation is invoked, and the response is returned. For something as simple as the CounterService, the wait for the callback to be called will most likely be short (unless there is network delay). For more complex services, the delay may be longer, and the client may want to perform other processing instead of just waiting.

6. Tutorials

6.1. Writing Clients for the BlogService

6.1.1. Introduction: A Blog Service

The Globus Toolkit C WS-Core codebase provides tools and APIs for interacting with web services from a client written in C. It provides additional support for interacting with resource enabled (WSRF) web services. This tutorial provides a walkthrough of the steps to take to create such a C client.

The client we implement interacts with the BlogService, which is a simple example of a Blog web service. See the Wikipedia entry on Blogs for more information on Blogging. In our simple example, the topic for each Blog is maintained as a WS-Resource. The primary ResourceProperty type associated with each Blog resource is an array of strings of all the entries made to that blog topic.

Clients can create new Blog resources with the createBlogTopic factory operation, and append their own entries to that resource with the addEntry operation. Because the blog stores the entries beyond the lifetime of a single web service invocation (such as addEntry), maintaining each blog topic as a resource is a natural use of the framework.

The public interface to a Blog's entry strings is through the resource property named BlogEntry, and the resource property operations (i.e. GetResourceProperty) that are inherited by the BlogService.

The tutorial walks through creation of a blog resource, invoking the addEntry operation on that resource, accessing the blog's entries, and finally destroying the blog resource.

For this tutorial we provide the following:

See: Section 6.2, “Implementing a Blog Service” for further information on the service side of the implementation. Here, we provide the steps for creating the C client:

6.1.2. Step 0: Acquire a WSDL schema

This is the first step to writing your own client. You must either obtain a pre-existing WSDL schema file (or files), or you must write your own. If you are just going to write a client that interacts with a pre-existing service, the WSDL schema for that service already exists, and you should be able to obtain it from the service author.

For the BlogService, we provide blog.wsdl that defines the factory operation createBlogTopic, the append operation addEntry, and the BlogEntry resource property for each blog resource. The WSDL schema files should be installed somewhere in the $GLOBUS_LOCATION/share/schema tree. In the case of the blog WSDL, you need to install them into $GLOBUS_LOCATION/share/schema/tutorials/blog/. This allows the relative paths in the schema import declarations to work.

6.1.3. Step 1: Generate Client Bindings

Once you have the WSDL schema(s) for the service, you need to generate the client bindings from that schema. This will provide the C types and functions (bindings) you need to use to interact with the service. The command used to generate the bindings is globus-wsrf-cgen.

To run this command on the blog schema files, they must be placed in $GLOBUS_LOCATION/share/schema/tutorials/blog/, as described in Step 0. The command for generating the blog client bindings looks like this:

$GLOBUS_LOCATION/bin/globus-wsrf-cgen -no-service -s blog_client \
    -flavor <flavor> -d $PWD/bindings \
    $GLOBUS_LOCATION/share/schema/tutorials/blog/blog_service.wsdl

This command will generate the GPT package listed above. The package can be built and installed using the following command:

$GLOBUS_LOCATION/sbin/gpt-build bindings/blog_client_bindings-0.2.tar.gz <flavor>

6.1.4. Step 2: Write the Client

In order to write a C WS-Core client, the following steps should be followed in general:

6.1.4.1. Include the Client Header

The client bindings generated from Section 6.1.3, “Step 1: Generate Client Bindings” include a client header which provides the necessary function declarations to perform the client invocations we need to make. In the case of the BlogService, the BlogService_client.h is the header we need, so it gets included at the top of the file:

#include "BlogService_client.h"

6.1.4.2. Module Activation

The first step of the client is to activate the module defined for the client. Module activation is a pattern used frequently in the Globus Toolkit. It provides initialization and setup for a particular library, and the libraries it depends on. In this case, the module we are activating is the BLOGSERVICE_MODULE, defined in BlogService_client.h, as follows:

globus_module_activate(BLOGSERVICE_MODULE);

6.1.4.3. Client Handle Init

Once the module is activated, the client handle must be initialized:

BlogService_client_handle_t  blog_handle;

...

result = BlogService_client_init(
    &blog_handle,
    NULL, NULL);

This handle provides abstraction for messaging and transport configuration parameters, and is used by all Blog service invocations. The second and third parameters are attrs and handler chains that determine how the message is serialized and transported. In this example, we use the default configuration, so the parameters are NULL.

In some scenarios, attrs and handlers will need to be setup explicitly by the user.

6.1.4.4. Creating a Resource

Once the client handle is initialized, the next step is to create the blog resource in the BlogService. The create_blog.c performs resource creation by invoking the createBlogTopic factory operation. The bindings call from the client looks like this:


    createBlogTopicType                 createBlogTopic;
    createBlogTopicResponseType *       createBlogTopicResponse;
    Blog_createBlogTopic_fault_t        create_fault_type;
    xsd_any *                           fault;

    ...

    createBlogTopic.Topic = "Emacs vs. vi: Which is better?";
    createBlogTopic.Creator = "slang";

    result = Blog_createBlogTopic(
        blog_handle,
        "http://the.service.host:8080/wsrf/services/BlogService",
        &createBlogTopic,
        &createBlogTopicResponse,
        &create_fault_type,
        &fault);

This is a code of the createBlogTopic invocation, similar to what's in the create_blog.c example. The Blog_createBlogTopic function is defined in BlogService_client.h. The parameters are the initialized blog handle, the endpoint URI to the BlogService (i.e. "http://the.service.host:8080/wsrf/services/BlogService"), the input and output parameters, and the fault parameters. In this particular example, the createBlogTopic input parameter holds the topic for the blog, and the creator of the blog. The createBlogTopicResponse output parameter is filled in by the function call, with the EndpointReference of the resource created by the createBlogTopic invocation. In our example code, we export the EndpointFeference to a file, which allows us to access it after the createBlogTopic process has completed.

    globus_soap_message_handle_t        epr_out_handle;

...

    result = globus_soap_message_handle_init_to_file(
        &epr_out_handle,
        "emacs_vi_epr.xml",
        GLOBUS_XIO_FILE_CREAT);

...

    result = wsa_EndpointReferenceType_serialize(
        &BlogEPR_qname,
        &createBlogTopicResponse->EndpointReference,
        epr_out_handle,
        0);
...

    globus_soap_message_handle_destroy(epr_out_handle);

Now we must destroy the response from createBlogTopic invocation:

createBlogTopicResponse_destroy(createBlogTopicResponse);

6.1.4.5. Invoking a Resource Operation

Once the EndpointReference has been written to file, we have a reference to the blog resource, so we can call the addEntry operation on that resource from another process. This is what the add_blog_entry.c client example does. The EndpointReference for the blog resource is first imported from the file:

    globus_soap_message_handle_t        epr_in_handle;

...

    result = globus_soap_message_handle_init_from_file(
        &epr_in_handle,
        "emacs_vi_epr.xml");

...

    result = wsa_EndpointReferenceType_init(&blog_resource_reference);

...

    result = wsa_EndpointReferenceType_deserialize(
        &BlogEPR_qname,
        blog_resource_reference,
        epr_in_handle,
        0);

...

    globus_soap_message_handle_destroy(epr_in_handle);

Once the EndpointReference is imported, the addEntry operation is invoked as follows:

    addEntryType                      entry;
    addEntryResponseType *            blog_entries;

    Blog_addEntry_fault_t             add_fault_type;
    xsd_any *                           fault;

    entry.Comment = "What's vi??";
    entry.Author = "EmacsPowerUser";

    result = Blog_addEntry_epr(
        blog_handle,
        blog_resource_reference,
        &entry,
        &blog_entries,
        &add_fault_type,
        &fault);

For this invocation, we're using the Blog_addEntry_epr function (instead of Blog_addEntry). This allows us to pass in the EndpointReference of the resource directly as the second parameter (that's why the function ends in _epr). The first parameter is the client handle, The third and fourth parameters are the input and output parameters to the operation (the blog entry to add, and the resulting entries on the blog), followed by the fault parameters. Once this function call returns successfully, the addEntryResponse parameter will contain all the entries made to the blog. This call can be made subsequently and entries will continue to be appended to the resource. Once the response is no longer needed after a call to Blog_addEntry_epr, we must destroy it:

xsd_string_destroy(addEntryResponse);

The output of running add-blog-entry will look something like this:

./add-blog-entry emacs_vi_blog.xml "Emacs rocks!" anonymous

BLOG ENTRIES:

On Wed Dec 22 04:57:42 CST 2004, anonymous said: "Emacs rocks!"

On Tue Oct 26 01:01:11 CST 2004, wq said: "CTRL-ALT-SHIFT-X CTRL-C...I'm running out of fingers..."

On Thu Aug 12 10:44:32 CST 2004, EmacsPowerUser said: "What's vi??"

6.1.4.6. Getting a Resource Property Value

The WSDL schema for the BlogService defines a Resource Property BlogEntry as part of the resource property document for the Blog port type. This resource property allows us to access the state of the resource (get the entries) with the GetResourceProperty operation defined in the WS-ResourceProperties schema and inherited by the Blog portType. The get_blog_entries.c client example performs this operation on the Blog resource. The invocation is made as follows:

#include "BlogEntry.h"

...

    wsrp_GetResourcePropertyResponseType *      RPResponse;
    Blog_GetResourceProperty_fault_t            getrp_fault_type;
    xsd_any *                                   fault;

    ...

    result = Blog_GetResourceProperty_epr(
        blog_handle,
        blog_resource_reference,
        &BlogEntry_qname,
        &RPResponse,
        &getrp_fault_type,
        &fault);

In this function call, the client handle and endpoint reference are passed as the first two parameters. The third parameter (the operation input) is the qualified name of the Resource Property. In this case, the QName is declared in the generated header BlogEntry.h as the global variable BlogEntry_qname. The output parameter RPResponse is the response from the GetResourceProperty operation. On successful completion of the function, this response parameter will contain the value(s) of the ResourceProperty. Because resource properties can have any type, the response is deserialized as an array of xsd_any * instances. In order to access the actual value from this structure, the type of the xsd_any * instance must be verified to match the expected type:

    if(RPResponse->any.elements[i].any_info->type !=
       (&BlogEntry_qname) &&
       (RPResponse->any.elements[i].any_info->type !=
       (&Blog_BlogEntry_rp_qname))
    {
        /* error!  Unexpected type */
    }

What's happening here? The wsrp_GetResourcePropertyResponseType structure contains the field any, which is an xsd_any_array. This array is assumed to contain one element at index 0. In order to check that the element was deserialized as the appropriate element (i.e. BlogEntry), we must compare the any_info field against the reference to the global variable BlogEntry_qname declared in BlogEntry.h.

Once the type of the element in the response is verified, we can access the value contained in the value field of the xsd_any.

    blog_entry = *RPResponse->any.elements[i].value;

    printf("BLOG ENTRIES:\n\n%s\n", blog_comments);

After the value of the resource property has been accessed, we need to destroy the response instance created by the Blog_GetResourceProperty_epr function call:

wsrp_GetResourcePropertyResponseType_destroy(RPResponse);

The output of running get-blog-entries will look something like this:

./get-blog-entries emacs_vi_blog.xml

BLOG ENTRIES:

On Wed Dec 22 04:57:42 CST 2004, anonymous said: "Emacs rocks!"

On Tue Oct 26 01:01:11 CST 2004, wq said: "CTRL-ALT-SHIFT-X CTRL-C...I'm running out of fingers..."

On Thu Aug 12 10:44:32 CST 2004, EmacsPowerUser said: "What's vi??"

6.1.4.7. Destroy the Resource

In order to destroy the resource we've created after all our invocations to it are complete, we use the Destroy operation defined in WS-ResourceLifetime schema and inherited by the Blog portType. The destroy_blog.c client is an example of using this operation for the blog resource. The example imports the resource reference, calls the Destroy operation, and then removes the file that referenced the resource.

    wsrl_DestroyType                    Destroy;
    wsrl_DestroyResponseType *          DestroyResponse;
    Blog_Destroy_fault_t                destroy_fault_type;
    xsd_any *                           fault;

    result = globus_wsrf_core_import_endpoint_reference(
        "emacs_vi_blog.xml", &blog_resource_reference, NULL);

    ...

    result = Blog_Destroy_epr(
        blog_handle,
        blog_resource_reference,
        &Destroy,
        &DestroyResponse,
        &destroy_fault_type,
        &fault);

As with the previous EndpointReference invocations, the first two parameters passed to this function are the client handle and the endpoint reference to the resource. In the case of invoking the Destroy operation, the Destroy and DestroyResponse input and output parameters are just empty structures and don't contain any pertinent information. Nevertheless, the DestroyResponse variable should be cleaned up after the Destroy operation has completed:

    wsrl_DestroyResponse_destroy(DestroyResponse);

6.1.4.8. Cleanup

Once all the desired invocations have completed for a particular process, the client handle needs to be destroyed, and the module must be deactivated.

    Blog_client_handle_destroy(blog_handle);

    globus_module_deactivate(BLOGSERVICE_MODULE);

These calls exist in each of the client examples.

6.1.5. Step 3: Build the Client

Now you've written an end-to-end C WS-Core WSRF-enabled client. In order to compile the client we demonstrate how to write a Makefile for it. First, the following command must be run:

$GLOBUS_LOCATION/bin/globus-makefile-header \
    --flavor=<flavor> <package> \
    > MyMakefile.include

Assuming you compiled the Globus Toolkit with a gcc32dbg flavor, and using the blog client bindings package from this tutorial, the command would be:

$GLOBUS_LOCATION/bin/globus-makefile-header \
    --flavor=gcc32dbg blog_client_bindings \
    > BlogClientMakefile.include

The resulting BlogClientMakefile.include contains include and link definitions for our client. Now we just need to write a Makefile, using the variables defined in the output of the globus-makefile-header command. We've provided a blog client Makefile. Once your Makefile is written, running make will generate the client executables. At this point you're not quite ready to run it. The client needs to have a service running somewhere to interact with. See Section 6.2, “Implementing a Blog Service” in order to create and run a BlogService that you can invoke with your new client.

6.2. Implementing a Blog Service

6.2.1. Introduction: A Blog Service

The Globus Toolkit's C WS-Core component provides tools and APIs for creating web services in C. It also provides additional support for creating web services which are WSRF-enabled, meaning the service can manage resources and the associated resource properties. This tutorial provides a walkthrough of the steps needed to create a WSRF-enabled service in C, from defining a WSDL schema for the service to actually running the service in the C service container.

The service we implement in this tutorial is the BlogService, which is a simple service that allows new Blog topics to be created as resources, and then allows comments to be added to a particular Blog topic. See the Blog Wikipedia entry for more information on Blogs.

In our BlogService, the primary resource property is the BlogEntry element, which is an array of BlogEntryType instances containing the comment, author, and timestamp of each entry posted to the Blog topic. For the tutorial, we will demonstrate how to generate the service stubs and skeletons for the BlogService, and how to provide the service implementation, including creation of new Blog topics as resources, and adding new blog entries to the BlogEntry Resource Property. For the purposes of this tutorial, we provide the following:

  • WSDL schema files for the BlogService:

    • blog.wsdl - Includes the input/output type definitions for the BlogService operations, the ResourceProperty definitions, and the portType definition.
    • blog_bindings.wsdl - Includes the binding definition for the BlogService. The blog.wsdl schema file is imported.
    • blog_service.wsdl - Includes the service definition. The blog_binding.wsdl schema file is imported.

  • Source file for the complete BlogService implementation:

  • A GPT package blog_service_bindings-0.2.tar.gz that contains the complete BlogService implementation (includes the skeleton from the above bullet).

This tutorial defines 5 steps needed to create any WSRF-enabled service using C WS-Core, and then provides example walkthroughs of those steps with the BlogService.

6.2.2. Step 1: Acquiring a WSDL Schema

You must either obtain pre-existing WSDL schema files or write your own. The schema files must contain a service definition that defines the service. Please note that the C WS-Core only supports document/literal style WSDL schema files at present.

For the BlogService, we provide blog.wsdl that defines the factory operation createBlogTopic and the append operation addEntry, as well as the BlogEntry resource property for each blog resource.

6.2.3. Step 2: Generating Service Bindings

Once you have the WSDL schema(s) for the service, you need to generate the service bindings from that schema. This will provide the C skeleton functions for the service implementation. The command used to generate the bindings is globus-wsrf-cgen.

To run this command on the Blog schema files, they must be placed in $GLOBUS_LOCATION/share/schema/tutorials/blog/, so that the relative import paths are correct. The command for generating the blog service bindings looks like this:

$GLOBUS_LOCATION/bin/globus-wsrf-cgen -no-client -s blog_service \
    -d $PWD/bindings -flavor <flavor> \
    $GLOBUS_LOCATION/share/schema/tutorials/blog/blog_service.wsdl

This command generates source and header files for the service, and as a final step, creates a GPT package (a .tar.gz file) that contains all the source, headers and necessary build files. Building this package is described in Section 6.2.5, “Step 4: Building/Installing the Service Package”. The above command generates build files and type bindings files in the bindings directory as a sub-directory of the current directory. Service specific files are output to a sub-directory of bindings named <service name> ($PWD/bindings/<servicename>/). In this example the sub-directory is named BlogService.

The -d <dir> argument outputs the generated files to <dir>. Use the -help argument to get further info.

6.2.4. Step 3: Writing the Service implementation

Once the service binding generation has completed, the service skeleton functions will reside in the <service name>_skeleton.c source file contained in the <service name> directory. This is the file with the operation functions that must be filled in to complete the implementation of the service. For this example, the file we must modify is BlogService/BlogService_skeleton.c. This source file includes skeleton functions for each of the operations defined in the blog.wsdl schema file. The two operations that need to be implemented are createBlogTopic and addEntry. The associated functions in BlogService_skeleton.c are Blog_createBlogTopic_impl and Blog_addEntry_impl.

6.2.4.1. Creating a Resource

In the WS-ResourceFramework, operations which create new resources and provide us with references to them are called factories. In the BlogService, the createBlogTopic operation is the factory that creates a new resource (a new Blog topic), and returns a reference to it (as an EnpointReference). This function creates the resource instance, fills in the EndpointReference to be returned, and creates a resource property BlogEntry on the resource.

6.2.4.2. The Resource ID

As the first step of creating a resource in our Blog_createBlogTopic_impl function, we must acquire a resource ID. The resource ID is an application specific object that acts as a unique identifier for the resource within the service, and gets embedded within the EndpointRerence for the new resource. For C WS-Core, the resource ID must be in the form of a string. In many services, the resource ID is a UUID string, generated by the globus_uuid_create function. See the UUID library documentation for further info.

In the case of the BlogService, we assume that no two Blogs created by the same person will have the same topic, so we can join the author and topic strings together as the resource ID for the new resource we are about to create.

globus_result_t
Blog_createBlogTopic_impl(
    globus_service_engine_t             engine,
    globus_soap_message_handle_t        message,
    globus_service_descriptor_t *       descriptor,
    createBlogTopicType * createBlogTopic,
    createBlogTopicResponseType * createBlogTopicResponse,
    const char ** fault_name,
    void ** fault)
{
    char *                              resource_id;
    globus_result_t                     result = GLOBUS_SUCCESS;

    GlobusFuncName(Blog_createBlogTopic_impl);
    BlogServiceDebugEnter();

    blog_id = globus_common_create_string(
        "%s#%s", createBlogTopic->Creator, createBlogTopic->Topic);

The blog_id is then passed to the globus_resource_create function, which will create a managed resource and return it in blog_resource.

    result = globus_resource_create(
        blog_id,
        &blog_resource);

    ...

    result = BlogServiceInitResource(blog_id);

The second call in the code listing above is the service's resource init function, which allows the operation providers to initialize the resource properties of the resource you've just created. For example, the WS-ResourceLifetime operation provider adds CurrentTime and TerminationTime resource properties to the resource.

The bindings for any service definition will include a <service name>InitResource([resource id]); macro which calls the resource initialization functions for each operation provider the service includes.

6.2.4.3. The EndpointReference (EPR)

Once the resource is created the EndpointReference must be created. The first step is to initialize a reference property of the EPR, which will contain the resource ID we just created. The reference property is a field in the wsa_EndpointReferenceType type. Since the property can be anything, it is typed to the XSD wildcard xsd_any, which we must create an instance of and initialize to contain the appropriate type and value for the reference property.

    result = xsd_any_init(&reference_property);
    reference_property->any_info = &xsd_string_info;

    ...

    result = xsd_QName_init(reference_property->element);

    ...

    reference_property->element->Namespace = globus_libc_strdup(
        BlogService_service_qname.Namespace);
    reference_property->element->local = globus_libc_strdup("BlogID");

    result = xsd_string_copy_cstr(
        (xsd_string **)&reference_property->value, 
        blog_id);

The xsd_any type we initialize has 3 important fields. The any_info field contains the type information used by the marshalling engine to determine how to serialize the reference property. In this case the reference property is just a string, so we set the any_info field to the globally defined xsd_string_info variable. For more information on using wildcards in your service implementation, see Section 5, “Usage scenarios ”.

The element field in xsd_any is a QName of the element to define for serializing the type. In the BlogService case, we set the element to http://globus.org/blog#BlogID. The other field we need to set in the reference property is the value, which is a (void *), set to the pointer of the instance of the resource id (in this case the blog id string). We use the xsd_string_copy_cstr function to actually copy the contents of the string to the value field.

Once the reference property has been initialized, we can create the EndpointReference. The globus_wsrf_core_create_endpoint_reference convenience function has been provided to create the endpoint reference.

    result = globus_wsrf_core_create_endpoint_reference(
        engine,
        BLOGSERVICE_BASE_PATH,
        &reference_property,
        &createBlogTopicResponse->EndpointReference);

This call takes the engine passed into the skeleton function, the base path of the URI for the service (each service has a <service name>_BASE_PATH variable defined), and the reference property we just initialized. The resulting EndpointReference must be set to the EndpointReference field in the createBlogTopicResponse variable passed into the skeleton function.

6.2.4.4. The Resource Property

As the last step of the Blog_createBlogTopic_impl function, we set the BlogEntry resource property of the resource. Since the Blog initially doesn't contain any entries, we set the resource property to an empty array. We will add new entries to this resource property in the Blog_addEntry_impl skeleton function.

    result = BlogEntryType_array_init(&blog_entries);

    ...

    result = globus_resource_create_property(
        blog_resource,
        &Blog_BlogEntry_rp_qname, 
        &BlogEntry_array_info,
        blog_entries);

The arguments passed to this function are the created resource, the QName of the resource property (in this case, BlogEntry), the info variable of the resource property type to create, and the empty blog array instance. See the Resource API for further documentation.

6.2.4.5. Add an Entry to the Blog Topic

Once a resource has been created, clients will invoke the addEntry operation to add new entries to the blog. The implementation of the Blog_addEntry_impl adds the new entry to the blog topic.

6.2.4.6. Access the Resource

The resource is accessed through the EndpointReference contained in the message. The utility function globus_wsrf_core_get_resource is used to access the resource. The EndpointReference is accessed through the first parameter (message) passed to the function.

    result = globus_wsrf_core_get_resource(
        message,
        descriptor,
        &blog_resource);

Information about how the resource ID is accessed from the EndpointReference is maintained by the service descriptor, so this gets passed in as the second parameter (service).

6.2.4.7. Get the Resource Property

Once we have the resource we can access the BlogEntry resource property using the globus_resource_get_property function.

    
    result = globus_resource_get_property(
        resource,
        &Blog_BlogEntry_rp_qname,
        (void **)&blog_entries,
        NULL);

The first parameter is the blog resource we just accessed, the second parameter is the QName of the BlogEntry resource property. Blog_BlogEntry_rp_qname is a global variable declared in BlogService.h. Global QName variables exist for each resource property in a service. The third parameter is the array of blog entries we want to get. The last parameter is the type info structure of the resource property we're accessing. Since we know the type of the resource property, we can just set this to NULL.

6.2.4.8. Add the Blog Entry

Now that we have the array of blog entries, we need to add a new element to the end of it with the values of the entry. Each array type generated from an XML schema document has an associated _array_push function, which creates a new instance of the type and adds it to the end of the array, returning the new instance. In this case, we create a new entry at the end of the array with the BlogEntryType_array_push function.

    new_entry = BlogEntryType_array_push(blog_entries);

Now we need to fill in this entry with the values passed into the skeleton function.

    tstamp = time(NULL)
    result = xsd_dateTime_copy_contents(
        &new_entry->Timestamp,
        (xsd_dateTime *)localtime(&tstamp));

    ...

    result = xsd_string_copy_contents(
        &new_entry->Author,
        (xsd_string *)&addEntry->Author);

    ...

    result = xsd_string_copy_contents(
        &new_entry->Comment,
        (xsd_string *)&addEntry->Comment);

These functions copy the entry's comment and author from the input parameter to the new entry instance we've created. The timestamp of the entry is set to the current local time. This completes the addition of a resource property value to the resource property maintained by the resource instance.

The addEntry operation expects as the response a list of the entries in the Blog. Since this is just the array of blog entries that we just added to, we can simply copy this array to the response output parameter:

    result = BlogEntryType_array_copy_contents(
        &addEntryResponse->BlogEntries,
        blog_entries);

6.2.4.9. Resource Finish

As a last step of the Blog_addEntry_impl function, we need to release the blog resource we accessed in the first step. This allows the resource management computeroutput to handle locking and reference counting for the resource.

    globus_resource_finish(blog_resource);

6.2.4.10. Other Issues

In this section we describe other parts of implementing the skeleton functions that might be of interest.

6.2.4.11. Service Initialization

Besides the skeleton functions defined for each operation in a service, BlogService_skeleton.c also contains functions for initializing and finalizing the BlogService. The BlogService_init function should contain any service specific computeroutput that needs to be run when the service is loaded, and the BlogService_finalize function should contain computeroutput that needs to be run when the service is unloaded (presumably cleanup from BlogService_init). These functions most likely can remain empty no-ops, but if for example you want a service to have persistent resources which exist throughout the lifetime of the service, they should be created in the service's init function and destroyed in the finalize function.

6.2.4.12. Error Handling

Almost all of the function calls in our BlogService return a globus_result_t type. The globus_result_t informs the caller of the success or failure of the function call, and is used to reference the error object created if an the function call failed. The standard practice in the Globus Toolkit for handling errors is to check the return value of the function:

    if(result != GLOBUS_SUCCESS)

and if an error occurred, either chain the error or handle the error at that level (exit the process, print an error message, etc.). The skeleton functions we've implemented in this tutorial have a globus_result_t return value, so the skeleton function needs to create and return error values if and when they occur within the service implementation. The bindings generated for a service include macros for each operation in the service's header file that create globus_result_t error values to be returned by the skeleton function. For example, the signatures of the macros generated for the addEntry operation are:

globus_result_t
Blog_addEntry_error(const char *);

globus_result_t
Blog_addEntry_chain_error(globus_result_t, const char *);

In general, each operation will have an associated error create function that takes a string and returns a globus_result_t error as well as an error function that takes a base error globus_result_t and a string and returns a new globus_result_t.

The first function macro listed is useful for error cases where the error is the primary base cause, while the second function is useful when another globus function has been called and value which is not equal to GLOBUS_SUCCESS.

6.2.4.13. Operation Providers

For the operations inherited from the WSRF schemas (GetResourceProperty, Destroy, SetTerminationTime), their implementation has already been provided for us. This is achieved using operation providers, which replace the functions defined in the BlogService_skeleton.c source file with generic pre-defined versions of those functions when the service is loaded by the container. Even though the contents of those functions remain empty in the skeleton source file, they don't get used, so they can be safely ignored.

6.2.4.14. Service-Side Notifications

BlogService_skeleton.c also includes functions for the Subscribe and GetCurrentMessage operations that are part of the WS-BaseN schema (inherited by the BlogService), but the C WS-Core currently doesn't provide implementations of NotificationProducer or SubscriptionManager at present, so these skeleton functions can remain empty as well.

6.2.5. Step 4: Building/Installing the Service Package

6.2.5.1. Packaging

Once the service implementation is complete, the service package can be re-packaged (create the tarball) with the implemented computeroutput using make. Change the working directory to the directory the bindings were generated in, and run:

make dist

This will create (or re-create) the blog_service_bindings-0.2.tar.gz package in that directory with the new service implementation. This package can be distributed to any machine with a C WS-Core installation and installed there.

6.2.5.2. Building

To build the package you just created, run the following command:

$GPT_LOCATION/sbin/gpt-build blog_service_bindings-0.2.tar.gz <flavor>

This will compile the source files for the types and service and build them into a library module named libblog_service_bindings.so (the suffix of the library may differ depending on the platform). The header files are installed into $GLOBUS_LOCATION/include/<flavor> and the library is installed in $GLOBUS_LOCATION/lib/<service base path>.

6.2.6. Step 5: Running the Service Container

Once the BlogService library module has been installed, the service container can be run and the BlogService can be invoked, causing execution of the service implementation. The service container is run with the command:

$GLOBUS_LOCATION/bin/globus-wsc-container

6.2.7. Step 6: Debugging the Service Implementation

6.2.7.1. Adding Debug Statements

Each service module includes debugging macros that allow the service developer to print debug statements in a configurable way. The debug statements can have different levels of severity, and are controlled by environment variables. Debug statements are only printed when the service module is compiled with a debug flavor (such as gcc32dbg).

The macro declaration for printing a debug statement in the service skeleton is:

<service>DebugPrintf(LEVEL, MESSAGE);

Where LEVEL is one of:

  • <SERVICE>_INFO
  • <SERVICE>_DEBUG
  • <SERVICE>_TRACE
  • <SERVICE>_WARN
  • <SERVICE>_ERROR

The MESSAGE parameter consists of the debug message to be printed. It must contain parentheses () around the actual message. Inside the parentheses can be a format string, and a variable number of arguments (like printf). For example, in the BlogService's addEntry skeleton implementation (Blog_addEntry_impl), the developer may want to see the entry to be added for debugging purposes. The following statement would print the debug message if the DEBUG level was turned on:

BlogServiceDebugPrintf(BLOGSERVICE_DEBUG,
                       ("ADD ENTRY:\n\tCOMMENT: %s\n\tAUTHOR: %s\n",
                        addEntry->Comment,
                        addEntry->Author));

6.2.7.2. Setting Debug Environment Variables

In order for debug statements to be printed to the terminal, the user must set the appropriate environment variable before running the service container. Each service has a separate debug environment variable that can be set to different debug levels. Optionally, the value of the variable can include a filename to write the debug output to as well.

The environment variable to set for service debugging is:

<SERVICE>_DEBUG=<DEBUG LEVEL>

This environment variable has five disjoint debug levels that can be set, and match the level definitions used for the debug statement in the previous section. The five levels are:

  • INFO - general information useful to users of the service.
  • DEBUG - debug output used by the service skeleton implementor to verify code works.
  • TRACE - output the entry and exit points of each of the service skeleton functions.
  • WARN - warn the user that something bad may be happening.
  • ERROR - output an error for the user to see as it gets returned.

There is also a ALL level that will show the debug output for all the levels.

For our BlogService example, if we wanted to see the debug statements at the DEBUG level, then in bash we would set:

export BLOGSERVICE_DEBUG=DEBUG

If the user wants to see output from multiple debug levels, the levels can be joined together:

export BLOGSERVICE_DEBUG="DEBUG|TRACE"

7. Debugging

Besides the standard debugging tools available on your platform for C programs, the C WS-Core has a number of environment variables that can be set to debug or trace program execution of the service container. The useful environment variables that can be set are:

  • GLOBUS_SERVICE_ENGINE_DEBUG - useful for tracing execution of the service engine. The possible values this variable can have are:

    • DEBUG - show debug messages about execution of the engine.
    • INFO - show information regarding service invocations.
    • TRACE - show entry and exit points of functions for the service engine.
    • ERROR - show error occurring during service invocation.
    • ALL - all the above levels joined together.

8. Troubleshooting

This is a new component. If you're having trouble, please let us know!

9. Related Documentation

None at present.