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