| Version 1 (modified by , 16 years ago) ( diff ) |
|---|
JsonRpc
This is an easy-to-use implementation of a JSON-RPC handler for Django.
Features:
- Easy to use
- Extensible
- 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:
def my_rpc_view(request):
from jsonrpc import JsonRpc
class MyRpcMethods(object):
url = reverse("myapp-rpc")
public__add(self, x, y):
return x+y
public__subtract(self, x, y):
return x-y
@staticmethod
public__multiply(x, y):
return x*y
public__find_person(self, attribs):
filters = {}
for key in attribs.keys():
if key in ("first_name", "last_name"):
filters[key] = attribs.get(key)
return Person.objects.filter(**filters).values()
rpc = JsonRpc( MyRpcMethods() )
result = rpc.handle_request(request)
return HttpResponse(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("subtract", [9,5]).addCallback(function(result) {
console.log(result);
});
>>> 4
rpc.multiply(5, 4).addCallback(function(result) {
console.log(result);
});
>>> 20
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
class JsonRpc(object):
def __init__(self, instance=None, url=None, allow_errors=True, auto_smd=True):
"""
'instance' is required and is an object containing public
RPC methods (like what you would pass to register_instance in SimpleXMLRPCServer)
Passing arbitrary functions (register_function) isn't supported yet
'url' is optional if given as an attibute of the instance. It's where the JSON-RPC
client posts its RPC requests
'allow_errors' tells the serializer to report exception values under the key 'error' in
the result dict back to the client. If set to False and an exception occurs, the client
will just get: {'error': 'error'}
'auto_smd' lets us introspect the instance to find the RPC methods and generate the
the appropriate method/signature descriptions.
If the instance contains a dict attribute 'methods', then that will be used instead
class MyRpcMethods(object):
methods = {'add': ['x','y']}
# Will be reported
public__add(self, x,y): return x+y
# Wont be reported
public__subtract(self, x,y): return x-y
This is useful if you want to filter the reported methods based on some other condition
than the 'public__' prefix --- be advised that a clever client can still call non-reported
methods via a stringified method name ie with Dojo's 'callRemote' method. So this shouldn't
be used for securty. See the 'dispatch' method below if you want to secure your public RPC methods.
"""
if instance is None:
raise Exception("'instance' needed")
self.instance = instance
if url is None:
if hasattr(self.instance, "url"):
url = str(self.instance.url)
else:
raise Exception("'url' needed")
self.url = url
self.allow_errors = allow_errors
self.smd = {
"serviceType": "JSON-RPC",
"serviceURL": self.url,
"methods": []
}
if hasattr(self.instance, "methods") and \
isinstance(self.instance.methods, dict):
for key in self.instance.methods.keys():
params = [ {"name": val} for val in self.instance.methods[key] ]
self.smd["methods"].append({
"name": key,
"parameters": params
})
elif auto_smd:
public_methods = [ m for m in dir(instance) \
if ( m.startswith("public__") and callable(getattr(instance, m)) ) ]
if public_methods:
try:
import inspect
for method in public_methods:
sig = inspect.getargspec(getattr(instance, method))
self.smd["methods"].append({
"name": method.replace("public__", ""),
"parameters": [ {"name": val} for val in sig.args if (val != "self") ]
})
except:
pass
def dispatch(self, method, params):
"""
The default dispatcher method.
It looks for a method in the supplied instance
of the form 'public__method' and calls it
with the params. If a method 'dispatch' exists
in the supplied instance, then it will be used
instead.
Example:
class MyRPCMethods(object):
def dispatch(self, method, params):
if method == 'get_soup':
return 'No soup for you!'
elif method in ('get_cake', 'get_pie'):
return getattr(self, method)(*params)
else:
return 'Not on the menu!'
...
"""
if hasattr(self.instance, "dispatch") and \
callable(self.instance.dispatch):
return self.instance.dispatch(method, params)
else:
if hasattr(self.instance, "public__" + method):
return getattr(self.instance, "public__" + method)(*params)
else:
return "method not supported"
def serialize(self, raw_post_data):
"""
Serializes the POST data from request.raw_post_data into JSON,
calls the dispatcher to route the method call, then serializes
and returns the response.
TODO: Better error handling!
"""
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):
"""
Takes an HttpRequest object and returns a JSON data structure
either of the results of an RPC method call or
the SMD if it was a GET request, or the POST was empty.
In most cases, this can be returned directly to the client:
return HttpResponse(rpc.handle_request(request))
"""
if request.method == "POST" and \
len(request.POST) > 0:
return self.serialize(request.raw_post_data)
else:
return simplejson.dumps(self.smd)
Note:
See TracWiki
for help on using the wiki.