Simple object based RPC for Python

46 views
Skip to first unread message

Wojciech M. Zabolotny

unread,
Jul 16, 2012, 4:58:53 PM7/16/12
to
Archive-name: python-object-rpc
Version: 1.0
Submitted-by: wz...@ise.pw.edu.pl
Last-modified: 2012-07-16

Last time I had to develop a simple solution providing
remote procedure calls between two Python programs.
Additional requirement was, that arguments and results
should be sent as objects or at least as lists.
The most obvious solution seemed to be based on
the standard pickle module, but unfortunately
it is known as unsafe.

Therefore I've searched for other solutions, and
finally found the msgpack library:
http://msgpack.org/

The msgpack library provides even the RPC support,
but currently it is not supported in Python.
Therefore I've developed very simple solution consisting
of simple server and client allowing to send a list, tuple
or dictionary as a request, and receive another list, tuple
or dictionary as result.

The code is very simple and lacks proper error detection.

The code is heavily based on examples provided in Python
so even though my part is released as PUBLIC DOMAIN, you
should consider the whole as licensed under PSF LICENSE.

I hope, that the presented code may be useful for someone.

Wojciech Zabolotny wzab<at>ise.pw.edu.pl

#!/bin/sh
# This is a shell archive (produced by GNU sharutils 4.11.1).
# 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=_sh13253
# Made on 2012-07-16 22:55 CEST by <wzab@wzab>.
# Source directory was `/home/wzab/biezace/python-object-server/publik/send'.
#
# Existing files will *not* be overwritten, unless `-c' is specified.
#
# This shar contains:
# length mode name
# ------ ---------- ------------------------------------------
# 1628 -rwxr--r-- obj_rpc_srv_simple.py
# 844 -rwxr--r-- obj_rpc_cli_simple.py
#
MD5SUM=${MD5SUM-md5sum}
f=`${MD5SUM} --version | egrep '^md5sum .*(core|text)utils'`
test -n "${f}" && md5check=true || md5check=false
${md5check} || \
echo 'Note: not verifying md5sums. Consider installing GNU coreutils.'
if test "X$1" = "X-c"
then keep_file=''
else keep_file=true
fi
echo=echo
save_IFS="${IFS}"
IFS="${IFS}:"
gettext_dir=
locale_dir=
set_echo=false

for dir in $PATH
do
if test -f $dir/gettext \
&& ($dir/gettext --version >/dev/null 2>&1)
then
case `$dir/gettext --version 2>&1 | sed 1q` in
*GNU*) gettext_dir=$dir
set_echo=true
break ;;
esac
fi
done

if ${set_echo}
then
set_echo=false
for dir in $PATH
do
if test -f $dir/shar \
&& ($dir/shar --print-text-domain-dir >/dev/null 2>&1)
then
locale_dir=`$dir/shar --print-text-domain-dir`
set_echo=true
break
fi
done

if ${set_echo}
then
TEXTDOMAINDIR=$locale_dir
export TEXTDOMAINDIR
TEXTDOMAIN=sharutils
export TEXTDOMAIN
echo="$gettext_dir/gettext -s"
fi
fi
IFS="$save_IFS"
if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null
then if (echo -n test; echo 1,2,3) | grep n >/dev/null
then shar_n= shar_c='
'
else shar_n=-n shar_c= ; fi
else shar_n= shar_c='\c' ; fi
f=shar-touch.$$
st1=200112312359.59
st2=123123592001.59
st2tr=123123592001.5 # old SysV 14-char limit
st3=1231235901

if touch -am -t ${st1} ${f} >/dev/null 2>&1 && \
test ! -f ${st1} && test -f ${f}; then
shar_touch='touch -am -t $1$2$3$4$5$6.$7 "$8"'

elif touch -am ${st2} ${f} >/dev/null 2>&1 && \
test ! -f ${st2} && test ! -f ${st2tr} && test -f ${f}; then
shar_touch='touch -am $3$4$5$6$1$2.$7 "$8"'

elif touch -am ${st3} ${f} >/dev/null 2>&1 && \
test ! -f ${st3} && test -f ${f}; then
shar_touch='touch -am $3$4$5$6$2 "$8"'

