Simple RPC Python Module Part III
Last part of this series, let’s wrap up the details of the server and exception handling, plus some other small details.
The server object is not much different than the client, in that it accepts incoming connections, receives data over them, executes the requested method, and returns the results. Let’s skip over the boring data transmissions details, which are very similar to the code of the client object, and jump right to the interesting bit: the execution.
# Perform the actual call.
try:
method = self.__getattribute__(data[0])
if not hasattr(method, "__publish"):
result = PyRPCException(AttributeError(
"Not a public method."), data[0])
else:
result = method(*data[1], **data[2])
except Exception, exc:
# Pass the exception back, packed in a PyRPCException object.
result = PyRPCException(exc, traceback.format_exc())
try:
data = pickle.dumps(result)
except Exception, exc:
# Could not pickle the result. Send this exception back.
data = pickle.dumps(PyRPCException(exc,
traceback.format_exc()))
Notice that — like with the client object — the method to be invoked is
accessed using the __getattribute__ method. On line 4, the code
checks for a special attribute called __publish. This is used to
distinguish between those methods, that are public, and therefore can
be accessed over RPC calls, and those that are intended for internal use or
otherwise should not be allowed access over RPC. Remember, this code is not
meant to be “safe” or anyhow resilient to any kind of abuse. This feature is
mainly meant to help users from accidentally calling the wrong methods.
The special __publish attribute is added to methods using a
simple Python decorator:
def public(function):
"""
Decorator to wrap methods into a PyRPCPublicMethod object, to distinguish
public methods.
This relies on adding a custom attribute `__publish' to the method
objects. The attribute is set to `True', but the value is irrelevant, as
the server only checks for its presence.
"""
function.__publish = True
return function
The value of the attribute is actually irrelevant, the mere existence is
sufficient.
Lines 5, 11 and 17 show what happens when an error occurs. A
PyRPCException instance is created to replace the result. This
happens on three occasions:
- when the method being called is not public (line 5)
- when the method being called raised an exception (line 11)
- when the result of the method being called cannot be serialised (line 17)
PyRPCException class is not a subclass of any Python
exception class.
The server object implementation offers one more method worth discussing:
@public
def shutdown(self, value=0):
"""
Causes the system to shut down and return the (optional) value.
"""
self.exitValue = value
self.loop = False
# The following will cause the socket.accept() call to raise an error
# (22, 'Invalid argument'), which will cause the while-loop to be
# repeated and thus the condition to be re-evaluated. This way we
# catch the case in which the shutdown call is executed after the
# while-condition is evaluated.
# NOTE: socket.SHUT_RD is sufficient, as SHUT_WR does not cause the
# exception (and therefore there's no need to use SHUT_RDWR either).
self.socket.shutdown(socket.SHUT_RD)
Note the use of the public decorator: this method is meant to be
used by the client. The exitValue attribute is used as a return
value from the call to the server’s main execution loop, which can be used
when deploying the server (e.g. to distinguish whether the server was
terminated due to an error or not). The loop attribute is the
flag that governs the repetition of the main execution loop. What is most
interesting is line 15. As the comment already explains, this little trick
causes the call to socket.accept() to raise an error, which
ensures that the server is not waiting for a connection when, in fact, it
should be shutting down.
An implementation simply needs to subclass the server object class to add
additional functionality (not forgetting the public decorator
where needed). The module I wrote offers two versions of the server class
implementation: a serial and a threaded one. The threaded version is
functionally very similar to the serial one, but uses a Python thread object
to deal with incoming client requests.
Should you be interested, here’s a copy of my code. Should you have any comments, please feel free to leave them below, and as always, any contributions will be gratefully welcomed.
in Python