Wojciech M. Zabolotny
2012-07-16 20:58:53 UTC
Archive-name: python-object-rpc
Version: 1.0
Submitted-by: ***@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>.
# 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
Version: 1.0
Submitted-by: ***@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>.
# 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