XMLRPC-C Library Documentation

index

page jumps: |- preamble -|- error handling -|- memory block -|- xmlrpc value -|- format characters -|- creating XML stream -|- decompose/decoding -|- Server -|- Client -|- end

preamble

20120312: The source and WIN32 binaries xmlrpc-c.htm

As part of the FGCOM project, I got a little involved in the XMLRPC-C library, used by FGCOM to carry the voice traffic. This is only a collection of things found while exploring that library.

XMLRPC-C LIBRARY INTERFACE

The static xmlrpc-c library offers a number of functions and facilities, some of which are documented below. There exists an online manual at http://xmlrpc-c.sourceforge.net/doc/ which generally describes what xmlrpc is, and http://xmlrpc-c.sourceforge.net/doc/libxmlrpc.html which describes in some detail the full library interface, error handling, structures used, and functions, and has many examples. This document should be read first.

What follows here is another way to view the same information. A personal experience, as I learned about using xmlrpc. Of course, this document may become stale over time, and thus the best source of such documentation is the above online manuals, and the source code itself, especially the test examples provided. Be aware, the xmlrpc-c source maintainer LOVES 'const', and it appears all over the place, but do not be phased by it ;=))

top


xmlrpc_env

This "environment" object is passed into many functions, and on return contains any error that has occurred. It is simply defined as a structure like - see <xmlrpc-c/util..h> for details -

typedef struct _xmlrpc_env {
    int    fault_occurred;
    int    fault_code;
    char * fault_string;
} xmlrpc_env;

The structure is initialized by a call to -

void xmlrpc_env_init (xmlrpc_env* env);

where the members are unconditionally cleared. Errors can be set using xmlrpc_env_set_fault(...) or xmlrpc_env_set_fault_formatted(...), and cleared (freed) using xmlrpc_env_clean(...).

After a call to any function, it is necessary to check if the fault_occurred member is non-zero. When it is, the fault_code will contain a defined error code, and fault_string will contain string message. It would be normal to abort all further processing when such an error occurs, but that may depend on the type of error.

Several macros have been defined to deal with this "environment" pointer, like XMLRPC_ASSERT_ENV_OK(envP), a simple debugging assertion. See <xmlrpc-c/util.h> for the complete list.

top


xmlrpc_mem_block

This is a resizable chunk of memory. This is mostly used internally, but it is also used by the public API in a few places. The structure fields are intended to be private! And functions are provided to access the 'main' members, a pointer and a size -

To get the size and contents of the xmlrpc_mem_block -

size_t xmlrpc_mem_block_size( blockP );
void * xmlrpc_mem_block_contents( blockP );

A new block is allocated by -

xmlrpc_mem_block * 
xmlrpc_mem_block_new(xmlrpc_env * const env, 
                     size_t       const size);

And to destroy an existing xmlrpc_mem_block, and everything it contains -

void xmlrpc_mem_block_free (xmlrpc_mem_block* const block);

These are also available in macro form -

XMLRPC_MEMBLOCK_NEW(type,env,size)
XMLRPC_MEMBLOCK_FREE(type,block)  

There are several other functions and macros to add, or append to the contents of a xmlrpc_mem_block. Because size is an integral component of a "memory block" the data can be any form, certainly not only strings.

top


xmlrpc_value

This object contains an XMLRPC value (of any type). The present 'types', enumerated in <xmlrpc-c/base.h>, are as follows, their names being indicative of the 'type' of content.

XMLRPC_TYPE_INT, XMLRPC_TYPE_BOOL, XMLRPC_TYPE_DOUBLE, XMLRPC_TYPE_DATETIME, XMLRPC_TYPE_STRING, XMLRPC_TYPE_BASE64, XMLRPC_TYPE_ARRAY, XMLRPC_TYPE_STRUCT, XMLRPC_TYPE_C_PTR, XMLRPC_TYPE_NIL, XMLRPC_TYPE_I8

The xmlrpc_value is a structure where essentially the members are private, and is always allocated on the stack. Certain types of data are directly stored in a union-ed member, while others, like strings will be stored in an xmlrpc_mem_block member.

Further it contains a reference count member, and will not be deleted until this reference count falls to zero. This reference count is incremented by -
void xmlrpc_INCREF (valueP);
and decrement by -
void xmlrpc_DECREF (valueP);
If there are no more references, this will also free it.

