Package library :: Package pyjamas :: Module JSONService
[hide private]
[frames] | no frames]

Source Code for Module library.pyjamas.JSONService

  1  # Check http://json-rpc.org/wiki for the sepcification of JSON-RPC 
  2  # Currently: 
  3  #     http://groups.google.com/group/json-rpc/web/json-rpc-1-1-wd 
  4  #     http://groups.google.com/group/json-rpc/web/json-rpc-1-2-proposal 
  5   
  6   
  7  """ JSONService is a module providing JSON RPC Client side proxying. 
  8  """ 
  9   
 10  import sys 
 11  from HTTPRequest import HTTPRequest 
 12   
 13  try: 
 14      # included in python 2.6... 
 15      from json import dumps, loads 
 16  except ImportError: 
 17      # recommended library (python 2.5) 
 18      from simplejson import dumps, loads 
 19   
20 -class JSONServiceError(Exception):
21 pass
22 23 __requestID = 0 24 __requestIDPrefix = 'ID' 25 __lastRequestID = None
26 -def nextRequestID():
27 """ 28 Return Next Request identifier. 29 MUST be a JSON scalar (String, Number, True, False), but SHOULD normally 30 not be Null, and Numbers SHOULD NOT contain fractional parts. 31 """ 32 global __requestID, __requestIDPrefix, __lastRequestID 33 __requestID += 1 34 id = "%s%s" % (__requestIDPrefix, __requestID) 35 if __lastRequestID == id: 36 # javascript integer resolution problem 37 __requestIDPrefix += '_' 38 __requestID = 0 39 id = "%s%s" % (__requestIDPrefix, __requestID) 40 __lastRequestID = id 41 return id
42 43 # no stream support
44 -class JSONService(object):
45 content_type = 'application/json-rpc' 46 accept = 'application/json-rpc' 47
48 - def __init__(self, url, handler=None, headers=None):
49 """ 50 Create a JSON remote service object. The url is the URL that will 51 receive POST data with the JSON request. See the JSON-RPC spec for 52 more information. 53 54 The handler object should implement:: 55 56 onRemoteResponse(value, requestInfo) 57 58 to accept the return value of the remote method, and:: 59 60 onRemoteError(code, error_dict, requestInfo) 61 code = http-code or 0 62 error_dict is an jsonrpc 2.0 error dict: 63 { 64 'code': jsonrpc-error-code (integer) , 65 'message': jsonrpc-error-message (string) , 66 'data' : extra-error-data 67 } 68 69 to handle errors. 70 """ 71 self.url = url 72 self.handler = handler 73 self.headers = headers if headers is not None else {} 74 if not self.headers.get("Accept"): 75 self.headers["Accept"] = self.accept
76
77 - def callMethod(self, method, params, handler = None):
78 if handler is None: 79 handler = self.handler 80 81 if handler is None: 82 return self.sendNotify(method, params) 83 else: 84 return self.sendRequest(method, params, handler)
85
86 - def onCompletion(self, response):
87 pass
88
89 - def sendNotify(self, method, params):
90 # jsonrpc: A String specifying the version of the JSON-RPC protocol. 91 # MUST be exactly "2.0" 92 # If jsonrpc is missing, the server MAY handle the Request as 93 # JSON-RPC V1.0-Request. 94 # version: String specifying the version of the JSON-RPC protocol. 95 # MUST be exactly "1.1" 96 # NOTE: We send both, to indicate that we can handle both. 97 # 98 # id: If omitted, the Request is a Notification 99 # NOTE: JSON-RPC 1.0 uses an id of Null for Notifications. 100 # method: A String containing the name of the procedure to be invoked. 101 # params: An Array or Object, that holds the actual parameter values 102 # for the invocation of the procedure. Can be omitted if 103 # empty. 104 # NOTE: JSON-RPC 1.0 only a non-empty Array is used 105 # From the spec of 1.1: 106 # The Content-Type MUST be specified and # SHOULD read 107 # application/json. 108 # The Accept MUST be specified and SHOULD read application/json. 109 # 110 # From http://groups.google.com/group/json-rpc/web/json-rpc-over-http 111 # Content-Type SHOULD be 'application/json-rpc' but MAY be 112 # 'application/json' or 'application/jsonrequest' 113 # The Accept MUST be specified and SHOULD read 'application/json-rpc' 114 # but MAY be 'application/json' or 'application/jsonrequest'. 115 # 116 msg = {"jsonrpc": "2.0", 117 "version": "1.1", 118 "method": method, 119 "params": params 120 } 121 msg_data = dumps(msg) 122 if not HTTPRequest().asyncPost(self.url, msg_data, self, 123 False, self.content_type, 124 self.headers): 125 return -1 126 return 1
127
128 - def sendRequest(self, method, params, handler):
129 id = nextRequestID() 130 msg = {"jsonrpc": "2.0", 131 "id": id, 132 "method": method, 133 "params": params 134 } 135 msg_data = dumps(msg) 136 137 request_info = JSONRequestInfo(id, method, handler) 138 if not HTTPRequest().asyncPost(self.url, msg_data, 139 JSONResponseTextHandler(request_info), 140 False, self.content_type, 141 self.headers): 142 return -1 143 return id
144 145
146 -class JSONRequestInfo(object):
147 - def __init__(self, id, method, handler):
148 self.id = id 149 self.method = method 150 self.handler = handler
151
152 -def create_object(items):
153 """ creates an object by looking for __jsonclass__ hint, 154 loading the class from that and then constructing an 155 object from the rest of the dictionary as kwargs 156 """ 157 clsname = items.pop('__jsonclass__', None) 158 if not clsname: 159 return items 160 clsname = clsname[0] 161 dot = clsname.rfind('.') 162 modulename = clsname[:dot] 163 clsname = clsname[dot+1:] 164 vars = {} 165 exec "from %s import %s as kls" % (modulename, clsname) in vars 166 kls = vars['kls'] 167 vars = {} 168 for (k, v) in items.items(): 169 vars[str(k)] = v 170 return kls(**vars)
171
172 -def _decode_response(json_str):
173 return loads(json_str, object_hook=create_object)
174
175 -class JSONResponseTextHandler(object):
176 - def __init__(self, request):
177 self.request = request
178
179 - def onCompletion(self, json_str):
180 try: 181 response = _decode_response(json_str) 182 except: # just catch... everything. 183 # err.... help?!! 184 error = dict( 185 code=-32700, 186 message="Parse error while decoding response", 187 data=None, 188 ) 189 self.request.handler.onRemoteError(0, error, self.request) 190 return 191 192 if not response: 193 error = dict( 194 code=-32603, 195 message="Empty Response", 196 data=None, 197 ) 198 self.request.handler.onRemoteError(0, error, self.request) 199 elif response.get("error"): 200 error = response["error"] 201 jsonrpc = response.get("jsonrpc") 202 code = error.get("code", 0) 203 message = error.get("message", error) 204 data = error.get("data") 205 if not jsonrpc: 206 jsonrpc = response.get("version", "1.0") 207 if jsonrpc == "1.0": 208 message = error 209 else: 210 data = error.get("error") 211 error = dict( 212 code=code, 213 message=message, 214 data=data, 215 ) 216 self.request.handler.onRemoteError(0, error, self.request) 217 elif "result" in response: 218 self.request.handler.onRemoteResponse(response["result"], 219 self.request) 220 else: 221 error = dict( 222 code=-32603, 223 message="No result or error in response", 224 data=response, 225 ) 226 self.request.handler.onRemoteError(0, error, self.request)
227
228 - def onError(self, error_str, error_code):
229 error = dict( 230 code=error_code, 231 message=error_str, 232 data=None, 233 ) 234 self.request.handler.onRemoteError(error_code, error, self.request)
235
236 -class ServiceProxy(JSONService):
237 - def __init__(self, serviceURL, serviceName=None, headers=None):
238 JSONService.__init__(self, serviceURL, headers=headers) 239 self.__serviceName = serviceName
240
241 - def __call__(self, *params, **kwargs):
242 if isinstance(params, tuple): 243 params = list(params) 244 if params and hasattr(params[0], "onRemoteResponse"): 245 handler = params.pop(0) 246 elif params and hasattr(params[-1], "onRemoteResponse"): 247 handler = params.pop() 248 else: 249 handler = None 250 if kwargs: 251 if params: 252 if not isinstance(params, dict): 253 raise JSONServiceError("Cannot mix positional and keyword arguments") 254 params.update(kwargs) 255 else: 256 params = kwargs 257 if handler is not None: 258 return JSONService.sendRequest(self, self.__serviceName, 259 params, handler) 260 else: 261 return JSONService.sendNotify(self, self.__serviceName, params)
262 263 # reserved names: callMethod, onCompletion
264 -class JSONProxy(JSONService):
265 - def __init__(self, url, methods=None, headers=None):
266 self._serviceURL = url 267 self.methods = methods 268 self.headers = {} if headers is None else headers 269 # Init with JSONService, for the use of callMethod 270 JSONService.__init__(self, url, headers=self.headers) 271 self._registerMethods(methods)
272
273 - def _registerMethods(self, methods):
274 if methods: 275 for method in methods: 276 setattr(self, 277 method, 278 getattr(ServiceProxy(self._serviceURL, method, 279 headers=self.headers), 280 '__call__') 281 )
282 283 # It would be nice to use __getattr__ (instead of _registerMethods) 284 # However, that doesn't work with pyjs and the use of _registerMethods 285 # saves some repeated instance creations (now only once per method and 286 # not once per call) 287 #def __getattr__(self, name): 288 # if not name in self.methods: 289 # raise AttributeError("no such method %s" % name) 290 # return ServiceProxy(self._serviceURL, name, headers=self.headers) 291