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

Source Code for Module 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      from jsonrpc.json import dumps, loads, JSONDecodeException 
 15  except ImportError: 
 16      from pyjamas.JSONParser import JSONParser 
 17      parser = JSONParser() 
 18      dumps = getattr(parser, 'encode') 
 19      loads = getattr(parser, 'decodeAsObject') 
 20      JSONDecodeException = None 
 21   
 22   
23 -class JSONServiceError(Exception):
24 pass
25 26 __requestID = 0 27 __requestIDPrefix = 'ID' 28 __lastRequestID = None
29 -def nextRequestID():
30 """ 31 Return Next Request identifier. 32 MUST be a JSON scalar (String, Number, True, False), but SHOULD normally 33 not be Null, and Numbers SHOULD NOT contain fractional parts. 34 """ 35 global __requestID, __requestIDPrefix, __lastRequestID 36 __requestID += 1 37 id = "%s%s" % (__requestIDPrefix, __requestID) 38 if __lastRequestID == id: 39 # javascript integer resolution problem 40 __requestIDPrefix += '_' 41 __requestID = 0 42 id = "%s%s" % (__requestIDPrefix, __requestID) 43 __lastRequestID = id 44 return id
45 46 # no stream support
47 -class JSONService(object):
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 onRemoteResponse(value, requestInfo) 56 to accept the return value of the remote method, and 57 onRemoteError(code, error_dict, requestInfo) 58 code = http-code or 0 59 error_dict is an jsonrpc 2.0 error dict: 60 { 61 'code': jsonrpc-error-code (integer) , 62 'message': jsonrpc-error-message (string) , 63 'data' : extra-error-data 64 } 65 to handle errors. 66 """ 67 self.url = url 68 self.handler = handler 69 self.headers = headers if headers is not None else {} 70 if not self.headers.get("Accept"): 71 self.headers["Accept"] = "application/json"
72
73 - def callMethod(self, method, params, handler = None):
74 if handler is None: 75 handler = self.handler 76 77 if handler is None: 78 return self.sendNotify(method, params) 79 else: 80 return self.sendRequest(method, params, handler)
81
82 - def onCompletion(self, response):
83 pass
84
85 - def sendNotify(self, method, params):
86 # jsonrpc: A String specifying the version of the JSON-RPC protocol. 87 # MUST be exactly "2.0" 88 # If jsonrpc is missing, the server MAY handle the Request as 89 # JSON-RPC V1.0-Request. 90 # version: String specifying the version of the JSON-RPC protocol. 91 # MUST be exactly "1.1" 92 # NOTE: We send both, to indicate that we can handle both. 93 # 94 # id: If omitted, the Request is a Notification 95 # NOTE: JSON-RPC 1.0 uses an id of Null for Notifications. 96 # method: A String containing the name of the procedure to be invoked. 97 # params: An Array or Object, that holds the actual parameter values 98 # for the invocation of the procedure. Can be omitted if 99 # empty. 100 # NOTE: JSON-RPC 1.0 only a non-empty Array is used 101 # From the spec of 1.1: 102 # The Content-Type MUST be specified and # SHOULD read 103 # application/json. 104 # The Accept MUST be specified and SHOULD read application/json. 105 # 106 msg = {"jsonrpc": "2.0", 107 "version": "1.1", 108 "method": method, 109 "params": params 110 } 111 msg_data = dumps(msg) 112 if not HTTPRequest().asyncPost(self.url, msg_data, self, 113 False, "text/json", 114 self.headers): 115 return -1 116 return 1
117
118 - def sendRequest(self, method, params, handler):
119 id = nextRequestID() 120 msg = {"jsonrpc": "2.0", 121 "id": id, 122 "method": method, 123 "params": params 124 } 125 msg_data = dumps(msg) 126 127 request_info = JSONRequestInfo(id, method, handler) 128 if not HTTPRequest().asyncPost(self.url, msg_data, 129 JSONResponseTextHandler(request_info), 130 False, "text/json", 131 self.headers): 132 return -1 133 return id
134 135
136 -class JSONRequestInfo(object):
137 - def __init__(self, id, method, handler):
138 self.id = id 139 self.method = method 140 self.handler = handler
141 142
143 -class JSONResponseTextHandler(object):
144 - def __init__(self, request):
145 self.request = request
146
147 - def onCompletion(self, json_str):
148 try: 149 response = loads(json_str) 150 except JSONDecodeException: 151 # err.... help?!! 152 error = dict( 153 code=-32700, 154 message="Parse error while decoding response", 155 data=None, 156 ) 157 self.request.handler.onRemoteError(0, error, self.request) 158 return 159 160 if not response: 161 error = dict( 162 code=-32603, 163 message="Empty Response", 164 data=None, 165 ) 166 self.request.handler.onRemoteError(0, error, self.request) 167 elif response.get("error"): 168 error = response["error"] 169 jsonrpc = response.get("jsonrpc") 170 code = error.get("code", 0) 171 message = error.get("message", error) 172 data = error.get("data") 173 if not jsonrpc: 174 jsonrpc = response.get("version", "1.0") 175 if jsonrpc == "1.0": 176 message = error 177 else: 178 data = error.get("error") 179 error = dict( 180 code=code, 181 message=message, 182 data=data, 183 ) 184 self.request.handler.onRemoteError(0, error, self.request) 185 elif "result" in response: 186 self.request.handler.onRemoteResponse(response["result"], 187 self.request) 188 else: 189 error = dict( 190 code=-32603, 191 message="No result or error in response", 192 data=response, 193 ) 194 self.request.handler.onRemoteError(0, error, self.request)
195
196 - def onError(self, error_str, error_code):
197 error = dict( 198 code=error_code, 199 message=error_str, 200 data=None, 201 ) 202 self.request.handler.onRemoteError(error_code, error, self.request)
203
204 -class ServiceProxy(JSONService):
205 - def __init__(self, serviceURL, serviceName=None, headers=None):
206 JSONService.__init__(self, serviceURL, headers=headers) 207 self.__serviceName = serviceName
208
209 - def __call__(self, *params, **kwargs):
210 if isinstance(params, tuple): 211 params = list(params) 212 if params and hasattr(params[-1], "onRemoteResponse"): 213 handler = params.pop() 214 else: 215 handler = None 216 if kwargs: 217 if params: 218 if not isinstance(params, dict): 219 raise JSONServiceError("Cannot mix positional and keyword arguments") 220 params.update(kwargs) 221 else: 222 params = kwargs 223 if handler is not None: 224 return JSONService.sendRequest(self, self.__serviceName, 225 params, handler) 226 else: 227 return JSONService.sendNotify(self, self.__serviceName, params)
228 229 # reserved names: callMethod, onCompletion
230 -class JSONProxy(JSONService):
231 - def __init__(self, url, methods=None, headers=None):
232 self._serviceURL = url 233 self.methods = methods 234 self.headers = {} if headers is None else headers 235 # Init with JSONService, for the use of callMethod 236 JSONService.__init__(self, url, headers=self.headers) 237 self.__registerMethods(methods)
238
239 - def __registerMethods(self, methods):
240 if methods: 241 for method in methods: 242 setattr(self, 243 method, 244 getattr(ServiceProxy(self._serviceURL, method, 245 headers=self.headers), 246 '__call__') 247 )
248 249 # It would be nice to use __getattr__ (in stead of __registerMethods) 250 # However, that doesn't work with pyjs and the use of __registerMethods 251 # saves some repeated instance creations (now only once per method and 252 # not once per call) 253 #def __getattr__(self, name): 254 # if not name in self.methods: 255 # raise AttributeError("no such method %s" % name) 256 # return ServiceProxy(self._serviceURL, name, headers=self.headers) 257