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