Changes between Version 3 and Version 4 of Jsonrpc


Ignore:
Timestamp:
Jun 4, 2009, 12:30:59 PM (16 years ago)
Author:
anonymous
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Jsonrpc

    v3 v4  
    66'''Features:'''
    77
    8 - Easy to use[[BR]]
    9 - Extensible[[BR]]
    108- Automatic SMD generation[[BR]]
    119- safe method resolution (ie, none of this stuff: [http://secunia.com/advisories/14128/])[[BR]]
     
    1715myproject/myapp/views.py:
    1816
     17class MyRpcMethods(object):
     18
     19    url = reverse("myapp-rpc")
     20
     21    @publicmethod
     22    def add(x, y):
     23        return x+y
     24
     25    @publicmethod
     26    def sub(x, y):
     27        return x-y
     28
     29    def read_diary(self):
     30        return "Here's all my secrets ..."
     31
     32    @publicmethod
     33    def find_person(attribs):
     34
     35        filters = {}
     36        for key in attribs.keys():
     37            if key in ("first_name", "last_name"):
     38                filters[key] = attribs[key]
     39
     40        return [
     41            {"first_name": p.first_name, "last_name": p.last_name}
     42            for p in Person.objects.filter(**filters)
     43        ]
     44
    1945def my_rpc_view(request):
    2046
    21     from jsonrpc import JsonRpc
    22     class MyRpcMethods(object):
     47    rpc     = JsonRpc( MyRpcMethods() )
     48    result  = rpc.handle_request(request)
    2349
    24         url = reverse("myapp-rpc")
    25 
    26         def public__add(self, x, y):
    27             return x+y
    28 
    29         def public__subtract(self, x, y):
    30             return x-y
    31 
    32         @staticmethod
    33         def public__multiply(x, y):
    34             return x*y
    35 
    36         def public__find_person(self, attribs):
    37 
    38             filters = {}
    39             for key in attribs.keys():
    40                 if key in ("first_name", "last_name"):
    41                     filters[key] = attribs.get(key)
    42 
    43             return Person.objects.filter(**filters).values()
    44 
    45     rpc = JsonRpc( MyRpcMethods() )
    46     result = rpc.handle_request(request)
    47 
    48     return HttpResponse(result)
    49 
     50    return result
    5051}}}
    5152
     
    6061>>> 7
    6162
    62 rpc.callRemote("subtract", [9,5]).addCallback(function(result) {
     63rpc.callRemote("sub", [9,5]).addCallback(function(result) {
    6364    console.log(result);
    6465});
    6566>>> 4
    6667
    67 rpc.multiply(5, 4).addCallback(function(result) {
    68     console.log(result);
     68rpc.read_diary().addCallback(function(secrets) {
     69    console.log(secrets);
    6970});
    70 >>> 20
     71>>> no such method
    7172
    7273rpc.find_person({last_name: "Baggins"}).addCallback(function(result) {
     
    9495#
    9596
     97class publicmethod(object):
     98
     99    def __init__(self, method):
     100        self.method = method
     101        __public__  = True
     102
     103    def __call__(self, *args, **kwargs):
     104        return self.method(*args, **kwargs)
     105
     106    def get_args(self):
     107        from inspect import getargspec
     108        return [ a for a in getargspec(self.method).args if (a != "self") ]
     109
    96110class JsonRpc(object):
    97111
    98     def __init__(self, instance=None, url=None, allow_errors=True, auto_smd=True):
    99         """
    100             'instance' is required and is an object containing public
    101             RPC methods (like what you would pass to register_instance in SimpleXMLRPCServer)
    102             Passing arbitrary functions (register_function) isn't supported yet
     112    def __init__(self, instance, allow_errors=True, report_methods=True):
    103113
    104             'url' is optional if given as an attibute of the instance.  It's where the JSON-RPC
    105             client posts its RPC requests
     114        self.instance            = instance
     115        self.allow_errors        = allow_errors
     116        self.report_methods        = report_methods
    106117
    107             'allow_errors' tells the serializer to report exception values under the key 'error' in
    108             the result dict back to the client.  If set to False and an exception occurs, the client
    109             will just get: {'error': 'error'}
     118        if not hasattr(self.instance, "url"):
     119            raise Exception("'url' not present in supplied instance")
    110120
    111             'auto_smd' lets us introspect the instance to find the RPC methods and generate the
    112             the appropriate method/signature descriptions.
     121    def get_public_methods(self):
    113122
    114             If the instance contains a dict attribute 'methods', then that will be used instead
     123        return [
     124            m for m in dir(self.instance) if \
     125            (getattr(self.instance, m).__class__.__name__ == "publicmethod") and \
     126            (getattr(self.instance, m).__public__ == True)
     127        ]
    115128
    116             class MyRpcMethods(object):
    117                 methods = {'add': ['x','y']}
    118 
    119                 # Will be reported
    120                 public__add(self, x,y): return x+y
    121 
    122                 # Wont be reported
    123                 public__subtract(self, x,y): return x-y
    124 
    125             This is useful if you want to filter the reported methods based on some other condition
    126             than the 'public__' prefix  --- be advised that a clever client can still call non-reported
    127             methods via a stringified method name ie with Dojo's 'callRemote' method.  So this shouldn't
    128             be used for securty.  See the 'dispatch' method below if you want to secure your public RPC methods.
    129 
    130         """
    131 
    132         if instance is None:
    133             raise Exception("'instance' needed")
    134         self.instance = instance
    135 
    136         if url is None:
    137             if hasattr(self.instance, "url"):
    138                 url = str(self.instance.url)
    139             else:
    140                 raise Exception("'url' needed")
    141         self.url = url
    142 
    143         self.allow_errors = allow_errors
     129    def generate_smd(self):
    144130
    145131        self.smd = {
    146132            "serviceType": "JSON-RPC",
    147             "serviceURL": self.url,
     133            "serviceURL": self.instance.url,
    148134            "methods": []
    149135        }
    150136
    151         if hasattr(self.instance, "methods") and \
    152             isinstance(self.instance.methods, dict):
    153             for key in self.instance.methods.keys():
    154                 params = [ {"name": val} for val in self.instance.methods[key] ]
    155                 self.smd["methods"].append({
    156                     "name": key,
    157                     "parameters": params
    158                 })
    159         elif auto_smd:
    160             public_methods = [ m for m in dir(instance) \
    161                 if ( m.startswith("public__") and callable(getattr(instance, m)) ) ]
    162             if public_methods:
    163                 try:
    164                     import inspect
    165                     for method in public_methods:
    166                         sig = inspect.getargspec(getattr(instance, method))
    167                         self.smd["methods"].append({
    168                             "name": method.replace("public__", ""),
    169                             "parameters": [ {"name": val} for val in sig.args if (val != "self") ]
    170                         })
    171                 except:
    172                     pass
    173                    
     137        if self.report_methods:
     138            self.smd["methods"] += [
     139                {"name": method, "parameters": getattr(self.instance, method).get_args()} \
     140                for method in self.get_public_methods()
     141            ]
    174142
     143        return simplejson.dumps(self.smd)
     144 
    175145    def dispatch(self, method, params):
    176         """
    177             The default dispatcher method.
    178 
    179             It looks for a method in the supplied instance
    180             of the form 'public__method' and calls it
    181             with the params.  If a method 'dispatch' exists
    182             in the supplied instance, then it will be used
    183             instead.
    184 
    185             Example:
    186 
    187                 class MyRPCMethods(object):
    188 
    189                     def dispatch(self, method, params):
    190 
    191                         if method == 'get_soup':
    192                             return 'No soup for you!'
    193                         elif method in ('get_cake', 'get_pie'):
    194                             return getattr(self, method)(*params)
    195                         else:
    196                             return 'Not on the menu!'
    197                     ...
    198         """
    199146
    200147        if hasattr(self.instance, "dispatch") and \
    201148            callable(self.instance.dispatch):
    202149            return self.instance.dispatch(method, params)
     150        elif method in self.get_public_methods():
     151            return getattr(self.instance, method)(*params)
    203152        else:
    204             if hasattr(self.instance, "public__" + method):
    205                 return getattr(self.instance, "public__" + method)(*params)
    206             else:
    207                 return "method not supported"
     153            return "no such method"
    208154
    209155    def serialize(self, raw_post_data):
    210         """
    211             Serializes the POST data from request.raw_post_data into JSON,
    212             calls the dispatcher to route the method call, then serializes
    213             and returns the response.
    214 
    215             TODO: Better error handling!
    216         """
    217156
    218157        raw_request        = simplejson.loads(raw_post_data)
     
    235174
    236175    def handle_request(self, request):
    237         """
    238             Takes an HttpRequest object and returns a JSON data structure
    239             either of the results of an RPC method call or
    240             the SMD if it was a GET request, or the POST was empty.
    241 
    242             In most cases, this can be returned directly to the client:
    243 
    244             return HttpResponse(rpc.handle_request(request))
    245         """
    246176
    247177        if request.method == "POST" and \
     
    249179            return self.serialize(request.raw_post_data)
    250180        else:
    251             return simplejson.dumps(self.smd)
    252 }}}
    253        
     181            return self.generate_smd()
Back to Top