Web Service - XML-RPC

Content:

  1. Introduction
  2. XML-RPC
  3. Example
  4. Server
  5. Client
  6. Conclusion

Introduction:

Web services are common in most applications today.

Web services provide synchroneous request-response style integration between applications in a convenient way:

Any developer need to have a basic understanding of web services.

XML-RPC web services are rare today, but they could still be relevant to know about.

XML-RPC:

Web services exist in two types:

RPC style
RPC (Remote Procedure Call) style mean that the web service expose operations with arguments and return values.
RESTful style
REST (Representational state transfer) style mean that the web service expose CRUD for resources.

This article will cover RPC style XML-RPC web services.

For RPC style SOAP web services read here.

For RESTful style web services read here.

If asynchroneous integration is required then look at message queues. Read more here.

XML-RPC is a RPC style web service standard.

It was first released in 1998 and evolved into SOAP in 1999-2003.

It was never widely adopted as first SOAP RPC style and later RESTful style web services took over.

But the standard still exists and there are good libraries for most languages. So if one need/prefer RPC style and SOAP is considered too heavy, then XML-RPC is an interesting possibility.

SOAP certainly comes with a bunch of baggage WSDL, UDDI, WS-Security, WS-Addressing, WS-Coordination, WS-Transaction etc. that takes time to understand. SOAP wire XML format is also way more complex than XMP-RPC wire XML format. So if the preference is simple then XML-RPC may be better than SOAP.

Example:

We will look at an example with the following calls:

The focus will not on a realtsic example but more about showing how to get data and get data out. As this is the real issue in some libraries.

Most of the servers will be embedded in application - not deployed on general web server. As I believe that would be the most common scenario for XML-RPC today.

Server:

For Java we use Apache XML-RPC. I believe that is the de facto standard implementation.

package test.common;

