This is the extended version of my system developed in 2012, and published
in
http://ftp.funet.fi/pub/archive/alt.sources/2722.gz (available in Google
at
https://groups.google.com/g/alt.sources/c/QasPxJkKIUs/m/An1JnLooNCcJ )
The network connection and encapsulation of messages is handled by ZeroMQ.
There are examples of clients in: C++, Lua, Octave (clone of Matlab), and
Python.
Please note, that the solution may be easily modifed. E.g., you may replace
MessagePack with JSON.
This post contains sources in shar format. So probably it can't be correctly
loaded from Google archive. I'll send next message with base64-encoded tar.gz
archive.
The code is published as Public Domain or under
Creative Commons Zero v1.0 Universal license
(whichever better suits your needs).
The code is published without any warranty. You use it at your sole risk.
The code has been also anounced on
https://stackoverflow.com/questions/68322232/how-can-i-make-the-functions-provided-by-my-python-program-available-to-programs
The maintained version is available in
https://gitlab.com/WZab/python-versatile-rpc
#!/bin/sh
# This is a shell archive (produced by GNU sharutils 4.15.2).
# To extract the files from this archive, save it to some FILE, remove
# everything before the '#!/bin/sh' line above, then type 'sh FILE'.
#
lock_dir=_sh02850
# Made on 2021-07-11 12:20 CEST by <wzab@WZabHP>.
# Source directory was '/tmp/pyrpc'.
#
# Existing files will *not* be overwritten, unless '-c' is specified.
#
# This shar contains:
# length mode name
# ------ ---------- ------------------------------------------
# 1460 -rw-r--r-- obj_rpc_cli.cc
# 1073 -rwxr-xr-x obj_rpc_cli_simple.py
# 1065 -rw-r--r-- obj_rpc_cli.lua
# 75 -rw-r--r-- build.sh
# 1757 -rwxr-xr-x obj_rpc_srv_simple.py
# 753 -rw-r--r-- obj_rpc_cli.m
# ============= obj_rpc_cli.cc ==============
if test -n "${keep_file}" && test -f 'obj_rpc_cli.cc'
then
${echo} "x - SKIPPING obj_rpc_cli.cc (file already exists)"
else
${echo} "x - extracting obj_rpc_cli.cc (text)"
sed 's/^X//' << 'SHAR_EOF' > 'obj_rpc_cli.cc' &&
// Demonstrator of the communication with simple Python RPC server from C++
// Written by Wojciech M. Zabołotny (wzab01<at>
gmail.com or wzab<at>
ise.pw.edu.pl)
// Copyright: This program is released into the public domain or under
// Creative Commons Zero v1.0 Universal license (whichever better suits your needs).
#include <string>
#include <zmq.hpp>
#include <iostream>
#include <msgpack.hpp>
X
msgpack::object_handle rpc(zmq::socket_t &sock, auto req)
{
X std::size_t offset = 0;
X std::stringstream rstr;
X msgpack::pack(rstr,req);
X zmq::message_t msg(rstr.str());
X sock.send(msg,zmq::send_flags::none);
X auto res = sock.recv(msg, zmq::recv_flags::none);
X auto oh = msgpack::unpack((const char *)msg.data(),msg.size(),offset);
X return oh;
}
X
int main(void) {
X zmq::context_t ctx;
X zmq::socket_t sock(ctx, zmq::socket_type::req);
X sock.connect("tcp://localhost:9999");
X msgpack::object_handle res;
X res = rpc(sock,std::tuple<std::string, std::array<int,2>>({"mult", {7, 8}}));
X std::cout << res.get() << std::endl;
X res = rpc(sock,std::tuple<std::string, std::map<std::string,int>>({"div", {{"a",8},{"b",7}}}));
X std::cout << res.get() << std::endl;
X res = rpc(sock,std::tuple<std::string, std::map<std::string,int>>({"div", {{"b",8},{"a",7}}}));
X std::cout << res.get() << std::endl;
X res = rpc(sock,std::tuple<std::string, std::tuple<std::string>>({ "file", {"/etc/passwd"}}));
X std::cout << res.get() << std::endl;
}
SHAR_EOF
(set 20 21 07 11 12 18 00 'obj_rpc_cli.cc'
eval "${shar_touch}") && \
chmod 0644 'obj_rpc_cli.cc'
if test $? -ne 0
then ${echo} "restore of obj_rpc_cli.cc failed"
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'obj_rpc_cli.cc': 'MD5 check failed'
) << \SHAR_EOF
f2a46090f278d3c4fadea3f90ce7a70b obj_rpc_cli.cc
SHAR_EOF
else
test `LC_ALL=C wc -c < 'obj_rpc_cli.cc'` -ne 1460 && \
${echo} "restoration warning: size of 'obj_rpc_cli.cc' is not 1460"
fi
fi
# ============= obj_rpc_cli_simple.py ==============
if test -n "${keep_file}" && test -f 'obj_rpc_cli_simple.py'
then
${echo} "x - SKIPPING obj_rpc_cli_simple.py (file already exists)"
else
${echo} "x - extracting obj_rpc_cli_simple.py (text)"
sed 's/^X//' << 'SHAR_EOF' > 'obj_rpc_cli_simple.py' &&
#!/usr/bin/python3
"""
X The code below implements a simple ZeroMQ and MessagePack RPC client.
X This code is published as PUBLIC DOMAIN or under
X Creative Commons Zero v1.0 Universal license (whichever better suits your needs).
"""
import socket
import sys
import msgpack as p
import zmq
X
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
objects=[
X ["mult",(4,5)],
X ["mult",{"a":7,"b":8}],
X ["div",{"a":9,"b":4}],
X ["file",("/etc/passwd",)],
X ["file",("/etc/non_existing_file",)],
]
X
X
context = zmq.Context()
X
socket = context.socket(zmq.REQ)
socket.connect("tcp://localhost:9999")
X
for obj in objects:
X socket.send(p.packb(obj))
X # Get the reply.
X msg = socket.recv()
X resp = p.unpackb(msg)
X print("Received reply", resp)
SHAR_EOF
(set 20 21 07 11 12 18 00 'obj_rpc_cli_simple.py'
eval "${shar_touch}") && \
chmod 0755 'obj_rpc_cli_simple.py'
if test $? -ne 0
then ${echo} "restore of obj_rpc_cli_simple.py failed"
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'obj_rpc_cli_simple.py': 'MD5 check failed'
) << \SHAR_EOF
1e5e246d036d758ecb85dfdd1fb35094 obj_rpc_cli_simple.py
SHAR_EOF
else
test `LC_ALL=C wc -c < 'obj_rpc_cli_simple.py'` -ne 1073 && \
${echo} "restoration warning: size of 'obj_rpc_cli_simple.py' is not 1073"
fi
fi
# ============= obj_rpc_cli.lua ==============
if test -n "${keep_file}" && test -f 'obj_rpc_cli.lua'
then
${echo} "x - SKIPPING obj_rpc_cli.lua (file already exists)"
else
${echo} "x - extracting obj_rpc_cli.lua (text)"
sed 's/^X//' << 'SHAR_EOF' > 'obj_rpc_cli.lua' &&
-- Demonstrator of the communication with simple Python RPC server from Lua
-- Written by Wojciech M. Zabołotny (wzab01<at>
gmail.com or wzab<at>
ise.pw.edu.pl)
-- Copyright: This program is released into the public domain or under
-- Creative Commons Zero v1.0 Universal license (whichever better suits your needs).
local zmq = require "lzmq"
--require "utils"
local mp = require "mpack"
--print_version(zmq)
local pr = require "pl.pretty"
context = assert(zmq.context())
rpcsrv = assert(context:socket (zmq.REQ))
assert(rpcsrv:connect("tcp://localhost:9999"))
X
function rpc(params)
X local req=mp.pack(test)
X rpcsrv:send(req)
X local rcv=rpcsrv:recv()
X local res=mp.unpack(rcv)
X return res
end
X
test = {"file",{"/etc/passwd"}}
local res = rpc(test)
pr.dump(res)
test = {"mult",{7,8}}
res = rpc(test)
pr.dump(res)
test = {"div",{b=4.0,a=9.0}}
res = rpc(test)
pr.dump(res)
-- The above works, but 9/4 is printed as 2.
print(res[2])
test = {"div",{a=4.0,b=9.0}}
res = rpc(test)
pr.dump(res)
-- The above works, but 4/9 is printed as 0.
print(res[2])
X
SHAR_EOF
(set 20 21 07 11 12 18 00 'obj_rpc_cli.lua'
eval "${shar_touch}") && \
chmod 0644 'obj_rpc_cli.lua'
if test $? -ne 0
then ${echo} "restore of obj_rpc_cli.lua failed"
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'obj_rpc_cli.lua': 'MD5 check failed'
) << \SHAR_EOF
98327f1249753a0c26ca6010e927fc36 obj_rpc_cli.lua
SHAR_EOF
else
test `LC_ALL=C wc -c < 'obj_rpc_cli.lua'` -ne 1065 && \
${echo} "restoration warning: size of 'obj_rpc_cli.lua' is not 1065"
fi
fi
# ============= build.sh ==============
if test -n "${keep_file}" && test -f 'build.sh'
then
${echo} "x - SKIPPING build.sh (file already exists)"
else
${echo} "x - extracting build.sh (text)"
sed 's/^X//' << 'SHAR_EOF' > 'build.sh' &&
reset; c++ obj_rpc_cli.cc -o obj_rpc_cli -fconcepts-ts -g -lzmq -lmsgpackc
SHAR_EOF
(set 20 21 07 11 12 18 00 'build.sh'
eval "${shar_touch}") && \
chmod 0644 'build.sh'
if test $? -ne 0
then ${echo} "restore of build.sh failed"
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'build.sh': 'MD5 check failed'
) << \SHAR_EOF
161b668f7e55c340f21589aecb061a62 build.sh
SHAR_EOF
else
test `LC_ALL=C wc -c < 'build.sh'` -ne 75 && \
${echo} "restoration warning: size of 'build.sh' is not 75"
fi
fi
# ============= obj_rpc_srv_simple.py ==============
if test -n "${keep_file}" && test -f 'obj_rpc_srv_simple.py'
then
${echo} "x - SKIPPING obj_rpc_srv_simple.py (file already exists)"
else
${echo} "x - extracting obj_rpc_srv_simple.py (text)"
sed 's/^X//' << 'SHAR_EOF' > 'obj_rpc_srv_simple.py' &&
#!/usr/bin/python3
"""
X The code below implements a simple ZeroMQ and MessagePack RPC server.
X This code is published as PUBLIC DOMAIN or under
X Creative Commons Zero v1.0 Universal license (whichever better suits your needs).
"""
import time
import zmq
X
import msgpack as mp
import traceback
import os
X
#Functions which process requests
def remote_mult(a,b):
X return a*b
X
def remote_div(a,b):
X print(a,b,a/b)
X return a/b
X
def cat_file(fname):
X f=open(fname,"rb")
X return f.read()
X
#Table of functions
func={
X 'mult':remote_mult,
X 'div':remote_div,
X 'file':cat_file,
}
X
X
def handle(msg):
X try:
X obj = mp.unpackb(msg)
X if len(obj) != 2:
X raise Exception("Wrong number of RPC objects, should be 2: name and arguments")
X if isinstance(obj[1],tuple) or isinstance(obj[1],list):
X res=func[obj[0]](*obj[1])
X elif isinstance(obj[1],dict):
X res=func[obj[0]](**obj[1])
X else:
X raise Exception("Wrong type of arguments in RPC, should be list, tuple or dictionary")
X res = ("OK", res)
X except Exception:
X res=("error", traceback.format_exc())
X return mp.packb(res)
X
if __name__ == "__main__":
X context = zmq.Context()
X socket = context.socket(zmq.REP)
X # Create the server, binding to localhost on port 9999
X socket.bind("tcp://*:9999")
X while True:
X msg = socket.recv()
X res = handle(msg)
X socket.send(res)
SHAR_EOF
(set 20 21 07 11 12 18 00 'obj_rpc_srv_simple.py'
eval "${shar_touch}") && \
chmod 0755 'obj_rpc_srv_simple.py'
if test $? -ne 0
then ${echo} "restore of obj_rpc_srv_simple.py failed"
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'obj_rpc_srv_simple.py': 'MD5 check failed'
) << \SHAR_EOF
951011619f9c54987404bc8241e3c9b4 obj_rpc_srv_simple.py
SHAR_EOF
else
test `LC_ALL=C wc -c < 'obj_rpc_srv_simple.py'` -ne 1757 && \
${echo} "restoration warning: size of 'obj_rpc_srv_simple.py' is not 1757"
fi
fi
# ============= obj_rpc_cli.m ==============
if test -n "${keep_file}" && test -f 'obj_rpc_cli.m'
then
${echo} "x - SKIPPING obj_rpc_cli.m (file already exists)"
else
${echo} "x - extracting obj_rpc_cli.m (text)"
sed 's/^X//' << 'SHAR_EOF' > 'obj_rpc_cli.m' &&
% Demonstrator of the communication with simple Python RPC server from Octave
% Written by Wojciech M. Zabołotny (wzab01<at>
gmail.com or wzab<at>
ise.pw.edu.pl)
% Copyright: This program is released into the public domain.
pkg load jsonlab
pkg load zeromq
X
srv = zmq_socket (ZMQ_REQ);
zmq_connect (srv, "tcp://localhost:9999");
X
X
function res = rpc(req,fname,fargs,maxlen=10000)
X x={fname, fargs};
X a=savemsgpack(x);
X zmq_send(req,a);
X w=zmq_recv(req,maxlen);
X y=loadmsgpack(char(w));
X if strcmp(char(y{1}),"OK")
X res = y{2};
X end
X if strcmp(char(y{1}),"error")
X error(char(y{2}));
X end
endfunction
X
res = rpc(srv,"mult",struct("a",13,"b",20));
res
res = rpc(srv,"mult",{17,3});
res
res = rpc(srv,"file",{"/etc/passwd"});
char(res')
X
SHAR_EOF
(set 20 21 07 11 12 18 00 'obj_rpc_cli.m'
eval "${shar_touch}") && \
chmod 0644 'obj_rpc_cli.m'
if test $? -ne 0
then ${echo} "restore of obj_rpc_cli.m failed"
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'obj_rpc_cli.m': 'MD5 check failed'
) << \SHAR_EOF
54379be77e8e1d317370faa6a44cd32a obj_rpc_cli.m
SHAR_EOF
else
test `LC_ALL=C wc -c < 'obj_rpc_cli.m'` -ne 753 && \
${echo} "restoration warning: size of 'obj_rpc_cli.m' is not 753"