Welcome, guest | Sign In | My Account | Store | Cart

Python's xmlrpclib only raises the xmlrpclib.Fault exception, but it can be convenient to allow more different kinds of exceptions to be raised. This recipe provides a customized subclass of xmlrpclib.ServerProxy that looks for Fault exceptions where the message is of the form <exception name>:<message>, and raises the corresponding exception.

Python, 77 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
"""xmlrpc

Specialized XML-RPC support that can raise exceptions across the
client/server boundary.

To use this, just create an instance of Server and call methods on it.
Special exceptions raised on the server will cause the same
exceptions to be triggered on the client side.
"""

import xmlrpclib
import re

__all__ = ['Server']

# List of exceptions that are allowed.  Only exceptions listed here will be reconstructed
# from an xmlrpclib.Fault instance.
allowed_errors = [ValueError, TypeError]

error_pat = re.compile('(?P<exception>[^:]*):(?P<rest>.*$)')

class ExceptionUnmarshaller (xmlrpclib.Unmarshaller):
    def close(self):
        # return response tuple and target method
        if self._type is None or self._marks:
            raise xmlrpclib.ResponseError()
        if self._type == "fault":
            d = self._stack[0]
            m = error_pat.match(d['faultString'])
            if m:
                exception_name = m.group('exception')
                rest = m.group('rest')
                for exc in allowed_errors:
                    if exc.__name__ == exception_name:
                        raise exc(rest)

            # Fall through and just raise the fault
            raise xmlrpclib.Fault(**d)
        return tuple(self._stack)

class ExceptionTransport (xmlrpclib.Transport):
    # Override user-agent if desired
    ##user_agent = "xmlrpc-exceptions/0.0.1"

    def getparser (self):
        # We want to use our own custom unmarshaller
        unmarshaller = ExceptionUnmarshaller()
        parser = xmlrpclib.ExpatParser(unmarshaller)
        return parser, unmarshaller

# Alternatively you can just use the regular ServerProxy and pass it
# an instance of ExceptionTransport.

class Server (xmlrpclib.ServerProxy):
    def __init__ (self, *args, **kwargs):
        # Supply our own transport
        kwargs['transport'] = ExceptionTransport()
        xmlrpclib.ServerProxy.__init__(self, *args, **kwargs)

if __name__ == '__main__':
    s = Server('http://localhost:8000')
    print s.exception()

# Sample code for a server that returns exceptions in the right format
import sys, xmlrpclib
from SimpleXMLRPCServer import SimpleXMLRPCServer

def exception ():
    try:
        # Put code here, call other functions
        int('a')
    except Exception, exc:
        raise xmlrpclib.Fault(1, '%s:%s' % (exc.__class__.__name__, exc) )

server = SimpleXMLRPCServer(("localhost", 8000))
server.register_function(exception)
server.serve_forever()

Out of concern for security, you have to explicitly list allowable exceptions. You shouldn't have exception classes that do unsafe things with the provided message; few exception classes do much with the message beyond displaying it, so that usually won't be a problem.

You need to wrap your method implementations with a try...except clause that catches all exceptions and raises an xmlrpclib.Fault exception with a message in the right format. See the sample code above for an example of how to do this.

1 comment

Andres Falconer 15 years, 2 months ago  # | flag

I can't understand. Hello, I can't understand how use this code. I get the follow message:

Traceback (most recent call last): File "ejemplo.py", line 62, in ? print s.exception() File "/var/tmp/python2.4-2.4-root/usr/lib/python2.4/xmlrpclib.py", line 1096, in __call__ return self.__send(self.__name, args) File "/var/tmp/python2.4-2.4-root/usr/lib/python2.4/xmlrpclib.py", line 1383, in __request verbose=self.__verbose File "/var/tmp/python2.4-2.4-root/usr/lib/python2.4/xmlrpclib.py", line 1147, in request return self._parse_response(h.getfile(), sock) File "/var/tmp/python2.4-2.4-root/usr/lib/python2.4/xmlrpclib.py", line 1284, in _parse_response p.close() File "/var/tmp/python2.4-2.4-root/usr/lib/python2.4/xmlrpclib.py", line 530, in close self._parser.Parse("", 1) # end of data xml.parsers.expat.ExpatError: unclosed token: line 1, column 0

I think that isn't correct.

PD: sorry for my english, I only speak good spanish.