VMS Tech Demo 11 - XML-RPC

Content:

  1. Introduction
  2. Java, Python, PHP etc.
    1. Server
    2. Client
  3. C
    1. Setup
    2. Example
  4. Pascal
    1. Setup
    2. Example
  5. Basic
  6. Direct XML

Introduction:

XML-RPC is a 25 year old protocol that has never been widely used. It was replaced by SOAP in a few years - and SOAP has also faded away since then.

But it is a relative simple protocol and sometimes RPC style is actually a better match than RESTful style for the problems at hand.

For more info on XML-RPC in general see the article Web Service - XML-RPC.

Java, Python, PHP etc.:

The same libraries available on other platforms for these languages can also be used on VMS.

The code below is simply copied from the general XML-RPC article unchanged.

Server:

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;
        }
    }
}
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);
    }
}
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

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);
    }
}
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();
    }
}
$ cp = "/javalib/commons-logging-1_1.jar:/javalib/ws-commons-util-1_0_2.jar:/javalib/xmlrpc-client-3_1_2.jar:/javalib/xmlrpc-common-3_1_2.jar:/javalib/xmlrpc-server-3_1_2.jar"
$ javac -cp 'cp' *.java
$ define/nolog sys$input sys$command
$ java -cp .:'cp' "TestServer"
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 server.py
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', 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 server.py

Client:

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;
        }
    }
}
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);
    }
}
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

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

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);
        {
            @SuppressWarnings("unchecked")
            int v = (Integer)client.execute("Test.getInt", new Object[0]);
            System.out.println(v);
        }
        {
            @SuppressWarnings("unchecked")
            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);
        }
        {
            @SuppressWarnings("unchecked")
            Object[] lst = (Object[])client.execute("Test.getListOfInts", new Object[0]);
            System.out.print("[");
            boolean first = true;
            for(Object o : lst) {
                if(first) first = false; else System.out.print(",");
                int v = (Integer)o;
                System.out.print(v);
            }
            System.out.println("]");
        }
        {
            @SuppressWarnings("unchecked")
            Object[] lst = (Object[])client.execute("Test.getListOfStrings", new Object[0]);
            System.out.print("[");
            boolean first = true;
            for(Object o : lst) {
                if(first) first = false; else System.out.print(",");
                String s = (String)o;
                System.out.print(s);
            }
            System.out.println("]");
        }
        {
            @SuppressWarnings("unchecked")
            Object[] lst = (Object[])client.execute("Test.getListOfData", new Object[0]);
            System.out.print("[");
            boolean first = true;
            for(Object o : lst) {
                if(first) first = false; else System.out.print(",");
                @SuppressWarnings("unchecked")
                Data obj = Util.fromMap((Map<String,Object>)o, Data.class);
                System.out.print(obj);
            }
            System.out.println("]");
        }
        {
            int v1 = 123;
            int v2 = 456;
            @SuppressWarnings("unchecked")
            int v3 = (Integer)client.execute("Test.add", new Object[] { v1, v2 });
            System.out.println(v3);
        }
        {
            String s1 = "ABC";
            String s2 = "DEF";
            @SuppressWarnings("unchecked")
            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:8001/");
        test("http://localhost:8002/");
    }
}
$ cp = "/javalib/commons-logging-1_1.jar:/javalib/ws-commons-util-1_0_2.jar:/javalib/xmlrpc-client-3_1_2.jar:/javalib/xmlrpc-common-3_1_2.jar:/javalib/xmlrpc-server-3_1_2.jar"
$ javac -cp 'cp' *.java
$ java -cp .:'cp' "TestClient"
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:8001/")
test("http://localhost:8002/")
$ groovy_cp = "/javalib/commons-logging-1_1.jar:/javalib/ws-commons-util-1_0_2.jar:/javalib/xmlrpc-client-3_1_2.jar:/javalib/xmlrpc-common-3_1_2.jar:/javalib/xmlrpc-server-3_1_2.jar"
$ groovy client.groovy
import xmlrpc.client

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/')
$ python client.py
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/')
$ python client.py
<?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/');

?>
$ php client.php

C:

There exist an excellent XML-RPC C/C++ library called libxmlrpc.

