xref: /aosp_15_r20/external/cronet/net/tools/testserver/testserver_base.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1*6777b538SAndroid Build Coastguard Worker# Copyright 2013 The Chromium Authors
2*6777b538SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
3*6777b538SAndroid Build Coastguard Worker# found in the LICENSE file.
4*6777b538SAndroid Build Coastguard Worker
5*6777b538SAndroid Build Coastguard Workerfrom six.moves import BaseHTTPServer
6*6777b538SAndroid Build Coastguard Workerimport errno
7*6777b538SAndroid Build Coastguard Workerimport json
8*6777b538SAndroid Build Coastguard Workerimport optparse
9*6777b538SAndroid Build Coastguard Workerimport os
10*6777b538SAndroid Build Coastguard Workerimport re
11*6777b538SAndroid Build Coastguard Workerimport socket
12*6777b538SAndroid Build Coastguard Workerfrom six.moves import socketserver as SocketServer
13*6777b538SAndroid Build Coastguard Workerimport struct
14*6777b538SAndroid Build Coastguard Workerimport sys
15*6777b538SAndroid Build Coastguard Workerimport warnings
16*6777b538SAndroid Build Coastguard Worker
17*6777b538SAndroid Build Coastguard Worker# Ignore deprecation warnings, they make our output more cluttered.
18*6777b538SAndroid Build Coastguard Workerwarnings.filterwarnings("ignore", category=DeprecationWarning)
19*6777b538SAndroid Build Coastguard Worker
20*6777b538SAndroid Build Coastguard Workerif sys.platform == 'win32':
21*6777b538SAndroid Build Coastguard Worker  import msvcrt
22*6777b538SAndroid Build Coastguard Worker
23*6777b538SAndroid Build Coastguard Worker# Using debug() seems to cause hangs on XP: see http://crbug.com/64515.
24*6777b538SAndroid Build Coastguard Workerdebug_output = sys.stderr
25*6777b538SAndroid Build Coastguard Workerdef debug(string):
26*6777b538SAndroid Build Coastguard Worker  debug_output.write(string + "\n")
27*6777b538SAndroid Build Coastguard Worker  debug_output.flush()
28*6777b538SAndroid Build Coastguard Worker
29*6777b538SAndroid Build Coastguard Worker
30*6777b538SAndroid Build Coastguard Workerclass Error(Exception):
31*6777b538SAndroid Build Coastguard Worker  """Error class for this module."""
32*6777b538SAndroid Build Coastguard Worker
33*6777b538SAndroid Build Coastguard Worker
34*6777b538SAndroid Build Coastguard Workerclass OptionError(Error):
35*6777b538SAndroid Build Coastguard Worker  """Error for bad command line options."""
36*6777b538SAndroid Build Coastguard Worker
37*6777b538SAndroid Build Coastguard Worker
38*6777b538SAndroid Build Coastguard Workerclass FileMultiplexer(object):
39*6777b538SAndroid Build Coastguard Worker  def __init__(self, fd1, fd2) :
40*6777b538SAndroid Build Coastguard Worker    self.__fd1 = fd1
41*6777b538SAndroid Build Coastguard Worker    self.__fd2 = fd2
42*6777b538SAndroid Build Coastguard Worker
43*6777b538SAndroid Build Coastguard Worker  def __del__(self) :
44*6777b538SAndroid Build Coastguard Worker    if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
45*6777b538SAndroid Build Coastguard Worker      self.__fd1.close()
46*6777b538SAndroid Build Coastguard Worker    if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
47*6777b538SAndroid Build Coastguard Worker      self.__fd2.close()
48*6777b538SAndroid Build Coastguard Worker
49*6777b538SAndroid Build Coastguard Worker  def write(self, text) :
50*6777b538SAndroid Build Coastguard Worker    self.__fd1.write(text)
51*6777b538SAndroid Build Coastguard Worker    self.__fd2.write(text)
52*6777b538SAndroid Build Coastguard Worker
53*6777b538SAndroid Build Coastguard Worker  def flush(self) :
54*6777b538SAndroid Build Coastguard Worker    self.__fd1.flush()
55*6777b538SAndroid Build Coastguard Worker    self.__fd2.flush()
56*6777b538SAndroid Build Coastguard Worker
57*6777b538SAndroid Build Coastguard Worker
58*6777b538SAndroid Build Coastguard Workerclass ClientRestrictingServerMixIn:
59*6777b538SAndroid Build Coastguard Worker  """Implements verify_request to limit connections to our configured IP
60*6777b538SAndroid Build Coastguard Worker  address."""
61*6777b538SAndroid Build Coastguard Worker
62*6777b538SAndroid Build Coastguard Worker  def verify_request(self, _request, client_address):
63*6777b538SAndroid Build Coastguard Worker    return client_address[0] == self.server_address[0]
64*6777b538SAndroid Build Coastguard Worker
65*6777b538SAndroid Build Coastguard Worker
66*6777b538SAndroid Build Coastguard Workerclass BrokenPipeHandlerMixIn:
67*6777b538SAndroid Build Coastguard Worker  """Allows the server to deal with "broken pipe" errors (which happen if the
68*6777b538SAndroid Build Coastguard Worker  browser quits with outstanding requests, like for the favicon). This mix-in
69*6777b538SAndroid Build Coastguard Worker  requires the class to derive from SocketServer.BaseServer and not override its
70*6777b538SAndroid Build Coastguard Worker  handle_error() method. """
71*6777b538SAndroid Build Coastguard Worker
72*6777b538SAndroid Build Coastguard Worker  def handle_error(self, request, client_address):
73*6777b538SAndroid Build Coastguard Worker    value = sys.exc_info()[1]
74*6777b538SAndroid Build Coastguard Worker    if isinstance(value, socket.error):
75*6777b538SAndroid Build Coastguard Worker      err = value.args[0]
76*6777b538SAndroid Build Coastguard Worker      if sys.platform in ('win32', 'cygwin'):
77*6777b538SAndroid Build Coastguard Worker        # "An established connection was aborted by the software in your host."
78*6777b538SAndroid Build Coastguard Worker        pipe_err = 10053
79*6777b538SAndroid Build Coastguard Worker      else:
80*6777b538SAndroid Build Coastguard Worker        pipe_err = errno.EPIPE
81*6777b538SAndroid Build Coastguard Worker      if err == pipe_err:
82*6777b538SAndroid Build Coastguard Worker        print("testserver.py: Broken pipe")
83*6777b538SAndroid Build Coastguard Worker        return
84*6777b538SAndroid Build Coastguard Worker      if err == errno.ECONNRESET:
85*6777b538SAndroid Build Coastguard Worker        print("testserver.py: Connection reset by peer")
86*6777b538SAndroid Build Coastguard Worker        return
87*6777b538SAndroid Build Coastguard Worker    SocketServer.BaseServer.handle_error(self, request, client_address)
88*6777b538SAndroid Build Coastguard Worker
89*6777b538SAndroid Build Coastguard Worker
90*6777b538SAndroid Build Coastguard Workerclass StoppableHTTPServer(BaseHTTPServer.HTTPServer):
91*6777b538SAndroid Build Coastguard Worker  """This is a specialization of BaseHTTPServer to allow it
92*6777b538SAndroid Build Coastguard Worker  to be exited cleanly (by setting its "stop" member to True)."""
93*6777b538SAndroid Build Coastguard Worker
94*6777b538SAndroid Build Coastguard Worker  def serve_forever(self):
95*6777b538SAndroid Build Coastguard Worker    self.stop = False
96*6777b538SAndroid Build Coastguard Worker    self.nonce_time = None
97*6777b538SAndroid Build Coastguard Worker    while not self.stop:
98*6777b538SAndroid Build Coastguard Worker      self.handle_request()
99*6777b538SAndroid Build Coastguard Worker    self.socket.close()
100*6777b538SAndroid Build Coastguard Worker
101*6777b538SAndroid Build Coastguard Worker
102*6777b538SAndroid Build Coastguard Workerdef MultiplexerHack(std_fd, log_fd):
103*6777b538SAndroid Build Coastguard Worker  """Creates a FileMultiplexer that will write to both specified files.
104*6777b538SAndroid Build Coastguard Worker
105*6777b538SAndroid Build Coastguard Worker  When running on Windows XP bots, stdout and stderr will be invalid file
106*6777b538SAndroid Build Coastguard Worker  handles, so log_fd will be returned directly.  (This does not occur if you
107*6777b538SAndroid Build Coastguard Worker  run the test suite directly from a console, but only if the output of the
108*6777b538SAndroid Build Coastguard Worker  test executable is redirected.)
109*6777b538SAndroid Build Coastguard Worker  """
110*6777b538SAndroid Build Coastguard Worker  if std_fd.fileno() <= 0:
111*6777b538SAndroid Build Coastguard Worker    return log_fd
112*6777b538SAndroid Build Coastguard Worker  return FileMultiplexer(std_fd, log_fd)
113*6777b538SAndroid Build Coastguard Worker
114*6777b538SAndroid Build Coastguard Worker
115*6777b538SAndroid Build Coastguard Workerclass BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
116*6777b538SAndroid Build Coastguard Worker
117*6777b538SAndroid Build Coastguard Worker  def __init__(self, request, client_address, socket_server,
118*6777b538SAndroid Build Coastguard Worker               connect_handlers, get_handlers, head_handlers, post_handlers,
119*6777b538SAndroid Build Coastguard Worker               put_handlers):
120*6777b538SAndroid Build Coastguard Worker    self._connect_handlers = connect_handlers
121*6777b538SAndroid Build Coastguard Worker    self._get_handlers = get_handlers
122*6777b538SAndroid Build Coastguard Worker    self._head_handlers = head_handlers
123*6777b538SAndroid Build Coastguard Worker    self._post_handlers = post_handlers
124*6777b538SAndroid Build Coastguard Worker    self._put_handlers = put_handlers
125*6777b538SAndroid Build Coastguard Worker    BaseHTTPServer.BaseHTTPRequestHandler.__init__(
126*6777b538SAndroid Build Coastguard Worker      self, request, client_address, socket_server)
127*6777b538SAndroid Build Coastguard Worker
128*6777b538SAndroid Build Coastguard Worker  def log_request(self, *args, **kwargs):
129*6777b538SAndroid Build Coastguard Worker    # Disable request logging to declutter test log output.
130*6777b538SAndroid Build Coastguard Worker    pass
131*6777b538SAndroid Build Coastguard Worker
132*6777b538SAndroid Build Coastguard Worker  def _ShouldHandleRequest(self, handler_name):
133*6777b538SAndroid Build Coastguard Worker    """Determines if the path can be handled by the handler.
134*6777b538SAndroid Build Coastguard Worker
135*6777b538SAndroid Build Coastguard Worker    We consider a handler valid if the path begins with the
136*6777b538SAndroid Build Coastguard Worker    handler name. It can optionally be followed by "?*", "/*".
137*6777b538SAndroid Build Coastguard Worker    """
138*6777b538SAndroid Build Coastguard Worker
139*6777b538SAndroid Build Coastguard Worker    pattern = re.compile('%s($|\?|/).*' % handler_name)
140*6777b538SAndroid Build Coastguard Worker    return pattern.match(self.path)
141*6777b538SAndroid Build Coastguard Worker
142*6777b538SAndroid Build Coastguard Worker  def do_CONNECT(self):
143*6777b538SAndroid Build Coastguard Worker    for handler in self._connect_handlers:
144*6777b538SAndroid Build Coastguard Worker      if handler():
145*6777b538SAndroid Build Coastguard Worker        return
146*6777b538SAndroid Build Coastguard Worker
147*6777b538SAndroid Build Coastguard Worker  def do_GET(self):
148*6777b538SAndroid Build Coastguard Worker    for handler in self._get_handlers:
149*6777b538SAndroid Build Coastguard Worker      if handler():
150*6777b538SAndroid Build Coastguard Worker        return
151*6777b538SAndroid Build Coastguard Worker
152*6777b538SAndroid Build Coastguard Worker  def do_HEAD(self):
153*6777b538SAndroid Build Coastguard Worker    for handler in self._head_handlers:
154*6777b538SAndroid Build Coastguard Worker      if handler():
155*6777b538SAndroid Build Coastguard Worker        return
156*6777b538SAndroid Build Coastguard Worker
157*6777b538SAndroid Build Coastguard Worker  def do_POST(self):
158*6777b538SAndroid Build Coastguard Worker    for handler in self._post_handlers:
159*6777b538SAndroid Build Coastguard Worker      if handler():
160*6777b538SAndroid Build Coastguard Worker        return
161*6777b538SAndroid Build Coastguard Worker
162*6777b538SAndroid Build Coastguard Worker  def do_PUT(self):
163*6777b538SAndroid Build Coastguard Worker    for handler in self._put_handlers:
164*6777b538SAndroid Build Coastguard Worker      if handler():
165*6777b538SAndroid Build Coastguard Worker        return
166*6777b538SAndroid Build Coastguard Worker
167*6777b538SAndroid Build Coastguard Worker
168*6777b538SAndroid Build Coastguard Workerclass TestServerRunner(object):
169*6777b538SAndroid Build Coastguard Worker  """Runs a test server and communicates with the controlling C++ test code.
170*6777b538SAndroid Build Coastguard Worker
171*6777b538SAndroid Build Coastguard Worker  Subclasses should override the create_server method to create their server
172*6777b538SAndroid Build Coastguard Worker  object, and the add_options method to add their own options.
173*6777b538SAndroid Build Coastguard Worker  """
174*6777b538SAndroid Build Coastguard Worker
175*6777b538SAndroid Build Coastguard Worker  def __init__(self):
176*6777b538SAndroid Build Coastguard Worker    self.option_parser = optparse.OptionParser()
177*6777b538SAndroid Build Coastguard Worker    self.add_options()
178*6777b538SAndroid Build Coastguard Worker
179*6777b538SAndroid Build Coastguard Worker  def main(self):
180*6777b538SAndroid Build Coastguard Worker    self.options, self.args = self.option_parser.parse_args()
181*6777b538SAndroid Build Coastguard Worker
182*6777b538SAndroid Build Coastguard Worker    logfile = open(self.options.log_file, 'w')
183*6777b538SAndroid Build Coastguard Worker
184*6777b538SAndroid Build Coastguard Worker    # http://crbug.com/248796 : Error logs streamed to normal sys.stderr will be
185*6777b538SAndroid Build Coastguard Worker    # written to HTTP response payload when remote test server is used.
186*6777b538SAndroid Build Coastguard Worker    # For this reason, some tests like ResourceFetcherTests.ResourceFetcher404
187*6777b538SAndroid Build Coastguard Worker    # were failing on Android because remote test server is being used there.
188*6777b538SAndroid Build Coastguard Worker    # To fix them, we need to use sys.stdout as sys.stderr if remote test server
189*6777b538SAndroid Build Coastguard Worker    # is used.
190*6777b538SAndroid Build Coastguard Worker    if self.options.on_remote_server:
191*6777b538SAndroid Build Coastguard Worker      sys.stderr = sys.stdout
192*6777b538SAndroid Build Coastguard Worker
193*6777b538SAndroid Build Coastguard Worker    sys.stderr = MultiplexerHack(sys.stderr, logfile)
194*6777b538SAndroid Build Coastguard Worker    if self.options.log_to_console:
195*6777b538SAndroid Build Coastguard Worker      sys.stdout = MultiplexerHack(sys.stdout, logfile)
196*6777b538SAndroid Build Coastguard Worker    else:
197*6777b538SAndroid Build Coastguard Worker      sys.stdout = logfile
198*6777b538SAndroid Build Coastguard Worker
199*6777b538SAndroid Build Coastguard Worker    server_data = {
200*6777b538SAndroid Build Coastguard Worker      'host': self.options.host,
201*6777b538SAndroid Build Coastguard Worker    }
202*6777b538SAndroid Build Coastguard Worker    self.server = self.create_server(server_data)
203*6777b538SAndroid Build Coastguard Worker    self._notify_startup_complete(server_data)
204*6777b538SAndroid Build Coastguard Worker    self.run_server()
205*6777b538SAndroid Build Coastguard Worker
206*6777b538SAndroid Build Coastguard Worker  def create_server(self, server_data):
207*6777b538SAndroid Build Coastguard Worker    """Creates a server object and returns it.
208*6777b538SAndroid Build Coastguard Worker
209*6777b538SAndroid Build Coastguard Worker    Must populate server_data['port'], and can set additional server_data
210*6777b538SAndroid Build Coastguard Worker    elements if desired."""
211*6777b538SAndroid Build Coastguard Worker    raise NotImplementedError()
212*6777b538SAndroid Build Coastguard Worker
213*6777b538SAndroid Build Coastguard Worker  def run_server(self):
214*6777b538SAndroid Build Coastguard Worker    try:
215*6777b538SAndroid Build Coastguard Worker      self.server.serve_forever()
216*6777b538SAndroid Build Coastguard Worker    except KeyboardInterrupt:
217*6777b538SAndroid Build Coastguard Worker      print('shutting down server')
218*6777b538SAndroid Build Coastguard Worker      self.server.stop = True
219*6777b538SAndroid Build Coastguard Worker
220*6777b538SAndroid Build Coastguard Worker  def add_options(self):
221*6777b538SAndroid Build Coastguard Worker    self.option_parser.add_option('--startup-pipe', type='int',
222*6777b538SAndroid Build Coastguard Worker                                  dest='startup_pipe',
223*6777b538SAndroid Build Coastguard Worker                                  help='File handle of pipe to parent process')
224*6777b538SAndroid Build Coastguard Worker    self.option_parser.add_option('--log-to-console', action='store_const',
225*6777b538SAndroid Build Coastguard Worker                                  const=True, default=False,
226*6777b538SAndroid Build Coastguard Worker                                  dest='log_to_console',
227*6777b538SAndroid Build Coastguard Worker                                  help='Enables or disables sys.stdout logging '
228*6777b538SAndroid Build Coastguard Worker                                  'to the console.')
229*6777b538SAndroid Build Coastguard Worker    self.option_parser.add_option('--log-file', default='testserver.log',
230*6777b538SAndroid Build Coastguard Worker                                  dest='log_file',
231*6777b538SAndroid Build Coastguard Worker                                  help='The name of the server log file.')
232*6777b538SAndroid Build Coastguard Worker    self.option_parser.add_option('--port', default=0, type='int',
233*6777b538SAndroid Build Coastguard Worker                                  help='Port used by the server. If '
234*6777b538SAndroid Build Coastguard Worker                                  'unspecified, the server will listen on an '
235*6777b538SAndroid Build Coastguard Worker                                  'ephemeral port.')
236*6777b538SAndroid Build Coastguard Worker    self.option_parser.add_option('--host', default='127.0.0.1',
237*6777b538SAndroid Build Coastguard Worker                                  dest='host',
238*6777b538SAndroid Build Coastguard Worker                                  help='Hostname or IP upon which the server '
239*6777b538SAndroid Build Coastguard Worker                                  'will listen. Client connections will also '
240*6777b538SAndroid Build Coastguard Worker                                  'only be allowed from this address.')
241*6777b538SAndroid Build Coastguard Worker    self.option_parser.add_option('--data-dir', dest='data_dir',
242*6777b538SAndroid Build Coastguard Worker                                  help='Directory from which to read the '
243*6777b538SAndroid Build Coastguard Worker                                  'files.')
244*6777b538SAndroid Build Coastguard Worker    self.option_parser.add_option('--on-remote-server', action='store_const',
245*6777b538SAndroid Build Coastguard Worker                                  const=True, default=False,
246*6777b538SAndroid Build Coastguard Worker                                  dest='on_remote_server',
247*6777b538SAndroid Build Coastguard Worker                                  help='Whether remote server is being used or '
248*6777b538SAndroid Build Coastguard Worker                                  'not.')
249*6777b538SAndroid Build Coastguard Worker
250*6777b538SAndroid Build Coastguard Worker  def _notify_startup_complete(self, server_data):
251*6777b538SAndroid Build Coastguard Worker    # Notify the parent that we've started. (BaseServer subclasses
252*6777b538SAndroid Build Coastguard Worker    # bind their sockets on construction.)
253*6777b538SAndroid Build Coastguard Worker    if self.options.startup_pipe is not None:
254*6777b538SAndroid Build Coastguard Worker      server_data_json = json.dumps(server_data).encode()
255*6777b538SAndroid Build Coastguard Worker      server_data_len = len(server_data_json)
256*6777b538SAndroid Build Coastguard Worker      print('sending server_data: %s (%d bytes)' %
257*6777b538SAndroid Build Coastguard Worker            (server_data_json, server_data_len))
258*6777b538SAndroid Build Coastguard Worker      if sys.platform == 'win32':
259*6777b538SAndroid Build Coastguard Worker        fd = msvcrt.open_osfhandle(self.options.startup_pipe, 0)
260*6777b538SAndroid Build Coastguard Worker      else:
261*6777b538SAndroid Build Coastguard Worker        fd = self.options.startup_pipe
262*6777b538SAndroid Build Coastguard Worker      startup_pipe = os.fdopen(fd, "wb")
263*6777b538SAndroid Build Coastguard Worker      # First write the data length as an unsigned 4-byte value.  This
264*6777b538SAndroid Build Coastguard Worker      # is _not_ using network byte ordering since the other end of the
265*6777b538SAndroid Build Coastguard Worker      # pipe is on the same machine.
266*6777b538SAndroid Build Coastguard Worker      startup_pipe.write(struct.pack('=L', server_data_len))
267*6777b538SAndroid Build Coastguard Worker      startup_pipe.write(server_data_json)
268*6777b538SAndroid Build Coastguard Worker      startup_pipe.close()
269