|  | # Copyright 2014 The Chromium Authors. All rights reserved. | 
|  | # Use of this source code is governed by a BSD-style license that can be | 
|  | # found in the LICENSE file. | 
|  |  | 
|  | """Module to implement the SimpleXMLRPCServer module using JSON-RPC. | 
|  |  | 
|  | This module uses SimpleXMLRPCServer as the base and only overrides those | 
|  | portions that implement the XML-RPC protocol. These portions are rewritten | 
|  | to use the JSON-RPC protocol instead. | 
|  |  | 
|  | When large portions of code need to be rewritten the original code and | 
|  | comments are preserved. The intention here is to keep the amount of code | 
|  | change to a minimum. | 
|  |  | 
|  | This module only depends on default Python modules, as well as jsonrpclib | 
|  | which also uses only default modules. No third party code is required to | 
|  | use this module. | 
|  | """ | 
|  |  | 
|  | import json | 
|  | import SimpleXMLRPCServer as _base | 
|  | import SocketServer | 
|  | import sys | 
|  | import traceback | 
|  | try: | 
|  | import fcntl | 
|  | except ImportError: | 
|  | fcntl = None | 
|  | try: | 
|  | import gzip | 
|  | except ImportError: | 
|  | gzip = None #python can be built without zlib/gzip support | 
|  |  | 
|  | #pylint: disable=relative-import | 
|  | import jsonrpclib | 
|  |  | 
|  |  | 
|  | class SimpleJSONRPCRequestHandler(_base.SimpleXMLRPCRequestHandler): | 
|  | """Request handler class for received requests. | 
|  |  | 
|  | This class extends the functionality of SimpleXMLRPCRequestHandler and only | 
|  | overrides the operations needed to change the protocol from XML-RPC to | 
|  | JSON-RPC. | 
|  | """ | 
|  |  | 
|  | def do_POST(self): | 
|  | """Handles the HTTP POST request. | 
|  |  | 
|  | Attempts to interpret all HTTP POST requests as JSON-RPC calls, | 
|  | which are forwarded to the server's _dispatch method for handling. | 
|  | """ | 
|  | # Check that the path is legal | 
|  | if not self.is_rpc_path_valid(): | 
|  | self.report_404() | 
|  | return | 
|  |  | 
|  | try: | 
|  | # Get arguments by reading body of request. | 
|  | # We read this in chunks to avoid straining | 
|  | # socket.read(); around the 10 or 15Mb mark, some platforms | 
|  | # begin to have problems (bug #792570). | 
|  | max_chunk_size = 10*1024*1024 | 
|  | size_remaining = int(self.headers['content-length']) | 
|  | data = [] | 
|  | while size_remaining: | 
|  | chunk_size = min(size_remaining, max_chunk_size) | 
|  | chunk = self.rfile.read(chunk_size) | 
|  | if not chunk: | 
|  | break | 
|  | data.append(chunk) | 
|  | size_remaining -= len(data[-1]) | 
|  | data = ''.join(data) | 
|  | data = self.decode_request_content(data) | 
|  |  | 
|  | if data is None: | 
|  | return  # response has been sent | 
|  |  | 
|  | # In previous versions of SimpleXMLRPCServer, _dispatch | 
|  | # could be overridden in this class, instead of in | 
|  | # SimpleXMLRPCDispatcher. To maintain backwards compatibility, | 
|  | # check to see if a subclass implements _dispatch and dispatch | 
|  | # using that method if present. | 
|  | response = self.server._marshaled_dispatch( | 
|  | data, getattr(self, '_dispatch', None), self.path) | 
|  |  | 
|  | except Exception, e: # This should only happen if the module is buggy | 
|  | # internal error, report as HTTP server error | 
|  | self.send_response(500) | 
|  | # Send information about the exception if requested | 
|  | if (hasattr(self.server, '_send_traceback_header') and | 
|  | self.server._send_traceback_header): | 
|  | self.send_header('X-exception', str(e)) | 
|  | self.send_header('X-traceback', traceback.format_exc()) | 
|  |  | 
|  | self.send_header('Content-length', '0') | 
|  | self.end_headers() | 
|  | else: | 
|  | # got a valid JSON RPC response | 
|  | self.send_response(200) | 
|  | self.send_header('Content-type', 'application/json') | 
|  |  | 
|  | if self.encode_threshold is not None: | 
|  | if len(response) > self.encode_threshold: | 
|  | q = self.accept_encodings().get('gzip', 0) | 
|  | if q: | 
|  | try: | 
|  | response = jsonrpclib.gzip_encode(response) | 
|  | self.send_header('Content-Encoding', 'gzip') | 
|  | except NotImplementedError: | 
|  | pass | 
|  |  | 
|  | self.send_header('Content-length', str(len(response))) | 
|  | self.end_headers() | 
|  | self.wfile.write(response) | 
|  |  | 
|  |  | 
|  | class SimpleJSONRPCDispatcher(_base.SimpleXMLRPCDispatcher): | 
|  | """Dispatcher for received JSON-RPC requests. | 
|  |  | 
|  | This class extends the functionality of SimpleXMLRPCDispatcher and only | 
|  | overrides the operations needed to change the protocol from XML-RPC to | 
|  | JSON-RPC. | 
|  | """ | 
|  |  | 
|  | def _marshaled_dispatch(self, data, dispatch_method=None, path=None): | 
|  | """Dispatches an JSON-RPC method from marshalled (JSON) data. | 
|  |  | 
|  | JSON-RPC methods are dispatched from the marshalled (JSON) data | 
|  | using the _dispatch method and the result is returned as | 
|  | marshalled data. For backwards compatibility, a dispatch | 
|  | function can be provided as an argument (see comment in | 
|  | SimpleJSONRPCRequestHandler.do_POST) but overriding the | 
|  | existing method through subclassing is the preferred means | 
|  | of changing method dispatch behavior. | 
|  |  | 
|  | Returns: | 
|  | The JSON-RPC string to return. | 
|  | """ | 
|  | method = '' | 
|  | params = [] | 
|  | ident = '' | 
|  | try: | 
|  | request = json.loads(data) | 
|  | jsonrpclib.ValidateRequest(request) | 
|  | method = request['method'] | 
|  | params = request['params'] | 
|  | ident = request['id'] | 
|  |  | 
|  | # generate response | 
|  | if dispatch_method is not None: | 
|  | response = dispatch_method(method, params) | 
|  | else: | 
|  | response = self._dispatch(method, params) | 
|  | response = jsonrpclib.CreateResponseString(response, ident) | 
|  |  | 
|  | except jsonrpclib.Fault as fault: | 
|  | response = jsonrpclib.CreateResponseString(fault, ident) | 
|  |  | 
|  | # Catch all exceptions here as they should be raised on the caller side. | 
|  | except:  #pylint: disable=bare-except | 
|  | # report exception back to server | 
|  | exc_type, exc_value, _ = sys.exc_info() | 
|  | response = jsonrpclib.CreateResponseString( | 
|  | jsonrpclib.Fault(1, '%s:%s' % (exc_type, exc_value)), ident) | 
|  | return response | 
|  |  | 
|  |  | 
|  | class SimpleJSONRPCServer(SocketServer.TCPServer, | 
|  | SimpleJSONRPCDispatcher): | 
|  | """Simple JSON-RPC server. | 
|  |  | 
|  | This class mimics the functionality of SimpleXMLRPCServer and only | 
|  | overrides the operations needed to change the protocol from XML-RPC to | 
|  | JSON-RPC. | 
|  | """ | 
|  |  | 
|  | allow_reuse_address = True | 
|  |  | 
|  | # Warning: this is for debugging purposes only! Never set this to True in | 
|  | # production code, as will be sending out sensitive information (exception | 
|  | # and stack trace details) when exceptions are raised inside | 
|  | # SimpleJSONRPCRequestHandler.do_POST | 
|  | _send_traceback_header = False | 
|  |  | 
|  | def __init__(self, addr, requestHandler=SimpleJSONRPCRequestHandler, | 
|  | logRequests=True, allow_none=False, encoding=None, | 
|  | bind_and_activate=True): | 
|  | self.logRequests = logRequests | 
|  | SimpleJSONRPCDispatcher.__init__(self, allow_none, encoding) | 
|  | SocketServer.TCPServer.__init__(self, addr, requestHandler, | 
|  | bind_and_activate) | 
|  |  | 
|  | # [Bug #1222790] If possible, set close-on-exec flag; if a | 
|  | # method spawns a subprocess, the subprocess shouldn't have | 
|  | # the listening socket open. | 
|  | 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) |