But it is not available for VMS and porting it seemed like a huge effort. So I came up with a simple library of my own instead.

This library is not as robust as libxmlrpc and I am sure that there are a lot of stuff that will not work. Among potential problems then it does not use a real XML parser but instead use strstr function to process response messages. Evaluate carefully before using this library in production. It does work for the examples provided communication with Java server and Python server.

Setup:

Required:

Stack:

XMLRPC stack (C)

Example:

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

#include "xmlrpc_format.h"
#include "vms_http.h"
#include "xmlrpc_parse.h"

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

void test(const char *host, int port)
{
    printf("http://%s:%d/\n", host, port);
    {
        char req[1000];
        xmlrpc_request(req, sizeof(req), "Test.getInt", 0);
        struct vms_http *ctx = http_post(host, port, "/", "text/xml", "text/xml", req);
        char resp[10000];
        short resplen;
        http_recv_all(ctx, resp, sizeof(resp), &resplen);
        resp[resplen] = 0;
        http_close(ctx);
        int v; 
        xmlrpc_response(resp, &v);
        printf("%d\n", v);
    }
    {
        char req[1000];
        xmlrpc_request(req, sizeof(req), "Test.getString", 0);
        struct vms_http *ctx = http_post(host, port, "/", "text/xml", "text/xml", req);
        char resp[10000];
        short resplen;
        http_recv_all(ctx, resp, sizeof(resp), &resplen);
        resp[resplen] = 0;
        http_close(ctx);
        char *s;
        xmlrpc_response(resp, &s);
        printf("%s\n", s);
        free(s);
    }
    {
        char req[1000];
        xmlrpc_request(req, sizeof(req), "Test.getData", 0);
        struct vms_http *ctx = http_post(host, port, "/", "text/xml", "text/xml", req);
        char resp[10000];
        short resplen;
        http_recv_all(ctx, resp, sizeof(resp), &resplen);
        resp[resplen] = 0;
        http_close(ctx);
        struct data d;
        struct xmlrpc_struct_i4_or_str gas[2];
        xmlrpc_response(resp, gas);
        for(int i = 0; i < 2; i++)
        {
            if(strcmp(gas[i].name, "ival") == 0)
            {
                d.ival = gas[i].value.i4;
            }
            if(strcmp(gas[i].name, "sval") == 0)
            {
                d.sval = gas[i].value.str;
            }
        }
        printf("(%d,%s)\n", d.ival, d.sval);
        free(d.sval);
    }
    {
        char req[1000];
        xmlrpc_request(req, sizeof(req), "Test.getData", 0);
        struct vms_http *ctx = http_post(host, port, "/", "text/xml", "text/xml", req);
        char resp[10000];
        short resplen;
        http_recv_all(ctx, resp, sizeof(resp), &resplen);
        resp[resplen] = 0;
        http_close(ctx);
        struct data d;
        STRUCT_PARSE(resp, 1000)
        {
            RECFLD(i4, d, ival);
            RECFLD(str, d, sval);
        }
        printf("(%d,%s)\n", d.ival, d.sval);
        free(d.sval);
    }
    {
        char req[1000];
        xmlrpc_request(req, sizeof(req), "Test.getData", 0);
        struct vms_http *ctx = http_post(host, port, "/", "text/xml", "text/xml", req);
        char resp[10000];
        short resplen;
        http_recv_all(ctx, resp, sizeof(resp), &resplen);
        resp[resplen] = 0;
        http_close(ctx);
        struct data d;
        void *stctx = xmlrpc_struct_init(resp, 2);
        d.ival = xmlrpc_struct_unpack_i4(stctx, 0, "ival");
        d.sval = xmlrpc_struct_unpack_string(stctx, 0, "sval");
        xmlrpc_struct_cleanup(stctx);
        printf("(%d,%s)\n", d.ival, d.sval);
        free(d.sval);
    }
    {
        char req[1000];
        xmlrpc_request(req, sizeof(req), "Test.getListOfInts", 0);
        struct vms_http *ctx = http_post(host, port, "/", "text/xml", "text/xml", req);
        char resp[100000];
        short resplen;
        http_recv_all(ctx, resp, sizeof(resp), &resplen);
        resp[resplen] = 0;
        http_close(ctx);
        int a[1000];
        int nelm = xmlrpc_response(resp, a);
        printf("[");
        for(int i = 0; i < nelm; i++) 
        {
            if(i > 0) printf(",");
            printf("%d", a[i]);
        }
        printf("]\n");
    }
    {
        char req[1000];
        xmlrpc_request(req, sizeof(req), "Test.getListOfStrings", 0);
        struct vms_http *ctx = http_post(host, port, "/", "text/xml", "text/xml", req);
        char resp[100000];
        short resplen;
        http_recv_all(ctx, resp, sizeof(resp), &resplen);
        resp[resplen] = 0;
        http_close(ctx);
        char *a[1000];
        int nelm = xmlrpc_response(resp, a);
        printf("[");
        for(int i = 0; i < nelm; i++) 
        {
            if(i > 0) printf(",");
            printf("%s", a[i]);
            free(a[i]);
        }
        printf("]\n");
    }
    {
        char req[1000];
        xmlrpc_request(req, sizeof(req), "Test.getListOfData", 0);
        struct vms_http *ctx = http_post(host, port, "/", "text/xml", "text/xml", req);
        char resp[100000];
        short resplen;
        http_recv_all(ctx, resp, sizeof(resp), &resplen);
        resp[resplen] = 0;
        http_close(ctx);
        struct data da[1000];
        struct xmlrpc_struct_i4_or_str gas[2000];
        int nelm = xmlrpc_response(resp, gas);
        for(int i = 0; i < nelm; i++)
        {
            if(strcmp(gas[i].name, "ival") == 0)
            {
                da[i / 2].ival = gas[i].value.i4;
            }
            if(strcmp(gas[i].name, "sval") == 0)
            {
                da[i / 2].sval = gas[i].value.str;
            }
        }
        printf("[");
        for(int i = 0; i < nelm / 2; i++) 
        {
            if(i > 0) printf(",");
            printf("(%d,%s)", da[i].ival, da[i].sval);
            free(da[i].sval);
        }
        printf("]\n");
    }
    {
        char req[1000];
        xmlrpc_request(req, sizeof(req), "Test.getListOfData", 0);
        struct vms_http *ctx = http_post(host, port, "/", "text/xml", "text/xml", req);
        char resp[100000];
        short resplen;
        http_recv_all(ctx, resp, sizeof(resp), &resplen);
        resp[resplen] = 0;
        http_close(ctx);
        struct data da[1000];
        STRUCT_PARSE(resp, 1000)
        {
            RECFLD(i4, da[i / 2], ival);
            RECFLD(str, da[i / 2], sval);
        }
        printf("[");
        for(int i = 0; i < nitms / 2; i++) 
        {
            if(i > 0) printf(",");
            printf("(%d,%s)", da[i].ival, da[i].sval);
            free(da[i].sval);
        }
        printf("]\n");
    }
    {
        char req[1000];
        xmlrpc_request(req, sizeof(req), "Test.getListOfData", 0);
        struct vms_http *ctx = http_post(host, port, "/", "text/xml", "text/xml", req);
        char resp[100000];
        short resplen;
        http_recv_all(ctx, resp, sizeof(resp), &resplen);
        resp[resplen] = 0;
        http_close(ctx);
        struct data da[1000];
        void *stctx = xmlrpc_struct_init(resp, 2);
        int n = xmlrpc_struct_n(stctx);
        for(int i = 0; i < n; i++)
        {
            da[i].ival = xmlrpc_struct_unpack_i4(stctx, i, "ival");
            da[i].sval = xmlrpc_struct_unpack_string(stctx, i, "sval");
        }
        xmlrpc_struct_cleanup(stctx);
        printf("[");
        for(int i = 0; i < n; i++) 
        {
            if(i > 0) printf(",");
            printf("(%d,%s)", da[i].ival, da[i].sval);
            free(da[i].sval);
        }
        printf("]\n");
    }
    {
        char req[1000];
        int v1 = 123;
        int v2 = 456;
        xmlrpc_request(req, sizeof(req), "Test.add", 2, xmlrpc_i4(v1), xmlrpc_i4(v2));
        struct vms_http *ctx = http_post(host, port, "/", "text/xml", "text/xml", req);
        char resp[10000];
        short resplen;
        http_recv_all(ctx, resp, sizeof(resp), &resplen);
        resp[resplen] = 0;
        http_close(ctx);
        int v3;
        xmlrpc_response(resp, &v3);
        printf("%d\n", v3);
    }
    {
        char req[1000];
        char *s1 = "ABC";
        char *s2 = "DEF";
        xmlrpc_request(req, sizeof(req), "Test.concat", 2, xmlrpc_string(s1), xmlrpc_string(s2));
        struct vms_http *ctx = http_post(host, port, "/", "text/xml", "text/xml", req);
        char resp[10000];
        short resplen;
        http_recv_all(ctx, resp, sizeof(resp), &resplen);
        resp[resplen] = 0;
        http_close(ctx);
        char *s3;
        xmlrpc_response(resp, &s3);
        printf("%s\n", s3);
        free(s3);
    }
    {
        char req[1000];
        struct data d;
        d.ival = 123;
        d.sval = "ABC";
        xmlrpc_request(req, sizeof(req), "Test.modify", 1, xmlrpc_struct(2, "ival", xmlrpc_i4(d.ival), "sval", xmlrpc_string(d.sval)));
        struct vms_http *ctx = http_post(host, port, "/", "text/xml", "text/xml", req);
        char resp[10000];
        short resplen;
        http_recv_all(ctx, resp, sizeof(resp), &resplen);
        resp[resplen] = 0;
        http_close(ctx);
        struct data d2;
        struct xmlrpc_struct_i4_or_str gas[2];
        xmlrpc_response(resp, gas);
        for(int i = 0; i < 2; i++)
        {
            if(strcmp(gas[i].name, "ival") == 0)
            {
                d2.ival = gas[i].value.i4;
            }
            if(strcmp(gas[i].name, "sval") == 0)
            {
                d2.sval = gas[i].value.str;
            }
        }
        printf("(%d,%s)\n", d2.ival, d2.sval);
        free(d2.sval);
    }
    {
        char req[1000];
        struct data d;
        d.ival = 123;
        d.sval = "ABC";
        xmlrpc_request(req, sizeof(req), "Test.modify", 1, xmlrpc_struct(2, "ival", xmlrpc_i4(d.ival), "sval", xmlrpc_string(d.sval)));
        struct vms_http *ctx = http_post(host, port, "/", "text/xml", "text/xml", req);
        char resp[10000];
        short resplen;
        http_recv_all(ctx, resp, sizeof(resp), &resplen);
        resp[resplen] = 0;
        http_close(ctx);
        struct data d2;
        STRUCT_PARSE(resp, 1000)
        {
            RECFLD(i4, d2, ival);
            RECFLD(str, d2, sval);
        }
        printf("(%d,%s)\n", d2.ival, d2.sval);
        free(d2.sval);
    }
    {
        char req[1000];
        struct data d;
        d.ival = 123;
        d.sval = "ABC";
        xmlrpc_request(req, sizeof(req), "Test.modify", 1, xmlrpc_struct(2, "ival", xmlrpc_i4(d.ival), "sval", xmlrpc_string(d.sval)));
        struct vms_http *ctx = http_post(host, port, "/", "text/xml", "text/xml", req);
        char resp[10000];
        short resplen;
        http_recv_all(ctx, resp, sizeof(resp), &resplen);
        resp[resplen] = 0;
        http_close(ctx);
        struct data d2;
        void *stctx = xmlrpc_struct_init(resp, 2);
        d2.ival = xmlrpc_struct_unpack_i4(stctx, 0, "ival");
        d2.sval = xmlrpc_struct_unpack_string(stctx, 0, "sval");
        xmlrpc_struct_cleanup(stctx);
        printf("(%d,%s)\n", d2.ival, d2.sval);
        free(d2.sval);
    }
}

