Oops -- NOW adding the new jsonrpclib/ files (jsonclass, config, history, __init__)
[invirt/packages/python-jsonrpclib.git] / jsonrpclib / SimpleJSONRPCServer.py
diff --git a/jsonrpclib/SimpleJSONRPCServer.py b/jsonrpclib/SimpleJSONRPCServer.py
new file mode 100644 (file)
index 0000000..ae41f93
--- /dev/null
@@ -0,0 +1,188 @@
+import jsonrpclib
+from jsonrpclib import Fault
+import SimpleXMLRPCServer
+import SocketServer
+import types
+import traceback
+import fcntl
+import sys
+
+def get_version(request):
+    if type(request) not in (types.ListType, types.DictType):
+        return None
+    if type(request) is types.ListType:
+        if len(request) == 0:
+            return None
+        if 'jsonrpc' not in request[0].keys():
+            return None
+        return '2.0'
+    # must be a dict
+    if 'jsonrpc' in request.keys():
+        return 2.0
+    if 'id' in request.keys():
+        return 1.0
+    return None
+
+class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
+
+    def __init__(self, encoding=None):
+        SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self,
+                                        allow_none=True,
+                                        encoding=encoding)
+
+    def _marshaled_dispatch(self, data, dispatch_method = None):
+        response = None
+        try:
+            request = jsonrpclib.loads(data)
+        except:
+            fault = Fault(-32600, 'Request %s invalid.' % data)
+            response = fault.response()
+            return response
+        version = get_version(request)
+        if not version:
+            fault = Fault(-32600, 'Request %s invalid.' % data)
+            response = fault.response()
+            return response
+        if type(request) is types.ListType:
+            # This SHOULD be a batch, by spec
+            responses = []
+            for req_entry in request:
+                resp_entry = self._marshaled_single_dispatch(req_entry)
+                if resp_entry is not None:
+                    responses.append(resp_entry)
+            response = '[%s]' % ','.join(responses)
+        else:
+            response = self._marshaled_single_dispatch(request)
+        return response
+
+    def _marshaled_single_dispatch(self, request):
+        # TODO - Use the multiprocessing and skip the response if
+        # it is a notification
+        method = request['method']
+        params = request['params']
+        # Put in support for custom dispatcher here
+        # (See SimpleXMLRPCServer._marshaled_dispatch)
+        try:
+            response = self._dispatch(method, params)
+        except:
+            exc_type, exc_value, exc_tb = sys.exc_info()
+            fault = Fault(-32603, '%s:%s' % (exc_type, exc_value))
+            return fault.response()
+        if 'id' not in request.keys() or request['id'] == None:
+            # It's a notification
+            return None
+        try:
+            response = jsonrpclib.dumps(response,
+                                        methodresponse=True,
+                                        rpcid=request['id']
+                                        )
+            return response
+        except:
+            exc_type, exc_value, exc_tb = sys.exc_info()
+            fault = Fault(-32603, '%s:%s' % (exc_type, exc_value))
+            return fault.response()
+
+    def _dispatch(self, method, params):
+        func = None
+        try:
+            func = self.funcs[method]
+        except KeyError:
+            if self.instance is not None:
+                if hasattr(self.instance, '_dispatch'):
+                    return self.instance._dispatch(method, params)
+                else:
+                    try:
+                        func = resolve_dotted_attribute(
+                            self.instance,
+                            method,
+                            True
+                            )
+                    except AttributeError:
+                        pass
+        if func is not None:
+            try:
+                if type(params) is types.ListType:
+                    response = func(*params)
+                else:
+                    response = func(**params)
+                return response
+            except TypeError:
+                return Fault(-32602, 'Invalid parameters.')
+            except:
+                err_lines = traceback.format_exc().splitlines()
+                trace_string = '%s | %s' % (err_lines[-3], err_lines[-1])
+                fault = jsonrpclib.Fault(-32603, 'Server error: %s' % 
+                                         trace_string)
+                return fault
+        else:
+            return Fault(-32601, 'Method %s not supported.' % method)
+
+class SimpleJSONRPCRequestHandler(
+        SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
+    
+    def do_POST(self):
+        if not self.is_rpc_path_valid():
+            self.report_404()
+            return
+        try:
+            max_chunk_size = 10*1024*1024
+            size_remaining = int(self.headers["content-length"])
+            L = []
+            while size_remaining:
+                chunk_size = min(size_remaining, max_chunk_size)
+                L.append(self.rfile.read(chunk_size))
+                size_remaining -= len(L[-1])
+            data = ''.join(L)
+            response = self.server._marshaled_dispatch(data)
+            self.send_response(200)
+        except Exception, e:
+            self.send_response(500)
+            err_lines = traceback.format_exc().splitlines()
+            trace_string = '%s | %s' % (err_lines[-3], err_lines[-1])
+            fault = jsonrpclib.Fault(-32603, 'Server error: %s' % trace_string)
+            response = fault.response()
+        if response == None:
+            response = ''
+        self.send_header("Content-type", "application/json-rpc")
+        self.send_header("Content-length", str(len(response)))
+        self.end_headers()
+        self.wfile.write(response)
+        self.wfile.flush()
+        self.connection.shutdown(1)
+
+class SimpleJSONRPCServer(SocketServer.TCPServer,
+                         SimpleJSONRPCDispatcher):
+
+    allow_reuse_address = True
+
+    def __init__(self, addr, requestHandler=SimpleJSONRPCRequestHandler,
+                 logRequests=True, encoding=None, bind_and_activate=True):
+        self.logRequests = logRequests
+        SimpleJSONRPCDispatcher.__init__(self, encoding)
+        SocketServer.TCPServer.__init__(self, addr, requestHandler,
+                                        bind_and_activate)
+        if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
+            flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
+            flags |= fcntl.FD_CLOEXEC
+            fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
+
+class CGIJSONRPCRequestHandler(SimpleJSONRPCDispatcher):
+
+    def __init__(self, encoding=None):
+        SimpleJSONRPCDispatcher.__init__(self, encoding)
+
+    def handle_jsonrpc(self, request_text):
+        response = self._marshaled_dispatch(request_text)
+        print 'Content-Type: application/json-rpc'
+        print 'Content-Length: %d' % len(response)
+        print
+        sys.stdout.write(response)
+
+    handle_xmlrpc = handle_jsonrpc
+
+if __name__ == '__main__':
+    print 'Running JSON-RPC server on port 8000'
+    server = SimpleJSONRPCServer(("localhost", 8000))
+    server.register_function(pow)
+    server.register_function(lambda x,y: x+y, 'add')
+    server.serve_forever()