diff wibble/server/__init__.py @ 4:ad5a8748afcf

Add test framework
author Ben Croston <ben@croston.org>
date Wed, 31 Aug 2011 21:35:14 +0100
parents server/JsonRpcApp.py@c7a236de5214
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wibble/server/__init__.py	Wed Aug 31 21:35:14 2011 +0100
@@ -0,0 +1,149 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2011 Ben Croston
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to
+# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is furnished to do
+# so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from json import loads, dumps
+import traceback
+import sys
+from webob import Request, Response, exc
+
+class JsonRpcApp(object):
+    """
+    Serve the given object via json-rpc (http://json-rpc.org/)
+    """
+
+    def __init__(self, obj, auth=None):
+        """
+        obj  - a class containing functions available using jsonrpc
+        auth - an authentication function (optional)
+        """
+        self.obj = obj
+        self.auth = auth
+
+    def __call__(self, environ, start_response):
+        req = Request(environ)
+        try:
+            resp = self._process(req)
+        except ValueError, e:
+            resp = exc.HTTPBadRequest(str(e))
+        except exc.HTTPException, e:
+            resp = e
+        return resp(environ, start_response)
+
+    def _process(self, req):
+        """
+        Process the JSONRPC request.
+        req - a webob Request object
+        """
+        if not req.method == 'POST':
+            raise exc.HTTPMethodNotAllowed("Only POST allowed").exception
+
+        try:
+            json = loads(req.body)
+        except ValueError, e:
+            raise ValueError('Bad JSON: %s' % e)
+
+        try:
+            method = json['method']
+            params = json['params']
+            id = json['id']
+            username = json['username'] if 'username' in json else None
+            password = json['password'] if 'password' in json else None
+        except KeyError, e:
+            raise ValueError("JSON body missing parameter: %s" % e)
+
+        if params is None:
+            params = []
+        if not isinstance(params, list):
+            raise ValueError("Bad params %r: must be a list" % params)
+            text = traceback.format_exc()
+            exc_value = sys.exc_info()[1]
+            error_value = dict(
+                name='JSONRPCError',
+                code=100,
+                message=str(exc_value),
+                error=text)
+            return Response(
+                status=500,
+                content_type='application/json',
+                body=dumps(dict(result=None,
+                                error=error_value,
+                                id=id)))
+
+        obj = self.obj
+        if isinstance(self.obj,tuple) or isinstance(self.obj,list):
+            for x in self.obj:
+                if method.startswith('%s.'%x.__class__.__name__):
+                   obj = x
+                   method = method.replace('%s.'%obj.__class__.__name__,'',1)
+                   break
+        elif method.startswith('%s.'%self.obj.__class__.__name__):
+            method = method.replace('%s.'%self.obj.__class__.__name__,'',1)
+        if method.startswith('_'):
+            raise exc.HTTPForbidden("Bad method name %s: must not start with _" % method).exception
+        try:
+            method = getattr(obj, method)
+        except AttributeError:
+            raise ValueError("No such method %s" % method)
+
+        if self.auth is not None:
+            try:
+                auth_result = self.auth(username, password, req.user_agent)
+            except:
+                text = traceback.format_exc()
+                exc_value = sys.exc_info()[1]
+                error_value = dict(
+                    name='JSONRPCError',
+                    code=100,
+                    message=str(exc_value),
+                    error=text)
+                return Response(
+                    status=500,
+                    content_type='application/json',
+                    body=dumps(dict(result=None,
+                                    error=error_value,
+                                    id=id)))
+            if not auth_result:
+                raise exc.HTTPUnauthorized().exception
+
+        try:
+            result = method(*params)
+        except:
+            text = traceback.format_exc()
+            exc_value = sys.exc_info()[1]
+            error_value = dict(
+                name='JSONRPCError',
+                code=100,
+                message=str(exc_value),
+                error=text)
+            return Response(
+                status=500,
+                content_type='application/json',
+                body=dumps(dict(result=None,
+                                error=error_value,
+                                id=id)))
+
+        return Response(
+            content_type='application/json',
+            body=dumps(dict(result=result,
+                            error=None,
+                            id=id)))
+