int main()
{
    test("localhost", 8001);
    test("localhost", 8002);
    return 0;
}
$ define/nolog xmlrpcdir disk2:[arne.xmlrpc]
$ cc/incl=xmlrpcdir client
$ link client + xmlrpcdir:xmlrpc/lib
$ run client

Pascal:

For Pascal I created a thin Pascal library on top of the C library to provide a Pascal friendly API.

Setup:

Required:

Stack:

PXMLRPC stack (Pascal)

Example:

[inherit('pxmlrpcdir:common', 'pxmlrpcdir:xmlrpc_format', 'pxmlrpcdir:phttp', 'pxmlrpcdir:xmlrpc_parse')]
program client(input,output);

type
   data = record
             ival : integer;
             sval : pstr;
          end;

procedure test(host : pstr; port : integer);

procedure test_getInt(host : pstr; port : integer);

var
   req : pstr;
   ctx : http;
   resp : pstr;
   v : integer;

begin
   pxmlrpc_request(req, 'Test.getInt');
   ctx := http_post(fix(host), port, '/', 'text/xml', 'text/xml', fix(req));
   http_recv_all(ctx, resp.body, resp.length);
   pxmlrpc_response(resp, iaddress(v));
   writeln(v:1);
end;

procedure test_getString(host : pstr; port : integer);

