Vortex XML-RPC programming manual (C API)

Introduction

This manual shows how to use the XML-RPC invocation interface built of top Vortex Library. The implementation is based on the experimental protocol defined at RFC3529. This manual assumes you are already familiar with the Vortex API. If you don't, you can check this tutorial as a starting point.

On this manual you'll find the following sections:

Section 1: An introduction to RPC systems

Section 2: Using Vortex Library XML-RPC

Section 3: Using Vortex Library xml-rpc-gen-1.1 tool

Section 4: Using the output produced

Section 5: Additional topics

Some concepts and background before starting

If you are familiar with RPC environments such CORBA, Web services, Sun RPC or Af-Arch, you can safely skip this section. If you didn't use any kind of RPC software, you should read this section to get valuable information that will help you to not only understand XML-RPC.

All RPC environments are based on a basic concept: to be able to cross the process boundary to execute a method/service/procedure hosted by other process that is reachable through a decoupled link. This decoupled link could be a loopback connection or a network connection to a remote station.

In fact, RPC stands for Remote Produce Call. There are several types of RPC environments, but all of them are characterized by its encapsulation format and its invocation model. While the first characterization is based on textual or binary encapsulation format, the second one is based on environments that execute method inside remote objects or services/procedures hosted by a remote process.

XML-RPC is service-invocation based, using textual encapsulation format, that is, XML. For the case of CORBA, it is method-invocation based, using binary format (its GIOP and IIOP).

Method-invocation environments requires a reference to the remote object that will receive the method invocation. This is not required for service-invocation environments. They just execute the service/produce exposed by the remote object.

All of these environments provide an infrastructure to locate the server that are actually exposing services/methods/procedures to be invoked. However, RPC developers usually fall into providing a host and a port (TCP/IP) to locate the resource, bypassing the location facilities.

The software that provides location services is usually called the binder. For the case of XML-RPC, there is no binder. So, you have to provide all information required to locate your component that will accept to process the service invocation received.

Obviously, in most cases, this isn't a problem, because most of system network design is based on a client/server interaction, making only necessary to know where is located the server component.

Usually, these RPC environments provides two API (or way to use the framework) that could be classified as: Raw invocation interfaces and High level invocation interfaces.

As you may guessing, there are more fun (and pain!) while using the Raw invocation interface than the High level one. Both offers (dis)advantages and both provides a specific functionality, that is required in particular situations.

The Raw invocation, The High level invocation and the protocol compiler

In general term, the Raw invocation interface provides an API to produce a invocation, defining all the details, while the High level invocation interface is built on top of the Raw interface, and it is usually produced by a protocol compiler tool. This tools is usually called the Interface Definition Language compiler, or just IDL compiler.

This protocol compiler reads a service definition description (every RPC platform has its own version about this language), and produces two products: the client stub and the server skeleton.

The client stub is a small library that exposes the services as they were local functions. Inside these local functions are implemented all the voodoo, using the Raw interface, to actually produce the invocation.

This is great because this client stub component makes the invocation to be really easy, like you were interfacing with local function, making you to forget you are actually calling to a remote object.

The server stub component is the piece of software provided to enable the programmer to actually implement the method to be invoked. It is called skeleton because it is a server with all method/procedures/services without being implemented.

In most cases, RPC developers just don't want to hear about the Raw invocation interface, they use the IDL compiler.

Using the Raw API invocation

XML-RPC Raw interface, inside Vortex, is composed by the XML-RPC type API and the invocation interface API.

The first one exposes all types and enum values that are used across all XML-RPC API. The second one is the API that actually do the useful work.

There are two high level type definitions, XmlRpcMethodCall and XmlRpcMethodResponse that, as their names shows, represents the method call object and the method response.

The idea is that you use the XML-RPC type API to create the XmlRpcMethodCall, that represents your method at the remote side to be executed, and then, using the invocation interface, you actually produce a service invocation.

Performing a client invocation

Let's assume we are going to invoke a simple method, called sum, which receives integer 2 arguments, a and b and return an integer, as a result of adding a to b. What we need is to produce a representation of our method so we can use it to actually produce an invocation. Here is an example on how it could be done:

// declare our method call reference
XmlRpcMethodCall * invocator;
// create the method call, called sum, with 2 arguments
invocator = vortex_xml_rpc_method_call_new ("sum", 2);
// now create method parameters
PTR_TO_INT(2));
// add the method value to the invocator object
// create the other method parameter
PTR_TO_INT(3));
// add the method value to the invocator object

Now we have a representation of our method call. What we need now, is to use this it to produce an invocation. This is done by first creating a connection to the remote server, booting a XML-RPC channel. Here is an example:

