Index: remoting/tools/remote_test_helper/jsonrpclib.py |
diff --git a/remoting/tools/remote_test_helper/jsonrpclib.py b/remoting/tools/remote_test_helper/jsonrpclib.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..fb9abf52ddda9dbaafd151477ee9b7e9a39f32ac |
--- /dev/null |
+++ b/remoting/tools/remote_test_helper/jsonrpclib.py |
@@ -0,0 +1,347 @@ |
+# Copyright (c) 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 JSON-RPC protocol. |
+ |
+This module uses xmlrpclib 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. No third party code is |
+required to use this module. |
+""" |
+import json |
+import urllib |
+import xmlrpclib as _base |
+ |
+gzip_encode = _base.gzip_encode |
+ |
+ |
+class Error(Exception): |
+ |
+ def __str__(self): |
+ return repr(self) |
+ |
+ |
+class ProtocolError(Error): |
+ """Indicates a JSON protocol error.""" |
+ |
+ def __init__(self, url, errcode, errmsg, headers): |
+ Error.__init__(self) |
+ self.url = url |
+ self.errcode = errcode |
+ self.errmsg = errmsg |
+ self.headers = headers |
+ |
+ def __repr__(self): |
+ return ( |
+ '<ProtocolError for %s: %s %s>' % |
+ (self.url, self.errcode, self.errmsg)) |
+ |
+ |
+class ResponseError(Error): |
+ """Indicates a broken response package.""" |
+ pass |
+ |
+ |
+class Fault(Error): |
+ """Indicates an JSON-RPC fault package.""" |
+ |
+ def __init__(self, code, message): |
+ Error.__init__(self) |
+ if not isinstance(code, int): |
+ raise ProtocolError('Fault code must be an integer.') |
+ self.code = code |
+ self.message = message |
+ |
+ def __repr__(self): |
+ return ( |
+ '<Fault %s: %s>' % |
+ (self.code, repr(self.message)) |
+ ) |
+ |
+ |
+def CreateRequest(methodname, params, ident=''): |
+ """Create a valid JSON-RPC request. |
+ |
+ Args: |
+ methodname: The name of the remote method to invoke. |
+ params: The parameters to pass to the remote method. This should be a |
+ list or tuple and able to be encoded by the default JSON parser. |
+ |
+ Returns: |
+ A valid JSON-RPC request object. |
+ """ |
+ request = { |
+ 'jsonrpc': '2.0', |
+ 'method': methodname, |
+ 'params': params, |
+ 'id': ident |
+ } |
+ |
+ return request |
+ |
+ |
+def CreateRequestString(methodname, params, ident=''): |
+ """Create a valid JSON-RPC request string. |
+ |
+ Args: |
+ methodname: The name of the remote method to invoke. |
+ params: The parameters to pass to the remote method. |
+ These parameters need to be encode-able by the default JSON parser. |
+ ident: The request identifier. |
+ |
+ Returns: |
+ A valid JSON-RPC request string. |
+ """ |
+ return json.dumps(CreateRequest(methodname, params, ident)) |
+ |
+def CreateResponse(data, ident): |
+ """Create a JSON-RPC response. |
+ |
+ Args: |
+ data: The data to return. |
+ ident: The response identifier. |
+ |
+ Returns: |
+ A valid JSON-RPC response object. |
+ """ |
+ if isinstance(data, Fault): |
+ response = { |
+ 'jsonrpc': '2.0', |
+ 'error': { |
+ 'code': data.code, |
+ 'message': data.message}, |
+ 'id': ident |
+ } |
+ else: |
+ response = { |
+ 'jsonrpc': '2.0', |
+ 'response': data, |
+ 'id': ident |
+ } |
+ |
+ return response |
+ |
+ |
+def CreateResponseString(data, ident): |
+ """Create a JSON-RPC response string. |
+ |
+ Args: |
+ data: The data to return. |
+ ident: The response identifier. |
+ |
+ Returns: |
+ A valid JSON-RPC response object. |
+ """ |
+ return json.dumps(CreateResponse(data, ident)) |
+ |
+ |
+def ParseHTTPResponse(response): |
+ """Parse an HTTP response object and return the JSON object. |
+ |
+ Args: |
+ response: An HTTP response object. |
+ |
+ Returns: |
+ The returned JSON-RPC object. |
+ |
+ Raises: |
+ ProtocolError: if the object format is not correct. |
+ Fault: If a Fault error is returned from the server. |
+ """ |
+ # Check for new http response object, else it is a file object |
+ if hasattr(response, 'getheader'): |
+ if response.getheader('Content-Encoding', '') == 'gzip': |
+ stream = _base.GzipDecodedResponse(response) |
+ else: |
+ stream = response |
+ else: |
+ stream = response |
+ |
+ data = '' |
+ while 1: |
+ chunk = stream.read(1024) |
+ if not chunk: |
+ break |
+ data += chunk |
+ |
+ response = json.loads(data) |
+ ValidateBasicJSONRPCData(response) |
+ |
+ if 'response' in response: |
+ ValidateResponse(response) |
+ return response['response'] |
+ elif 'error' in response: |
+ ValidateError(response) |
+ code = response['error']['code'] |
+ message = response['error']['message'] |
+ raise Fault(code, message) |
+ else: |
+ raise ProtocolError('No valid JSON returned') |
+ |
+ |
+def ValidateRequest(data): |
+ """Validate a JSON-RPC request object. |
+ |
+ Args: |
+ data: The JSON-RPC object (dict). |
+ |
+ Raises: |
+ ProtocolError: if the object format is not correct. |
+ """ |
+ ValidateBasicJSONRPCData(data) |
+ if 'method' not in data or 'params' not in data: |
+ raise ProtocolError('JSON is not a valid request') |
+ |
+ |
+def ValidateResponse(data): |
+ """Validate a JSON-RPC response object. |
+ |
+ Args: |
+ data: The JSON-RPC object (dict). |
+ |
+ Raises: |
+ ProtocolError: if the object format is not correct. |
+ """ |
+ ValidateBasicJSONRPCData(data) |
+ if 'response' not in data: |
+ raise ProtocolError('JSON is not a valid response') |
+ |
+ |
+def ValidateError(data): |
+ """Validate a JSON-RPC error object. |
+ |
+ Args: |
+ data: The JSON-RPC object (dict). |
+ |
+ Raises: |
+ ProtocolError: if the object format is not correct. |
+ """ |
+ ValidateBasicJSONRPCData(data) |
+ if ('error' not in data or |
+ 'code' not in data['error'] or |
+ 'message' not in data['error']): |
+ raise ProtocolError('JSON is not a valid error response') |
+ |
+ |
+def ValidateBasicJSONRPCData(data): |
+ """Validate a basic JSON-RPC object. |
+ |
+ Args: |
+ data: The JSON-RPC object (dict). |
+ |
+ Raises: |
+ ProtocolError: if the object format is not correct. |
+ """ |
+ error = None |
+ if not isinstance(data, dict): |
+ error = 'JSON data is not a dictionary' |
+ elif 'jsonrpc' not in data or data['jsonrpc'] != '2.0': |
+ error = 'JSON is not a valid JSON RPC 2.0 message' |
+ elif 'id' not in data: |
+ error = 'JSON data missing required id entry' |
+ if error: |
+ raise ProtocolError(error) |
+ |
+ |
+class Transport(_base.Transport): |
+ """RPC transport class. |
+ |
+ This class extends the functionality of xmlrpclib.Transport and only |
+ overrides the operations needed to change the protocol from XML-RPC to |
+ JSON-RPC. |
+ """ |
+ |
+ def single_request(self, host, handler, request_body, verbose=0): |
+ """Issue a single JSON-RPC request.""" |
+ |
+ h = self.make_connection(host) |
+ if verbose: |
+ h.set_debuglevel(1) |
+ try: |
+ self.send_request(h, handler, request_body) |
+ self.send_host(h, host) |
+ self.send_user_agent(h) |
+ self.send_content(h, request_body) |
+ |
+ response = h.getresponse(buffering=True) |
+ if response.status == 200: |
+ self.verbose = verbose |
+ |
+ return self.parse_response(response) |
+ |
+ except Fault: |
+ raise |
+ except Exception: |
+ # All unexpected errors leave connection in |
+ # a strange state, so we clear it. |
+ self.close() |
+ raise |
+ |
+ # discard any response data and raise exception |
+ if response.getheader('content-length', 0): |
+ response.read() |
+ raise ProtocolError( |
+ host + handler, |
+ response.status, response.reason, |
+ response.msg, |
+ ) |
+ |
+ def parse_response(self, response): |
+ """Parse the HTTP resoponse from the server.""" |
+ return ParseHTTPResponse(response) |
+ |
+ |
+class SafeTransport(_base.SafeTransport): |
+ """Transport class for HTTPS servers. |
+ |
+ This class extends the functionality of xmlrpclib.SafeTransport and only |
+ overrides the operations needed to change the protocol from XML-RPC to |
+ JSON-RPC. |
+ """ |
+ |
+ def parse_response(self, response): |
+ return ParseHTTPResponse(response) |
+ |
+ |
+class ServerProxy(_base.ServerProxy): |
+ """Proxy class to the RPC server. |
+ |
+ This class extends the functionality of xmlrpclib.ServerProxy and only |
+ overrides the operations needed to change the protocol from XML-RPC to |
+ JSON-RPC. |
+ """ |
+ |
+ def __init__(self, uri, transport=None, encoding=None, verbose=0, |
+ allow_none=0, use_datetime=0): |
+ urltype, _ = urllib.splittype(uri) |
+ if urltype not in ('http', 'https'): |
+ raise IOError('unsupported JSON-RPC protocol') |
+ |
+ _base.ServerProxy.__init__(self, uri, transport, encoding, verbose, |
+ allow_none, use_datetime) |
+ |
+ if transport is None: |
+ if type == 'https': |
+ transport = SafeTransport(use_datetime=use_datetime) |
+ else: |
+ transport = Transport(use_datetime=use_datetime) |
+ self.__transport = transport |
+ |
+ def __request(self, methodname, params): |
+ """Call a method on the remote server.""" |
+ request = CreateRequestString(methodname, params) |
+ |
+ response = self.__transport.request( |
+ self.__host, |
+ self.__handler, |
+ request, |
+ verbose=self.__verbose |
+ ) |
+ |
+ return response |