Changes between Version 3 and Version 4 of Jsonrpc
- Timestamp:
- Jun 4, 2009, 12:30:59 PM (15 years ago)
Legend:
- Unmodified
- Added
- Removed
- Modified
-
Jsonrpc
v3 v4 6 6 '''Features:''' 7 7 8 - Easy to use[[BR]]9 - Extensible[[BR]]10 8 - Automatic SMD generation[[BR]] 11 9 - safe method resolution (ie, none of this stuff: [http://secunia.com/advisories/14128/])[[BR]] … … 17 15 myproject/myapp/views.py: 18 16 17 class 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 19 45 def my_rpc_view(request): 20 46 21 from jsonrpc import JsonRpc22 class MyRpcMethods(object):47 rpc = JsonRpc( MyRpcMethods() ) 48 result = rpc.handle_request(request) 23 49 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 50 51 }}} 51 52 … … 60 61 >>> 7 61 62 62 rpc.callRemote("sub tract", [9,5]).addCallback(function(result) {63 rpc.callRemote("sub", [9,5]).addCallback(function(result) { 63 64 console.log(result); 64 65 }); 65 66 >>> 4 66 67 67 rpc. multiply(5, 4).addCallback(function(result) {68 console.log( result);68 rpc.read_diary().addCallback(function(secrets) { 69 console.log(secrets); 69 70 }); 70 >>> 2071 >>> no such method 71 72 72 73 rpc.find_person({last_name: "Baggins"}).addCallback(function(result) { … … 94 95 # 95 96 97 class 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 96 110 class JsonRpc(object): 97 111 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): 103 113 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 106 117 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") 110 120 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): 113 122 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 ] 115 128 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): 144 130 145 131 self.smd = { 146 132 "serviceType": "JSON-RPC", 147 "serviceURL": self. url,133 "serviceURL": self.instance.url, 148 134 "methods": [] 149 135 } 150 136 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 ] 174 142 143 return simplejson.dumps(self.smd) 144 175 145 def dispatch(self, method, params): 176 """177 The default dispatcher method.178 179 It looks for a method in the supplied instance180 of the form 'public__method' and calls it181 with the params. If a method 'dispatch' exists182 in the supplied instance, then it will be used183 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 """199 146 200 147 if hasattr(self.instance, "dispatch") and \ 201 148 callable(self.instance.dispatch): 202 149 return self.instance.dispatch(method, params) 150 elif method in self.get_public_methods(): 151 return getattr(self.instance, method)(*params) 203 152 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" 208 154 209 155 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 serializes213 and returns the response.214 215 TODO: Better error handling!216 """217 156 218 157 raw_request = simplejson.loads(raw_post_data) … … 235 174 236 175 def handle_request(self, request): 237 """238 Takes an HttpRequest object and returns a JSON data structure239 either of the results of an RPC method call or240 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 """246 176 247 177 if request.method == "POST" and \ … … 249 179 return self.serialize(request.raw_post_data) 250 180 else: 251 return simplejson.dumps(self.smd) 252 }}} 253 181 return self.generate_smd()