var
   req : pstr;
   ctx : http;
   resp : pstr;
   s : c_str_t;
   s2 : pstr;

begin
   pxmlrpc_request(req, 'Test.getString');
   ctx := http_post(fix(host), port, '/', 'text/xml', 'text/xml', fix(req));
   http_recv_all(ctx, resp.body, resp.length);
   pxmlrpc_response(resp, iaddress(s));
   s2 := pas_str(s);
   writeln(s2);
   free_c_str(s);
end;

procedure test_getData(host : pstr; port : integer);

var
   req : pstr;
   ctx : http;
   resp : pstr;
   stctx : xmlrpc_struct_t;
   d : data;

begin
   pxmlrpc_request(req, 'Test.getData');
   ctx := http_post(fix(host), port, '/', 'text/xml', 'text/xml', fix(req));
   http_recv_all(ctx, resp.body, resp.length);
   stctx := pxmlrpc_struct_init(resp, 2);
   d.ival := pxmlrpc_struct_unpack_i4(stctx, 0, 'ival');
   d.sval := pxmlrpc_struct_unpack_string(stctx, 0, 'sval');
   pxmlrpc_struct_cleanup(stctx);
   writeln('(', d.ival:1, ',', d.sval, ')');
end;

procedure test_getListOfInts(host : pstr; port : integer);

