Setting fcntl to None if not importable, adding tests module, updating README to...
[invirt/packages/python-jsonrpclib.git] / jsonrpclib / SimpleJSONRPCServer.py
index ae41f93..370ae40 100644 (file)
@@ -4,24 +4,43 @@ import SimpleXMLRPCServer
 import SocketServer
 import types
 import traceback
-import fcntl
 import sys
+try:
+    import fcntl
+except ImportError:
+    # For Windows
+    fcntl = None
 
 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
+    
+def validate_request(request):
+    if type(request) is not types.DictType:
+        fault = Fault(
+            -32600, 'Request must be {}, not %s.' % type(request)
+        )
+        return fault
+    rpcid = request.get('id', None)
+    version = get_version(request)
+    if not version:
+        fault = Fault(-32600, 'Request %s invalid.' % request, rpcid=rpcid)
+        return fault        
+    request.setdefault('params', [])
+    method = request.get('method', None)
+    params = request.get('params')
+    param_types = (types.ListType, types.DictType, types.TupleType)
+    if not method or type(method) not in types.StringTypes or \
+        type(params) not in param_types:
+        fault = Fault(
+            -32600, 'Invalid request parameters or method.', rpcid=rpcid
+        )
+        return fault
+    return True
 
 class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
 
@@ -34,34 +53,42 @@ class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
         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)
+        except Exception, e:
+            fault = Fault(-32700, 'Request %s invalid. (%s)' % (data, e))
             response = fault.response()
             return response
+        if not request:
+            fault = Fault(-32600, 'Request invalid -- no request data.')
+            return fault.response()
         if type(request) is types.ListType:
             # This SHOULD be a batch, by spec
             responses = []
             for req_entry in request:
+                result = validate_request(req_entry)
+                if type(result) is Fault:
+                    responses.append(result.response())
+                    continue
                 resp_entry = self._marshaled_single_dispatch(req_entry)
                 if resp_entry is not None:
                     responses.append(resp_entry)
-            response = '[%s]' % ','.join(responses)
-        else:
+            if len(responses) > 0:
+                response = '[%s]' % ','.join(responses)
+            else:
+                response = ''
+        else:    
+            result = validate_request(request)
+            if type(result) is Fault:
+                return result.response()
             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)
+        method = request.get('method')
+        params = request.get('params')
         try:
             response = self._dispatch(method, params)
         except:
@@ -92,7 +119,7 @@ class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
                     return self.instance._dispatch(method, params)
                 else:
                     try:
-                        func = resolve_dotted_attribute(
+                        func = SimpleXMLRPCServer.resolve_dotted_attribute(
                             self.instance,
                             method,
                             True
@@ -150,8 +177,7 @@ class SimpleJSONRPCRequestHandler(
         self.wfile.flush()
         self.connection.shutdown(1)
 
-class SimpleJSONRPCServer(SocketServer.TCPServer,
-                         SimpleJSONRPCDispatcher):
+class SimpleJSONRPCServer(SocketServer.TCPServer, SimpleJSONRPCDispatcher):
 
     allow_reuse_address = True
 
@@ -159,8 +185,15 @@ class SimpleJSONRPCServer(SocketServer.TCPServer,
                  logRequests=True, encoding=None, bind_and_activate=True):
         self.logRequests = logRequests
         SimpleJSONRPCDispatcher.__init__(self, encoding)
-        SocketServer.TCPServer.__init__(self, addr, requestHandler,
-                                        bind_and_activate)
+        # TCPServer.__init__ has an extra parameter on 2.6+, so
+        # check Python version and decide on how to call it
+        vi = sys.version_info
+        # if python 2.5 and lower
+        if vi[0] < 3 and vi[1] < 6:
+            SocketServer.TCPServer.__init__(self, addr, requestHandler)
+        else:
+            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
@@ -179,10 +212,3 @@ class CGIJSONRPCRequestHandler(SimpleJSONRPCDispatcher):
         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()