Package Gnumed :: Package proxiedpyjamas :: Module jsonserver
[frames] | no frames]

Source Code for Module Gnumed.proxiedpyjamas.jsonserver

  1   
  2  # Originally taken from: 
  3  # http://code.activestate.com/recipes/552751/ 
  4  # thanks to david decotigny 
  5   
  6  # Heavily based on the XML-RPC implementation in python. 
  7  # Based on the json-rpc specs: http://json-rpc.org/wiki/specification 
  8  # The main deviation is on the error treatment. The official spec 
  9  # would set the 'error' attribute to a string. This implementation 
 10  # sets it to a dictionary with keys: message/traceback/type 
 11   
 12  import cjson 
 13  import SocketServer 
 14  import SimpleHTTPServer 
 15  import BaseHTTPServer 
 16  import sys 
 17  import traceback 
 18  import socket 
 19  import os 
 20  import cgi 
 21  import urllib 
 22  try: 
 23      import fcntl 
 24  except ImportError: 
 25      fcntl = None 
 26  try: 
 27      from cStringIO import StringIO 
 28  except ImportError: 
 29      from StringIO import StringIO 
 30   
31 -class CloseConnection(Exception):
32 pass
33 34 35 ### 36 ### Server code 37 ### 38 import SimpleXMLRPCServer 39
40 -def _quote_html(html):
41 return html.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
42
43 -class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
44 - def _marshaled_dispatch(self, data, dispatch_method = None):
45 id = None 46 try: 47 print "about to decode. pid:", os.getpid(), repr(data) 48 req = cjson.decode(data) 49 method = req['method'] 50 params = req['params'] or () 51 id = req['id'] 52 print "decoded and about to call. pid:", os.getpid(), method, params 53 54 close_connection = False 55 try: 56 if dispatch_method is not None: 57 result = dispatch_method(method, params) 58 else: 59 result = self._dispatch(method, params) 60 except CloseConnection, e: 61 close_connection = True 62 result = e.args[0] 63 print "result", close_connection, result 64 response = dict(id=id, result=result, error=None) 65 except: 66 extpe, exv, extrc = sys.exc_info() 67 err = dict(type=str(extpe), 68 message=str(exv), 69 traceback=''.join(traceback.format_tb(extrc))) 70 response = dict(id=id, result=None, error=err) 71 try: 72 print "response", response 73 return (cjson.encode(response), close_connection) 74 except: 75 extpe, exv, extrc = sys.exc_info() 76 err = dict(type=str(extpe), 77 message=str(exv), 78 traceback=''.join(traceback.format_tb(extrc))) 79 response = dict(id=id, result=None, error=err) 80 return (cjson.encode(response), close_connection)
81 82
83 -class SimpleJSONRPCRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
84 """Simple JSONRPC request handler class and HTTP GET Server 85 86 Handles all HTTP POST requests and attempts to decode them as 87 JSONRPC requests. 88 89 Handles all HTTP GET requests and serves the content from the 90 current directory. 91 92 """ 93 94 # Class attribute listing the accessible path components; 95 # paths not on this list will result in a 404 error. 96 rpc_paths = ('/', '/JSON') 97
98 - def is_rpc_path_valid(self):
99 if self.rpc_paths: 100 return self.path in self.rpc_paths 101 else: 102 # If .rpc_paths is empty, just assume all paths are legal 103 return True
104
105 - def send_head(self):
106 """Common code for GET and HEAD commands. 107 108 This sends the response code and MIME headers. 109 110 Return value is either a file object (which has to be copied 111 to the outputfile by the caller unless the command was HEAD, 112 and must be closed by the caller under all circumstances), or 113 None, in which case the caller has nothing further to do. 114 115 """ 116 print "send_head. pid:", os.getpid() 117 path = self.translate_path(self.path) 118 f = None 119 if os.path.isdir(path): 120 if not self.path.endswith('/'): 121 # redirect browser - doing basically what apache does 122 self.send_response(301) 123 self.send_header("Location", self.path + "/") 124 self.end_headers() 125 return None 126 for index in "index.html", "index.htm": 127 index = os.path.join(path, index) 128 if os.path.exists(index): 129 path = index 130 break 131 else: 132 return self.list_directory(path) 133 ctype = self.guess_type(path) 134 if ctype.startswith('text/'): 135 mode = 'r' 136 else: 137 mode = 'rb' 138 try: 139 f = open(path, mode) 140 except IOError: 141 self.send_error(404, "File not found") 142 return None 143 self.send_response(200) 144 self.send_header("Content-type", ctype) 145 fs = os.fstat(f.fileno()) 146 self.send_header("Content-Length", str(fs[6])) 147 self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) 148 if self.request_version == "HTTP/1.1": 149 self.send_header("Connection", "keep-alive") 150 151 self.end_headers() 152 return f
153
154 - def list_directory(self, path):
155 """Helper to produce a directory listing (absent index.html). 156 157 Return value is either a file object, or None (indicating an 158 error). In either case, the headers are sent, making the 159 interface the same as for send_head(). 160 161 """ 162 try: 163 list = os.listdir(path) 164 except os.error: 165 self.send_error(404, "No permission to list directory") 166 return None 167 list.sort(key=lambda a: a.lower()) 168 f = StringIO() 169 displaypath = cgi.escape(urllib.unquote(self.path)) 170 f.write("<title>Directory listing for %s</title>\n" % displaypath) 171 f.write("<h2>Directory listing for %s</h2>\n" % displaypath) 172 f.write("<hr>\n<ul>\n") 173 for name in list: 174 fullname = os.path.join(path, name) 175 displayname = linkname = name 176 # Append / for directories or @ for symbolic links 177 if os.path.isdir(fullname): 178 displayname = name + "/" 179 linkname = name + "/" 180 if os.path.islink(fullname): 181 displayname = name + "@" 182 # Note: a link to a directory displays with @ and links with / 183 f.write('<li><a href="%s">%s</a>\n' 184 % (urllib.quote(linkname), cgi.escape(displayname))) 185 f.write("</ul>\n<hr>\n") 186 f.write("\n<hr>\nCookies: %s\n<hr>\n" % str(self.headers.get("Cookie", None))) 187 length = f.tell() 188 f.seek(0) 189 self.send_response(200) 190 self.send_header("Content-type", "text/html") 191 if self.request_version == "HTTP/1.1": 192 self.send_header("Connection", "keep-alive") 193 self.send_header("Content-Length", str(length)) 194 self.end_headers() 195 return f
196
197 - def send_error(self, code, message=None):
198 """Send and log an error reply. 199 200 Arguments are the error code, and a detailed message. 201 The detailed message defaults to the short entry matching the 202 response code. 203 204 This sends an error response (so it must be called before any 205 output has been generated), logs the error, and finally sends 206 a piece of HTML explaining the error to the user. 207 208 """ 209 210 try: 211 short, long = self.responses[code] 212 except KeyError: 213 short, long = '???', '???' 214 if message is None: 215 message = short 216 explain = long 217 self.log_error("code %d, message %s", code, message) 218 # using _quote_html to prevent Cross Site Scripting attacks (see bug #1100201) 219 content = (self.error_message_format % 220 {'code': code, 'message': _quote_html(message), 'explain': explain}) 221 self.send_response(code, message) 222 self.send_header("Content-Type", "text/html") 223 if self.command != 'HEAD' and code >= 200 and code not in (204, 304): 224 self.send_header("Content-Length", str(len(content))) 225 if self.request_version == "HTTP/1.1": 226 self.send_header("Connection", "keep-alive") 227 else: 228 self.send_header('Connection', 'close') 229 self.end_headers() 230 if self.command != 'HEAD' and code >= 200 and code not in (204, 304): 231 self.wfile.write(content)
232
233 - def do_POST(self):
234 """Handles the HTTP POST request. 235 236 Attempts to interpret all HTTP POST requests as XML-RPC calls, 237 which are forwarded to the server's _dispatch method for handling. 238 """ 239 240 self.connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 241 242 # Check that the path is legal 243 if not self.is_rpc_path_valid(): 244 self.report_404() 245 return 246 247 try: 248 # Get arguments by reading body of request. 249 # We read this in chunks to avoid straining 250 # socket.read(); around the 10 or 15Mb mark, some platforms 251 # begin to have problems (bug #792570). 252 max_chunk_size = 10*1024*1024 253 size_remaining = int(self.headers["content-length"]) 254 L = [] 255 while size_remaining: 256 chunk_size = min(size_remaining, max_chunk_size) 257 L.append(self.rfile.read(chunk_size)) 258 size_remaining -= len(L[-1]) 259 data = ''.join(L) 260 261 # In previous versions of SimpleXMLRPCServer, _dispatch 262 # could be overridden in this class, instead of in 263 # SimpleXMLRPCDispatcher. To maintain backwards compatibility, 264 # check to see if a subclass implements _dispatch and dispatch 265 # using that method if present. 266 response, close_connection = self.server._marshaled_dispatch( 267 data, getattr(self, '_dispatch', None) 268 ) 269 except: # This should only happen if the module is buggy 270 # internal error, report as HTTP server error 271 self.send_response(500) 272 self.end_headers() 273 return 274 # got a valid JSONRPC response 275 self.send_response(200) 276 self.send_header("Content-type", "text/x-json") 277 self.send_header("Connection", "keep-alive") 278 self.send_header("Content-length", str(len(response))) 279 self.end_headers() 280 print "response", repr(response) 281 self.wfile.write(response) 282 283 self.wfile.flush() 284 print "flushed" 285 if close_connection: 286 self.connection.shutdown(1)
287
288 - def report_404 (self):
289 # Report a 404 error 290 self.send_response(404) 291 response = 'No such page' 292 self.send_header("Content-type", "text/plain") 293 self.send_header("Content-length", str(len(response))) 294 self.end_headers() 295 self.wfile.write(response) 296 # shut down the connection 297 self.wfile.flush() 298 self.connection.shutdown(1)
299
300 - def log_request(self, code='-', size='-'):
301 """Selectively log an accepted request.""" 302 303 if self.server.logRequests: 304 BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size)
305 306
307 -class SimpleForkingJSONRPCServer(SocketServer.ForkingTCPServer, 308 SimpleJSONRPCDispatcher):
309 """Simple JSON-RPC server. 310 311 Simple JSON-RPC server that allows functions and a single instance 312 to be installed to handle requests. The default implementation 313 attempts to dispatch JSON-RPC calls to the functions or instance 314 installed in the server. Override the _dispatch method inhereted 315 from SimpleJSONRPCDispatcher to change this behavior. 316 """ 317 318 allow_reuse_address = True 319
320 - def __init__(self, addr, requestHandler=SimpleJSONRPCRequestHandler, 321 logRequests=True):
322 self.logRequests = logRequests 323 324 SimpleJSONRPCDispatcher.__init__(self, allow_none=True, encoding=None) 325 SocketServer.ForkingTCPServer.__init__(self, addr, requestHandler) 326 327 # [Bug #1222790] If possible, set close-on-exec flag; if a 328 # method spawns a subprocess, the subprocess shouldn't have 329 # the listening socket open. 330 if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'): 331 flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD) 332 flags |= fcntl.FD_CLOEXEC 333 fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
334
335 -class SimpleJSONRPCServer(SocketServer.ThreadingTCPServer, 336 SimpleJSONRPCDispatcher):
337 """Simple JSON-RPC server. 338 339 Simple JSON-RPC server that allows functions and a single instance 340 to be installed to handle requests. The default implementation 341 attempts to dispatch JSON-RPC calls to the functions or instance 342 installed in the server. Override the _dispatch method inhereted 343 from SimpleJSONRPCDispatcher to change this behavior. 344 """ 345 346 allow_reuse_address = True 347
348 - def __init__(self, addr, requestHandler=SimpleJSONRPCRequestHandler, 349 logRequests=True):
350 self.logRequests = logRequests 351 352 SimpleJSONRPCDispatcher.__init__(self, allow_none=True, encoding=None) 353 SocketServer.ThreadingTCPServer.__init__(self, addr, requestHandler) 354 355 # [Bug #1222790] If possible, set close-on-exec flag; if a 356 # method spawns a subprocess, the subprocess shouldn't have 357 # the listening socket open. 358 if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'): 359 flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD) 360 flags |= fcntl.FD_CLOEXEC 361 fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
362 363 364 ### 365 ### Client code 366 ### 367 import xmlrpclib 368
369 -class ResponseError(xmlrpclib.ResponseError):
370 pass
371 -class Fault(xmlrpclib.ResponseError):
372 pass
373
374 -def _get_response(file, sock):
375 data = "" 376 while 1: 377 if sock: 378 response = sock.recv(1024) 379 else: 380 response = file.read(1024) 381 if not response: 382 break 383 data += response 384 385 file.close() 386 387 return data
388
389 -class Transport(xmlrpclib.Transport):
390 - def _parse_response(self, file, sock):
391 return _get_response(file, sock)
392
393 -class SafeTransport(xmlrpclib.SafeTransport):
394 - def _parse_response(self, file, sock):
395 return _get_response(file, sock)
396
397 -class ServerProxy:
398 - def __init__(self, uri, id=None, transport=None, use_datetime=0):
399 # establish a "logical" server connection 400 401 # get the url 402 import urllib 403 type, uri = urllib.splittype(uri) 404 if type not in ("http", "https"): 405 raise IOError, "unsupported JSON-RPC protocol" 406 self.__host, self.__handler = urllib.splithost(uri) 407 if not self.__handler: 408 self.__handler = "/JSON" 409 410 if transport is None: 411 if type == "https": 412 transport = SafeTransport(use_datetime=use_datetime) 413 else: 414 transport = Transport(use_datetime=use_datetime) 415 416 self.__transport = transport 417 self.__id = id
418
419 - def __request(self, methodname, params):
420 # call a method on the remote server 421 422 request = cjson.encode(dict(id=self.__id, method=methodname, 423 params=params)) 424 425 data = self.__transport.request( 426 self.__host, 427 self.__handler, 428 request, 429 verbose=False 430 ) 431 432 response = cjson.decode(data) 433 434 if response["id"] != self.__id: 435 raise ResponseError("Invalid request id (is: %s, expected: %s)" \ 436 % (response["id"], self.__id)) 437 if response["error"] is not None: 438 raise Fault("JSON Error", response["error"]) 439 return response["result"]
440
441 - def __repr__(self):
442 return ( 443 "<ServerProxy for %s%s>" % 444 (self.__host, self.__handler) 445 )
446 447 __str__ = __repr__ 448
449 - def __getattr__(self, name):
450 # magic method dispatcher 451 return xmlrpclib._Method(self.__request, name)
452 453 454 if __name__ == '__main__': 455 if not len(sys.argv) > 1: 456 import socket 457 print 'Running JSON-RPC server on port 8000' 458 server = SimpleJSONRPCServer(("localhost", 8000)) 459 server.register_function(pow) 460 server.register_function(lambda x,y: x+y, 'add') 461 server.register_function(lambda x: x, 'echo') 462 server.serve_forever() 463 else: 464 remote = ServerProxy(sys.argv[1]) 465 print 'Using connection', remote 466 467 print repr(remote.add(1, 2)) 468 aaa = remote.add 469 print repr(remote.pow(2, 4)) 470 print aaa(5, 6) 471 472 try: 473 # Invalid parameters 474 aaa(5, "toto") 475 print "Successful execution of invalid code" 476 except Fault: 477 pass 478 479 try: 480 # Invalid parameters 481 aaa(5, 6, 7) 482 print "Successful execution of invalid code" 483 except Fault: 484 pass 485 486 try: 487 # Invalid method name 488 print repr(remote.powx(2, 4)) 489 print "Successful execution of invalid code" 490 except Fault: 491 pass 492