var
   req : pstr;
   ctx : http;
   resp : pstr;
   a : array [1..1000] of integer;
   n, i : integer;

begin
   pxmlrpc_request(req, 'Test.getListOfInts');
   ctx := http_post(fix(host), port, '/', 'text/xml', 'text/xml', fix(req));
   http_recv_all(ctx, resp.body, resp.length);
   n := pxmlrpc_response(resp, iaddress(a));
   write('[');
   for i := 1 to n do begin
      if i > 1 then write(',');
      write(a[i]:1);
   end;
   writeln(']');
end;

procedure test_getListOfStrings(host : pstr; port : integer);

var
   req : pstr;
   ctx : http;
   resp : pstr;
   a : array [1..1000] of c_str_t;
   a2 : array [1..1000] of pstr;
   n, i : integer;

begin
   pxmlrpc_request(req, 'Test.getListOfStrings');
   ctx := http_post(fix(host), port, '/', 'text/xml', 'text/xml', fix(req));
   http_recv_all(ctx, resp.body, resp.length);
   n := pxmlrpc_response(resp, iaddress(a));
   for i := 1 to n do begin
      a2[i] := pas_str(a[i]);
      free_c_str(a[i]);
   end;
   write('[');
   for i := 1 to n do begin
      if i > 1 then write(',');
      write(a2[i]);
   end;
   writeln(']');
end;

procedure test_getListOfData(host : pstr; port : integer);

var
   req : pstr;
   ctx : http;
   resp : pstr;
   stctx : xmlrpc_struct_t;
   da : array [1..1000] of data;
   n, i : integer;

