JsonRpc
This is an easy-to-use implementation of a JSON-RPC handler for Django. (A second alternative implementation which is even easier to use - but has less configuration options - is also included, below)
Features:
- Automatic SMD generation
- safe method resolution (ie, none of this stuff: http://secunia.com/advisories/14128/)
- No special urlconf or middleware magic
Example Django Usage
myproject/myapp/views.py: from jsonrpc import JsonRpc, publicmethod from django.core.urlresolvers import reverse from django.http import HttpResponse class MyRpcMethods(object): url = reverse("myapp-rpc") @publicmethod def add(x, y): return x+y @publicmethod def sub(x, y): return x-y def read_diary(self): return "Here's all my secrets ..." @publicmethod def find_person(attribs): filters = dict((key, val) for key, val in attribs.items() if key in ("first_name", "last_name")) return [ {"first_name": p.first_name, "last_name": p.last_name} \ for p in Person.objects.filter(**filters) ] @publicmethod def sayHello(*args): return "hello " # set the urls /myapp/rpc/ to this myapp.views.my_rpc_view and give it a name='myapp-rpc' # looks like this url(r'^/myapp/rpc/$', 'myapp.views.my_rpc_view', name='myapp-rpc'), # i cannot use the result give by this example, i use # return HttpResponse(result, mimetype='application/json') def my_rpc_view(request): rpc = JsonRpc( MyRpcMethods() ) result = rpc.handle_request(request) return result
Example Javascript client usage
var rpc = new dojo.rpc.JsonService("/myapp/rpc/"); rpc.add(3,4).addCallback(function(result) { console.log(result); }); >>> 7 rpc.callRemote("sub", [9,5]).addCallback(function(result) { console.log(result); }); >>> 4 rpc.callRemote("read_diary").addCallback(function(secrets) { console.log(secrets); }); >>> no such method rpc.find_person({last_name: "Baggins"}).addCallback(function(result) { dojo.forEach(result, function(person) { console.log("Found: " + person.first_name, person.last_name); }); }); >>> Found: Bilbo Baggins >>> Found: Frodo Baggins
JsonRpc
jsonrpc.py # # Copyright (c) 2009, Ben Wilber (benwilber@gmail.com) # All rights reserved # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License. You should have # received a copy of the GPL license along with this program; if you # did not, you can find it at http://www.gnu.org/ # class publicmethod(object): def __init__(self, method): self.method = method __public__ = True def __call__(self, *args, **kwargs): return self.method(*args, **kwargs) def get_args(self): from inspect import getargspec return [ a for a in getargspec(self.method).args if a != "self" ] class JsonRpc(object): def __init__(self, instance, allow_errors=True, report_methods=True): self.instance = instance self.allow_errors = allow_errors self.report_methods = report_methods if not hasattr(self.instance, "url"): raise Exception("'url' not present in supplied instance") def get_public_methods(self): return [ m for m in dir(self.instance) if \ getattr(self.instance, m).__class__.__name__ == "publicmethod" and \ getattr(self.instance, m).__public__ == True ] def generate_smd(self): smd = { "serviceType": "JSON-RPC", "serviceURL": self.instance.url, "methods": [] } if self.report_methods: smd["methods"] = [ {"name": method, "parameters": getattr(self.instance, method).get_args()} \ for method in self.get_public_methods() ] return simplejson.dumps(smd) def dispatch(self, method, params): if hasattr(self.instance, "dispatch") and \ callable(self.instance.dispatch): return self.instance.dispatch(method, params) elif method in self.get_public_methods(): return getattr(self.instance, method)(*params) else: return "no such method" def serialize(self, raw_post_data): raw_request = simplejson.loads(raw_post_data) request_id = raw_request.get("id", 0) request_method = raw_request.get("method") request_params = raw_request.get("params", []) response = {"id": request_id} try: response["result"] = self.dispatch(request_method, request_params) except: if self.allow_errors: from sys import exc_type, exc_value response["error"] = "%s: %s" % (exc_type, exc_value) else: response["error"] = "error" return simplejson.dumps(response) def handle_request(self, request): if request.method == "POST" and \ len(request.POST) > 0: return self.serialize(request.raw_post_data) else: return self.generate_smd()
Alternative JsonRpc Implementation
This is the proven and working JSON-RPC Django handler developed incrementally since 2007 and deployed in production Pyjamas Web 2.0 applications. It is much shorter, simpler, easier to use and less cumbersome than the above, although it does not have support for GET (to view a list of methods) and does not have the same level of configurability. Importantly, the original request object is passed to all functions in the JSONRPC service, thus allowing the application to gain access to Django session information, and cookies etc.
Usage examples are included inline in the source. Note in particular that the JSONRPCService class instance itself is handed to urlpatterns, and so all POST requests result in JSONRPCService's __call__
method being called, resulting in a much simpler approach than the above.
This code is maintained at http://groups.google.com/group/pyjamas-dev/files and a version is included in http://pyjs.org
# jsonrpc.py # original code: http://trac.pyworks.org/pyjamas/wiki/DjangoWithPyJamas # also from: http://www.pimentech.fr/technologies/outils from django.utils import simplejson import sys # JSONRPCService and jsonremote are used in combination to drastically # simplify the provision of JSONRPC services. use as follows: # # from jsonrpc import JSONRPCService, jsonremote # # jsonservice = JSONRPCService() # # @jsonremote(jsonservice) # def test(request, echo_param): # return "echoing the param back: %s", echo_param # # then dump jsonservice into urlpatterns: # (r'^service1/$', 'djangoapp.views.jsonservice'), def response(id, result): return simplejson.dumps({'version': '1.1', 'id':id, 'result':result, 'error':None}) def error(id, code, message): return simplejson.dumps({'id': id, 'version': '1.1', 'error': {'name': 'JSONRPCError', 'code': code, 'message': message } }) class JSONRPCService: def __init__(self, method_map={}): self.method_map = method_map def add_method(self, name, method): self.method_map[name] = method def __call__(self, request, extra=None): # We do not yet support GET requests, something pyjamas does # not use anyways. data = simplejson.loads(request.raw_post_data) # Altered to forward the request parameter when a member method # is invoked <julien@pimentech.net> id, method, params = data["id"],data["method"],[request,]+data["params"] if method in self.method_map: try: result = self.method_map[method](*params) return response(id, result) except BaseException: etype, eval, etb = sys.exc_info() return error(id, 100, '%s: %s' %(etype.__name__, eval)) except: etype, eval, etb = sys.exc_info() return error(id, 100, 'Exception %s: %s' %(etype, eval)) else: return error(id, 100, 'method "%s" does not exist' % method) def jsonremote(service): """Make JSONRPCService a decorator so that you can write : from jsonrpc import JSONRPCService chatservice = JSONRPCService() @jsonremote(chatservice) def login(request, user_name): (...) """ def remotify(func): if isinstance(service, JSONRPCService): service.add_method(func.__name__, func) else: emsg = 'Service "%s" not found' % service.__name__ raise NotImplementedError, emsg return func return remotify