mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
371 lines
9.3 KiB
Python
371 lines
9.3 KiB
Python
# 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
|
|
gzip = _base.gzip
|
|
|
|
|
|
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 a 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 #pylint: disable=attribute-defined-outside-init
|
|
|
|
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)
|
|
transport_type, uri = urllib.splittype(uri)
|
|
if transport is None:
|
|
if transport_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
|
|
|
|
|
|
Server = ServerProxy
|