Table of Contents
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.
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.
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
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.)
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)
C WS-Core supports secure transport (https) and secure message (just X509 signing, not encryption).
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.
Mapping WSDL to C Bindings - describes how to use the C bindings generated from WSDL and XML schema.
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.
Here we provide some scenarios for using C WS-Core that aren't described in the tutorials.
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(¤t)); /* 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.
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.
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:
Complete source for the clients:
WSDL schemas:
- 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.
-
A GPT package
blog_client_bindings-0.2.tar.gz
of the blog bindings source code. This is the package generated from
the WSDL schemas using the
globus-wsrf-cgen
command. - A tarball blog_client.tar.gz of the counter client source and Makefiles described in this tutorial.
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:
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.
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>
In order to write a C WS-Core client, the following steps should be followed in general:
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"
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);
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.
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);
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??"
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??"
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);
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.
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.
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.
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.
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.
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
.
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.
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.
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.
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.
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.
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
).
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
.
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);
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);
In this section we describe other parts of implementing the skeleton functions that might be of interest.
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.
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
.
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.
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.
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.
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>
.
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
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));
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"
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.