begin
   pxmlrpc_request(req, 'Test.getListOfData');
   ctx := http_post(fix(host), port, '/', 'text/xml', 'text/xml', fix(req));
   http_recv_all(ctx, resp.body, resp.length);
   stctx := pxmlrpc_struct_init(resp, 2);
   n := pxmlrpc_struct_n(stctx);
   for i := 1 to n do begin
      da[i].ival := pxmlrpc_struct_unpack_i4(stctx, i - 1, 'ival');
      da[i].sval := pxmlrpc_struct_unpack_string(stctx, i - 1, 'sval');
   end;
   pxmlrpc_struct_cleanup(stctx);
   write('[');
   for i := 1 to n do begin
      if i > 1 then write(',');
      write('(', da[i].ival:1, ',', da[i].sval, ')');
   end;
   writeln(']');
end;

procedure test_add(host : pstr; port : integer);

var
   req : pstr;
   ctx : http;
   resp : pstr;
   v1, v2, v3 : integer;

begin
   v1 := 123;
   v2 := 456;
   pxmlrpc_request(req, 'Test.add', pxmlrpc_i4(v1), pxmlrpc_i4(v2));
   ctx := http_post(fix(host), port, '/', 'text/xml', 'text/xml', fix(req));
   http_recv_all(ctx, resp.body, resp.length);
   pxmlrpc_response(resp, iaddress(v3));
   writeln(v3:1);
end;

procedure test_concat(host : pstr; port : integer);

var
   req : pstr;
   ctx : http;
   resp : pstr;
   s : c_str_t;
   s1, s2, s3 : pstr;

begin
   s1 := 'ABC';
   s2 := 'DEF';
   pxmlrpc_request(req, 'Test.concat', pxmlrpc_string(s1), pxmlrpc_string(s2));
   ctx := http_post(fix(host), port, '/', 'text/xml', 'text/xml', fix(req));
   http_recv_all(ctx, resp.body, resp.length);
   pxmlrpc_response(resp, iaddress(s));
   s3 := pas_str(s);
   writeln(s3);
   free_c_str(s);
end;

procedure test_modify(host : pstr; port : integer);

var
   req : pstr;
   ctx : http;
   resp : pstr;
   stctx : xmlrpc_struct_t;
   d, d2 : data;

begin
   d.ival := 123;
   d.sval := 'ABC';
   pxmlrpc_request(req, 'Test.modify', pxmlrpc_struct2(
                                           pxmlrpc_member('ival', pxmlrpc_i4(d.ival)),
                                           pxmlrpc_member('sval', pxmlrpc_string(d.sval))
                                       ));
   ctx := http_post(fix(host), port, '/', 'text/xml', 'text/xml', fix(req));
   http_recv_all(ctx, resp.body, resp.length);
   stctx := pxmlrpc_struct_init(resp, 2);
   d2.ival := pxmlrpc_struct_unpack_i4(stctx, 0, 'ival');
   d2.sval := pxmlrpc_struct_unpack_string(stctx, 0, 'sval');
   pxmlrpc_struct_cleanup(stctx);
   writeln('(', d2.ival:1, ',', d2.sval, ')');
end;

begin
   writeln('http://', host, ':', port:1, '/');
   test_getInt(host, port);
   test_getString(host, port);
   test_getData(host, port);
   test_getListOfInts(host, port);
   test_getListOfStrings(host, port);
   test_getListOfData(host, port);
   test_add(host, port);
   test_concat(host, port);
   test_modify(host, port);
end;

begin
   test('localhost', 8001);
   test('localhost', 8002);
end.
$ define/nolog xmlrpcdir disk2:[arne.xmlrpc]
$ define/nolog pxmlrpcdir disk2:[arne.vmspascal.pxmlrpc]
$ pas client
$ link client + pxmlrpcdir:pxmlrpc/lib + xmlrpcdir:xmlrpc/lib
$ run client

Basic:

If anyone express an interest then I may look into creating a BXMLRPC library for usage from VMS Basic.

Direct XML:

The XML used by XML-RPC is actually reasonable simple (unlike the XML used by SOAP). And it is possible to format and parse the XML directly without any XML-RPC library.

For some VMS examples in C/Pascal/Fortran/Basic see VMS Tech Demo 12 - XML-RPC direct XML.

Article history:

Version Date Description
1.0 February 11th 2024 Initial version

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj