Client-Server¶
Rserve is currently the default solution when looking
for a server solution for R, but rpy2
can be used
to develop very easily one’s own server, tailored to answer
specific requirements. Such requirements can include for example
access restriction, a security model, access to subsets of the R
engine, distribution of jobs to other servers, all of which
are currently difficult or impossible to achieve with R serve.
The pyRserve
package addresses the connection to Rserve
from Python, and although it frees one from handling the R server is
also constrains one to use Rserve.
Simple socket-based server and client¶
Server¶
An implementation of a simplistic socket server listening on a given port for a string to evaluate as R code is straightforward with Python’s SocketServer module.
Our example server will be in a file rpyserve.py, containing the following code.
import socketserver
import sys
import rpy2.robjects as robjects
class MyTCPHandler(socketserver.StreamRequestHandler):
def handle(self):
# verbose server
print("%s wrote:" % self.client_address[0])
# self.rfile is a file-like object created by the handler;
# we can now use e.g. readline() instead of raw recv() calls
encoding = self.rfile.readline().strip()
encoding = str(encoding, 'ASCII')
print(' encoding: %s' % encoding)
size = int.from_bytes(self.rfile.read(8), 'little')
print(' size: %i bytes' % size)
rcv = self.rfile.read(size)
rcv = str(rcv, encoding)
# verbose server
print(' R code string:')
print(rcv)
# evaluate the data passed as a string of R code
results = robjects.r(rcv)
# return the result of the evaluation as a string
# to the client
results = bytes(str(results), encoding)
size_res = len(results).to_bytes(8, 'little')
print(' Result size: %i' % len(results))
self.wfile.write(size_res +
results)
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-p', '--port',
type=int,
default=8080,
help='port')
parser.add_argument('--hostname',
default='localhost')
options = parser.parse_args()
# Create the server, binding to localhost on port 9999
server = socketserver.TCPServer((options.hostname, options.port),
MyTCPHandler)
print('Server listening on %s:%i' % (options.hostname, options.port))
# Activate the server; this will keep running until you
# interrupt the program with Ctrl-C
server.serve_forever()
Running a server listening on port 9090 is then:
python rpyserve.py --hostname localhost
Client¶
Using Python’s socket module, implementing a client is just as easy. We write the code for ours into a file rpyclient.py:
import socket
import sys
import locale
import argparse
def send_R_code(rcode, hostname, port):
"""
Evaluate the R code in `rcode` (on a possibly remote machine)
"""
# Create a socket (SOCK_STREAM means a TCP socket)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Connect to server and send data
sock.connect((hostname, port))
size_send = len(rcode).to_bytes(8, 'little')
sock.send(bytes(encoding, 'ASCII') + b'\n' + \
size_send + \
rcode)
# Receive data from the server and shut down
print("Received:")
size = int.from_bytes(sock.recv(8), 'little') # 64 bits max
print(" size: %i bytes" % size)
received = sock.recv(size)
sock.close()
print(" R output:")
print(str(received, encoding))
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-p', '--port',
type=int,
default=8080)
parser.add_argument('--hostname',
default='localhost')
options = parser.parse_args()
# read R code from STDIN
rcode = sys.stdin.read()
encoding = locale.getlocale()[1]
rcode = bytes(rcode, encoding)
send_R_code(rcode, options.hostname, options.port)
Evaluating R code on a local server as defined in the previous section, listening on port 9090 is then:
echo 'R.version' | python rpyclient.py --hostname localhost
In this example, the client is querying the R version.