summaryrefslogtreecommitdiffstats
path: root/testing/legion/SimpleJSONRPCServer.py
blob: 6d5aa6c1012aeec028ed1a4d9920d2ca4fb5b0fb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Module to implement the SimpleXMLRPCServer module using JSON-RPC.

This module uses SimpleXMLRPCServer as the base and only overrides those
portions that implement the XML-RPC protocol. These portions are rewritten
to use the JSON-RPC protocol instead.

When large portions of code need to be rewritten the original code and
comments are preserved. The intention here is to keep the amount of code
change to a minimum.

This module only depends on default Python modules, as well as jsonrpclib
which also uses only default modules. No third party code is required to
use this module.
"""

import json
import SimpleXMLRPCServer as _base
import SocketServer
import sys
import traceback
try:
  import fcntl
except ImportError:
  fcntl = None
try:
  import gzip
except ImportError:
  gzip = None #python can be built without zlib/gzip support

#pylint: disable=relative-import
import jsonrpclib


class SimpleJSONRPCRequestHandler(_base.SimpleXMLRPCRequestHandler):
  """Request handler class for received requests.

  This class extends the functionality of SimpleXMLRPCRequestHandler and only
  overrides the operations needed to change the protocol from XML-RPC to
  JSON-RPC.
  """

  def do_POST(self):
    """Handles the HTTP POST request.

    Attempts to interpret all HTTP POST requests as JSON-RPC calls,
    which are forwarded to the server's _dispatch method for handling.
    """
    # Check that the path is legal
    if not self.is_rpc_path_valid():
      self.report_404()
      return

    try:
      # Get arguments by reading body of request.
      # We read this in chunks to avoid straining
      # socket.read(); around the 10 or 15Mb mark, some platforms
      # begin to have problems (bug #792570).
      max_chunk_size = 10*1024*1024
      size_remaining = int(self.headers['content-length'])
      data = []
      while size_remaining:
        chunk_size = min(size_remaining, max_chunk_size)
        chunk = self.rfile.read(chunk_size)
        if not chunk:
          break
        data.append(chunk)
        size_remaining -= len(data[-1])
      data = ''.join(data)
      data = self.decode_request_content(data)

      if data is None:
        return  # response has been sent

      # In previous versions of SimpleXMLRPCServer, _dispatch
      # could be overridden in this class, instead of in
      # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
      # check to see if a subclass implements _dispatch and dispatch
      # using that method if present.
      response = self.server._marshaled_dispatch(
          data, getattr(self, '_dispatch', None), self.path)

    except Exception, e: # This should only happen if the module is buggy
      # internal error, report as HTTP server error
      self.send_response(500)
      # Send information about the exception if requested
      if (hasattr(self.server, '_send_traceback_header') and
          self.server._send_traceback_header):
        self.send_header('X-exception', str(e))
        self.send_header('X-traceback', traceback.format_exc())

      self.send_header('Content-length', '0')
      self.end_headers()
    else:
      # got a valid JSON RPC response
      self.send_response(200)
      self.send_header('Content-type', 'application/json')

      if self.encode_threshold is not None:
        if len(response) > self.encode_threshold:
          q = self.accept_encodings().get('gzip', 0)
          if q:
            try:
              response = jsonrpclib.gzip_encode(response)
              self.send_header('Content-Encoding', 'gzip')
            except NotImplementedError:
              pass

      self.send_header('Content-length', str(len(response)))
      self.end_headers()
      self.wfile.write(response)


class SimpleJSONRPCDispatcher(_base.SimpleXMLRPCDispatcher):
  """Dispatcher for received JSON-RPC requests.

  This class extends the functionality of SimpleXMLRPCDispatcher and only
  overrides the operations needed to change the protocol from XML-RPC to
  JSON-RPC.
  """

  def _marshaled_dispatch(self, data, dispatch_method=None, path=None):
    """Dispatches an JSON-RPC method from marshalled (JSON) data.

    JSON-RPC methods are dispatched from the marshalled (JSON) data
    using the _dispatch method and the result is returned as
    marshalled data. For backwards compatibility, a dispatch
    function can be provided as an argument (see comment in
    SimpleJSONRPCRequestHandler.do_POST) but overriding the
    existing method through subclassing is the preferred means
    of changing method dispatch behavior.

    Returns:
      The JSON-RPC string to return.
    """
    method = ''
    params = []
    ident = ''
    try:
      request = json.loads(data)
      jsonrpclib.ValidateRequest(request)
      method = request['method']
      params = request['params']
      ident = request['id']

      # generate response
      if dispatch_method is not None:
        response = dispatch_method(method, params)
      else:
        response = self._dispatch(method, params)
      response = jsonrpclib.CreateResponseString(response, ident)

    except jsonrpclib.Fault as fault:
      response = jsonrpclib.CreateResponseString(fault, ident)

    # Catch all exceptions here as they should be raised on the caller side.
    except:  #pylint: disable=bare-except
      # report exception back to server
      exc_type, exc_value, _ = sys.exc_info()
      response = jsonrpclib.CreateResponseString(
          jsonrpclib.Fault(1, '%s:%s' % (exc_type, exc_value)), ident)
    return response


class SimpleJSONRPCServer(SocketServer.TCPServer,
                          SimpleJSONRPCDispatcher):
  """Simple JSON-RPC server.

  This class mimics the functionality of SimpleXMLRPCServer and only
  overrides the operations needed to change the protocol from XML-RPC to
  JSON-RPC.
  """

  allow_reuse_address = True

  # Warning: this is for debugging purposes only! Never set this to True in
  # production code, as will be sending out sensitive information (exception
  # and stack trace details) when exceptions are raised inside
  # SimpleJSONRPCRequestHandler.do_POST
  _send_traceback_header = False

  def __init__(self, addr, requestHandler=SimpleJSONRPCRequestHandler,
               logRequests=True, allow_none=False, encoding=None,
               bind_and_activate=True):
    self.logRequests = logRequests
    SimpleJSONRPCDispatcher.__init__(self, allow_none, encoding)
    SocketServer.TCPServer.__init__(self, addr, requestHandler,
                                    bind_and_activate)

    # [Bug #1222790] If possible, set close-on-exec flag; if a
    # method spawns a subprocess, the subprocess shouldn't have
    # the listening socket open.
    if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
      flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
      flags |= fcntl.FD_CLOEXEC
      fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)