Code

Changes between Version 3 and Version 4 of Jsonrpc


Ignore:
Timestamp:
06/04/09 10:30:59 (5 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()