| 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
|
|
|