Values, of various types, can be added to an xmlrpc_value, by using 'format' characters. The set of format characters mirrors the various type of data contained.

The set of 'format' characters, and briefly what they represent, is presented below, but the online documentation has more :-

switch (formatChar) {
 case 'i': = xmlrpc_int_new
 case 'b': = xmlrpc_bool_new
 case 'd': = xmlrpc_double_new
 case 's': = getString
 case 'w': = getWideString
 case 't': = xmlrpc_datetime_new_sec
 case '8': = xmlrpc_datetime_new_str
 case '6': = getBase64
 case 'n': = xmlrpc_nil_new
 case 'I': = xmlrpc_i8_new
 case 'p': = xmlrpc_cptr_new
 case 'A': = mkArrayFromVal
 case 'S': = mkStructFromVal
 case 'V': = (xmlrpc_value*) va_arg
 case '(': = getArray
 case '{': = getStruct
 default:  = badCharacter

The function to create and return a new xmlrpc_value is (in <xmlrpc-c/base.h>) :-

xmlrpc_value *
xmlrpc_build_value(xmlrpc_env * const env,
                   const char * const format,
                   ...);

In general either the 'value' or a 'pointer' to the data of the type is passed in. Some data types, like say binary data, which will later be converted to base64 ASCII data for transmission, require that a length parameter also be passed.

And a varied set of values can be created in an array. Here is a code snippet to create an array of an int, a string, and some binary data - it assumes envP, an xmlrpc_env *, has been initialized -

xmlprc_value * valueP =
xmlrpc_build_value(envP,
                   "(is6)",    /* format characters, in an array () */
                   1,          /* the int value */
                   "string"    /* pointer to the string */
                   data_ptr,   /* pointer to binary data */
                   data_len ); /* length of binary data */

Note the actual int value is passed, the pointer to the string, assumed to be a C/C++ type string, thus the length is obtained by using strlen(strP), and of course the binary data must include a length.

There is an additional format character if only portion of the string was required, and this is "s#", or "w#", then a pointer and the length must be passed in.

This will build a xmlprc_value, which, in this case, is actually an array of values.

top


XML stream

To transmit this array, the xmlrpc_value is converted to an XML ASCII stream using :-

For the Client, calling the Server -
xmlrpc_serialize_call( envP, outputP, methodNameP, valueP );

For the Server, responding to the Client -
xmlrpc_serialize_response( envP, outputP, valueP );

This would store the respective XML stream, call stream, or response stream in the xmlrpc_mem_block * outputP ready for HTTP transmission to the other party ...

The XML call stream would look something like -

<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>method.name</methodName>
<params>
<param><value><i4>1</i4></value></param>
<param><value><string>string</string></value></param>
<param><value><base64>
AFNvdW5kIEZvcmdlIDQuNQA=
</base64></value></param>
</params>
</methodCall>

The response stream would look something like -

<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
<params>
<param><value><array><data>
<value><i4>1</i4></value>
<value><string>string</string></value>
<value><base64>
AFNvdW5kIEZvcmdlIDQuNQA=
</base64></value>
</data></array></value></param>
</params>
</methodResponse>

top


Decompose (decode)

The above shows the creation, serialization of an XML data stream. This is dispatched to the 'partner' using HTTP protocol transfer - the 'partner' being the Server in the case of the methodCall made by the Client, or the Client in the case of the methodResponse made by the Server.

The Server

When the Server gets a methodCall, it can decompile the XML stream to obtain the methodName to be used, if it supports such a 'method', and that method can decompile the XML stream to obtain the parameters passed. The server can use the following function for this :-

/* Parse an XML-RPC call. If an error occurs, set a fault and set
** the output variables to NULL.
** The caller is responsible for calling free(*out_method_name) and
** xmlrpc_DECREF(*out_param_array). */
void 
xmlrpc_parse_call(xmlrpc_env *    const envP,
                  const char *    const xmlData,
                  size_t          const xmlDataLen,
                  const char **   const out_method_name,
                  xmlrpc_value ** const out_param_array);

If no error, out_method_name will contain the method name to use, and out_param_array contains the xmlrpc_value *, an array of the parameters. This xmlrpc_value * can be used in the xmlrpc_decompose_value(...) function to retrieve the actual values, using the same format string as the Client used to create this array.

The Client

When the Client gets a methodResponse, it can likewise decompile the XML stream to obtain the parameters returned. The Client can use the following function for this -

void
xmlrpc_parse_response2(xmlrpc_env *    const envP,
                       const char *    const xmlData,
                       size_t          const xmlDataLen,
                       xmlrpc_value ** const out_param_array,
                       int *           const faultCodeP,
                       const char **   const faultStringP);

If no error, out_param_array contains the xmlrpc_value *, an array of parameters. Like the Server, this xmlrpc_value * can be used in the xmlrpc_decompose_value(...) function to retrieve the actual values, using the same format string as the Server used to create this array.

The Parameters and Values

Both the Server and the Client can use the following the get the parameter values, using the same formatting string used to build this xmlrpc_value array.

   int             ret_int = 0;
   char *          ret_stg = 0;
   unsigned char * ret_dat = 0;
   size_t          ret_len = 0;
   xmlrpc_env      env;

   xmlrpc_env_init(&env);

   // finally get it all back
   xmlrpc_decompose_value(&env,
      output_param_array,  // xmlrpc_value * const value,
      "(is6)",             // const char * const format, 
      &ret_int,            // get back the int value
      &ret_stg,            // the string
      &ret_dat,            // binary data, and
      &ret_len );          // binary data length
   if( env.fault_occurred ) {
      //* deal with FAULT */
      xmlrpc_env_clean(&env);
   } else {
      //* use VALUES as desired, and free them when done */
      free(ret_stg);       // free the string buffer
      free(ret_dat);       // free the data buffer
   }

Remember, if a fault, an error of some type occurred, then the returned values are 'undefined'!

top


Server

The xmlrpc library has some high level functions to facilitate creating a Server. This includes setting up the HTTP protocol to received the Client traffic, configuring the Server, and setting up 'methods'. There is a great example encapsulated in one module. It is set up to handle basic authentication, where the username and password are transmitted in base64, which is basically open text, and not really secure, but contains some great notes about setting up the Secure Socket Layer (SSL) and generating a certificate in WIN32. This file, which is part of the source, <examples/xmlrpc_sample_add_server_w32httpsys.c>, is here presented in its entirety -

/* Copyright (C) 2005 by Steven A. Bone, sbone@pobox.com. All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission. 
**  
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
** SUCH DAMAGE. */

/* COMPILATION NOTE:
   Note that the Platform SDK headers and
   link libraries for Windows XP SP2 or newer are required to compile
   xmlrpc-c for this module.  If you are not using this server, it is 
   safe to exclude the xmlrpc_server_w32httpsys.c file from the xmlrpc
   project and these dependencies will not be required.  You can get the 
   latest platform SDK at 
   http://www.microsoft.com/msdownload/platformsdk/sdkupdate/
   Be sure after installation to choose the program to "register the PSDK
   directories with Visual Studio" so the newer headers are found.
*/

#include <string.h>
#include <stdio.h>
#include <xmlrpc-c/base.h>
#include <xmlrpc-c/server.h>
#include <xmlrpc-c/server_w32httpsys.h>


/*  SECURITY NOTE: Using HTTP Authorization

The current implementation of HTTP Authorization in the win32httpsys
server only uses basic Authorization.  This means the userid and password
is sent in clear-text over the network (Technically, it is Base64 encoded,
but this is essentially clear text).  This method is not secure, as it
can be captured and decoded.  The use of HTTP Basic Authorization with SSL
is considered much more secure.  See the note below for configuring SSL
support.
*/


/*  
HOWTO: Configure SSL for the XMLRPC-C Server

To use SSL you need an SSL certificate.  For testing purposes,
it is possible to create a self-signed SSL certificate.  To do so,
you must download the IIS 6.0 Resource Kit tools.  The current
URL to get the download link is http://support.microsoft.com/kb/840671
We will be using the SelfSSL version 1.0 from this toolkit for
this example.  The other tool you will need is httpcfg.exe, which
can be compiled from the sources in the Windows XP SP2 (or newer) Platform SDK,
or downloaded as part of the Windows XP SP2 Support Tools at the following URL:
http://www.microsoft.com/downloads/details.aspx?FamilyID=49ae8576-9bb9-4126-9761-ba8011fabf38&displaylang=en
The last assumption is that this procedure is being done on the machine that is
hosting the XMLRPC-C server application.

1) Make sure that IIS is installed, and you are running at least Windows XP SP2
or Windows Server 2003.  WARNING: This process will replace any existing IIS SSL
certificates currently installed.

2) In a command prompt, navigate to the directory of the
IIS Support Tools where the selfssl program exists (usually 
C:\Program Files\IIS Resources\SelfSSL).  Assuming (as we are for this example)
that we are going to run on port 8443, use the following command line (see the
documentation for all the flags):

selfssl /T /V:365 /P:8443

3) In the Control Panel, Administrative tools, run the Internet Information Services
program.  Drill down to the Default Web Site.  Right-click it and choose Properties.
On the "Web Site" tab, you will notice that the SSL port is now set to 8443.  Change
it back to 443.  On the Directory Security tab, pick "View Certificate".  In the 
"Details" tab, select the "Thumbprint" line.  The edit box below the listbox will
display a series of hex numbers.  Copy these to the clipboard and paste into notepad.
OK yourself out of the IIS program.

4) Remove all the spaces in the hex string so you are left with a string with no spaces.
This is your SSL Thumbprint hash which you will need in the next step.

5) At your command prompt, navigate to the support tools directory (or the location
where you built httpcfg.exe) - usually C:\Program Files\Support Tools.  Run the following
command line, replacing both the brackets and text with your thumbprint hash from step 4 above:

httpcfg.exe set ssl -i 0.0.0.0:8443 -h <replace with thumbprint hash> -g "{2bb50d9c-7f6a-4d6f-873d-5aee7fb43290}" -c "MY" -t "" -n ""

6) You can check the setup by performing a "httpcfg.exe query ssl" if you wish.

7) Modify the example server code below to use SSL.  Set the xmlrpc_server_httpsys_parms.useSSL
to '1' and the xmlrpc_server_httpsys_parms.portNum to be '8443'.  You can test the server by using 
IE to browse to the URL https://127.0.0.1:8443/rpc2.  An error 405 (Resource not allowed) is the 
expected result if everything is working properly.

NOTE: Testing clients with a 'test' or not real SSL certificate involves changing some of the default
code in the client samples, as by default the transports will fail if there are any issues with the
certificate.  The WinInet transport as of 1.2 has a transport-specific setting to allow 
invalid SSL certificates.  See the libxmlrpc_client.html documentation for more details.

NOTE: Failure to follow all the steps listed above correctly will result in no application
errors, event log messages, or HTTP.SYS log messages indicating failure or the cause.  If
anyone can provide information on debugging SSL certificate issues in HTTP.SYS, please
submit to us!
*/


static xmlrpc_value *
sample_add(xmlrpc_env *   const env, 
           xmlrpc_value * const param_array, 
           void *         const user_data ) {

    xmlrpc_int32 x, y, z;

    /* Parse our argument array. */
    xmlrpc_decompose_value(env, param_array, "(ii)", &x, &y);
    if (env->fault_occurred)
        return NULL;

    /* Add our two numbers. */
    z = x + y;

    /* Return our result. */
    return xmlrpc_build_value(env, "i", z);
}

static void handleAuthorization(
      xmlrpc_env * envP,
        char * userid,
        char * password)
{
   if (strcmp(userid,"jrandom")==0 && strcmp(password,"secret")==0)
      return;

   xmlrpc_env_set_fault( envP, XMLRPC_REQUEST_REFUSED_ERROR, 
                    "Username and/or password do not match.");
}

int __cdecl wmain( int argc, wchar_t * argv[])
{
   xmlrpc_server_httpsys_parms serverparm;
    xmlrpc_registry * registryP;
    xmlrpc_env env;

   xmlrpc_env_init(&env);

   registryP = xmlrpc_registry_new(&env);

    xmlrpc_registry_add_method(
        &env, registryP, NULL, "sample.add", &sample_add, NULL);

    wprintf(L"Starting XML-RPC server...\n");

   //Sets the port number we are listening on
   serverparm.portNum=8080;

   //if this is set, we will use the authorization function
   //serverparm.authfn=NULL;
   serverparm.authfn=&handleAuthorization;

   //set the logging level and log file
   serverparm.logLevel=2;
   serverparm.logFile="C:\\httpsysserverlog.txt";

   //set the use of SSL
   serverparm.useSSL=0;

    serverparm.registryP = registryP;

    xmlrpc_server_httpsys(&env, &serverparm, XMLRPC_HSSIZE(authfn));

   wprintf(L"Stopping XML-RPC server...\n");

   xmlrpc_registry_free(registryP);
   xmlrpc_env_clean(&env);

    return 0;
}

Note all this Server does is add two integers, and return the result, which is not very helpful, but shows all the setup, and coding required to build your own Server.

top


Client

Likewise, the xmlrpc library has some high level functions to facilitate creating a Client. This includes setting up the HTTP protocol to received the Server response. There is a great example encapsulated in one module. It is set up to handle basic authentication, where the username and password are transmitted in base64, which is basically open text, and not really secure, but the above Server code contains some great notes about setting up the Secure Socket Layer (SSL) and generating a certificate in WIN32. The file, which is part of the source, <examples/auth_client.c> is here presented in its entirety :-

/* A demonstration of using HTTP basic authentication with XML-RPC.
**
** In general, you shouldn't write XML-RPC servers which require basic
** authenticaion. (Few XML-RPC clients are capable of it, and it's not part of
** any standard.) Instead, you should pass any authentication information
** as a regular XML-RPC parameter (or look into using SSL).
**
** But certain XML-RPC servers, including Zope, rely heavily on HTTP
** basic authentication. Here's how to talk to them. */

#include <stdlib.h>
#include <stdio.h>

#include <xmlrpc-c/base.h>
#include <xmlrpc-c/client.h>

#include "config.h"  /* information about this build environment */

#define NAME       "XML-RPC C Auth Client"
#define VERSION    "1.0"
#define SERVER_URL "http://localhost:8080/RPC2"



static void
die_if_fault_occurred(xmlrpc_env * const envP) {
    if (envP->fault_occurred) {
        fprintf(stderr, "XML-RPC Fault: %s (%d)\n",
                envP->fault_string, envP->fault_code);
        exit(1);
    }
}



int 
main(int           const argc, 
     const char ** const argv ATTR_UNUSED) {

    xmlrpc_env env;
    xmlrpc_server_info * serverP;
    xmlrpc_value * resultP;
    xmlrpc_int sum;
    
    if (argc-1 > 0) {
        fprintf(stderr, "There are no arguments.  You specified %d", argc-1);
        exit(1);
    }

    /* Start up our XML-RPC client library. */
    xmlrpc_client_init(XMLRPC_CLIENT_NO_FLAGS, NAME, VERSION);
    xmlrpc_env_init(&env);

    /* Make a new object to represent our XML-RPC server. */
    serverP = xmlrpc_server_info_new(&env, SERVER_URL);
    die_if_fault_occurred(&env);

    /* Set up our authentication information. */
    xmlrpc_server_info_set_basic_auth(&env, serverP, "jrandom", "secret");
    die_if_fault_occurred(&env);

    resultP = 
        xmlrpc_client_call_server(
            &env, serverP, "sample.add", "(ii)", 
            (xmlrpc_int32) 5, (xmlrpc_int32) 7);
    die_if_fault_occurred(&env);

    /* Dispose of our server object. */
    xmlrpc_server_info_free(serverP);
    
    /* Get the authentication information and print it out. */
    xmlrpc_read_int(&env, resultP, &sum);
    die_if_fault_occurred(&env);
    printf("The sum is %d\n", sum);
    
    /* Dispose of our result value. */
    xmlrpc_DECREF(resultP);

    /* Shut down our XML-RPC client library. */
    xmlrpc_env_clean(&env);
    xmlrpc_client_cleanup();

    return 0;
}

 

top


***TBD*** ??? What else?

To be completed .... ??? under construction

As indicated in the preamble, this document in no way attempts to be complete. For that you need to read, and understand the online xmlrpc-c and interface documentation. This document came about as I was building and testing the FGCOM project, which uses XMLRPC to transmit and receive the voice traffic.

EOF - xmlrpc-c-doc01.doc

top


checked by tidy  Valid HTML 4.01 Transitional