Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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. | |
| 5 | |
| 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. | |
| 9 | |
| 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. | |
| 13 | |
| 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 | |
| 20 | |
| 21 gzip_encode = _base.gzip_encode | |
| 22 | |
| 23 | |
| 24 class Error(Exception): | |
| 25 | |
| 26 def __str__(self): | |
| 27 return repr(self) | |
| 28 | |
| 29 | |
| 30 class ProtocolError(Error): | |
| 31 """Indicates a JSON protocol error.""" | |
| 32 | |
| 33 def __init__(self, url, errcode, errmsg, headers): | |
| 34 Error.__init__(self) | |
| 35 self.url = url | |
| 36 self.errcode = errcode | |
| 37 self.errmsg = errmsg | |
| 38 self.headers = headers | |
| 39 | |
| 40 def __repr__(self): | |
| 41 return ( | |
| 42 '<ProtocolError for %s: %s %s>' % | |
| 43 (self.url, self.errcode, self.errmsg)) | |
| 44 | |
| 45 | |
| 46 class ResponseError(Error): | |
| 47 """Indicates a broken response package.""" | |
| 48 pass | |
| 49 | |
| 50 | |
| 51 class Fault(Error): | |
| 52 """Indicates an JSON-RPC fault package.""" | |
| 53 | |
| 54 def __init__(self, code, message): | |
| 55 Error.__init__(self) | |
| 56 if not isinstance(code, int): | |
| 57 raise ProtocolError('Fault code must be an integer.') | |
| 58 self.code = code | |
| 59 self.message = message | |
| 60 | |
| 61 def __repr__(self): | |
| 62 return ( | |
| 63 '<Fault %s: %s>' % | |
| 64 (self.code, repr(self.message)) | |
| 65 ) | |
| 66 | |
| 67 | |
| 68 def CreateRequest(methodname, params, ident=''): | |
| 69 """Create a valid JSON-RPC request. | |
| 70 | |
| 71 Args: | |
| 72 methodname: The name of the remote method to invoke. | |
| 73 params: The parameters to pass to the remote method. This should be a | |
| 74 list or tuple and able to be encoded by the default JSON parser. | |
| 75 | |
| 76 Returns: | |
| 77 A valid JSON-RPC request object. | |
| 78 """ | |
| 79 request = { | |
| 80 'jsonrpc': '2.0', | |
| 81 'method': methodname, | |
| 82 'params': params, | |
| 83 'id': ident | |
| 84 } | |
| 85 | |
| 86 return request | |
| 87 | |
| 88 | |
| 89 def CreateRequestString(methodname, params, ident=''): | |
| 90 """Create a valid JSON-RPC request string. | |
| 91 | |
| 92 Args: | |
| 93 methodname: The name of the remote method to invoke. | |
| 94 params: The parameters to pass to the remote method. | |
| 95 These parameters need to be encode-able by the default JSON parser. | |
| 96 ident: The request identifier. | |
| 97 | |
| 98 Returns: | |
| 99 A valid JSON-RPC request string. | |
| 100 """ | |
| 101 return json.dumps(CreateRequest(methodname, params, ident)) | |
| 102 | |
| 103 def CreateResponse(data, ident): | |
| 104 """Create a JSON-RPC response. | |
| 105 | |
| 106 Args: | |
| 107 data: The data to return. | |
| 108 ident: The response identifier. | |
| 109 | |
| 110 Returns: | |
| 111 A valid JSON-RPC response object. | |
| 112 """ | |
| 113 if isinstance(data, Fault): | |
| 114 response = { | |
| 115 'jsonrpc': '2.0', | |
| 116 'error': { | |
| 117 'code': data.code, | |
| 118 'message': data.message}, | |
| 119 'id': ident | |
| 120 } | |
|
andrewrs
2014/12/18 19:10:32
This trailing brace doesn't look quite aligned in
Mike Meade
2015/01/05 23:18:58
Actually this whole block was badly formatted. Tha
| |
| 121 else: | |
| 122 response = { | |
| 123 'jsonrpc': '2.0', | |
| 124 'response': data, | |
| 125 'id': ident | |
| 126 } | |
| 127 | |
| 128 return response | |
| 129 | |
| 130 | |
| 131 def CreateResponseString(data, ident): | |
| 132 """Create a JSON-RPC response string. | |
| 133 | |
| 134 Args: | |
| 135 data: The data to return. | |
| 136 ident: The response identifier. | |
| 137 | |
| 138 Returns: | |
| 139 A valid JSON-RPC response object. | |
| 140 """ | |
| 141 return json.dumps(CreateResponse(data, ident)) | |
| 142 | |
| 143 | |
| 144 def ParseHTTPResponse(response): | |
| 145 """Parse an HTTP response object and return the JSON object. | |
| 146 | |
| 147 Args: | |
| 148 response: An HTTP response object. | |
| 149 | |
| 150 Returns: | |
| 151 The returned JSON-RPC object. | |
| 152 | |
| 153 Raises: | |
| 154 ProtocolError: if the object format is not correct. | |
| 155 Fault: If a Fault error is returned from the server. | |
| 156 """ | |
| 157 # Check for new http response object, else it is a file object | |
| 158 if hasattr(response, 'getheader'): | |
| 159 if response.getheader('Content-Encoding', '') == 'gzip': | |
| 160 stream = _base.GzipDecodedResponse(response) | |
| 161 else: | |
| 162 stream = response | |
| 163 else: | |
| 164 stream = response | |
| 165 | |
| 166 data = '' | |
| 167 while 1: | |
| 168 chunk = stream.read(1024) | |
| 169 if not chunk: | |
| 170 break | |
| 171 data += chunk | |
| 172 | |
| 173 response = json.loads(data) | |
| 174 ValidateBasicJSONRPCData(response) | |
| 175 | |
| 176 if 'response' in response: | |
| 177 ValidateResponse(response) | |
| 178 return response['response'] | |
| 179 elif 'error' in response: | |
| 180 ValidateError(response) | |
| 181 code = response['error']['code'] | |
| 182 message = response['error']['message'] | |
| 183 raise Fault(code, message) | |
| 184 else: | |
| 185 raise ProtocolError('No valid JSON returned') | |
| 186 | |
| 187 | |
| 188 def ValidateRequest(data): | |
| 189 """Validate a JSON-RPC request object. | |
| 190 | |
| 191 Args: | |
| 192 data: The JSON-RPC object (dict). | |
| 193 | |
| 194 Raises: | |
| 195 ProtocolError: if the object format is not correct. | |
| 196 """ | |
| 197 ValidateBasicJSONRPCData(data) | |
| 198 if 'method' not in data or 'params' not in data: | |
| 199 raise ProtocolError('JSON is not a valid request') | |
| 200 | |
| 201 | |
| 202 def ValidateResponse(data): | |
| 203 """Validate a JSON-RPC response object. | |
| 204 | |
| 205 Args: | |
| 206 data: The JSON-RPC object (dict). | |
| 207 | |
| 208 Raises: | |
| 209 ProtocolError: if the object format is not correct. | |
| 210 """ | |
| 211 ValidateBasicJSONRPCData(data) | |
| 212 if 'response' not in data: | |
| 213 raise ProtocolError('JSON is not a valid response') | |
| 214 | |
| 215 | |
| 216 def ValidateError(data): | |
| 217 """Validate a JSON-RPC error object. | |
| 218 | |
| 219 Args: | |
| 220 data: The JSON-RPC object (dict). | |
| 221 | |
| 222 Raises: | |
| 223 ProtocolError: if the object format is not correct. | |
| 224 """ | |
| 225 ValidateBasicJSONRPCData(data) | |
| 226 if ('error' not in data or | |
| 227 'code' not in data['error'] or | |
| 228 'message' not in data['error']): | |
| 229 raise ProtocolError('JSON is not a valid error response') | |
| 230 | |
| 231 | |
| 232 def ValidateBasicJSONRPCData(data): | |
| 233 """Validate a basic JSON-RPC object. | |
| 234 | |
| 235 Args: | |
| 236 data: The JSON-RPC object (dict). | |
| 237 | |
| 238 Raises: | |
| 239 ProtocolError: if the object format is not correct. | |
| 240 """ | |
| 241 error = None | |
| 242 if not isinstance(data, dict): | |
| 243 error = 'JSON data is not a dictionary' | |
| 244 elif 'jsonrpc' not in data or data['jsonrpc'] != '2.0': | |
| 245 error = 'JSON is not a valid JSON RPC 2.0 message' | |
| 246 elif 'id' not in data: | |
| 247 error = 'JSON data missing required id entry' | |
| 248 if error: | |
| 249 raise ProtocolError(error) | |
| 250 | |
| 251 | |
| 252 class Transport(_base.Transport): | |
| 253 """RPC transport class. | |
| 254 | |
| 255 This class extends the functionality of xmlrpclib.Transport and only | |
| 256 overrides the operations needed to change the protocol from XML-RPC to | |
| 257 JSON-RPC. | |
| 258 """ | |
| 259 | |
|
andrewrs
2014/12/18 19:10:32
What do you think about setting Transport.user_age
Mike Meade
2015/01/05 23:18:58
Done.
| |
| 260 def single_request(self, host, handler, request_body, verbose=0): | |
| 261 """Issue a single JSON-RPC request.""" | |
| 262 | |
| 263 h = self.make_connection(host) | |
| 264 if verbose: | |
| 265 h.set_debuglevel(1) | |
| 266 try: | |
| 267 self.send_request(h, handler, request_body) | |
| 268 self.send_host(h, host) | |
| 269 self.send_user_agent(h) | |
| 270 self.send_content(h, request_body) | |
|
andrewrs
2014/12/18 19:10:32
Looking at the default implementation of xmlrpclib
Mike Meade
2015/01/05 23:18:58
Good catch on that one. Done.
| |
| 271 | |
| 272 response = h.getresponse(buffering=True) | |
| 273 if response.status == 200: | |
| 274 self.verbose = verbose | |
| 275 | |
| 276 return self.parse_response(response) | |
| 277 | |
| 278 except Fault: | |
| 279 raise | |
| 280 except Exception: | |
| 281 # All unexpected errors leave connection in | |
| 282 # a strange state, so we clear it. | |
| 283 self.close() | |
| 284 raise | |
| 285 | |
| 286 # discard any response data and raise exception | |
| 287 if response.getheader('content-length', 0): | |
| 288 response.read() | |
| 289 raise ProtocolError( | |
| 290 host + handler, | |
| 291 response.status, response.reason, | |
| 292 response.msg, | |
| 293 ) | |
| 294 | |
| 295 def parse_response(self, response): | |
| 296 """Parse the HTTP resoponse from the server.""" | |
| 297 return ParseHTTPResponse(response) | |
| 298 | |
| 299 | |
| 300 class SafeTransport(_base.SafeTransport): | |
| 301 """Transport class for HTTPS servers. | |
| 302 | |
| 303 This class extends the functionality of xmlrpclib.SafeTransport and only | |
| 304 overrides the operations needed to change the protocol from XML-RPC to | |
| 305 JSON-RPC. | |
| 306 """ | |
| 307 | |
| 308 def parse_response(self, response): | |
| 309 return ParseHTTPResponse(response) | |
| 310 | |
| 311 | |
| 312 class ServerProxy(_base.ServerProxy): | |
| 313 """Proxy class to the RPC server. | |
| 314 | |
| 315 This class extends the functionality of xmlrpclib.ServerProxy and only | |
| 316 overrides the operations needed to change the protocol from XML-RPC to | |
| 317 JSON-RPC. | |
| 318 """ | |
| 319 | |
| 320 def __init__(self, uri, transport=None, encoding=None, verbose=0, | |
| 321 allow_none=0, use_datetime=0): | |
| 322 urltype, _ = urllib.splittype(uri) | |
| 323 if urltype not in ('http', 'https'): | |
| 324 raise IOError('unsupported JSON-RPC protocol') | |
| 325 | |
| 326 _base.ServerProxy.__init__(self, uri, transport, encoding, verbose, | |
| 327 allow_none, use_datetime) | |
| 328 | |
| 329 if transport is None: | |
| 330 if type == 'https': | |
| 331 transport = SafeTransport(use_datetime=use_datetime) | |
| 332 else: | |
| 333 transport = Transport(use_datetime=use_datetime) | |
| 334 self.__transport = transport | |
| 335 | |
| 336 def __request(self, methodname, params): | |
| 337 """Call a method on the remote server.""" | |
| 338 request = CreateRequestString(methodname, params) | |
| 339 | |
| 340 response = self.__transport.request( | |
| 341 self.__host, | |
| 342 self.__handler, | |
| 343 request, | |
| 344 verbose=self.__verbose | |
| 345 ) | |
| 346 | |
| 347 return response | |
| OLD | NEW |