// a method response declaration
// a vortex connection to the remote peer
VortexConnection * connection;
// a channel referece to the remote peer
VortexChannel * channel;
// some variables to get textual status
VortexStatus status;
char * message;
// the ctx variable represents a context already initialized with
// vortex_ctx_new followed by vortex_init_ctx
// call to enable XML-RPC on the context
if (! vortex_xml_rpc_init (ctx)) {
printf ("Unable to init XML-RPC support..\n");
return;
}
// create the connection to a known location (in a blocking manner for
// demostration purposes)
connection = vortex_connection_new (ctx, "localhost", "22000", NULL, NULL);
// boot an XML-RPC channel
channel = vortex_xml_rpc_boot_channel_sync (connection, NULL, NULL, &status, &message);
// show message returned
if (status == VortexError) {
printf ("Unable to create XML-RPC channel, message was: %s\n", message);
return;
}else {
printf ("XML-RPC channel created, message was: %s\n", message);
}
// peform a synchronous method
printf (" Performing XML-RPC invocation..\n");
response = vortex_xml_rpc_invoke_sync (channel, invocator);
switch (method_response_get_status (response)) {
printf ("Reply received ok, result is: %s\n", method_response_stringify (response));
break;
default:
printf ("RPC invocation have failed: (code: %d) : %s\n",
break;
}
// free the response received
// free the method invocator used
method_call_free (invocator);

Well, it is impressive the huge amount of things to be done to simple invoke a sum operation that is on the remote side, isn't it? That's why people doesn't use Raw interface, preferring to use the High level one while producing common RPC tasks.

However, this interface is required if you need to produce a custom invocator that perform some special task before doing the actual invocation, or just because you need a general invocation software to track down all invocations received, at server side, so you can re-send that invocation to another system, acting as a proxy.

Raw client invocation considerations

Before seeing previous example, here are some issues to be considered:

Processing an incoming XML-RPC invocation

We have seen in previous sections how a XML-RPC invocation is produced. Now, it is time to know what happens on the remote side, because, until now, we have just moved the problem from a machine to another. However, the problem remains unresolved.

Listener side implementation is built on top of two handlers: the validation resource handler and the the service dispatch handler.

The first is used to provide a way to validate resource context, when a channel boot request is received. According to the RFC3529, a resource could be interpreted in many ways, for example, as a supported interface or as a domain that encloses several services. It could be used also to provide a versioning mechanism for the same service.

So the resource "/version/1.0" and the resource "/version/1.2" could allow to support the same service name, but with different versions.

Let's considered the following XML-RPC uri value to clearly understand the resource concept:

xmlrpc.beep://example.server.com/NumberToName

Previous XML-RPC uri states that there is a XML-RPC listener server at example.server.com located at the IANA registered port 602 (because no port was specified by appending to the server name, the value in the form :port-num), and it is required to ask for the resource NumberToName before producing the actual invocation.

NumberToName is not the service name. It is just a resource that the XML-RPC listener may reply that it is actually supported or not. In fact, you can still export services without doing anything with resources. Here is an example of a XML-RPC uri used to connect to a particular server at the default resource "/"

xmlrpc.beep://example.server.com/

In this context, the validation resource handler is used to notify the Vortex engine if the listener is willing to accept a particular resource value once a XML-RPC channel is being started. In many cases, you can safely avoid setting the resource handler. This will make Vortex XML-RPC engine to accept all resources requested.

Now, let's talk about the service dispatch handler. As it names shows, it is used to enable the Vortex engine to notify user space code that a new method invocation has arrived (XmlRpcMethodCall) and that it has to be dispatched to the appropriate handler. This handler is mainly provided to allow developers to be able to produce its own service dispatching policy.

Let's see a simple dispatching implementation for the sum service introduced at the client invocation section. Let's see the example first to later see some considerations:

First, the listener side must active a listener that is willing to accept the XML-RPC profile:

// vortex global context
VortexCtx * ctx = NULL;
int main (int argc, char ** argv)
{
// create an empty context
ctx = vortex_ctx_new ();
// init the context
if (! vortex_init_ctx (ctx)) {
printf ("failed to init the library..\n");
}
// enable XML-RPC profile
vortex_xml_rpc_accept_negotiation (ctx, validate_resource,
// no user space data for
// the validation resource
// function.
NULL,
service_dispatch,
// no user space data for
// the dispatch function.
NULL);
// create a vortex server
vortex_listener_new (ctx, "0.0.0.0", "44000", NULL, NULL);
// wait for listeners (until vortex_exit is called)
// end vortex function
vortex_exit_ctx (ctx, axl_true);
return 0;
}

The example is quite simple, first, Vortex Library is initialized, then a call to vortex_xml_rpc_accept_negotiation is done to active the XML-RPC. Then a call to activate a listener, for any host name that the local machine may have, at the port 44000, is done by using vortex_listener_new. Finally, a call to vortex_listener_wait is done to ensure the server initialization code is blocked until the server finish.

Here is an example of a validation resource function that only accept some resources names:

axl_bool validate_resource (VortexConnection * connection,
int channel_number,
char * serverName,
char * resource_path)
{
// check that resource received
if (axl_cmp (resource_path, "/aritmetic-services/1.0"))
return axl_true;
if (axl_cmp (resource_path, "/aritmetic-services/1.1"))
return axl_true;
// resource not recognized, just return axl_false to signal
// vortex engine that the channel must not be accepted.
return axl_false;
}

And here is a service dispatch implementation, for our service example sum:

int __sum_2_int_int (int a, int b, char ** fault_error, int * fault_code)
{
if (a == 2 && b == 7) {
// this is an example on how the return a fault reply that includes
// an error code an error string.
REPLY_FAULT ("Current implementation is not allowed to sum the 2 and 7 values", -1, 0);
}
// for the rest of cases, just sum the two incoming values
return a + b;
}
XmlRpcMethodResponse * sum_2_int_int (XmlRpcMethodCall * method_call)
{
int result;
char * fault_error = NULL;
int fault_code = -1;
// get values inside the method call
int a = method_call_get_param_value_as_int (method_call, 0);
int b = method_call_get_param_value_as_int (method_call, 1);
// perform invocation
result = __sum_2_int_int (a, b, &fault_error, &fault_code);
// check error reply looking at the fault_error
if (fault_error != NULL) {
// we have a error reply
return CREATE_FAULT_REPLY (fault_code, fault_error);
}
// return reply generated
return CREATE_OK_REPLY (XML_RPC_INT_VALUE, INT_TO_PTR (result));
}
XmlRpcMethodResponse * service_dispatch (VortexChannel * channel, XmlRpcMethodCall * method_call, axlPointer user_data)
{
// check if the incoming method call is called sum, and has
// two arguments
if (method_call_is (method_call, "sum", 2, XML_RPC_INT_VALUE, XML_RPC_INT_VALUE, -1)) {
return sum_2_int_int (method_call);
}
// return that the method to be invoked, is not supported
return CREATE_FAULT_REPLY (-1, "Method call received couldn't be dispatched because it not supported by this server");
}

The example shows how the service dispatch handler is the service_dispatch function, which first recognizes if the service is supported and then call to the appropriate handler.

Here is the first interesting thing, the function method_call_is. It is used to recognize service patterns like name, number of parameter and the parameter type. This allows to easily recognize the service to actually dispatch.

In this function, service_dispatch, should be created a XmlRpcMethodResponse to be returned. So, the vortex engine could reply as fast as possible. However, the implementation is prepared to defer the reply. This allows, especially, to communicate with other language runtimes. Once the runtime have generated the reply, it must be used the following function vortex_xml_rpc_notify_reply, to actually perform the reply.

Abstraction required: The xml-rpc-gen-1.1 tool

Until now, we have seen that producing RPC enabled solutions is really complex. First, because we have to produce the server implementation, and then, all source code required to actually perform the invocation.

However, every RPC framework comes with a protocol compiler that helps on producing lot of source code. For the XML-RPC over BEEP implementation that comes with Vortex Library, this tool is xml-rpc-gen-1.1.

This tool reads IDL and XDL interface definitions and produce a ready server and a client library (usually called stub) that allows to perform the service invocation without paying attention to XML-RPC invocation details. Let's see an example to introduce the tool:

/* This is a test that invokes a simple service with two integers and
* returns its result.
*/
xml-rpc interface test {
/* add two integers */
int sum (int a, int b) {
/* sum values received */
return a + b;
}
/* empty service definition */
int operate (int a, int b);
}

Now write previous example (just copy the example) into a file (let's say reg-test01.idl, you have a copy inside the ../xml-rpc-gen/ directory, bundled with the Vortex Library source code) and exec the following:

>> ./xml-rpc-gen-1.1 reg-test01.idl
[*] compiling: reg-test01.idl..
[*] detected IDL format definition..
[*] detected xml-rpc definition: 'test'..
[*] document is well-formed: reg-test01.idl..
[*] document is valid: reg-test01.idl..
[*] component name: 'test'..
[*] using 'out' as out directory..
[*] generating client stub at: out/client-test..
[*] generating server stub at: out/server-test..
[*] generating autoconf files..
[*] compilation ok

Previous interface definition have produced two components:

Now, instead of producing all source code required to perform the invocation, the xml-rpc-gen-1.1 tool allows you define a xml-rpc interface definition, that produces that code for you. In this case, looking at: out/client-test/test_xml_rpc.h, you'll find a C API that hides all details to actually invoke the sum service.

In the other hand, instead of producing all source code, at the server side, to unmarshall and invoke the service, all this code is produced by xml-rpc-gen-1.1 tool. In this case, looking at: out/server-test/test_sum_int_int.c you'll find the sum service implementation.

In the following sections it is explained how to use the xml-rpc-gen-1.1 tool to produce RPC solutions.

Using xml-rpc-gen-1.1 tool: language syntax

xml-rpc-gen-1.1 is a compiler that produces a client component and a server component. The client component is just a library that hides all details required to perform the invocation. The tool support two formats: one structured, more based on the IDL (Interface Definition Language) language found in other platforms, and a XML based format, called XDL (XML Definition Language).

Internally, the tool is programmed on top of XDL definition language. If the tool detects that the input file is written using the IDL format, it translate that representation into an equivalent XDL representation, starting the processing as the input were XDL. In any case, both language representations are equivalent and well supported.

The interface definition is composed by the global interface declaration and, at least, one service definition. Here is an example using both formats:

IDL format:

/* This is a test that invokes a simple service with two integers and
* returns its result.
*/
xml-rpc interface test {
/* add two integers */
int sum (int a, int b) {
/* sum values received */
return a + b;
}
/* empty service definition */
int operate (int a, int b);
}

XDL format:

<xml-rpc-interface>
<!-- component name declaration -->
<name>test</name>
<!-- service declaration: sum (int a, int b); -->
<service>
<name>sum</name>
<returns>int</returns>
<params>
<param>
<name>a</name>
<type>int</type>
</param>
<param>
<name>b</name>
<type>int</type>
</param>
</params>
<code>
<content><![CDATA[
/* this is a test < & */
return a + b;
]]></content>
</code>
</service>
</xml-rpc-interface>

As you can see, XDL format is more verbose than IDL format, but provides a standard format to cross boundaries between process, it is easy to parse and it is more portable (there is a XML parser in every platform).

Types supported by xml-rpc-gen-1.1 tool

There are 6 basic types supported by the tool (well, it is more accurate to say by the XML-RPC definition) which are:

And two compound type definitions which allows to define more types:

Struct and array types allows to create richer type definitions (even composing both). Here is an example that uses a struct declaration:

IDL format:

xml-rpc interface test {
/* declare the struct Values */
struct Values {
int count;
double fraction;
boolean status;
}
/* declare the service using previous declaration */
Values get_struct (Values a, Values b) {
Values * result;
/* create the value to be returned */
result = test_values_new (a->count + b->count,
a->fraction + b->fraction,
a->status || b->status);
/* return the result provided */
return result;
}
}

XDL format:

<xml-rpc-interface>
<!-- component name declaration -->
<name>test</name>
<!-- struct declaration -->
<struct>
<name>Values</name>
<member>
<name>count</name>
<type>int</type>
</member>
<member>
<name>fraction</name>
<type>double</type>
</member>
<member>
<name>status</name>
<type>boolean</type>
</member>
</struct>
<!-- Values get_struct (Values a, Values b); -->
<!-- post: Returns the sum of the provided struct values -->
<service>
<name>get_struct</name>
<returns>Values</returns>
<params>
<param>
<name>a</name>
<type>Values</type>
</param>
<param>
<name>b</name>
<type>Values</type>
</param>
</params>
<code>
<content><![CDATA[
Values * result;
/* create the value to be returned */
result = test_values_new (a->count + b->count,
a->fraction + b->fraction,
a->status || b->status);
/* return the result provided */
return result;
]]></content>
</code>
</service>
</xml-rpc-interface>

Recursive declarations with Struct

You can also define recursive type declarations, which makes references to the type being defined. This allows, for example, to create a list with linked nodes represented by struct declarations. Here is an example:

IDL format:

xml-rpc interface test {
/* declare the struct Values */
struct Node {
int position;
Node next;
}
/* return a list, formed by nodes */
Node get_list () {
/* the pointer result */
Node * result;
/* some variables for the service */
Node * node;
Node * next;
int iterator = 2;
/* create the first node */
result = test_node_new (1, NULL);
node = result;
while (iterator < 10) {
/* create the next, but setting next as NULL
* because we don't have it yet */
next = test_node_new (iterator, NULL);
/* set previous the next created */
node->next = next;
/* update references */
node = next;
/* update index */
iterator ++;
}
/* return list created */
return result;
}
}

XDL format:

<xml-rpc-interface>
<!-- XML-RPC component name declaration -->
<name>test</name>
<!-- Node struct declaration -->
<struct>
<name>Node</name>
<member>
<name>position</name>
<type>int</type>
</member>
<member>
<name>next</name>
<type>Node</type>
</member>
</struct>
<!-- get_list service declaration -->
<service>
<name>get_list</name>
<returns>Node</returns>
<params>
<!-- A service without parameters -->
</params>
<code>
<!-- service implementation declaration -->
<content><![CDATA[
/* the pointer result */
Node * result;
/* some variables for the service */
Node * node;
Node * next;
int iterator = 2;
/* create the first node */
result = test_node_new (1, NULL);
node = result;
while (iterator < 10) {
/* create the next, but setting next as NULL
* because we don't have it yet */
next = test_node_new (iterator, NULL);
/* set previous the next created */
node->next = next;
/* update references */
node = next;
/* update index */
iterator ++;
}
/* return list created */
return result;
]]></content>
</code>
</service>
</xml-rpc-interface>

Considerations while using composing types: Struct and Arrays.

There are several question to consider while using structures and arrays:

Array declaration

Here is an example to define an array type (which is quite different from the array definition found in C/C#/Java):

IDL format:

/* hey emacs this is -*- c -*- mode */
xml-rpc interface test {
/* struct declaration */
struct Item {
int position;
string string_position;
}
/* array declaration */
array ItemArray of Item;
/* service declaration which returns the array */
ItemArray get_array () {
ItemArray * result;
Item * item;
int iterator;
/* create the item array */
result = test_itemarray_new (10);
for (iterator = 0; iterator < 10; iterator++) {
/* create the struct */
item = test_item_new (iterator, "test content");
/* store it into the array */
test_itemarray_set (result, iterator, item);
}
/* put here service content */
return result;
}
}

XDL format:

<xml-rpc-interface>
<name>test</name>
<!-- Item struct declaration -->
<struct>
<name>Item</name>
<member>
<name>position</name>
<type>int</type>
</member>
<member>
<name>string_position</name>
<type>string</type>
</member>
</struct>
<!-- array declaration -->
<array>
<name>ItemArray</name>
<type>Item</type>
<size>0</size>
</array>
<!-- ValuesArray get_array (); -->
<!-- post: Returns the sum of the provided struct values -->
<service>
<name>get_array</name>
<returns>ItemArray</returns>
<params>
<!-- no param -->
</params>
<code>
<content><![CDATA[
ItemArray * result;
Item * item;
int iterator;
/* create the item array */
result = test_itemarray_new (10);
for (iterator = 0; iterator < 10; iterator++) {
/* create the struct */
item = test_item_new (iterator, "test content");
/* store it into the array */
test_itemarray_set (result, iterator, item);
}
/* put here service content */
return result;
]]></content>
</code>
</service>
</xml-rpc-interface>

Services without parameters

Services could have no parameter. This is clear while using the IDL format, but for the XDL format, it is required to place the <params> declaration, with no <param> childs. Here is an example:

IDL format:

/* test interface with only one service that is called without
* parameters and returns an string */
xml-rpc interface test {
string get_the_string () {
/* return a test string (static version) */
return axl_strdup ("This is a test");
}
}

XDL format:

<xml-rpc-interface>
<name>test</name>
<service>
<name>get_the_string</name>
<returns>string</returns>
<params>
<!-- not parameters -->
</params>
<code>
<content><![CDATA[
/* return a test string (static version) */
return g_strdup ("This is a test");
]]></content>
</code>
</service>
</xml-rpc-interface>

Services using and returning boolean types

Declaring services that receive or return boolean types is pretty straightforward. Here is an example:

IDL format:

xml-rpc interface test {
boolean get_the_bool_1 () {
/* return false */
return FALSE;
}
boolean get_the_bool_2 () {
/* return true */
return TRUE;
}
}

XDL format:

<xml-rpc-interface>
<name>test</name>
<service>
<!-- bool get_the_bool_1 (); -->
<!-- post: Always returns false -->
<name>get_the_bool_1</name>
<returns>boolean</returns>
<params>
<!-- not parameters -->
</params>
<code>
<content><![CDATA[
/* return false */
return FALSE;
]]></content>
</code>
</service>
<service>
<!-- bool get_the_bool_2 (); -->
<!-- post: Always returns true -->
<name>get_the_bool_2</name>
<returns>boolean</returns>
<params>
<!-- not parameters -->
</params>
<code>
<content><![CDATA[
/* return true */
return TRUE;
]]></content>
</code>
</service>
</xml-rpc-interface>

Services using and returning double types

This is no special consideration while declaring services that makes use of the double type. Here is an example:

IDL format:

/* test double support, invoking a service with two double parameters,
* and returning as a result another double */
xml-rpc interface test {
double get_double_sum (double a, double b) {
/* return the sum */
return a + b;
}
}

XDL format:

<xml-rpc-interface>
<name>test</name>
<!-- double get_double_sum (double a, double b); -->
<!-- post: Returns the sum of the provided double values -->
<service>
<name>get_double_sum</name>
<returns>double</returns>
<params>
<param>
<name>a</name>
<type>double</type>
</param>
<param>
<name>b</name>
<type>double</type>
</param>
</params>
<code>
<content><![CDATA[
/* return the sum */
return a + b;
]]></content>
</code>
</service>
</xml-rpc-interface>

Changing the method name for a service declared

Due to the kind of output produced by the xml-rpc-gen-1.1 tool, it has to create "method names" for services declared at the IDL processed in a synchronized way to a client invocation, using a particular service, is properly processed by the remote service entry point.

Under some situations it is required to change the name that used by default the xml-rpc-gen-1.1 tool. This is done by using a prefix declaration before the service:

/* af-arch service definition to invoke them easily using the idl
* compiler */
xml-rpc interface af-arch {
struct HostLocation {
/* logical name */
string name;
/* server host location */
string host;
/* server port location */
string port;
}
/* an array of host location items */
array HostLocationArray of HostLocation;
[method_name="af_kernel::server::get_list"];
HostLocationArray get_list ();
} /* end af-arch interface */

In the example, the service get_list won't be invoked using that name (the default xml-rpc-gen-1.1 behavior), but the name declared at the method_name will be used.

Previous IDL declaration is used by shaper to invoke a service exported by the Af-Arch central server, through the XML-RPC bridge, to get host location information.

Using resources to group your services

During the XML-RPC channel negotiation phase, the client request to create a channel under a particular resource. By default all services declared at the IDL are grouped under the same resource: "/".

This resource declaration can be used in several ways to achieve some interesting features:

To change the resource under which the service is grouped you must use the attribute "resource" as used in: Changing the method name for a service declared.

xml-rpc interface test {
[resource="arimetic-operations"];
int sum (int a, int b) {
return a + b;
}
}

Note this will produce a client and a server component that handles the service under the provided resource. However the validation handler is not provided.

Enforcing resources to be used at the IDL

Previous section provided information about declaring resources and grouping services under them. However, knowing that a resource declaration is a string, it could be required to enforce resources that are usable across the IDL. This is done by using the following:

allowed resources "resource1", "resource2", "resource3";

This list declaration must be used before any service with the attribute "resource". The list configured will enforce the xml-rpc-gen-1.1 tool to allow and check all resources used.

Including additional code to be placed at the service module file

Examples showed allows to include code that is placed at the appropriate file at the server side created. However, real situation requires calling to functions that are defined at the same modules or other modules. This is because it is required a mechanism that allows to include arbitrary code, not only in the service body.

Suppose the following example:

IDL format:

/* include example */
xml-rpc interface test {
/* add two integers */
int sum (int a, int b) {
/* sum values received */
return do_sum_operation (a, b);
} options {
include on body {
int do_sum_operation (int a, int b) {
return a + b;
} /* do_sum_operation */
} /* end include on body */
}
}

The service sum is implemented making a call to a user defined function do_sum_operation. Using the "include on body" declaration it is possible to include the content of the function. The content provide will be included at the top of the body implementation for the service sum to avoid prototypes.

There is also a way to include content into the module header. This is done using the same structure: "include on header".

In the case the body content to be included is to large you can place it into a file and make the tool to include it. This is done as follows:

IDL format:

/* include example */
xml-rpc interface test {
/* add two integers */
int sum (int a, int b) {
/* sum values received */
return do_sum_operation (a, b);
} options {
include on body "do_sum_operation.c";
}
}

In this case the implementation of the do_sum_operation is included at the file: "do_sum_operation.c". The same applies to the content included at the module header file.

Using the output produced by xml-rpc-gen-1.1 tool at the CLIENT SIDE

The output produced by the tool are two software pieces: the client stub library and the server component. The client stub is small library that hides all the details to produce the invocation, marshalling all data, and to unmarshall replies received.

Let's recall our first example, to see what is the result generated for the client stub:

IDL format:

/* This is a test that invokes a simple service with two integers and
* returns its result.
*/
xml-rpc interface test {
/* add two integers */
int sum (int a, int b) {
/* sum values received */
return a + b;
}
/* empty service definition */
int operate (int a, int b);
}

If previous example is compiled (supposing the file is located at reg-test01.idl) as follows:

bash:~$ xml-rpc-gen-1.1 reg-test01.idl

By default, the client library is placed at: out/client-<component-name>. In this case, the output will be out/client-test. You can modify this behavior by using the –out-dir switch.

Inside a the out/client-test directory you'll find the main API file out/client-test/test_xml_rpc.h. This file contains all invocation functions required to perform a method call to all services exported by the component compiled.

Again, this file will follow the next naming convention according to the component name: out/client-<component-name>/<component-name>_xml_rpc.h

Let's see its content:

#ifndef __XML_RPC_TEST_H__
#define __XML_RPC_TEST_H__
#include <test_types.h>
/* include base library */
#include <vortex.h>
/* include xml-rpc library */
#include <vortex_xml_rpc.h>
/* support for c++ declarations */
BEGIN_C_DECLS
/* service: sum */
int test_sum_int_int_s (int a, int b, VortexChannel * channel, XmlRpcResponseStatus * status, int * fault_code, char ** fault_string);
void test_sum_int_int (int a, int b, VortexChannel * channel, XmlRpcProcessInt process);
/* service: operate */
int test_operate_int_int_s (int a, int b, VortexChannel * channel, XmlRpcResponseStatus * status, int * fault_code, char ** fault_string);
void test_operate_int_int (int a, int b, VortexChannel * channel, XmlRpcProcessInt process);
/* service: get_the_string */
char * test_get_the_string_s (VortexChannel * channel, XmlRpcResponseStatus * status, int * fault_code, char ** fault_string);
void test_get_the_string (VortexChannel * channel, XmlRpcProcessString process);
/* service: get_the_bool_1 */
axl_bool test_get_the_bool_1_s (VortexChannel * channel, XmlRpcResponseStatus * status, int * fault_code, char ** fault_string);
void test_get_the_bool_1 (VortexChannel * channel, XmlRpcProcessInt process);
/* service: get_the_bool_2 */
axl_bool test_get_the_bool_2_s (VortexChannel * channel, XmlRpcResponseStatus * status, int * fault_code, char ** fault_string);
void test_get_the_bool_2 (VortexChannel * channel, XmlRpcProcessInt process);
/* service: get_double_sum */
double test_get_double_sum_double_double_s (double a, double b, VortexChannel * channel, XmlRpcResponseStatus * status, int * fault_code, char ** fault_string);
void test_get_double_sum_double_double (double a, double b, VortexChannel * channel, XmlRpcProcessDouble process);
/* service: get_struct */
Values * test_get_struct_values_values_s (Values * a, Values * b, VortexChannel * channel, XmlRpcResponseStatus * status, int * fault_code, char ** fault_string);
void test_get_struct_values_values (Values * a, Values * b, VortexChannel * channel, XmlRpcProcessStruct process);
/* service: get_array */
ItemArray * test_get_array_s (VortexChannel * channel, XmlRpcResponseStatus * status, int * fault_code, char ** fault_string);
void test_get_array (VortexChannel * channel, XmlRpcProcessArray process);
/* service: get_list */
Node * test_get_list_s (VortexChannel * channel, XmlRpcResponseStatus * status, int * fault_code, char ** fault_string);
void test_get_list (VortexChannel * channel, XmlRpcProcessStruct process);
/* service: sum2 */
int test_sum2_int_int_s (int a, int b, VortexChannel * channel, XmlRpcResponseStatus * status, int * fault_code, char ** fault_string);
void test_sum2_int_int (int a, int b, VortexChannel * channel, XmlRpcProcessInt process);
END_C_DECLS
#endif

As you can see, there are two function declarations for every service exported by the XML-RPC component compiled. Both represent the same service to be invoked, however, one serve as a synchronous invocation, the one with the prefix "_s" and the other one serves for the asynchronous (non-blocking) invocation.

In the synchronous invocation case, the function ending with "_s", you'll find all parameters specified in the IDL/XDL definition, in this case: int a, and int b, plus three additional parameters, that are optional, and helps to get invocation error reporting.

Apart from the service parameters, in both cases, synchronous and asynchronous, you must also provide a reference to a VortexChannel already initialized (XML-RPC booted), with the XML-RPC profile, and under the particular resource where the service will be run.

To boot a channel there are several functions provided:

Finally, the asynchronous API is the same as the synchronous, providing the service parameters, the channel where the invocation will be run, and a handler where the service reply will be notified.

Now we know a bit more about the main API created by the xml-rpc-gen-1.1 tool, the file test_xml_rpc.h. However, you also have to take a look to test_types.h file. It contains all complex type definitions, that is, struct and array declarations. In this case, that file is empty.

NOTE: Both products generated, client-test and server-test have support for auto-tools. You have to follow the standard process to configure and compile:

bash:~$ cd Test/out/client-test
bash:~/Test/out/client-test$ ./autogen
bash:~/Test/out/client-test$ ./make
bash:~/Test/out/client-test$ ./make install

Ok, but, what about performing an invocation using this files. Here is a simple example:

#include <test_xml_rpc.h>
int main (int argc, char ** argv)
{
VortexConnection * connection;
VortexChannel * channel;
/* initialize the vortex */
vortex_init ();
/* create a connection to a local server */
connection = vortex_connection_new ("localhost", "44000", NULL, NULL);
/* create the xml-rpc channel */
channel = BOOT_CHANNEL (connection, NULL);
/* perform the invocation */
if (7 != test_sum_int_int_s (3, 4, channel, NULL, NULL, NULL)) {
fprintf (stderr, "An error was found while invoking..\n");
return -1;
}
/* close the channel */
vortex_channel_close (channel, NULL);
/* close the connection */
/* terminate vortex */
vortex_exit ();
return 0;
}

In the test we didn't perform any check, which isn't a good thing, (using vortex_connection_is_ok to check the connection, checking for NULL reference the channel created, and providing some variables to get invocation status, etc), but it helps you to get an idea.

Using the output produced by xml-rpc-gen-1.1 tool at the LISTENER SIDE

Following the example of the previous section, there is not too much to say. You have to compile the server and then run it. Try to compile the previous example and to compile the server as follows:

bash:~$ cd Test/out/server-test
bash:~/Test/out/server-test$ ./autogen
bash:~/Test/out/server-test$ ./make
bash:~/Test/out/server-test$ ./make install
bash:~/Test/out/server-test$ ./server-test

Because xml-rpc-gen-1.1 tool have support to include the service source code definition, into the IDL/XDL definition, the compiled product only required to be compiled. However programing a XML-RPC service usually is more complex than adding two integer. Here are some considerations:

If the server component is produced (by default client and server components are produced but it can be configured by using –only-client or –only-server), it contains a xml file that allows to configure the TCP/IP location. Look at: out/server-test/conf.xml. Here is the content:

You can modify and add more <listener> nodes to make your XML-RPC component to listen in other ports than the default one (produced 0.0.0.0:44000). It is supposed that the IANA authority have registered the 602 TCP port for the XML-RPC over BEEP protocol, but this isn't required.

While programming the server component, inside the IDL/XDL file, you'll have to use the REPLY_FAULT macro if you want to reply to the client component a faultCode and a faultString. The first two parameters are the fault string and the fault code. The last one is the empty value to be returned in order to compile the server. In most cases you can use 0.

If the service returns pointer types: XML_RPC_STRING_VALUE, XML_RPC_STRUCT_VALUE and XML_RPC_ARRAY_VALUE, all of them must return dynamically allocated objects. They will be deallocated by the XML-RPC engine, at the proper time.

XML-RPC authentication and Security

Until now, we didn't talk too much about these topics, mainly because they are fully integrated with the BEEP environment, and hence, inside Vortex Library. However, for those new to BEEP, here are some tips about this issue.

Every profile implemented on top of BEEP (the protocol) have already-made support to authenticate peers using SASL and to secure connections using TLS.

So, in the case a secure invocation is required you'll have to first create a connection with the XML-RPC listener, and then secure that connection. Then, once the connection is secured, perform the invocation creating channels as usual. All this tasks could be considered as client side one. Look at the following document to know about this issue: 5.1 Securing a Vortex Connection (or How to use the TLS profile).

At the server side you have to provide the enough mechanism to allow/ensure the client that is connected is doing so in a proper way. You can implement all your security politics at the start handler or at the resource validation handler. Unless it is required some special TLS function at the server side, like identifying the client certificate, it is not required to implement anything special. Look at the following document with provides a default TLS environment: 5.1 Securing a Vortex Connection (or How to use the TLS profile).

Because Vortex Library, at this moment, doesn't provide a profile path, allowing to hide non-secured profiles behind TLS or SASL profiles until they are entirely negotiated, you have to ensure that TLS is already activated, before accepting to dispatch a function, at the service dispatch handler. See also vortex_connection_is_tlsficated for more details.

The same philosofy applies to the authentication problem. You have to first establish the user authentication (and its authorization) and the perform the XML-RPC invocation. This means that, for a connection, first it is required to activate the user authentication through the SASL API, and then perform the XML-RPC invocation as normal.

Then, at the server side, inside the service dispatch function, the listener must check the connection credentials already activated to ensure the user that is actually executing the XML-RPC method, is allowed to do so. See the following document to know more about this: 5.3 Authenticating BEEP peers (or How to use SASL profiles family).

To summarize, there is no way to describe the "unique security and authentication" solution inside BEEP. Authentication (SASL), Security (TLS) and, for this case, the XML-RPC invocation mechanism are implemented in an independent way, like blocks that must be combined and mixed to build your specific solution that meets your requirements.

Maybe you only require to use TLS, or maybe you just need to do a SASL plain auth. However, the key concept is that: your required provisioning mechanism must be first activated before you actually perform useful work.