tags:

views:

188

answers:

2

Standard libraries (xmlrpclib+SimpleXMLRPCServer in Python 2 and xmlrpc.server in Python 3) report all errors (including usage errors) as python exceptions which is not suitable for public services: exception strings are often not easy understandable without python knowledge and might expose some sensitive information. It's not hard to fix this, but I prefer to avoid reinventing the wheel. Is there a third party library with better error reporting? I'm interested in good fault messages for all usage errors and hiding internals when reporting internal errors (this is better done with logging).

xmlrpclib already have the constants for such errors: NOT_WELLFORMED_ERROR, UNSUPPORTED_ENCODING, INVALID_ENCODING_CHAR, INVALID_XMLRPC, METHOD_NOT_FOUND, INVALID_METHOD_PARAMS, INTERNAL_ERROR.

A: 

I don't think you have a library specific problem. When using any library or framework you typically want to trap all errors, log them somewhere, and throw up "Oops, we're having problems. You may want to contact us at [email protected] with error number 100 and tell us what you did." So wrap your failable entry points in try/catches, create a generic logger and off you go...

popester
Then they will try to describe something, fail, and you finally end with enabling logging (of probably huge requests) to see what they actually do. Imagine hundreds of such clients. Some of them will develop step by step stepping through all errors you can imagine and even more.
Denis Otkidach
+1  A: 

It look like there is no ready library with my requirements, so a ended up with own implementation:

class ApplicationError(Fault):

    def __init__(self, exc_info):
        Fault.__init__(self, xmlrpclib.APPLICATION_ERROR,
                       u'Application internal error')


class NotWellformedError(Fault):

    def __init__(self, exc):
        Fault.__init__(self, xmlrpclib.NOT_WELLFORMED_ERROR, str(exc))


class UnsupportedEncoding(Fault):

    def __init__(self, exc):
        Fault.__init__(self, xmlrpclib.UNSUPPORTED_ENCODING, str(exc))


# XXX INVALID_ENCODING_CHAR is masked by xmlrpclib, so the error code will be
# INVALID_XMLRPC.
class InvalidRequest(Fault):

    def __init__(self, message):
        ault.__init__(self, xmlrpclib.INVALID_XMLRPC, message)


class MethodNotFound(Fault):

    def __init__(self, name):
        Fault.__init__(self, xmlrpclib.METHOD_NOT_FOUND,
                       u'Method %r is not supported' % name)


class WrongMethodUsage(Fault):

    def __init__(self, message):
        Fault.__init__(self, xmlrpclib.INVALID_METHOD_PARAMS, message)


class WrongType(Fault):

    def __init__(self, arg_name, type_name):
        Fault.__init__(self, xmlrpclib.INVALID_METHOD_PARAMS,
                       u'Parameter %s must be %s' % (arg_name, type_name))


class XMLRPCDispatcher(SimpleXMLRPCDispatcher, XMLRPCDocGenerator):

    server_name = server_title = 'Personalization center RPC interface'
    server_documentation = 'Available methods'

    def __init__(self, methods):
        SimpleXMLRPCDispatcher.__init__(self, allow_none=True, encoding=None)
        self.register_instance(methods)
        self.register_multicall_functions()
        #self.register_introspection_functions()

    def _dispatch(self, method_name, args):
        if self.funcs.has_key(method_name):
            method = self.funcs[method_name]
        else:
            method = self.instance._getMethod(method_name)
        arg_names, args_name, kwargs_name, defaults = \
                                                inspect.getargspec(method)
        assert arg_names[0]=='self'
        arg_names = arg_names[1:]
        n_args = len(args)
        if not (args_name or defaults):
            if n_args!=len(arg_names):
                raise WrongMethodUsage(
                    u'Method %s takes exactly %d parameters (%d given)' % \
                                (method_name, len(arg_names), n_args))
        else:
            min_args = len(arg_names)-len(defaults)
            if len(args)<min_args:
                raise WrongMethodUsage(
                    u'Method %s requires at least %d parameters (%d given)' % \
                                (method_name, min_args, n_args))
            if not args_name and n_args>len(arg_names):
                raise WrongMethodUsage(
                    u'Method %s requires at most %d parameters (%d given)' % \
                                (method_name, len(arg_names), n_args))
        try:
            return method(*args)
        except Fault:
            raise
        except:
            logger.exception('Application internal error for %s%r',
                             method_name, args)
            raise ApplicationError(sys.exc_info())

    def dispatch(self, data):
        try:
            try:
                args, method_name = xmlrpclib.loads(data)
            except ExpatError, exc:
                raise NotWellformedError(exc)
            except LookupError, exc:
                raise UnsupportedEncoding(exc)
            except xmlrpclib.ResponseError:
                raise InvalidRequest('Request structure is invalid')
            method_name = method_name.encode('ascii', 'replace')
            result = self._dispatch(method_name, args)
        except Fault, exc:
            logger.warning('Fault %s: %s', exc.faultCode, exc.faultString)
            return xmlrpclib.dumps(exc)
        else:
            try:
                return xmlrpclib.dumps((result,), methodresponse=1)
            except:
                logger.exception('Application internal error when marshalling'\
                                 ' result for %s%r', method_name, args)
                return xmlrpclib.dumps(ApplicationError(sys.exc_info()))


class InterfaceMethods:

    def _getMethod(self, name):
        if name.startswith('_'):
            raise MethodNotFound(name)
        try:
            method = getattr(self, name)
        except AttributeError:
            raise MethodNotFound(name)
        if not inspect.ismethod(method):
            raise MethodNotFound(name)
        return method
Denis Otkidach