SimpleJSONRPCServer.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  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 SimpleXMLRPCServer module using JSON-RPC.
  5. This module uses SimpleXMLRPCServer as the base and only overrides those
  6. portions that implement the XML-RPC protocol. These portions are rewritten
  7. to use the JSON-RPC protocol instead.
  8. When large portions of code need to be rewritten the original code and
  9. comments are preserved. The intention here is to keep the amount of code
  10. change to a minimum.
  11. This module only depends on default Python modules, as well as jsonrpclib
  12. which also uses only default modules. No third party code is required to
  13. use this module.
  14. """
  15. import fcntl
  16. import json
  17. import SimpleXMLRPCServer as _base
  18. import SocketServer
  19. import sys
  20. import traceback
  21. import jsonrpclib
  22. try:
  23. import gzip
  24. except ImportError:
  25. gzip = None #python can be built without zlib/gzip support
  26. class SimpleJSONRPCRequestHandler(_base.SimpleXMLRPCRequestHandler):
  27. """Request handler class for received requests.
  28. This class extends the functionality of SimpleXMLRPCRequestHandler and only
  29. overrides the operations needed to change the protocol from XML-RPC to
  30. JSON-RPC.
  31. """
  32. def do_POST(self):
  33. """Handles the HTTP POST request.
  34. Attempts to interpret all HTTP POST requests as JSON-RPC calls,
  35. which are forwarded to the server's _dispatch method for handling.
  36. """
  37. # Check that the path is legal
  38. if not self.is_rpc_path_valid():
  39. self.report_404()
  40. return
  41. try:
  42. # Get arguments by reading body of request.
  43. # We read this in chunks to avoid straining
  44. # socket.read(); around the 10 or 15Mb mark, some platforms
  45. # begin to have problems (bug #792570).
  46. max_chunk_size = 10*1024*1024
  47. size_remaining = int(self.headers['content-length'])
  48. data = []
  49. while size_remaining:
  50. chunk_size = min(size_remaining, max_chunk_size)
  51. chunk = self.rfile.read(chunk_size)
  52. if not chunk:
  53. break
  54. data.append(chunk)
  55. size_remaining -= len(data[-1])
  56. data = ''.join(data)
  57. data = self.decode_request_content(data)
  58. if data is None:
  59. return # response has been sent
  60. # In previous versions of SimpleXMLRPCServer, _dispatch
  61. # could be overridden in this class, instead of in
  62. # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
  63. # check to see if a subclass implements _dispatch and dispatch
  64. # using that method if present.
  65. response = self.server._marshaled_dispatch(
  66. data, getattr(self, '_dispatch', None), self.path)
  67. except Exception, e: # This should only happen if the module is buggy
  68. # internal error, report as HTTP server error
  69. self.send_response(500)
  70. # Send information about the exception if requested
  71. if (hasattr(self.server, '_send_traceback_header') and
  72. self.server._send_traceback_header):
  73. self.send_header('X-exception', str(e))
  74. self.send_header('X-traceback', traceback.format_exc())
  75. self.send_header('Content-length', '0')
  76. self.end_headers()
  77. else:
  78. # got a valid JSON RPC response
  79. self.send_response(200)
  80. self.send_header('Content-type', 'application/json')
  81. if self.encode_threshold is not None:
  82. if len(response) > self.encode_threshold:
  83. q = self.accept_encodings().get('gzip', 0)
  84. if q:
  85. try:
  86. response = jsonrpclib.gzip_encode(response)
  87. self.send_header('Content-Encoding', 'gzip')
  88. except NotImplementedError:
  89. pass
  90. self.send_header('Content-length', str(len(response)))
  91. self.end_headers()
  92. self.wfile.write(response)
  93. class SimpleJSONRPCDispatcher(_base.SimpleXMLRPCDispatcher):
  94. """Dispatcher for received JSON-RPC requests.
  95. This class extends the functionality of SimpleXMLRPCDispatcher and only
  96. overrides the operations needed to change the protocol from XML-RPC to
  97. JSON-RPC.
  98. """
  99. def _marshaled_dispatch(self, data, dispatch_method=None, path=None):
  100. """Dispatches an JSON-RPC method from marshalled (JSON) data.
  101. JSON-RPC methods are dispatched from the marshalled (JSON) data
  102. using the _dispatch method and the result is returned as
  103. marshalled data. For backwards compatibility, a dispatch
  104. function can be provided as an argument (see comment in
  105. SimpleJSONRPCRequestHandler.do_POST) but overriding the
  106. existing method through subclassing is the preferred means
  107. of changing method dispatch behavior.
  108. Returns:
  109. The JSON-RPC string to return.
  110. """
  111. method = ''
  112. params = []
  113. ident = ''
  114. try:
  115. request = json.loads(data)
  116. print 'request:', request
  117. jsonrpclib.ValidateRequest(request)
  118. method = request['method']
  119. params = request['params']
  120. ident = request['id']
  121. # generate response
  122. if dispatch_method is not None:
  123. response = dispatch_method(method, params)
  124. else:
  125. response = self._dispatch(method, params)
  126. response = jsonrpclib.CreateResponseString(response, ident)
  127. except jsonrpclib.Fault as fault:
  128. response = jsonrpclib.CreateResponseString(fault, ident)
  129. except:
  130. # report exception back to server
  131. exc_type, exc_value, _ = sys.exc_info()
  132. response = jsonrpclib.CreateResponseString(
  133. jsonrpclib.Fault(1, '%s:%s' % (exc_type, exc_value)), ident)
  134. print 'response:', response
  135. return response
  136. class SimpleJSONRPCServer(SocketServer.TCPServer,
  137. SimpleJSONRPCDispatcher):
  138. """Simple JSON-RPC server.
  139. This class mimics the functionality of SimpleXMLRPCServer and only
  140. overrides the operations needed to change the protocol from XML-RPC to
  141. JSON-RPC.
  142. """
  143. allow_reuse_address = True
  144. # Warning: this is for debugging purposes only! Never set this to True in
  145. # production code, as will be sending out sensitive information (exception
  146. # and stack trace details) when exceptions are raised inside
  147. # SimpleJSONRPCRequestHandler.do_POST
  148. _send_traceback_header = False
  149. def __init__(self, addr, requestHandler=SimpleJSONRPCRequestHandler,
  150. logRequests=True, allow_none=False, encoding=None,
  151. bind_and_activate=True):
  152. self.logRequests = logRequests
  153. SimpleJSONRPCDispatcher.__init__(self, allow_none, encoding)
  154. SocketServer.TCPServer.__init__(self, addr, requestHandler,
  155. bind_and_activate)
  156. # [Bug #1222790] If possible, set close-on-exec flag; if a
  157. # method spawns a subprocess, the subprocess shouldn't have
  158. # the listening socket open.
  159. if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
  160. flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
  161. flags |= fcntl.FD_CLOEXEC
  162. fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)