else
shar_touch=:
echo
${echo} 'WARNING: not restoring timestamps. Consider getting and
installing GNU `touch'\'', distributed in GNU coreutils...'
echo
fi
rm -f ${st1} ${st2} ${st2tr} ${st3} ${f}
#
if test ! -d ${lock_dir} ; then :
else ${echo} "lock directory ${lock_dir} exists"
exit 1
fi
if mkdir ${lock_dir}
then ${echo} "x - created lock directory ${lock_dir}."
else ${echo} "x - failed to create lock directory ${lock_dir}."
exit 1
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/python
import msgpack as mp
import SocketServer
import traceback
import os
X
#Functions which process requests
def remote_mult(obj):
X return ('OK', obj[1]*obj[2])
X
def cat_file(obj):
X f=open(obj[1],"rb")
X res=f.read()
X return ('OK', res)
X
#Table of functions
func={
X 'mult':remote_mult,
X 'file':cat_file,
X }
X
class Obj_RPC_Handler(SocketServer.BaseRequestHandler):
X """
X The RequestHandler class for our server.
X
X It is instantiated once per connection to the server, and must
X override the handle() method to implement communication to the
X client.
X """
X
X def handle(self):
X # self.request is the TCP socket connected to the client
X # We send the greeting message to the client
X self.request.sendall("Object RPC server v. 1.0\n")
X # Create the unpacker and packer objects
X unp=mp.Unpacker()
X # Now we enter the loop waiting for objects and processing them
X while True:
X rdta = self.request.recv(4096)
X if not rdta:
X break # Client shut the connection down
X #Process received object, sending the response back
X unp.feed(rdta)
X for obj in unp:
X try:
X res=func[obj[0]](obj)
X except Exception:
X res=("error", traceback.format_exc())
X self.request.sendall(mp.packb(res))
X
if __name__ == "__main__":
X HOST, PORT = "localhost", 9999
X
X # Create the server, binding to localhost on port 9999
X server = SocketServer.TCPServer((HOST, PORT), Obj_RPC_Handler)
X
X # Activate the server; this will keep running until you
X # interrupt the program with Ctrl-C
X server.serve_forever()
SHAR_EOF
(set 20 12 07 16 22 54 35 'obj_rpc_srv_simple.py'
eval "${shar_touch}") && \
chmod 0744 '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
ece1c9fb4c514a4a21ebd65d48d17789 obj_rpc_srv_simple.py
SHAR_EOF
else
test `LC_ALL=C wc -c < 'obj_rpc_srv_simple.py'` -ne 1628 && \
${echo} "restoration warning: size of 'obj_rpc_srv_simple.py' is not 1628"
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/python
import socket
import sys
import msgpack as p
X
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
objects=[
X ["mult",4,5],
X ["mult",7,8],
X ["file","/etc/passwd"],
X ["file","/etc/akuku"],
X ]
X
X
class client:
X def __init__(self,host,port):
X self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
X self.sock.connect((host,port))
X #Prepare the unpacker
X self.unp=p.Unpacker()
X #Receive the greeting string
X r=""
X while True:
X c=self.sock.recv(1);
X r+=c
X if c=="\n":
X print r
X break
X def do_cmd(self,obj):
X self.sock.sendall(p.packb(obj))
X while True:
X self.unp.feed(self.sock.recv(4096))
X try:
X res = self.unp.unpack()
X return res
X except StopIteration:
X pass
X
cli=client(HOST,PORT)
for i in objects:
X print cli.do_cmd(i)
X
X
SHAR_EOF
(set 20 12 07 16 22 55 14 'obj_rpc_cli_simple.py'
eval "${shar_touch}") && \
chmod 0744 '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
d47f9bf33c4a8bca2f43519fd4dbf620 obj_rpc_cli_simple.py
SHAR_EOF
else
test `LC_ALL=C wc -c < 'obj_rpc_cli_simple.py'` -ne 844 && \
${echo} "restoration warning: size of 'obj_rpc_cli_simple.py' is not 844"
fi
fi
if rm -fr ${lock_dir}
then ${echo} "x - removed lock directory ${lock_dir}."
else ${echo} "x - failed to remove lock directory ${lock_dir}."
exit 1
fi
exit 0

Wojciech M. Zabołotny

unread,
Jun 29, 2021, 7:24:50 PM6/29/21
to
This is an improved version of my object based RPC for Python from 2012.
It uses ZeroMQ and enables providing the arguments either as a list
or as a dictionary.
On the output, the user gets a tuple:
a) ("OK", result) - if there was no error
b) ("error", info_about_exception) - if there was an error

The code is published as PUBLIC DOMAIN. Of course, I'd appreciate
if you reference me as the original author.

#!/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=_sh261577
# Made on 2021-06-30 01:17 CEST by <wzab@wzab>.
# Source directory was '/tmp/yyyy'.
#
# Existing files will *not* be overwritten, unless '-c' is specified.
#
# This shar contains:
# length mode name
# ------ ---------- ------------------------------------------
# 940 -rwxr--r-- obj_rpc_cli_simple.py
# 1577 -rwxr--r-- obj_rpc_srv_simple.py
# ============= 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/python
"""
X The code below implements a simple ZeroMQ and Message Pack RPC client.
X Written by Wojciech M. Zabolotny, wzab<at>ise.pw.edu.pl or wzab01<at>gmail.com
X 13.04.2021
X
X The code is based on a post: "Simple object based RPC for Python",
X https://groups.google.com/g/alt.sources/c/QasPxJkKIUs/m/An1JnLooNCcJ
X https://www.funet.fi/pub/archive/alt.sources/2722.gz
X
X This is a PUBLIC DOMAIN code.
"""
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 ["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 06 30 01 09 38 'obj_rpc_cli_simple.py'
eval "${shar_touch}") && \
chmod 0744 '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
88fa813709a204e5c1f4d873d33d37a3 obj_rpc_cli_simple.py
SHAR_EOF

else
test `LC_ALL=C wc -c < 'obj_rpc_cli_simple.py'` -ne 940 && \
${echo} "restoration warning: size of 'obj_rpc_cli_simple.py' is not 940"
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 Message Pack RPC server.
X Written by Wojciech M. Zabolotny, wzab<at>ise.pw.edu.pl or wzab01<at>gmail.com
X 13.04.2021
X
X The code is based on a post: "Simple object based RPC for Python",
X https://groups.google.com/g/alt.sources/c/QasPxJkKIUs/m/An1JnLooNCcJ
X https://www.funet.fi/pub/archive/alt.sources/2722.gz
X
X This is a PUBLIC DOMAIN code.
X
"""
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 cat_file(fname):
X f=open(fname,"rb")
X return f.read()
X
#Table of functions
func={
X 'mult':remote_mult,
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 06 30 01 15 56 'obj_rpc_srv_simple.py'
eval "${shar_touch}") && \
chmod 0744 '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
1ca1a2e90174f58d4b4fa5c461aadc98 obj_rpc_srv_simple.py
SHAR_EOF

else
test `LC_ALL=C wc -c < 'obj_rpc_srv_simple.py'` -ne 1577 && \
${echo} "restoration warning: size of 'obj_rpc_srv_simple.py' is not 1577"

Wojciech M. Zabołotny

unread,
Jun 29, 2021, 7:58:33 PM6/29/21
to
Because the google groups is now the main provider of the archived
posts to alt.sources, and because it does not support displaying
the original message, which disables recovery of the original Python
formatting, I send my sources again, now as a compressed and BASE64
encoded archive.

This is an improved version of my object based RPC for Python from 2012.
It uses ZeroMQ and enables providing the arguments either as a list
or as a dictionary.
On the output, the user gets a tuple:
a) ("OK", result) - if there was no error
b) ("error", info_about_exception) - if there was an error

The code is published as PUBLIC DOMAIN. Of course, I'd appreciate
if you reference me as the original author.

====== BASE64 encoded obj_rpc_simple.tar.gz file ===================
H4sIAAAAAAAAA+1WUW/bNhDOs37FVXmIFAiS7Tj1ZswDuqzd0jaNk6Yo0CAQKIq2mUikSlJx3KL/
fUfJdpwlXfuQrRjA7yGxeMfjx7vj3cnsMlUVTWnBU83LqmBxtdh6XHQQg37f/u8O9jub/y26T/f7
W91+5+nTQQ//9rY63b0BLkHnkXk8iFobogC25p9I9k9635L/T7H9JKm1SjIukmphZlJ4vu97AHA2
Y0BlziBjhZxDkxolE0YDgTZR4ANT8ugEiMjhiGlNpgzGhF7B6fgAMJ9QObaW3ituDBOQLeC9vKSc
0RkcxfCBZLKQRiwisL79hZhfucbsm8csr+OqAKkaQadrRdOS8CKmsrQWu3txpx/3Or2ud4cq15AR
zXKQAllWUpsh+G9bsjK7ZNQs5ZbhBO2Pmyv7kbUyM6bSwySZKllXOp5KOcXHgCcm04QUJtayVpTp
hCYnRI9vXl69OnynkzJ5JrovxWsp3xzQl5t25vN5PKkFM/GEJ1WdJUTRGb9md4z1Br1ePP20vAby
59a/43e/vT48gN+Pj54dvmmuFjdhwZtIZUBLesXM+muhVz9LPa1sAIiGarX2qfzoeX8evz2LYHx8
egYj8AtJSTFD7/gR/IzwcmKIFYAfX0ouAjQZEzW9Pu8OL0Kv9ZwenVuScO6XdYE7g360H15Ed9Y+
+8QfDiI/84c/fVnLJrxgqO8nzNCkIlrPcz8KHxYLKVJ2w7XhYpq2Eqt54XkelcKwG4M88UrxQfsV
hJ7XugPXlxpxuxBYtdPnJ+FSAUMpBF4k8A2tMD5rLwytD/zQsvE8mxV4YeBimTF62PBc2tBM5EEV
Wy9nASqEYSPdBvgDKRhMRMWqYtHkvQ0HslruVIxeB622YrpCQRXXojWEiq2kUlwgwVNGGWZK3hrD
KNkdofdvvH+5rP9aXf+o+r/X2e+u67/tBR386A5c/f8vcK/+7z1SA9BMXTPlGsCjNYDNDmB4ye4U
+PsNoFx3AKMIZRkurxak9rztF7WghkuhYT7jGJBKSeSjsdR8rJk22svZBD9KaVhqq3tAoiwcLuuX
qRU6eDfzGi1KTFOrg4kgJVsqTUayYqJdinyV+eHm3gnWQ5Lb6r19RjIbnQlMVow8+2v0GfV37Mk7
ww0aNlI79rCd4erYyPvitURmmIlIw5bTloRRi/aHhS3rI/TL/bJrwSdQIF9b0+HJCHq3+xraBFMT
nt9QVlmKgf9eSTEFUZcZU5a8Tahlw4hAz2Rd5Phw0AxYBzRPBDtq3Twg/86pXHOBNUhQZg8/715E
psZ8DW3635cV2BvDv3FjemQ9dm5VOhcXwW6re3sKKx48J+f027YeMKbZdznHLKomrut726aKftr0
j71OBM2F7X0tI7RA1GLDR8gJ4xb4x6/aTthKWHPc7anDTf1R4DOlpMIN6+yP8b2XmDK4MQjvJGO5
auiNcQ99laY2amkKI5yK0hRLj0hTvz3ia3PI7ZTwtVFkvBoWDjD5DWvGhbZMRoAFOOfWaRLWg4mt
Ys2DbYa0jSnEKq/GmN3l+NLI8SmjI89UvRGhr00ht67deDZryea807jlRzcqBwcHBwcHBwcHBwcH
BwcHBwcHBwcHBwcHh+/CXzood9IAKAAA

Wojciech M. Zabołotny

unread,
Jul 11, 2021, 6:39:13 AM7/11/21
to
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 Written by Wojciech M. Zabolotny, wzab<at>ise.pw.edu.pl or wzab01<at>gmail.com
X 13.04.2021
X
X The code is based on a post: "Simple object based RPC for Python",
X https://groups.google.com/g/alt.sources/c/QasPxJkKIUs/m/An1JnLooNCcJ
X https://www.funet.fi/pub/archive/alt.sources/2722.gz
X
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 Written by Wojciech M. Zabolotny, wzab<at>ise.pw.edu.pl or wzab01<at>gmail.com
X 13.04.2021
X
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"

Wojciech M. Zabołotny

unread,
Jul 11, 2021, 6:49:05 AM7/11/21
to
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 tar.gz format encoded with base64.
It does not agree with alt.sources policy, but as the Google archive
disables access to raw messages, it is the only option to store the Python
sources so that they can be accessed in not corrupted form.
The sources in standard shar format are sent in the previous message.

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

======== Base64-encoded pyrpc.tar.gz =============
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
kinChIMeUlTI
Reply all
Reply to author
Forward
0 new messages