summaryrefslogtreecommitdiffstats
path: root/external_libs/python/jsonrpclib-pelix-0.2.5/jsonrpclib/SimpleJSONRPCServer.py
diff options
context:
space:
mode:
Diffstat (limited to 'external_libs/python/jsonrpclib-pelix-0.2.5/jsonrpclib/SimpleJSONRPCServer.py')
-rw-r--r--external_libs/python/jsonrpclib-pelix-0.2.5/jsonrpclib/SimpleJSONRPCServer.py602
1 files changed, 602 insertions, 0 deletions
diff --git a/external_libs/python/jsonrpclib-pelix-0.2.5/jsonrpclib/SimpleJSONRPCServer.py b/external_libs/python/jsonrpclib-pelix-0.2.5/jsonrpclib/SimpleJSONRPCServer.py
new file mode 100644
index 00000000..e9fe4e68
--- /dev/null
+++ b/external_libs/python/jsonrpclib-pelix-0.2.5/jsonrpclib/SimpleJSONRPCServer.py
@@ -0,0 +1,602 @@
+#!/usr/bin/python
+# -- Content-Encoding: UTF-8 --
+"""
+Defines a request dispatcher, a HTTP request handler, a HTTP server and a
+CGI request handler.
+
+:authors: Josh Marshall, Thomas Calmant
+:copyright: Copyright 2015, isandlaTech
+:license: Apache License 2.0
+:version: 0.2.5
+
+..
+
+ Copyright 2015 isandlaTech
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+# Module version
+__version_info__ = (0, 2, 5)
+__version__ = ".".join(str(x) for x in __version_info__)
+
+# Documentation strings format
+__docformat__ = "restructuredtext en"
+
+# ------------------------------------------------------------------------------
+# Local modules
+from jsonrpclib import Fault
+import jsonrpclib.config
+import jsonrpclib.utils as utils
+import jsonrpclib.threadpool
+
+# Standard library
+import logging
+import socket
+import sys
+import traceback
+
+# Prepare the logger
+_logger = logging.getLogger(__name__)
+
+try:
+ # Python 3
+ # pylint: disable=F0401,E0611
+ import xmlrpc.server as xmlrpcserver
+ import socketserver
+except (ImportError, AttributeError):
+ # Python 2 or IronPython
+ # pylint: disable=F0401,E0611
+ import SimpleXMLRPCServer as xmlrpcserver
+ import SocketServer as socketserver
+
+try:
+ # Windows
+ import fcntl
+except ImportError:
+ # Other systems
+ # pylint: disable=C0103
+ fcntl = None
+
+# ------------------------------------------------------------------------------
+
+
+def get_version(request):
+ """
+ Computes the JSON-RPC version
+
+ :param request: A request dictionary
+ :return: The JSON-RPC version or None
+ """
+ if 'jsonrpc' in request:
+ return 2.0
+ elif 'id' in request:
+ return 1.0
+
+ return None
+
+
+def validate_request(request, json_config):
+ """
+ Validates the format of a request dictionary
+
+ :param request: A request dictionary
+ :param json_config: A JSONRPClib Config instance
+ :return: True if the dictionary is valid, else a Fault object
+ """
+ if not isinstance(request, utils.DictType):
+ # Invalid request type
+ fault = Fault(-32600, 'Request must be a dict, not {0}'
+ .format(type(request).__name__),
+ config=json_config)
+ _logger.warning("Invalid request content: %s", fault)
+ return fault
+
+ # Get the request ID
+ rpcid = request.get('id', None)
+
+ # Check request version
+ version = get_version(request)
+ if not version:
+ fault = Fault(-32600, 'Request {0} invalid.'.format(request),
+ rpcid=rpcid, config=json_config)
+ _logger.warning("No version in request: %s", fault)
+ return fault
+
+ # Default parameters: empty list
+ request.setdefault('params', [])
+
+ # Check parameters
+ method = request.get('method', None)
+ params = request.get('params')
+ param_types = (utils.ListType, utils.DictType, utils.TupleType)
+
+ if not method or not isinstance(method, utils.string_types) or \
+ not isinstance(params, param_types):
+ # Invalid type of method name or parameters
+ fault = Fault(-32600, 'Invalid request parameters or method.',
+ rpcid=rpcid, config=json_config)
+ _logger.warning("Invalid request content: %s", fault)
+ return fault
+
+ # Valid request
+ return True
+
+# ------------------------------------------------------------------------------
+
+
+class NoMulticallResult(Exception):
+ """
+ No result in multicall
+ """
+ pass
+
+
+class SimpleJSONRPCDispatcher(xmlrpcserver.SimpleXMLRPCDispatcher, object):
+ """
+ Mix-in class that dispatches JSON-RPC requests.
+
+ This class is used to register JSON-RPC method handlers
+ and then to dispatch them. This class doesn't need to be
+ instanced directly when used by SimpleJSONRPCServer.
+ """
+ def __init__(self, encoding=None, config=jsonrpclib.config.DEFAULT):
+ """
+ Sets up the dispatcher with the given encoding.
+ None values are allowed.
+ """
+ xmlrpcserver.SimpleXMLRPCDispatcher.__init__(
+ self, allow_none=True, encoding=encoding or "UTF-8")
+ self.json_config = config
+
+ # Notification thread pool
+ self.__notification_pool = None
+
+ def set_notification_pool(self, thread_pool):
+ """
+ Sets the thread pool to use to handle notifications
+ """
+ self.__notification_pool = thread_pool
+
+ def _unmarshaled_dispatch(self, request, dispatch_method=None):
+ """
+ Loads the request dictionary (unmarshaled), calls the method(s)
+ accordingly and returns a JSON-RPC dictionary (not marshaled)
+
+ :param request: JSON-RPC request dictionary (or list of)
+ :param dispatch_method: Custom dispatch method (for method resolution)
+ :return: A JSON-RPC dictionary (or an array of) or None if the request
+ was a notification
+ :raise NoMulticallResult: No result in batch
+ """
+ if not request:
+ # Invalid request dictionary
+ fault = Fault(-32600, 'Request invalid -- no request data.',
+ config=self.json_config)
+ _logger.warning("Invalid request: %s", fault)
+ return fault.dump()
+
+ if isinstance(request, utils.ListType):
+ # This SHOULD be a batch, by spec
+ responses = []
+ for req_entry in request:
+ # Validate the request
+ result = validate_request(req_entry, self.json_config)
+ if isinstance(result, Fault):
+ responses.append(result.dump())
+ continue
+
+ # Call the method
+ resp_entry = self._marshaled_single_dispatch(req_entry,
+ dispatch_method)
+
+ # Store its result
+ if isinstance(resp_entry, Fault):
+ # pylint: disable=E1103
+ responses.append(resp_entry.dump())
+ elif resp_entry is not None:
+ responses.append(resp_entry)
+
+ if not responses:
+ # No non-None result
+ _logger.error("No result in Multicall")
+ raise NoMulticallResult("No result")
+
+ return responses
+
+ else:
+ # Single call
+ result = validate_request(request, self.json_config)
+ if isinstance(result, Fault):
+ return result.dump()
+
+ # Call the method
+ response = self._marshaled_single_dispatch(request,
+ dispatch_method)
+ if isinstance(response, Fault):
+ # pylint: disable=E1103
+ return response.dump()
+
+ return response
+
+ def _marshaled_dispatch(self, data, dispatch_method=None, path=None):
+ """
+ Parses the request data (marshaled), calls method(s) and returns a
+ JSON string (marshaled)
+
+ :param data: A JSON request string
+ :param dispatch_method: Custom dispatch method (for method resolution)
+ :param path: Unused parameter, to keep compatibility with xmlrpclib
+ :return: A JSON-RPC response string (marshaled)
+ """
+ # Parse the request
+ try:
+ request = jsonrpclib.loads(data, self.json_config)
+ except Exception as ex:
+ # Parsing/loading error
+ fault = Fault(-32700, 'Request {0} invalid. ({1}:{2})'
+ .format(data, type(ex).__name__, ex),
+ config=self.json_config)
+ _logger.warning("Error parsing request: %s", fault)
+ return fault.response()
+
+ # Get the response dictionary
+ try:
+ response = self._unmarshaled_dispatch(request, dispatch_method)
+ if response is not None:
+ # Compute the string representation of the dictionary/list
+ return jsonrpclib.jdumps(response, self.encoding)
+ else:
+ # No result (notification)
+ return ''
+ except NoMulticallResult:
+ # Return an empty string (jsonrpclib internal behaviour)
+ return ''
+
+ def _marshaled_single_dispatch(self, request, dispatch_method=None):
+ """
+ Dispatches a single method call
+
+ :param request: A validated request dictionary
+ :param dispatch_method: Custom dispatch method (for method resolution)
+ :return: A JSON-RPC response dictionary, or None if it was a
+ notification request
+ """
+ method = request.get('method')
+ params = request.get('params')
+
+ # Prepare a request-specific configuration
+ if 'jsonrpc' not in request and self.json_config.version >= 2:
+ # JSON-RPC 1.0 request on a JSON-RPC 2.0
+ # => compatibility needed
+ config = self.json_config.copy()
+ config.version = 1.0
+ else:
+ # Keep server configuration as is
+ config = self.json_config
+
+ # Test if this is a notification request
+ is_notification = 'id' not in request or request['id'] in (None, '')
+ if is_notification and self.__notification_pool is not None:
+ # Use the thread pool for notifications
+ if dispatch_method is not None:
+ self.__notification_pool.enqueue(dispatch_method,
+ method, params)
+ else:
+ self.__notification_pool.enqueue(self._dispatch,
+ method, params, config)
+
+ # Return immediately
+ return None
+ else:
+ # Synchronous call
+ try:
+ # Call the method
+ if dispatch_method is not None:
+ response = dispatch_method(method, params)
+ else:
+ response = self._dispatch(method, params, config)
+ except Exception as ex:
+ # Return a fault
+ fault = Fault(-32603, '{0}:{1}'.format(type(ex).__name__, ex),
+ config=config)
+ _logger.error("Error calling method %s: %s", method, fault)
+ return fault.dump()
+
+ if is_notification:
+ # It's a notification, no result needed
+ # Do not use 'not id' as it might be the integer 0
+ return None
+
+ # Prepare a JSON-RPC dictionary
+ try:
+ return jsonrpclib.dump(response, rpcid=request['id'],
+ is_response=True, config=config)
+ except Exception as ex:
+ # JSON conversion exception
+ fault = Fault(-32603, '{0}:{1}'.format(type(ex).__name__, ex),
+ config=config)
+ _logger.error("Error preparing JSON-RPC result: %s", fault)
+ return fault.dump()
+
+ def _dispatch(self, method, params, config=None):
+ """
+ Default method resolver and caller
+
+ :param method: Name of the method to call
+ :param params: List of arguments to give to the method
+ :param config: Request-specific configuration
+ :return: The result of the method
+ """
+ config = config or self.json_config
+
+ func = None
+ try:
+ # Look into registered methods
+ func = self.funcs[method]
+ except KeyError:
+ if self.instance is not None:
+ # Try with the registered instance
+ try:
+ # Instance has a custom dispatcher
+ return getattr(self.instance, '_dispatch')(method, params)
+ except AttributeError:
+ # Resolve the method name in the instance
+ try:
+ func = xmlrpcserver.resolve_dotted_attribute(
+ self.instance, method, True)
+ except AttributeError:
+ # Unknown method
+ pass
+
+ if func is not None:
+ try:
+ # Call the method
+ if isinstance(params, utils.ListType):
+ return func(*params)
+ else:
+ return func(**params)
+ except TypeError as ex:
+ # Maybe the parameters are wrong
+ fault = Fault(-32602, 'Invalid parameters: {0}'.format(ex),
+ config=config)
+ _logger.warning("Invalid call parameters: %s", fault)
+ return fault
+ except:
+ # Method exception
+ err_lines = traceback.format_exc().splitlines()
+ trace_string = '{0} | {1}'.format(err_lines[-3], err_lines[-1])
+ fault = Fault(-32603, 'Server error: {0}'.format(trace_string),
+ config=config)
+ _logger.exception("Server-side exception: %s", fault)
+ return fault
+ else:
+ # Unknown method
+ fault = Fault(-32601, 'Method {0} not supported.'.format(method),
+ config=config)
+ _logger.warning("Unknown method: %s", fault)
+ return fault
+
+# ------------------------------------------------------------------------------
+
+
+class SimpleJSONRPCRequestHandler(xmlrpcserver.SimpleXMLRPCRequestHandler):
+ """
+ HTTP request handler.
+
+ The server that receives the requests must have a json_config member,
+ containing a JSONRPClib Config instance
+ """
+ def do_POST(self):
+ """
+ Handles POST requests
+ """
+ if not self.is_rpc_path_valid():
+ self.report_404()
+ return
+
+ # Retrieve the configuration
+ config = getattr(self.server, 'json_config', jsonrpclib.config.DEFAULT)
+
+ try:
+ # Read the request body
+ max_chunk_size = 10 * 1024 * 1024
+ size_remaining = int(self.headers["content-length"])
+ chunks = []
+ while size_remaining:
+ chunk_size = min(size_remaining, max_chunk_size)
+ raw_chunk = self.rfile.read(chunk_size)
+ if not raw_chunk:
+ break
+ chunks.append(utils.from_bytes(raw_chunk))
+ size_remaining -= len(chunks[-1])
+ data = ''.join(chunks)
+
+ try:
+ # Decode content
+ data = self.decode_request_content(data)
+ if data is None:
+ # Unknown encoding, response has been sent
+ return
+ except AttributeError:
+ # Available since Python 2.7
+ pass
+
+ # Execute the method
+ response = self.server._marshaled_dispatch(
+ data, getattr(self, '_dispatch', None), self.path)
+
+ # No exception: send a 200 OK
+ self.send_response(200)
+ except:
+ # Exception: send 500 Server Error
+ self.send_response(500)
+ err_lines = traceback.format_exc().splitlines()
+ trace_string = '{0} | {1}'.format(err_lines[-3], err_lines[-1])
+ fault = jsonrpclib.Fault(-32603, 'Server error: {0}'
+ .format(trace_string), config=config)
+ _logger.exception("Server-side error: %s", fault)
+ response = fault.response()
+
+ if response is None:
+ # Avoid to send None
+ response = ''
+
+ # Convert the response to the valid string format
+ response = utils.to_bytes(response)
+
+ # Send it
+ self.send_header("Content-type", config.content_type)
+ self.send_header("Content-length", str(len(response)))
+ self.end_headers()
+ if response:
+ self.wfile.write(response)
+
+# ------------------------------------------------------------------------------
+
+
+class SimpleJSONRPCServer(socketserver.TCPServer, SimpleJSONRPCDispatcher):
+ """
+ JSON-RPC server (and dispatcher)
+ """
+ # This simplifies server restart after error
+ allow_reuse_address = True
+
+ # pylint: disable=C0103
+ def __init__(self, addr, requestHandler=SimpleJSONRPCRequestHandler,
+ logRequests=True, encoding=None, bind_and_activate=True,
+ address_family=socket.AF_INET,
+ config=jsonrpclib.config.DEFAULT):
+ """
+ Sets up the server and the dispatcher
+
+ :param addr: The server listening address
+ :param requestHandler: Custom request handler
+ :param logRequests: Flag to(de)activate requests logging
+ :param encoding: The dispatcher request encoding
+ :param bind_and_activate: If True, starts the server immediately
+ :param address_family: The server listening address family
+ :param config: A JSONRPClib Config instance
+ """
+ # Set up the dispatcher fields
+ SimpleJSONRPCDispatcher.__init__(self, encoding, config)
+
+ # Prepare the server configuration
+ # logRequests is used by SimpleXMLRPCRequestHandler
+ self.logRequests = logRequests
+ self.address_family = address_family
+ self.json_config = config
+
+ # Work on the request handler
+ class RequestHandlerWrapper(requestHandler, object):
+ """
+ Wraps the request handle to have access to the configuration
+ """
+ def __init__(self, *args, **kwargs):
+ """
+ Constructs the wrapper after having stored the configuration
+ """
+ self.config = config
+ super(RequestHandlerWrapper, self).__init__(*args, **kwargs)
+
+ # Set up the server
+ socketserver.TCPServer.__init__(self, addr, requestHandler,
+ bind_and_activate)
+
+ # Windows-specific
+ 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 PooledJSONRPCServer(SimpleJSONRPCServer, socketserver.ThreadingMixIn):
+ """
+ JSON-RPC server based on a thread pool
+ """
+ def __init__(self, addr, requestHandler=SimpleJSONRPCRequestHandler,
+ logRequests=True, encoding=None, bind_and_activate=True,
+ address_family=socket.AF_INET,
+ config=jsonrpclib.config.DEFAULT, thread_pool=None):
+ """
+ Sets up the server and the dispatcher
+
+ :param addr: The server listening address
+ :param requestHandler: Custom request handler
+ :param logRequests: Flag to(de)activate requests logging
+ :param encoding: The dispatcher request encoding
+ :param bind_and_activate: If True, starts the server immediately
+ :param address_family: The server listening address family
+ :param config: A JSONRPClib Config instance
+ :param thread_pool: A ThreadPool object. The pool must be started.
+ """
+ # Normalize the thread pool
+ if thread_pool is None:
+ # Start a thread pool with 30 threads max, 0 thread min
+ thread_pool = jsonrpclib.threadpool.ThreadPool(
+ 30, 0, logname="PooledJSONRPCServer")
+ thread_pool.start()
+
+ # Store the thread pool
+ self.__request_pool = thread_pool
+
+ # Prepare the server
+ SimpleJSONRPCServer.__init__(self, addr, requestHandler, logRequests,
+ encoding, bind_and_activate,
+ address_family, config)
+
+ def process_request(self, request, client_address):
+ """
+ Handle a client request: queue it in the thread pool
+ """
+ self.__request_pool.enqueue(self.process_request_thread,
+ request, client_address)
+
+ def server_close(self):
+ """
+ Clean up the server
+ """
+ SimpleJSONRPCServer.server_close(self)
+ self.__request_pool.stop()
+
+# ------------------------------------------------------------------------------
+
+
+class CGIJSONRPCRequestHandler(SimpleJSONRPCDispatcher):
+ """
+ JSON-RPC CGI handler (and dispatcher)
+ """
+ def __init__(self, encoding=None, config=jsonrpclib.config.DEFAULT):
+ """
+ Sets up the dispatcher
+
+ :param encoding: Dispatcher encoding
+ :param config: A JSONRPClib Config instance
+ """
+ SimpleJSONRPCDispatcher.__init__(self, encoding, config)
+
+ def handle_jsonrpc(self, request_text):
+ """
+ Handle a JSON-RPC request
+ """
+ response = self._marshaled_dispatch(request_text)
+ sys.stdout.write('Content-Type: {0}\r\n'
+ .format(self.json_config.content_type))
+ sys.stdout.write('Content-Length: {0:d}\r\n'.format(len(response)))
+ sys.stdout.write('\r\n')
+ sys.stdout.write(response)
+
+ # XML-RPC alias
+ handle_xmlrpc = handle_jsonrpc