Simple RPC Python Module Part III

April 13th, 2011

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:

  1. when the method being called is not public (line 5)
  2. when the method being called raised an exception (line 11)
  3. when the result of the method being called cannot be serialised (line 17)
When the server object creates an exception object, it gives the instance two parameters: the Python exception object, and a string. When there is a meaningful traceback, the string is the textual representation of the traceback. It is not possible to serialise a Python traceback object, because in any other context than the actual Python context in which the corresponding exception was raised, the binary data would be completely meaningless. Nevertheless, it can be very useful for debugging, to get an idea of how the exception got generated, so to be able to print somewhat useful information to the user, the textual representation is sent back along with the exception object. Only when the error is not due to an actual exception (line 5), the string is actually just the name of the requested method. To avoid confusion, the 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

Leave a Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Spam protection by WP Captcha-Free