public class Data {
    private int ival;
    private String sval;
    public Data() {
        this(0, "");
    }
    public Data(int ival, String sval) {
        super();
        this.ival = ival;
        this.sval = sval;
    }
    public int getIval() {
        return ival;
    }
    public void setIval(int ival) {
        this.ival = ival;
    }
    public String getSval() {
        return sval;
    }
    public void setSval(String sval) {
        this.sval = sval;
    }
    @Override
    public String toString() {
        return String.format("(%d,%s)", ival, sval);
    }
}
package test.common;

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class Util {
    public static <T> Map<String,Object> toMap(T o) {
        try {
            Map<String,Object> res = new HashMap<String,Object>();
            for(PropertyDescriptor pd : Introspector.getBeanInfo(o.getClass()).getPropertyDescriptors()) {
                Method getter = pd.getReadMethod();
                if(pd.getWriteMethod() != null) {
                    res.put(pd.getName(), getter.invoke(o));
                }
            }
            return res;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    public static <T> T fromMap(Map<String,Object> m, Class<T> clz) {
        try {
            T res = clz.newInstance();
            for(PropertyDescriptor pd : Introspector.getBeanInfo(clz).getPropertyDescriptors()) {
                Method setter = pd.getWriteMethod();
                Object o = m.get(pd.getName());
                if(o != null) {
                    setter.invoke(res, o);
                }
            }
            return res;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
package test.server;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import test.common.Data;
import test.common.Util;

public class Test {
    public int getInt() {
        return 123;
    }
    public String getString() {
        return "ABC";
    }
    public Map<String,Object> getData() {
        return Util.toMap(new Data(123, "ABC"));
    }
    public List<Integer> getListOfInts() {
        ArrayList<Integer> res = new ArrayList<Integer>();
        res.add(123);
        res.add(456);
        return res;
    }
    public List<String> getListOfStrings() {
        ArrayList<String> res = new ArrayList<String>();
        res.add("ABC");
        res.add("DEF");
        return res;
    }
    public List<Map<String,Object>> getListOfData() {
        ArrayList<Map<String,Object>> res = new ArrayList<Map<String,Object>>();
        res.add(Util.toMap(new Data(123, "ABC")));
        res.add(Util.toMap(new Data(456, "DEF")));
        return res;
    }
    public int add(int a, int b) {
        return a + b;
    }
    public String concat(String a, String b) {
        return a + b;
    }
    public Map<String,Object> modify(Map<String,Object> m) {
        Data o = Util.fromMap(m, Data.class);
        Data o2 = new Data(o.getIval() + 1, o.getSval() + "X");
        return Util.toMap(o2);
    }
}

And to define the proper servlet a WEB-INF/web.xml:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <servlet>
        <servlet-name>XmlRpcServlet</servlet-name>
        <servlet-class>org.apache.xmlrpc.webserver.XmlRpcServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>XmlRpcServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

For Java we use Apache XML-RPC. I believe that is the de facto standard implementation.

package test.common;

public class Data {
    private int ival;
    private String sval;
    public Data() {
        this(0, "");
    }
    public Data(int ival, String sval) {
        super();
        this.ival = ival;
        this.sval = sval;
    }
    public int getIval() {
        return ival;
    }
    public void setIval(int ival) {
        this.ival = ival;
    }
    public String getSval() {
        return sval;
    }
    public void setSval(String sval) {
        this.sval = sval;
    }
    @Override
    public String toString() {
        return String.format("(%d,%s)", ival, sval);
    }
}
package test.common;

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class Util {
    public static <T> Map<String,Object> toMap(T o) {
        try {
            Map<String,Object> res = new HashMap<String,Object>();
            for(PropertyDescriptor pd : Introspector.getBeanInfo(o.getClass()).getPropertyDescriptors()) {
                Method getter = pd.getReadMethod();
                if(pd.getWriteMethod() != null) {
                    res.put(pd.getName(), getter.invoke(o));
                }
            }
            return res;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    public static <T> T fromMap(Map<String,Object> m, Class<T> clz) {
        try {
            T res = clz.newInstance();
            for(PropertyDescriptor pd : Introspector.getBeanInfo(clz).getPropertyDescriptors()) {
                Method setter = pd.getWriteMethod();
                Object o = m.get(pd.getName());
                if(o != null) {
                    setter.invoke(res, o);
                }
            }
            return res;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
package test.server;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import test.common.Data;
import test.common.Util;

public class Test {
    public int getInt() {
        return 123;
    }
    public String getString() {
        return "ABC";
    }
    public Map<String,Object> getData() {
        return Util.toMap(new Data(123, "ABC"));
    }
    public List<Integer> getListOfInts() {
        ArrayList<Integer> res = new ArrayList<Integer>();
        res.add(123);
        res.add(456);
        return res;
    }
    public List<String> getListOfStrings() {
        ArrayList<String> res = new ArrayList<String>();
        res.add("ABC");
        res.add("DEF");
        return res;
    }
    public List<Map<String,Object>> getListOfData() {
        ArrayList<Map<String,Object>> res = new ArrayList<Map<String,Object>>();
        res.add(Util.toMap(new Data(123, "ABC")));
        res.add(Util.toMap(new Data(456, "DEF")));
        return res;
    }
    public int add(int a, int b) {
        return a + b;
    }
    public String concat(String a, String b) {
        return a + b;
    }
    public Map<String,Object> modify(Map<String,Object> m) {
        Data o = Util.fromMap(m, Data.class);
        Data o2 = new Data(o.getIval() + 1, o.getSval() + "X");
        return Util.toMap(o2);
    }
}
package test.server;

import org.apache.xmlrpc.server.PropertyHandlerMapping;
import org.apache.xmlrpc.server.XmlRpcServer;
import org.apache.xmlrpc.webserver.WebServer;

public class TestServer {
    public static void main(String[] args) throws Exception {
        WebServer srv = new WebServer(8001);
        XmlRpcServer xmlrpc = srv.getXmlRpcServer();
        srv.start();
        PropertyHandlerMapping phm = new PropertyHandlerMapping();
        phm.addHandler("Test", Test.class);
        xmlrpc.setHandlerMapping(phm);
        System.out.print("Press enter to exit");
        System.in.read();
        srv.shutdown();
    }
}

Python comes with XML-RPC support, but the module name is different in 2.x and 3.x.

import xmlrpc.client
from xmlrpc.server import SimpleXMLRPCServer

def getInt():
    return 123

def getString():
    return 'ABC'
    
def getData():
    return {'ival': 123, 'sval': 'ABC'}

def getListOfInts():
    return [ 123, 456 ]

def getListOfStrings():
    return [ 'ABC', 'DEF' ]
    
def getListOfData():
    return [ {'ival': 123, 'sval': 'ABC'}, {'ival': 456, 'sval': 'DEF'} ]

def add(a, b):
    return a + b

def concat(a, b):
    return a + b

def modify(o):
    return { 'ival': o['ival'] + 1, 'sval': o['sval'] + 'X' }

server = SimpleXMLRPCServer(('localhost', 8002))
server.register_function(getInt, 'Test.getInt')
server.register_function(getString, 'Test.getString')
server.register_function(getData, 'Test.getData')
server.register_function(getListOfInts, 'Test.getListOfInts')
server.register_function(getListOfStrings, 'Test.getListOfStrings')
server.register_function(getListOfData, 'Test.getListOfData')
server.register_function(add, 'Test.add')
server.register_function(concat, 'Test.concat')
server.register_function(modify, 'Test.modify')
server.serve_forever()

Python comes with XML-RPC support, but the module name is different in 2.x and 3.x.

import xmlrpclib
from SimpleXMLRPCServer import SimpleXMLRPCServer

def getInt():
    return 123

def getString():
    return 'ABC'
    
def getData():
    return {'ival': 123, 'sval': 'ABC'}

def getListOfInts():
    return [ 123, 456 ]

def getListOfStrings():
    return [ 'ABC', 'DEF' ]
    
def getListOfData():
    return [ {'ival': 123, 'sval': 'ABC'}, {'ival': 456, 'sval': 'DEF'} ]

def add(a, b):
    return a + b

def concat(a, b):
    return a + b

def modify(o):
    return { 'ival': o['ival'] + 1, 'sval': o['sval'] + 'X' }

server = SimpleXMLRPCServer(('localhost', 8003))
server.register_function(getInt, 'Test.getInt')
server.register_function(getString, 'Test.getString')
server.register_function(getData, 'Test.getData')
server.register_function(getListOfInts, 'Test.getListOfInts')
server.register_function(getListOfStrings, 'Test.getListOfStrings')
server.register_function(getListOfData, 'Test.getListOfData')
server.register_function(add, 'Test.add')
server.register_function(concat, 'Test.concat')
server.register_function(modify, 'Test.modify')
server.serve_forever()

libxmlrpc provides a C API and a C++ API and is a widely used implementation.

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

#include <xmlrpc.h>
#include <xmlrpc_server.h>
#include <xmlrpc-c/server_abyss.h>

struct data
{
    int ival;
    const char *sval;
};

xmlrpc_value *getInt(xmlrpc_env *env, xmlrpc_value *params, void *ctx)
{
    int v;
    v = 123;
    return xmlrpc_int_new(env, v);
}

xmlrpc_value *getString(xmlrpc_env *env, xmlrpc_value *params, void *ctx)
{
    char *s;
    s = "ABC";
    return xmlrpc_string_new(env, s);
}

xmlrpc_value *getData(xmlrpc_env *env, xmlrpc_value *params, void *ctx)
{
    struct data o;
    xmlrpc_value *obj;
    o.ival = 123;
    o.sval = "ABC";
    obj = xmlrpc_struct_new(env);
    xmlrpc_struct_set_value(env, obj, "ival", xmlrpc_int_new(env, o.ival));
    xmlrpc_struct_set_value(env, obj, "sval", xmlrpc_string_new(env, o.sval));
    return obj;
}

xmlrpc_value *getListOfInts(xmlrpc_env *env, xmlrpc_value *params, void *ctx)
{
    xmlrpc_value *a;
    a = xmlrpc_array_new(env);
    xmlrpc_array_append_item(env, a, xmlrpc_int_new(env, 123));
    xmlrpc_array_append_item(env, a, xmlrpc_int_new(env, 456));
    return a;
}

xmlrpc_value *getListOfStrings(xmlrpc_env *env, xmlrpc_value *params, void *ctx)
{
    xmlrpc_value *a;
    a = xmlrpc_array_new(env);
    xmlrpc_array_append_item(env, a, xmlrpc_string_new(env, "ABC"));
    xmlrpc_array_append_item(env, a, xmlrpc_string_new(env, "DEF"));
    return a;
}

xmlrpc_value *getListOfData(xmlrpc_env *env, xmlrpc_value *params, void *ctx)
{
    xmlrpc_value *a;
    struct data o;
    xmlrpc_value *obj;
    a = xmlrpc_array_new(env);
    o.ival = 123;
    o.sval = "ABC";
    obj = xmlrpc_struct_new(env);
    xmlrpc_struct_set_value(env, obj, "ival", xmlrpc_int_new(env, o.ival));
    xmlrpc_struct_set_value(env, obj, "sval", xmlrpc_string_new(env, o.sval));
    xmlrpc_array_append_item(env, a, obj);
    o.ival = 456;
    o.sval = "DEF";
    obj = xmlrpc_struct_new(env);
    xmlrpc_struct_set_value(env, obj, "ival", xmlrpc_int_new(env, o.ival));
    xmlrpc_struct_set_value(env, obj, "sval", xmlrpc_string_new(env, o.sval));
    xmlrpc_array_append_item(env, a, obj);
    return a;
}

xmlrpc_value *add(xmlrpc_env *env, xmlrpc_value *params, void *ctx)
{
    int v1, v2, v3;
    xmlrpc_parse_value(env, params, "(ii)", &v1, &v2);
    v3 = v1 + v2;
    return xmlrpc_int_new(env, v3);
}

xmlrpc_value *concat(xmlrpc_env *env, xmlrpc_value *params, void *ctx)
{
    char *s1, *s2, *s3;
    xmlrpc_parse_value(env, params, "(ss)", &s1, &s2);
    s3 = malloc(strlen(s1) + strlen(s2) + 1);
    strcpy(s3, s1);
    strcat(s3, s2);
    return xmlrpc_string_new(env, s3);
}

xmlrpc_value *modify(xmlrpc_env *env, xmlrpc_value *params, void *ctx)
{
    struct data o;
    char *temp;
    xmlrpc_value *res, *resival, *ressval, *obj;
    xmlrpc_parse_value(env, params, "(S)", &res);
    xmlrpc_struct_find_value(env, res, "ival", &resival);
    xmlrpc_read_int(env, resival, &o.ival);
    xmlrpc_struct_find_value(env, res, "sval", &ressval);
    xmlrpc_read_string(env, ressval, &o.sval);
    o.ival = o.ival + 1;
    temp = malloc(strlen(o.sval) + 1 + 1);
    strcpy(temp, o.sval);
    strcat(temp, "X");
    o.sval = temp;
    obj = xmlrpc_struct_new(env);
    xmlrpc_struct_set_value(env, obj, "ival", xmlrpc_int_new(env, o.ival));
    xmlrpc_struct_set_value(env, obj, "sval", xmlrpc_string_new(env, o.sval));
    free(temp);
    return obj;
}

int main()
{
    xmlrpc_server_abyss_parms abyss;
    xmlrpc_registry *reg;
    xmlrpc_env env;
    xmlrpc_env_init(&env);
    reg = xmlrpc_registry_new(&env);
    xmlrpc_registry_add_method(&env, reg, NULL, "Test.getInt", &getInt, NULL);
    xmlrpc_registry_add_method(&env, reg, NULL, "Test.getString", &getString, NULL);
    xmlrpc_registry_add_method(&env, reg, NULL, "Test.getData", &getData, NULL);
    xmlrpc_registry_add_method(&env, reg, NULL, "Test.getListOfInts", &getListOfInts, NULL);
    xmlrpc_registry_add_method(&env, reg, NULL, "Test.getListOfStrings", &getListOfStrings, NULL);
    xmlrpc_registry_add_method(&env, reg, NULL, "Test.getListOfData", &getListOfData, NULL);
    xmlrpc_registry_add_method(&env, reg, NULL, "Test.add", &add, NULL);
    xmlrpc_registry_add_method(&env, reg, NULL, "Test.concat", &concat, NULL);
    xmlrpc_registry_add_method(&env, reg, NULL, "Test.modify", &modify, NULL);
    memset(&abyss, 0, sizeof(abyss));
    abyss.registryP = reg;
    abyss.port_number = 8004;
    abyss.uri_path = "/";
    xmlrpc_server_abyss(&env, &abyss, sizeof(abyss));
    return 0;
}
gcc server.c -o server -lxmlrpc -lxmlrpc_server -lxmlrpc_util -lxmlrpc_abyss -lxmlrpc_server_abyss
./server

libxmlrpc provides a C API and a C++ API and is a widely used implementation.

#include <iostream>
#include <string>

using namespace std;

#include <xmlrpc-c/base.hpp>
#include <xmlrpc-c/registry.hpp>
#include <xmlrpc-c/server_abyss.hpp>

using namespace xmlrpc_c;

struct data
{
    int ival;
    string sval;
};

class getInt : public method
{
public:
    void execute(paramList const& param, value* const retval)
    {
        *retval = value_int(123);
    }
};

class getString : public method
{
public:
    void execute(paramList const& param, value* const retval)
    {
        *retval = value_string("ABC");
    }
};

class getData : public method
{
public:
    void execute(paramList const& param, value* const retval)
    {
        cstruct obj;
        obj.insert(pair<string,value>("ival", value_int(123)));
        obj.insert(pair<string,value>("sval", value_string("ABC")));
        *retval = value_struct(obj);
    }
};

class getListOfInts : public method
{
public:
    void execute(paramList const& param, value* const retval)
    {
        carray a;
        a.push_back(value_int(123));
        a.push_back(value_int(456));
        *retval = value_array(a);
    }
};

class getListOfStrings : public method
{
public:
    void execute(paramList const& param, value* const retval)
    {
        carray a;
        a.push_back(value_string("ABC"));
        a.push_back(value_string("DEF"));
        *retval = value_array(a);
    }
};

class getListOfData : public method
{
public:
    void execute(paramList const& param, value* const retval)
    {
        carray a;
        cstruct obj;
        struct data o;
        o.ival = 123;
        o.sval = "ABC";
        obj.insert(pair<string,value>("ival", value_int(o.ival)));
        obj.insert(pair<string,value>("sval", value_string(o.sval)));
        a.push_back(value_struct(obj));
        obj.clear();
        o.ival = 456;
        o.sval = "DEF";
        obj.insert(pair<string,value>("ival", value_int(o.ival)));
        obj.insert(pair<string,value>("sval", value_string(o.sval)));
        a.push_back(value_struct(obj));
        *retval = value_array(a);
    }
};

class add : public method
{
public:
    void execute(paramList const& param, value* const retval)
    {
        int v1 = param.getInt(0);
        int v2 = param.getInt(1);
        int v3 = v1 + v2;
        *retval = value_int(v3);
    }
};

class concat : public method
{
public:
    void execute(paramList const& param, value* const retval)
    {
        string s1 = param.getString(0);
        string s2 = param.getString(1);
        string s3 = s1 + s2;
        *retval = value_string(s3);
    }
};

class modify : public method
{
public:
    void execute(paramList const& param, value* const retval)
    {
        cstruct o = param.getStruct(0);
        struct data d;
        d.ival = ((value_int)o["ival"]).cvalue();
        d.sval = ((value_string)o["sval"]).cvalue();
        d.ival = d.ival + 1;
        d.sval = d.sval + "X";
        cstruct obj;
        obj.insert(pair<string,value>("ival", value_int(d.ival)));
        obj.insert(pair<string,value>("sval", value_string(d.sval)));
        *retval = value_struct(obj);
    }
};

int main()
{
    registry reg;
    reg.addMethod("Test.getInt", methodPtr(new getInt()));
    reg.addMethod("Test.getString", methodPtr(new getString()));
    reg.addMethod("Test.getData", methodPtr(new getData()));
    reg.addMethod("Test.getListOfInts", methodPtr(new getListOfInts()));
    reg.addMethod("Test.getListOfStrings", methodPtr(new getListOfStrings()));
    reg.addMethod("Test.getListOfData", methodPtr(new getListOfData()));
    reg.addMethod("Test.add", methodPtr(new add()));
    reg.addMethod("Test.concat", methodPtr(new concat()));
    reg.addMethod("Test.modify", methodPtr(new modify()));
    serverAbyss abyss(serverAbyss::constrOpt().registryPtr(&reg).portNumber(8005).uriPath("/"));
    abyss.run();
    return 0;
}
g++ server.cpp -o server -lxmlrpc++ -lxmlrpc_server++ -lxmlrpc_server_abyss++
./server

Client:

For Java we use Apache XML-RPC. I believe that is the de facto standard implementation.

package test.common;

public class Data {
    private int ival;
    private String sval;
    public Data() {
        this(0, "");
    }
    public Data(int ival, String sval) {
        super();
        this.ival = ival;
        this.sval = sval;
    }
    public int getIval() {
        return ival;
    }
    public void setIval(int ival) {
        this.ival = ival;
    }
    public String getSval() {
        return sval;
    }
    public void setSval(String sval) {
        this.sval = sval;
    }
    @Override
    public String toString() {
        return String.format("(%d,%s)", ival, sval);
    }
}
package test.common;

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class Util {
    public static <T> Map<String,Object> toMap(T o) {
        try {
            Map<String,Object> res = new HashMap<String,Object>();
            for(PropertyDescriptor pd : Introspector.getBeanInfo(o.getClass()).getPropertyDescriptors()) {
                Method getter = pd.getReadMethod();
                if(pd.getWriteMethod() != null) {
                    res.put(pd.getName(), getter.invoke(o));
                }
            }
            return res;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    public static <T> T fromMap(Map<String,Object> m, Class<T> clz) {
        try {
            T res = clz.newInstance();
            for(PropertyDescriptor pd : Introspector.getBeanInfo(clz).getPropertyDescriptors()) {
                Method setter = pd.getWriteMethod();
                Object o = m.get(pd.getName());
                if(o != null) {
                    setter.invoke(res, o);
                }
            }
            return res;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
package test.client;

import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;

import test.common.Data;
import test.common.Util;

public class TestClient {
    private static void test(String urlstr) throws Exception {
        System.out.println(urlstr);
        XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
        config.setServerURL(new URL(urlstr));
        XmlRpcClient client = new XmlRpcClient();
        client.setConfig(config);
        {
            int v = (Integer)client.execute("Test.getInt", new Object[0]);
            System.out.println(v);
        }
        {
            String s = (String)client.execute("Test.getString", new Object[0]);
            System.out.println(s);
        }
        {
            @SuppressWarnings("unchecked")
            Data d = Util.fromMap((Map<String,Object>)client.execute("Test.getData", new Object[0]), Data.class);
            System.out.println(d);
        }
        {
            List<Integer> lst = Arrays.asList((Object[])client.execute("Test.getListOfInts", new Object[0])).stream().map(o -> (Integer)o).collect(Collectors.toList());
            System.out.println(lst);
        }
        {
            List<String> lst = Arrays.asList((Object[])client.execute("Test.getListOfStrings", new Object[0])).stream().map(o -> (String)o).collect(Collectors.toList());
            System.out.println(lst);
        }
        {
            @SuppressWarnings("unchecked")
            List<Data> lst = Arrays.asList((Object[])client.execute("Test.getListOfData", new Object[0])).stream().map(o -> Util.fromMap((Map<String,Object>)o, Data.class)).collect(Collectors.toList());
            System.out.println(lst);
        }
        {
            int v1 = 123;
            int v2 = 456;
            int v3 = (Integer)client.execute("Test.add", new Object[] { v1, v2 });
            System.out.println(v3);
        }
        {
            String s1 = "ABC";
            String s2 = "DEF";
            String s3 = (String)client.execute("Test.concat", new Object[] { s1, s2 });
            System.out.println(s3);
        }
        {
            Data d = new Data(123, "ABC");
            @SuppressWarnings("unchecked")
            Data d2 = Util.fromMap((Map<String,Object>)client.execute("Test.modify", new Object[] { Util.toMap(d) }), Data.class);
            System.out.println(d2);
        }
    }
    public static void main(String[] args) throws Exception {
        test("http://localhost:8080/xmlrpc/");
        test("http://localhost:8001/");
        test("http://localhost:8002/");
        test("http://localhost:8003/");
    }
}

Groovy use same library as Java.

import org.apache.xmlrpc.client.*

def test(urlstr) {
    println(urlstr)
    config = new XmlRpcClientConfigImpl()
    config.setServerURL(new URL(urlstr))
    client = new XmlRpcClient()
    client.setConfig(config)
    v = client.execute("Test.getInt", new Object[0])
    println(v)
    s = (String)client.execute("Test.getString", new Object[0])
    println(s)
    d = client.execute("Test.getData", new Object[0])
    println(d)
    lst = client.execute("Test.getListOfInts", new Object[0])
    println(lst)
    lst = client.execute("Test.getListOfStrings", new Object[0])
    println(lst)
    lst = client.execute("Test.getListOfData", new Object[0])
    println(lst)
    v1 = 123
    v2 = 456
    v3 = client.execute("Test.add", new Object[] { v1, v2 })
    println(v3)
    s1 = "ABC"
    s2 = "DEF"
    s3 = client.execute("Test.concat", new Object[] { s1, s2 })
    println(s3)
    d = [ ival: 123, sval: "ABC" ]
    d2 = client.execute("Test.modify", new Object[] { d })
    println(d2)
}

test("http://localhost:8080/xmlrpc/")
test("http://localhost:8001/")
test("http://localhost:8002/")
test("http://localhost:8003/")

Python comes with XML-RPC support, but the module name is different in 2.x and 3.x.

import xmlrpc.client

def test(urlstr):
    print(urlstr)
    cli = xmlrpc.client.Server(urlstr)
    v = cli.Test.getInt()
    print(v)
    s = cli.Test.getString()
    print(s)
    d = cli.Test.getData()
    print(d)
    lst = cli.Test.getListOfInts()
    print(lst)
    lst = cli.Test.getListOfStrings()
    print(lst)
    lst = cli.Test.getListOfData()
    print(lst)
    v1 = 123
    v2 = 456
    v3 = cli.Test.add(v1, v2)
    print(v3)
    s1 = 'ABC'
    s2 = 'DEF'
    s3 = cli.Test.concat(s1, s2)
    print(s3)
    d = {'ival': 123, 'sval': 'ABC'}
    d2 = cli.Test.modify(d)
    print(d2);

test('http://localhost:8001/')
test('http://localhost:8002/')
test('http://localhost:8003/')

Python comes with XML-RPC support, but the module name is different in 2.x and 3.x.

import xmlrpclib

def test(urlstr):
    print(urlstr)
    cli = xmlrpclib.Server(urlstr)
    v = cli.Test.getInt()
    print(v)
    s = cli.Test.getString()
    print(s)
    d = cli.Test.getData()
    print(d)
    lst = cli.Test.getListOfInts()
    print(lst)
    lst = cli.Test.getListOfStrings()
    print(lst)
    lst = cli.Test.getListOfData()
    print(lst)
    v1 = 123
    v2 = 456
    v3 = cli.Test.add(v1, v2)
    print(v3)
    s1 = 'ABC'
    s2 = 'DEF'
    s3 = cli.Test.concat(s1, s2)
    print(s3)
    d = {'ival': 123, 'sval': 'ABC'}
    d2 = cli.Test.modify(d)
    print(d2);

test('http://localhost:8001/')
test('http://localhost:8002/')
test('http://localhost:8003/')

For .NET we use CookComputing.XmlRPCv2. I believe that is the most common implementation.

using System;
using System.Linq;

using CookComputing.XmlRpc;

namespace Test.Client
{
    public class Data
    {
        public int ival { get; set; }
        public string sval { get; set; }
        public override string ToString()
        {
            return string.Format("({0},{1})",ival, sval);
        }
    }
    public interface ITest : IXmlRpcProxy
    {
        [XmlRpcMethodAttribute("Test.getInt")]
        int GetInt();
        [XmlRpcMethodAttribute("Test.getString")]
        string GetString();
        [XmlRpcMethodAttribute("Test.getData")]
        Data GetData();
        [XmlRpcMethodAttribute("Test.getListOfInts")]
        int[] GetListOfInts();
        [XmlRpcMethodAttribute("Test.getListOfStrings")]
        string[] GetListOfStrings();
        [XmlRpcMethodAttribute("Test.getListOfData")]
        Data[] GetListOfData();
        [XmlRpcMethodAttribute("Test.add")]
        int Add(int a, int b);
        [XmlRpcMethodAttribute("Test.concat")]
        string Concat(string a, string b);
        [XmlRpcMethodAttribute("Test.modify")]
        Data Modify(Data o);
    }
    public class Program
    {
        public static void Dump<T>(T[] a)
        {
            Console.WriteLine("[" + String.Join(",", a.Select(e => e.ToString()).ToArray()) + "]");
        }
        public static void Test(string urlstr)
        {
            Console.WriteLine(urlstr);
            ITest client = XmlRpcProxyGen.Create<ITest>();
            client.Url = urlstr;
            {
                int v = client.GetInt();
                Console.WriteLine(v);
            }
            {
                string s = client.GetString();
                Console.WriteLine(s);
            }
            {
                Data d = client.GetData();
                Console.WriteLine(d);
            }
            {
                int[] lst = client.GetListOfInts();
                Console.WriteLine("[" + String.Join(",", lst) + "]");
            }
            {
                string[] lst = client.GetListOfStrings();
                Console.WriteLine("[" + String.Join(",", lst) + "]");
            }
            {
                Data[] lst = client.GetListOfData();
                Console.WriteLine("[" + String.Join(",", lst.Select(e => e.ToString()).ToArray()) + "]");
            }
            {
                int v1 = 123;
                int v2 = 456;
                int v3 = client.Add(v1, v2);
                Console.WriteLine(v3);
            }
            {
                string s1 = "ABC";
                string s2 = "DEF";
                string s3 = client.Concat(s1, s2);
                Console.WriteLine(s3);
            }
            {
                Data d = new Data { ival = 123, sval = "ABC" };
                Data d2 = client.Modify(d);
                Console.WriteLine(d2);
            }
        }
        public static void Main(string[] args)
        {
            Test("http://localhost:8001/");
            Test("http://localhost:8002/");
            Test("http://localhost:8003/");
        }
    }
}

PHP 5.x and 7.x come with XML-RPC support (it can be installed in 8.x).

<?php

class Client {
    private $urlstr;
    public function __construct($urlstr) {
        $this->urlstr = $urlstr;
    }
    function execute($func, $args) {
       $ctx = stream_context_create(array('http' => array(
            'method' => 'POST',
            'header' => 'Content-Type: text/xml',
            'content' => xmlrpc_encode_request($func, $args)
        )));
       return xmlrpc_decode(file_get_contents($this->urlstr, false, $ctx));
    }
}

function data2string($o) {
    return '(' . $o['ival'] . ',' . $o['sval'] . ')';    
}

function test($urlstr) {
    echo "$urlstr\r\n";
    $cli = new Client($urlstr);
    $v = $cli->execute('Test.getInt', array());
    echo  $v . "\r\n";
    $s = $cli->execute('Test.getString', array());
    echo $s . "\r\n";
    $d = $cli->execute('Test.getData', array());
    echo data2string($d) . "\r\n";
    $lst = $cli->execute('Test.getListOfInts', array());
    echo '[' . implode(',', $lst) . ']' . "\r\n";
    $lst = $cli->execute('Test.getListOfStrings', array());
    echo '[' . implode(',', $lst) . ']' . "\r\n";
    $lst = $cli->execute('Test.getListOfData', array());
    echo '[' . implode(',', array_map(function($elm) { return data2string($elm); }, $lst)) . ']' . "\r\n";
    $v1 = 123;
    $v2 = 456;
    $v3 = $cli->execute('Test.add', array($v1, $v2));
    echo $v3 . "\r\n";
    $s1 = 'ABC';
    $s2 = 'DEF';
    $s3 = $cli->execute('Test.concat', array($s1, $s2));
    echo $s3 . "\r\n";
    $d = array('ival' => 123, 'sval' => 'ABC');
    $d2 = $cli->execute('Test.modify', array($d));
    echo data2string($d2) . "\r\n";
}

test('http://localhost:8001/');
test('http://localhost:8002/');
test('http://localhost:8003/');

?>

libxmlrpc provides a C API and a C++ API and is a widely used implementation.

#include <stdio.h>

#include <xmlrpc.h>
#include <xmlrpc_client.h>

struct data
{
    int ival;
    const char *sval;
};

void test(const char *urlstr)
{
    xmlrpc_env env;
    printf("%s\n", urlstr);
    xmlrpc_env_init(&env);
    xmlrpc_client_init2(&env, XMLRPC_CLIENT_NO_FLAGS, "Demo client", "V1.0", NULL, 0);
    {
        xmlrpc_value *res = xmlrpc_client_call(&env, urlstr, "Test.getInt", "()");
        int v;
        xmlrpc_read_int(&env, res, &v);
        printf("%d\n", v);
        xmlrpc_DECREF(res);
    }
    {
        xmlrpc_value *res = xmlrpc_client_call(&env, urlstr, "Test.getString", "()");
        const char *s;
        xmlrpc_read_string(&env, res, &s);
        printf("%s\n", s);
        xmlrpc_DECREF(res);
    }
    {
        xmlrpc_value *res = xmlrpc_client_call(&env, urlstr, "Test.getData", "()");
        struct data d;
        xmlrpc_value *resival;
        xmlrpc_struct_find_value(&env, res, "ival", &resival);
        xmlrpc_read_int(&env, resival, &d.ival);
        xmlrpc_value *ressval;
        xmlrpc_struct_find_value(&env, res, "sval", &ressval);
        xmlrpc_read_string(&env, ressval, &d.sval);
        printf("(%d,%s)\n", d.ival, d.sval);
        xmlrpc_DECREF(res);
    }
    {
        printf("[");
        xmlrpc_value *res = xmlrpc_client_call(&env, urlstr, "Test.getListOfInts", "()");
        int n = xmlrpc_array_size(&env, res);
        for(int i = 0; i < n; i++)
        {
            if(i > 0) printf(",");
            xmlrpc_value *reselm;
            xmlrpc_array_read_item(&env, res, i, &reselm);
            int v;
            xmlrpc_read_int(&env, reselm, &v);
            printf("%d", v);
        }
        xmlrpc_DECREF(res);
        printf("]\n");
    }
    {
        printf("[");
        xmlrpc_value *res = xmlrpc_client_call(&env, urlstr, "Test.getListOfStrings", "()");
        int n = xmlrpc_array_size(&env, res);
        for(int i = 0; i < n; i++)
        {
            if(i > 0) printf(",");
            xmlrpc_value *reselm;
            xmlrpc_array_read_item(&env, res, i, &reselm);
            const char *s;
            xmlrpc_read_string(&env, reselm, &s);
            printf("%s", s);
        }
        xmlrpc_DECREF(res);
        printf("]\n");
    }
    {
        printf("[");
        xmlrpc_value *res = xmlrpc_client_call(&env, urlstr, "Test.getListOfData", "()");
        int n = xmlrpc_array_size(&env, res);
        for(int i = 0; i < n; i++)
        {
            if(i > 0) printf(",");
            xmlrpc_value *reselm;
            xmlrpc_array_read_item(&env, res, i, &reselm);
            struct data d;
            xmlrpc_value *resival;
            xmlrpc_struct_find_value(&env, reselm, "ival", &resival);
            xmlrpc_read_int(&env, resival, &d.ival);
            xmlrpc_value *ressval;
            xmlrpc_struct_find_value(&env, reselm, "sval", &ressval);
            xmlrpc_read_string(&env, ressval, &d.sval);
            printf("(%d,%s)", d.ival, d.sval);
        }
        xmlrpc_DECREF(res);
        printf("]\n");
    }
    {
        int v1 = 123;
        int v2 = 456;
        xmlrpc_value *res = xmlrpc_client_call(&env, urlstr, "Test.add", "(ii)", v1, v2);
        int v3;
        xmlrpc_read_int(&env, res, &v3);
        printf("%d\n", v3);
        xmlrpc_DECREF(res);
    }
    {
        char *s1 = "ABC";
        char *s2 = "DEF";
        xmlrpc_value *res = xmlrpc_client_call(&env, urlstr, "Test.concat", "(ss)", s1, s2);
        const char *s3;
        xmlrpc_read_string(&env, res, &s3);
        printf("%s\n", s3);
        xmlrpc_DECREF(res);
    }
    {
        xmlrpc_value *o = xmlrpc_struct_new(&env);
        xmlrpc_struct_set_value(&env, o, "ival", xmlrpc_int_new(&env, 123));
        xmlrpc_struct_set_value(&env, o, "sval", xmlrpc_string_new(&env, "ABC"));
        xmlrpc_value *res = xmlrpc_client_call(&env, urlstr, "Test.modify", "(S)", o);
        struct data d;
        xmlrpc_value *resival;
        xmlrpc_struct_find_value(&env, res, "ival", &resival);
        xmlrpc_read_int(&env, resival, &d.ival);
        xmlrpc_value *ressval;
        xmlrpc_struct_find_value(&env, res, "sval", &ressval);
        xmlrpc_read_string(&env, ressval, &d.sval);
        printf("(%d,%s)\n", d.ival, d.sval);
        xmlrpc_DECREF(res);
        xmlrpc_DECREF(o);
    }
    xmlrpc_env_clean(&env);
    xmlrpc_client_cleanup();
}

int main()
{
    test("http://arnepc5:8001/");
    test("http://arnepc5:8002/");
    test("http://arnepc5:8003/");
    test("http://localhost:8004/");
    test("http://localhost:8005/");
    return 0;
}
gcc client.c -o client -lxmlrpc -lxmlrpc_client -lxmlrpc_util -lxmlrpc_xmlparse
./client

libxmlrpc provides a C API and a C++ API and is a widely used implementation.

#include <iostream>
#include <string>

using namespace std;

#include <xmlrpc-c/base.hpp>
#include <xmlrpc-c/client_simple.hpp>

using namespace xmlrpc_c;

struct data
{
    int ival;
    string sval;
};

void test(const string urlstr)
{
    clientSimple cli;
    cout << urlstr << endl;
    {
        value res;
        cli.call(urlstr, string("Test.getInt"), "", &res);
        int v = value_int(res);
        cout << v << endl;
    }
    {
        value res;
        cli.call(urlstr, string("Test.getString"), "", &res);
        string s = value_string(res);
        cout << s << endl;
    }
    {
        value res;
        cli.call(urlstr, string("Test.getData"), "", &res);
        cstruct o = value_struct(res);
        struct data d;
        d.ival = value_int(o["ival"]) ;
        d.sval = value_string(o["sval"]);
        cout << "(" << d.ival << "," << d.sval << ")" << endl;
    }
    {
        value res;        
        cli.call(urlstr, string("Test.getListOfInts"), "", &res);
        carray a = value_array(res).vectorValueValue();
        cout << "[";
        for(carray::iterator it = a.begin(); it != a.end(); ++it)
        {
            if(it != a.begin()) cout << ",";
            int v = value_int(*it); 
            cout << v;
        }
        cout << "]" << endl;
    }
    {
        value res;        
        cli.call(urlstr, string("Test.getListOfStrings"), "", &res);
        carray a = value_array(res).vectorValueValue();
        cout << "[";
        for(carray::iterator it = a.begin(); it != a.end(); ++it)
        {
            if(it != a.begin()) cout << ",";
            string s = value_string(*it); 
            cout << s;
        }
        cout << "]" << endl;
    }
    {
        value res;        
        cli.call(urlstr, string("Test.getListOfData"), "", &res);
        carray a = value_array(res).vectorValueValue();
        cout << "[";
        for(carray::iterator it = a.begin(); it != a.end(); ++it)
        {
            if(it != a.begin()) cout << ",";
            cstruct o = value_struct(*it);
            struct data d;
            d.ival = value_int(o["ival"]) ;
            d.sval = value_string(o["sval"]);
            cout << "(" << d.ival << "," << d.sval << ")";
        }
        cout << "]" << endl;
    }
    {
        int v1 = 123;
        int v2 = 456;
        value res;
        cli.call(urlstr, string("Test.add"), "ii", &res, v1, v2);
        int v3 = value_int(res);
        cout << v3 << endl;
    }
    {
        string s1 = "ABC";
        string s2 = "DEF";
        value res;
        cli.call(urlstr, string("Test.concat"), "ss", &res, s1.c_str(), s2.c_str());
        string s3 = value_string(res);
        cout << s3 << endl;
    }
    /*
    // this does not work
    {
        cstruct o;
        o.insert(pair<string,value>("ival", value_int(123)));
        o.insert(pair<string,value>("sval", value_string("ABC")));
        value res;
        cli.call(urlstr, string("Test.modify"), "S", &res, value_struct(o));
        cstruct o2 = value_struct(res);
        struct data d;
        d.ival = value_int(o2["ival"]) ;
        d.sval = value_string(o2["sval"]);
        cout << "(" << d.ival << "," << d.sval << ")" << endl;
    }
    */
}

int main()
{
    test("http://arnepc5:8001/");
    test("http://arnepc5:8002/");
    test("http://arnepc5:8003/");
    test("http://localhost:8004/");
    test("http://localhost:8005/");
    return 0;
}
g++ client.cpp -o client -lxmlrpc++ -lxmlrpc_client++ 
./client

Conclusion:

It works and it has a good level of compatibility.

My evaluation compared to other web service technologies is:

Article history:

Version Date Description
1.0 January 28th 2024 Initial version

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj