123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- # 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 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
- __version__ = '1.0.0'
- 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.
- """
- user_agent = 'jsonrpclib.py/' + __version__
- def send_content(self, connection, request_body):
- """Send the request."""
- connection.putheader('Content-Type','application/json')
- #optionally encode the request
- if (self.encode_threshold is not None and
- self.encode_threshold < len(request_body) and
- gzip):
- connection.putheader('Content-Encoding', 'gzip')
- request_body = gzip_encode(request_body)
- connection.putheader('Content-Length', str(len(request_body)))
- connection.endheaders(request_body)
- 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
|