Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / remoting / tools / remote_test_helper / jsonrpclib.py
blob78ba7d8c6286124ac5abda1a86005fab1b4c9d99
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.
4 """Module to implement the JSON-RPC protocol.
6 This module uses xmlrpclib as the base and only overrides those
7 portions that implement the XML-RPC protocol. These portions are rewritten
8 to use the JSON-RPC protocol instead.
10 When large portions of code need to be rewritten the original code and
11 comments are preserved. The intention here is to keep the amount of code
12 change to a minimum.
14 This module only depends on default Python modules. No third party code is
15 required to use this module.
16 """
17 import json
18 import urllib
19 import xmlrpclib as _base
21 __version__ = '1.0.0'
22 gzip_encode = _base.gzip_encode
25 class Error(Exception):
27 def __str__(self):
28 return repr(self)
31 class ProtocolError(Error):
32 """Indicates a JSON protocol error."""
34 def __init__(self, url, errcode, errmsg, headers):
35 Error.__init__(self)
36 self.url = url
37 self.errcode = errcode
38 self.errmsg = errmsg
39 self.headers = headers
41 def __repr__(self):
42 return (
43 '<ProtocolError for %s: %s %s>' %
44 (self.url, self.errcode, self.errmsg))
47 class ResponseError(Error):
48 """Indicates a broken response package."""
49 pass
52 class Fault(Error):
53 """Indicates an JSON-RPC fault package."""
55 def __init__(self, code, message):
56 Error.__init__(self)
57 if not isinstance(code, int):
58 raise ProtocolError('Fault code must be an integer.')
59 self.code = code
60 self.message = message
62 def __repr__(self):
63 return (
64 '<Fault %s: %s>' %
65 (self.code, repr(self.message))
69 def CreateRequest(methodname, params, ident=''):
70 """Create a valid JSON-RPC request.
72 Args:
73 methodname: The name of the remote method to invoke.
74 params: The parameters to pass to the remote method. This should be a
75 list or tuple and able to be encoded by the default JSON parser.
77 Returns:
78 A valid JSON-RPC request object.
79 """
80 request = {
81 'jsonrpc': '2.0',
82 'method': methodname,
83 'params': params,
84 'id': ident
87 return request
90 def CreateRequestString(methodname, params, ident=''):
91 """Create a valid JSON-RPC request string.
93 Args:
94 methodname: The name of the remote method to invoke.
95 params: The parameters to pass to the remote method.
96 These parameters need to be encode-able by the default JSON parser.
97 ident: The request identifier.
99 Returns:
100 A valid JSON-RPC request string.
102 return json.dumps(CreateRequest(methodname, params, ident))
104 def CreateResponse(data, ident):
105 """Create a JSON-RPC response.
107 Args:
108 data: The data to return.
109 ident: The response identifier.
111 Returns:
112 A valid JSON-RPC response object.
114 if isinstance(data, Fault):
115 response = {
116 'jsonrpc': '2.0',
117 'error': {
118 'code': data.code,
119 'message': data.message},
120 'id': ident
122 else:
123 response = {
124 'jsonrpc': '2.0',
125 'response': data,
126 'id': ident
129 return response
132 def CreateResponseString(data, ident):
133 """Create a JSON-RPC response string.
135 Args:
136 data: The data to return.
137 ident: The response identifier.
139 Returns:
140 A valid JSON-RPC response object.
142 return json.dumps(CreateResponse(data, ident))
145 def ParseHTTPResponse(response):
146 """Parse an HTTP response object and return the JSON object.
148 Args:
149 response: An HTTP response object.
151 Returns:
152 The returned JSON-RPC object.
154 Raises:
155 ProtocolError: if the object format is not correct.
156 Fault: If a Fault error is returned from the server.
158 # Check for new http response object, else it is a file object
159 if hasattr(response, 'getheader'):
160 if response.getheader('Content-Encoding', '') == 'gzip':
161 stream = _base.GzipDecodedResponse(response)
162 else:
163 stream = response
164 else:
165 stream = response
167 data = ''
168 while 1:
169 chunk = stream.read(1024)
170 if not chunk:
171 break
172 data += chunk
174 response = json.loads(data)
175 ValidateBasicJSONRPCData(response)
177 if 'response' in response:
178 ValidateResponse(response)
179 return response['response']
180 elif 'error' in response:
181 ValidateError(response)
182 code = response['error']['code']
183 message = response['error']['message']
184 raise Fault(code, message)
185 else:
186 raise ProtocolError('No valid JSON returned')
189 def ValidateRequest(data):
190 """Validate a JSON-RPC request object.
192 Args:
193 data: The JSON-RPC object (dict).
195 Raises:
196 ProtocolError: if the object format is not correct.
198 ValidateBasicJSONRPCData(data)
199 if 'method' not in data or 'params' not in data:
200 raise ProtocolError('JSON is not a valid request')
203 def ValidateResponse(data):
204 """Validate a JSON-RPC response object.
206 Args:
207 data: The JSON-RPC object (dict).
209 Raises:
210 ProtocolError: if the object format is not correct.
212 ValidateBasicJSONRPCData(data)
213 if 'response' not in data:
214 raise ProtocolError('JSON is not a valid response')
217 def ValidateError(data):
218 """Validate a JSON-RPC error object.
220 Args:
221 data: The JSON-RPC object (dict).
223 Raises:
224 ProtocolError: if the object format is not correct.
226 ValidateBasicJSONRPCData(data)
227 if ('error' not in data or
228 'code' not in data['error'] or
229 'message' not in data['error']):
230 raise ProtocolError('JSON is not a valid error response')
233 def ValidateBasicJSONRPCData(data):
234 """Validate a basic JSON-RPC object.
236 Args:
237 data: The JSON-RPC object (dict).
239 Raises:
240 ProtocolError: if the object format is not correct.
242 error = None
243 if not isinstance(data, dict):
244 error = 'JSON data is not a dictionary'
245 elif 'jsonrpc' not in data or data['jsonrpc'] != '2.0':
246 error = 'JSON is not a valid JSON RPC 2.0 message'
247 elif 'id' not in data:
248 error = 'JSON data missing required id entry'
249 if error:
250 raise ProtocolError(error)
253 class Transport(_base.Transport):
254 """RPC transport class.
256 This class extends the functionality of xmlrpclib.Transport and only
257 overrides the operations needed to change the protocol from XML-RPC to
258 JSON-RPC.
261 user_agent = 'jsonrpclib.py/' + __version__
263 def send_content(self, connection, request_body):
264 """Send the request."""
265 connection.putheader('Content-Type','application/json')
267 #optionally encode the request
268 if (self.encode_threshold is not None and
269 self.encode_threshold < len(request_body) and
270 gzip):
271 connection.putheader('Content-Encoding', 'gzip')
272 request_body = gzip_encode(request_body)
274 connection.putheader('Content-Length', str(len(request_body)))
275 connection.endheaders(request_body)
277 def single_request(self, host, handler, request_body, verbose=0):
278 """Issue a single JSON-RPC request."""
280 h = self.make_connection(host)
281 if verbose:
282 h.set_debuglevel(1)
283 try:
284 self.send_request(h, handler, request_body)
285 self.send_host(h, host)
286 self.send_user_agent(h)
287 self.send_content(h, request_body)
289 response = h.getresponse(buffering=True)
290 if response.status == 200:
291 self.verbose = verbose
293 return self.parse_response(response)
295 except Fault:
296 raise
297 except Exception:
298 # All unexpected errors leave connection in
299 # a strange state, so we clear it.
300 self.close()
301 raise
303 # discard any response data and raise exception
304 if response.getheader('content-length', 0):
305 response.read()
306 raise ProtocolError(
307 host + handler,
308 response.status, response.reason,
309 response.msg,
312 def parse_response(self, response):
313 """Parse the HTTP resoponse from the server."""
314 return ParseHTTPResponse(response)
317 class SafeTransport(_base.SafeTransport):
318 """Transport class for HTTPS servers.
320 This class extends the functionality of xmlrpclib.SafeTransport and only
321 overrides the operations needed to change the protocol from XML-RPC to
322 JSON-RPC.
325 def parse_response(self, response):
326 return ParseHTTPResponse(response)
329 class ServerProxy(_base.ServerProxy):
330 """Proxy class to the RPC server.
332 This class extends the functionality of xmlrpclib.ServerProxy and only
333 overrides the operations needed to change the protocol from XML-RPC to
334 JSON-RPC.
337 def __init__(self, uri, transport=None, encoding=None, verbose=0,
338 allow_none=0, use_datetime=0):
339 urltype, _ = urllib.splittype(uri)
340 if urltype not in ('http', 'https'):
341 raise IOError('unsupported JSON-RPC protocol')
343 _base.ServerProxy.__init__(self, uri, transport, encoding, verbose,
344 allow_none, use_datetime)
346 if transport is None:
347 if type == 'https':
348 transport = SafeTransport(use_datetime=use_datetime)
349 else:
350 transport = Transport(use_datetime=use_datetime)
351 self.__transport = transport
353 def __request(self, methodname, params):
354 """Call a method on the remote server."""
355 request = CreateRequestString(methodname, params)
357 response = self.__transport.request(
358 self.__host,
359 self.__handler,
360 request,
361 verbose=self.__verbose
364 return response