view wibble/server/JsonRpcApp.py @ 0:6a61cfdf6930

Initial version
author Ben Croston <ben@croston.org>
date Tue, 30 Aug 2011 22:19:48 +0100
parents
children
line wrap: on
line source

from webob import Request, Response, exc
try:
  from json import loads, dumps
except:
  from simplejson import loads, dumps
import traceback
import sys

class JsonRpcApp(object):
    """
    Serve the given object via json-rpc (http://json-rpc.org/)
    """

    def __init__(self, obj, auth=None):
        """
        obj  - a class of functions available using jsonrpc
        auth - an authentication function (optional)
        """
        self.obj = obj
        self.auth = auth

    def __call__(self, environ, start_response):
        req = Request(environ)
        try:
            resp = self.process(req)
        except ValueError, e:
            resp = exc.HTTPBadRequest(str(e))
        except exc.HTTPException, e:
            resp = e
        return resp(environ, start_response)

    def process(self, req):
        if not req.method == 'POST':
            raise exc.HTTPMethodNotAllowed("Only POST allowed").exception

        try:
            json = loads(req.body)
        except ValueError, e:
            raise ValueError('Bad JSON: %s' % e)

        try:
            method = json['method']
            params = json['params']
            id = json['id']
            username = json['username'] if 'username' in json else None
            password = json['password'] if 'password' in json else None
        except KeyError, e:
            raise ValueError("JSON body missing parameter: %s" % e)

        if params is None:
            params = []
        if not isinstance(params, list):
            raise ValueError("Bad params %r: must be a list" % params)
            text = traceback.format_exc()
            exc_value = sys.exc_info()[1]
            error_value = dict(
                name='JSONRPCError',
                code=100,
                message=str(exc_value),
                error=text)
            return Response(
                status=500,
                content_type='application/json',
                body=dumps(dict(result=None,
                                error=error_value,
                                id=id)))

        obj = self.obj
        if isinstance(self.obj,tuple) or isinstance(self.obj,list):
            for x in self.obj:
                if method.startswith('%s.'%x.__class__.__name__):
                   obj = x
                   method = method.replace('%s.'%obj.__class__.__name__,'',1)
                   break
        elif method.startswith('%s.'%self.obj.__class__.__name__):
            method = method.replace('%s.'%self.obj.__class__.__name__,'',1)
        if method.startswith('_'):
            raise exc.HTTPForbidden("Bad method name %s: must not start with _" % method).exception
        try:
            method = getattr(obj, method)
        except AttributeError:
            raise ValueError("No such method %s" % method)

        if self.auth is not None:
            try:
                auth_result = self.auth(username, password, req.user_agent)
            except:
                text = traceback.format_exc()
                exc_value = sys.exc_info()[1]
                error_value = dict(
                    name='JSONRPCError',
                    code=100,
                    message=str(exc_value),
                    error=text)
                return Response(
                    status=500,
                    content_type='application/json',
                    body=dumps(dict(result=None,
                                    error=error_value,
                                    id=id)))
            if not auth_result:
                raise exc.HTTPUnauthorized().exception

        try:
            result = method(*params)
        except:
            text = traceback.format_exc()
            exc_value = sys.exc_info()[1]
            error_value = dict(
                name='JSONRPCError',
                code=100,
                message=str(exc_value),
                error=text)
            return Response(
                status=500,
                content_type='application/json',
                body=dumps(dict(result=None,
                                error=error_value,
                                id=id)))

        return Response(
            content_type='application/json',
            body=dumps(dict(result=result,
                            error=None,
                            id=id)))