Home | History | Annotate | Download | only in jsonrpc-example-code
      1 # A reaction to: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/552751
      2 from webob import Request, Response
      3 from webob import exc
      4 from simplejson import loads, dumps
      5 import traceback
      6 import sys
      7 
      8 class JsonRpcApp(object):
      9     """
     10     Serve the given object via json-rpc (http://json-rpc.org/)
     11     """
     12 
     13     def __init__(self, obj):
     14         self.obj = obj
     15 
     16     def __call__(self, environ, start_response):
     17         req = Request(environ)
     18         try:
     19             resp = self.process(req)
     20         except ValueError, e:
     21             resp = exc.HTTPBadRequest(str(e))
     22         except exc.HTTPException, e:
     23             resp = e
     24         return resp(environ, start_response)
     25 
     26     def process(self, req):
     27         if not req.method == 'POST':
     28             raise exc.HTTPMethodNotAllowed(
     29                 "Only POST allowed",
     30                 allowed='POST')
     31         try:
     32             json = loads(req.body)
     33         except ValueError, e:
     34             raise ValueError('Bad JSON: %s' % e)
     35         try:
     36             method = json['method']
     37             params = json['params']
     38             id = json['id']
     39         except KeyError, e:
     40             raise ValueError(
     41                 "JSON body missing parameter: %s" % e)
     42         if method.startswith('_'):
     43             raise exc.HTTPForbidden(
     44                 "Bad method name %s: must not start with _" % method)
     45         if not isinstance(params, list):
     46             raise ValueError(
     47                 "Bad params %r: must be a list" % params)
     48         try:
     49             method = getattr(self.obj, method)
     50         except AttributeError:
     51             raise ValueError(
     52                 "No such method %s" % method)
     53         try:
     54             result = method(*params)
     55         except:
     56             text = traceback.format_exc()
     57             exc_value = sys.exc_info()[1]
     58             error_value = dict(
     59                 name='JSONRPCError',
     60                 code=100,
     61                 message=str(exc_value),
     62                 error=text)
     63             return Response(
     64                 status=500,
     65                 content_type='application/json',
     66                 body=dumps(dict(result=None,
     67                                 error=error_value,
     68                                 id=id)))
     69         return Response(
     70             content_type='application/json',
     71             body=dumps(dict(result=result,
     72                             error=None,
     73                             id=id)))
     74 
     75 
     76 class ServerProxy(object):
     77     """
     78     JSON proxy to a remote service.
     79     """
     80 
     81     def __init__(self, url, proxy=None):
     82         self._url = url
     83         if proxy is None:
     84             from wsgiproxy.exactproxy import proxy_exact_request
     85             proxy = proxy_exact_request
     86         self.proxy = proxy
     87 
     88     def __getattr__(self, name):
     89         if name.startswith('_'):
     90             raise AttributeError(name)
     91         return _Method(self, name)
     92 
     93     def __repr__(self):
     94         return '<%s for %s>' % (
     95             self.__class__.__name__, self._url)
     96 
     97 class _Method(object):
     98 
     99     def __init__(self, parent, name):
    100         self.parent = parent
    101         self.name = name
    102 
    103     def __call__(self, *args):
    104         json = dict(method=self.name,
    105                     id=None,
    106                     params=list(args))
    107         req = Request.blank(self.parent._url)
    108         req.method = 'POST'
    109         req.content_type = 'application/json'
    110         req.body = dumps(json)
    111         resp = req.get_response(self.parent.proxy)
    112         if resp.status_code != 200 and not (
    113             resp.status_code == 500
    114             and resp.content_type == 'application/json'):
    115             raise ProxyError(
    116                 "Error from JSON-RPC client %s: %s"
    117                 % (self.parent._url, resp.status),
    118                 resp)
    119         json = loads(resp.body)
    120         if json.get('error') is not None:
    121             e = Fault(
    122                 json['error'].get('message'),
    123                 json['error'].get('code'),
    124                 json['error'].get('error'),
    125                 resp)
    126             raise e
    127         return json['result']
    128 
    129 class ProxyError(Exception):
    130     """
    131     Raised when a request via ServerProxy breaks
    132     """
    133     def __init__(self, message, response):
    134         Exception.__init__(self, message)
    135         self.response = response
    136 
    137 class Fault(Exception):
    138     """
    139     Raised when there is a remote error
    140     """
    141     def __init__(self, message, code, error, response):
    142         Exception.__init__(self, message)
    143         self.code = code
    144         self.error = error
    145         self.response = response
    146     def __str__(self):
    147         return 'Method error calling %s: %s\n%s' % (
    148             self.response.request.url,
    149             self.args[0],
    150             self.error)
    151 
    152 class DemoObject(object):
    153     """
    154     Something interesting to attach to
    155     """
    156     def add(self, *args):
    157         return sum(args)
    158     def average(self, *args):
    159         return sum(args) / float(len(args))
    160     def divide(self, a, b):
    161         return a / b
    162 
    163 def make_app(expr):
    164     module, expression = expr.split(':', 1)
    165     __import__(module)
    166     module = sys.modules[module]
    167     obj = eval(expression, module.__dict__)
    168     return JsonRpcApp(obj)
    169 
    170 def main(args=None):
    171     import optparse
    172     from wsgiref import simple_server
    173     parser = optparse.OptionParser(
    174         usage='%prog [OPTIONS] MODULE:EXPRESSION')
    175     parser.add_option(
    176         '-p', '--port', default='8080',
    177         help='Port to serve on (default 8080)')
    178     parser.add_option(
    179         '-H', '--host', default='127.0.0.1',
    180         help='Host to serve on (default localhost; 0.0.0.0 to make public)')
    181     options, args = parser.parse_args()
    182     if not args or len(args) > 1:
    183         print 'You must give a single object reference'
    184         parser.print_help()
    185         sys.exit(2)
    186     app = make_app(args[0])
    187     server = simple_server.make_server(options.host, int(options.port), app)
    188     print 'Serving on http://%s:%s' % (options.host, options.port)
    189     server.serve_forever()
    190     # Try python jsonrpc.py 'jsonrpc:DemoObject()'
    191 
    192 if __name__ == '__main__':
    193     main()
    194