diff options
Diffstat (limited to 'python/jsonrpclib/SimpleJSONRPCServer.py')
-rw-r--r-- | python/jsonrpclib/SimpleJSONRPCServer.py | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/python/jsonrpclib/SimpleJSONRPCServer.py b/python/jsonrpclib/SimpleJSONRPCServer.py new file mode 100644 index 0000000..d76da73 --- /dev/null +++ b/python/jsonrpclib/SimpleJSONRPCServer.py @@ -0,0 +1,229 @@ +import jsonrpclib +from jsonrpclib import Fault +from jsonrpclib.jsonrpc import USE_UNIX_SOCKETS +import SimpleXMLRPCServer +import SocketServer +import socket +import logging +import os +import types +import traceback +import sys +try: + import fcntl +except ImportError: + # For Windows + fcntl = None + +def get_version(request): + # 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): + + 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 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) + 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 + # 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: + 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 = SimpleXMLRPCServer.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, + address_family=socket.AF_INET): + self.logRequests = logRequests + SimpleJSONRPCDispatcher.__init__(self, encoding) + # TCPServer.__init__ has an extra parameter on 2.6+, so + # check Python version and decide on how to call it + vi = sys.version_info + self.address_family = address_family + if USE_UNIX_SOCKETS and address_family == socket.AF_UNIX: + # Unix sockets can't be bound if they already exist in the + # filesystem. The convention of e.g. X11 is to unlink + # before binding again. + if os.path.exists(addr): + try: + os.unlink(addr) + except OSError: + logging.warning("Could not unlink socket %s", addr) + # 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 + 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 |