Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / testing / legion / jsonrpclib.py
blob56b30e8dc3bffb2a53745be20de3e8a6a7093bb1
1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """Module to implement the JSON-RPC protocol.
7 This module uses xmlrpclib as the base and only overrides those
8 portions that implement the XML-RPC protocol. These portions are rewritten
9 to use the JSON-RPC protocol instead.
11 When large portions of code need to be rewritten the original code and
12 comments are preserved. The intention here is to keep the amount of code
13 change to a minimum.
15 This module only depends on default Python modules. No third party code is
16 required to use this module.
17 """
18 import json
19 import urllib
20 import xmlrpclib as _base
22 __version__ = '1.0.0'
23 gzip_encode = _base.gzip_encode
24 gzip = _base.gzip
27 class Error(Exception):
29 def __str__(self):
30 return repr(self)
33 class ProtocolError(Error):
34 """Indicates a JSON protocol error."""
36 def __init__(self, url, errcode, errmsg, headers):
37 Error.__init__(self)
38 self.url = url
39 self.errcode = errcode
40 self.errmsg = errmsg
41 self.headers = headers
43 def __repr__(self):
44 return (
45 '<ProtocolError for %s: %s %s>' %
46 (self.url, self.errcode, self.errmsg))
49 class ResponseError(Error):
50 """Indicates a broken response package."""
51 pass
54 class Fault(Error):
55 """Indicates a JSON-RPC fault package."""
57 def __init__(self, code, message):
58 Error.__init__(self)
59 if not isinstance(code, int):
60 raise ProtocolError('Fault code must be an integer.')
61 self.code = code
62 self.message = message
64 def __repr__(self):
65 return (
66 '<Fault %s: %s>' %
67 (self.code, repr(self.message))
71 def CreateRequest(methodname, params, ident=''):
72 """Create a valid JSON-RPC request.
74 Args:
75 methodname: The name of the remote method to invoke.
76 params: The parameters to pass to the remote method. This should be a
77 list or tuple and able to be encoded by the default JSON parser.
79 Returns:
80 A valid JSON-RPC request object.
81 """
82 request = {
83 'jsonrpc': '2.0',
84 'method': methodname,
85 'params': params,
86 'id': ident
89 return request
92 def CreateRequestString(methodname, params, ident=''):
93 """Create a valid JSON-RPC request string.
95 Args:
96 methodname: The name of the remote method to invoke.
97 params: The parameters to pass to the remote method.
98 These parameters need to be encode-able by the default JSON parser.
99 ident: The request identifier.
101 Returns:
102 A valid JSON-RPC request string.
104 return json.dumps(CreateRequest(methodname, params, ident))
107 def CreateResponse(data, ident):
108 """Create a JSON-RPC response.
110 Args:
111 data: The data to return.
112 ident: The response identifier.
114 Returns:
115 A valid JSON-RPC response object.
117 if isinstance(data, Fault):
118 response = {
119 'jsonrpc': '2.0',
120 'error': {
121 'code': data.code,
122 'message': data.message},
123 'id': ident
125 else:
126 response = {
127 'jsonrpc': '2.0',
128 'response': data,
129 'id': ident
132 return response
135 def CreateResponseString(data, ident):
136 """Create a JSON-RPC response string.
138 Args:
139 data: The data to return.
140 ident: The response identifier.
142 Returns:
143 A valid JSON-RPC response object.
145 return json.dumps(CreateResponse(data, ident))
148 def ParseHTTPResponse(response):
149 """Parse an HTTP response object and return the JSON object.
151 Args:
152 response: An HTTP response object.
154 Returns:
155 The returned JSON-RPC object.
157 Raises:
158 ProtocolError: if the object format is not correct.
159 Fault: If a Fault error is returned from the server.
161 # Check for new http response object, else it is a file object
162 if hasattr(response, 'getheader'):
163 if response.getheader('Content-Encoding', '') == 'gzip':
164 stream = _base.GzipDecodedResponse(response)
165 else:
166 stream = response
167 else:
168 stream = response
170 data = ''
171 while 1:
172 chunk = stream.read(1024)
173 if not chunk:
174 break
175 data += chunk
177 response = json.loads(data)
178 ValidateBasicJSONRPCData(response)
180 if 'response' in response:
181 ValidateResponse(response)
182 return response['response']
183 elif 'error' in response:
184 ValidateError(response)
185 code = response['error']['code']
186 message = response['error']['message']
187 raise Fault(code, message)
188 else:
189 raise ProtocolError('No valid JSON returned')
192 def ValidateRequest(data):
193 """Validate a JSON-RPC request object.
195 Args:
196 data: The JSON-RPC object (dict).
198 Raises:
199 ProtocolError: if the object format is not correct.
201 ValidateBasicJSONRPCData(data)
202 if 'method' not in data or 'params' not in data:
203 raise ProtocolError('JSON is not a valid request')
206 def ValidateResponse(data):
207 """Validate a JSON-RPC response object.
209 Args:
210 data: The JSON-RPC object (dict).
212 Raises:
213 ProtocolError: if the object format is not correct.
215 ValidateBasicJSONRPCData(data)
216 if 'response' not in data:
217 raise ProtocolError('JSON is not a valid response')
220 def ValidateError(data):
221 """Validate a JSON-RPC error object.
223 Args:
224 data: The JSON-RPC object (dict).
226 Raises:
227 ProtocolError: if the object format is not correct.
229 ValidateBasicJSONRPCData(data)
230 if ('error' not in data or
231 'code' not in data['error'] or
232 'message' not in data['error']):
233 raise ProtocolError('JSON is not a valid error response')
236 def ValidateBasicJSONRPCData(data):
237 """Validate a basic JSON-RPC object.
239 Args:
240 data: The JSON-RPC object (dict).
242 Raises:
243 ProtocolError: if the object format is not correct.
245 error = None
246 if not isinstance(data, dict):
247 error = 'JSON data is not a dictionary'
248 elif 'jsonrpc' not in data or data['jsonrpc'] != '2.0':
249 error = 'JSON is not a valid JSON RPC 2.0 message'
250 elif 'id' not in data:
251 error = 'JSON data missing required id entry'
252 if error:
253 raise ProtocolError(error)
256 class Transport(_base.Transport):
257 """RPC transport class.
259 This class extends the functionality of xmlrpclib.Transport and only
260 overrides the operations needed to change the protocol from XML-RPC to
261 JSON-RPC.
264 user_agent = 'jsonrpclib.py/' + __version__
266 def send_content(self, connection, request_body):
267 """Send the request."""
268 connection.putheader('Content-Type','application/json')
270 #optionally encode the request
271 if (self.encode_threshold is not None and
272 self.encode_threshold < len(request_body) and
273 gzip):
274 connection.putheader('Content-Encoding', 'gzip')
275 request_body = gzip_encode(request_body)
277 connection.putheader('Content-Length', str(len(request_body)))
278 connection.endheaders(request_body)
280 def single_request(self, host, handler, request_body, verbose=0):
281 """Issue a single JSON-RPC request."""
283 h = self.make_connection(host)
284 if verbose:
285 h.set_debuglevel(1)
286 try:
287 self.send_request(h, handler, request_body)
288 self.send_host(h, host)
289 self.send_user_agent(h)
290 self.send_content(h, request_body)
292 response = h.getresponse(buffering=True)
293 if response.status == 200:
294 self.verbose = verbose #pylint: disable=attribute-defined-outside-init
296 return self.parse_response(response)
298 except Fault:
299 raise
300 except Exception:
301 # All unexpected errors leave connection in
302 # a strange state, so we clear it.
303 self.close()
304 raise
306 # discard any response data and raise exception
307 if response.getheader('content-length', 0):
308 response.read()
309 raise ProtocolError(
310 host + handler,
311 response.status, response.reason,
312 response.msg,
315 def parse_response(self, response):
316 """Parse the HTTP resoponse from the server."""
317 return ParseHTTPResponse(response)
320 class SafeTransport(_base.SafeTransport):
321 """Transport class for HTTPS servers.
323 This class extends the functionality of xmlrpclib.SafeTransport and only
324 overrides the operations needed to change the protocol from XML-RPC to
325 JSON-RPC.
328 def parse_response(self, response):
329 return ParseHTTPResponse(response)
332 class ServerProxy(_base.ServerProxy):
333 """Proxy class to the RPC server.
335 This class extends the functionality of xmlrpclib.ServerProxy and only
336 overrides the operations needed to change the protocol from XML-RPC to
337 JSON-RPC.
340 def __init__(self, uri, transport=None, encoding=None, verbose=0,
341 allow_none=0, use_datetime=0):
342 urltype, _ = urllib.splittype(uri)
343 if urltype not in ('http', 'https'):
344 raise IOError('unsupported JSON-RPC protocol')
346 _base.ServerProxy.__init__(self, uri, transport, encoding, verbose,
347 allow_none, use_datetime)
348 transport_type, uri = urllib.splittype(uri)
349 if transport is None:
350 if transport_type == 'https':
351 transport = SafeTransport(use_datetime=use_datetime)
352 else:
353 transport = Transport(use_datetime=use_datetime)
354 self.__transport = transport
356 def __request(self, methodname, params):
357 """Call a method on the remote server."""
358 request = CreateRequestString(methodname, params)
360 response = self.__transport.request(
361 self.__host,
362 self.__handler,
363 request,
364 verbose=self.__verbose
367 return response
370 Server = ServerProxy