xref: /aosp_15_r20/external/angle/build/android/lighttpd_server.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1*8975f5c5SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*8975f5c5SAndroid Build Coastguard Worker#
3*8975f5c5SAndroid Build Coastguard Worker# Copyright 2012 The Chromium Authors
4*8975f5c5SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
5*8975f5c5SAndroid Build Coastguard Worker# found in the LICENSE file.
6*8975f5c5SAndroid Build Coastguard Worker
7*8975f5c5SAndroid Build Coastguard Worker"""Provides a convenient wrapper for spawning a test lighttpd instance.
8*8975f5c5SAndroid Build Coastguard Worker
9*8975f5c5SAndroid Build Coastguard WorkerUsage:
10*8975f5c5SAndroid Build Coastguard Worker  lighttpd_server PATH_TO_DOC_ROOT
11*8975f5c5SAndroid Build Coastguard Worker"""
12*8975f5c5SAndroid Build Coastguard Worker
13*8975f5c5SAndroid Build Coastguard Worker
14*8975f5c5SAndroid Build Coastguard Workerimport codecs
15*8975f5c5SAndroid Build Coastguard Workerimport contextlib
16*8975f5c5SAndroid Build Coastguard Workerimport http.client
17*8975f5c5SAndroid Build Coastguard Workerimport os
18*8975f5c5SAndroid Build Coastguard Workerimport random
19*8975f5c5SAndroid Build Coastguard Workerimport shutil
20*8975f5c5SAndroid Build Coastguard Workerimport socket
21*8975f5c5SAndroid Build Coastguard Workerimport subprocess
22*8975f5c5SAndroid Build Coastguard Workerimport sys
23*8975f5c5SAndroid Build Coastguard Workerimport tempfile
24*8975f5c5SAndroid Build Coastguard Workerimport time
25*8975f5c5SAndroid Build Coastguard Worker
26*8975f5c5SAndroid Build Coastguard Workerfrom pylib import constants
27*8975f5c5SAndroid Build Coastguard Workerfrom pylib import pexpect
28*8975f5c5SAndroid Build Coastguard Worker
29*8975f5c5SAndroid Build Coastguard Worker
30*8975f5c5SAndroid Build Coastguard Workerclass LighttpdServer:
31*8975f5c5SAndroid Build Coastguard Worker  """Wraps lighttpd server, providing robust startup.
32*8975f5c5SAndroid Build Coastguard Worker
33*8975f5c5SAndroid Build Coastguard Worker  Args:
34*8975f5c5SAndroid Build Coastguard Worker    document_root: Path to root of this server's hosted files.
35*8975f5c5SAndroid Build Coastguard Worker    port: TCP port on the _host_ machine that the server will listen on. If
36*8975f5c5SAndroid Build Coastguard Worker        omitted it will attempt to use 9000, or if unavailable it will find
37*8975f5c5SAndroid Build Coastguard Worker        a free port from 8001 - 8999.
38*8975f5c5SAndroid Build Coastguard Worker    lighttpd_path, lighttpd_module_path: Optional paths to lighttpd binaries.
39*8975f5c5SAndroid Build Coastguard Worker    base_config_path: If supplied this file will replace the built-in default
40*8975f5c5SAndroid Build Coastguard Worker        lighttpd config file.
41*8975f5c5SAndroid Build Coastguard Worker    extra_config_contents: If specified, this string will be appended to the
42*8975f5c5SAndroid Build Coastguard Worker        base config (default built-in, or from base_config_path).
43*8975f5c5SAndroid Build Coastguard Worker    config_path, error_log, access_log: Optional paths where the class should
44*8975f5c5SAndroid Build Coastguard Worker        place temporary files for this session.
45*8975f5c5SAndroid Build Coastguard Worker  """
46*8975f5c5SAndroid Build Coastguard Worker
47*8975f5c5SAndroid Build Coastguard Worker  def __init__(self, document_root, port=None,
48*8975f5c5SAndroid Build Coastguard Worker               lighttpd_path=None, lighttpd_module_path=None,
49*8975f5c5SAndroid Build Coastguard Worker               base_config_path=None, extra_config_contents=None,
50*8975f5c5SAndroid Build Coastguard Worker               config_path=None, error_log=None, access_log=None):
51*8975f5c5SAndroid Build Coastguard Worker    self.temp_dir = tempfile.mkdtemp(prefix='lighttpd_for_chrome_android')
52*8975f5c5SAndroid Build Coastguard Worker    self.document_root = os.path.abspath(document_root)
53*8975f5c5SAndroid Build Coastguard Worker    self.fixed_port = port
54*8975f5c5SAndroid Build Coastguard Worker    self.port = port or constants.LIGHTTPD_DEFAULT_PORT
55*8975f5c5SAndroid Build Coastguard Worker    self.server_tag = 'LightTPD ' + str(random.randint(111111, 999999))
56*8975f5c5SAndroid Build Coastguard Worker    self.lighttpd_path = lighttpd_path or '/usr/sbin/lighttpd'
57*8975f5c5SAndroid Build Coastguard Worker    self.lighttpd_module_path = lighttpd_module_path or '/usr/lib/lighttpd'
58*8975f5c5SAndroid Build Coastguard Worker    self.base_config_path = base_config_path
59*8975f5c5SAndroid Build Coastguard Worker    self.extra_config_contents = extra_config_contents
60*8975f5c5SAndroid Build Coastguard Worker    self.config_path = config_path or self._Mktmp('config')
61*8975f5c5SAndroid Build Coastguard Worker    self.error_log = error_log or self._Mktmp('error_log')
62*8975f5c5SAndroid Build Coastguard Worker    self.access_log = access_log or self._Mktmp('access_log')
63*8975f5c5SAndroid Build Coastguard Worker    self.pid_file = self._Mktmp('pid_file')
64*8975f5c5SAndroid Build Coastguard Worker    self.process = None
65*8975f5c5SAndroid Build Coastguard Worker
66*8975f5c5SAndroid Build Coastguard Worker  def _Mktmp(self, name):
67*8975f5c5SAndroid Build Coastguard Worker    return os.path.join(self.temp_dir, name)
68*8975f5c5SAndroid Build Coastguard Worker
69*8975f5c5SAndroid Build Coastguard Worker  @staticmethod
70*8975f5c5SAndroid Build Coastguard Worker  def _GetRandomPort():
71*8975f5c5SAndroid Build Coastguard Worker    # The ports of test server is arranged in constants.py.
72*8975f5c5SAndroid Build Coastguard Worker    return random.randint(constants.LIGHTTPD_RANDOM_PORT_FIRST,
73*8975f5c5SAndroid Build Coastguard Worker                          constants.LIGHTTPD_RANDOM_PORT_LAST)
74*8975f5c5SAndroid Build Coastguard Worker
75*8975f5c5SAndroid Build Coastguard Worker  def StartupHttpServer(self):
76*8975f5c5SAndroid Build Coastguard Worker    """Starts up a http server with specified document root and port."""
77*8975f5c5SAndroid Build Coastguard Worker    # If we want a specific port, make sure no one else is listening on it.
78*8975f5c5SAndroid Build Coastguard Worker    if self.fixed_port:
79*8975f5c5SAndroid Build Coastguard Worker      self._KillProcessListeningOnPort(self.fixed_port)
80*8975f5c5SAndroid Build Coastguard Worker    while True:
81*8975f5c5SAndroid Build Coastguard Worker      if self.base_config_path:
82*8975f5c5SAndroid Build Coastguard Worker        # Read the config
83*8975f5c5SAndroid Build Coastguard Worker        with codecs.open(self.base_config_path, 'r', 'utf-8') as f:
84*8975f5c5SAndroid Build Coastguard Worker          config_contents = f.read()
85*8975f5c5SAndroid Build Coastguard Worker      else:
86*8975f5c5SAndroid Build Coastguard Worker        config_contents = self._GetDefaultBaseConfig()
87*8975f5c5SAndroid Build Coastguard Worker      if self.extra_config_contents:
88*8975f5c5SAndroid Build Coastguard Worker        config_contents += self.extra_config_contents
89*8975f5c5SAndroid Build Coastguard Worker      # Write out the config, filling in placeholders from the members of |self|
90*8975f5c5SAndroid Build Coastguard Worker      with codecs.open(self.config_path, 'w', 'utf-8') as f:
91*8975f5c5SAndroid Build Coastguard Worker        f.write(config_contents % self.__dict__)
92*8975f5c5SAndroid Build Coastguard Worker      if (not os.path.exists(self.lighttpd_path) or
93*8975f5c5SAndroid Build Coastguard Worker          not os.access(self.lighttpd_path, os.X_OK)):
94*8975f5c5SAndroid Build Coastguard Worker        raise EnvironmentError(
95*8975f5c5SAndroid Build Coastguard Worker            'Could not find lighttpd at %s.\n'
96*8975f5c5SAndroid Build Coastguard Worker            'It may need to be installed (e.g. sudo apt-get install lighttpd)'
97*8975f5c5SAndroid Build Coastguard Worker            % self.lighttpd_path)
98*8975f5c5SAndroid Build Coastguard Worker      # pylint: disable=no-member
99*8975f5c5SAndroid Build Coastguard Worker      self.process = pexpect.spawn(self.lighttpd_path,
100*8975f5c5SAndroid Build Coastguard Worker                                   ['-D', '-f', self.config_path,
101*8975f5c5SAndroid Build Coastguard Worker                                    '-m', self.lighttpd_module_path],
102*8975f5c5SAndroid Build Coastguard Worker                                   cwd=self.temp_dir)
103*8975f5c5SAndroid Build Coastguard Worker      client_error, server_error = self._TestServerConnection()
104*8975f5c5SAndroid Build Coastguard Worker      if not client_error:
105*8975f5c5SAndroid Build Coastguard Worker        assert int(open(self.pid_file, 'r').read()) == self.process.pid
106*8975f5c5SAndroid Build Coastguard Worker        break
107*8975f5c5SAndroid Build Coastguard Worker      self.process.close()
108*8975f5c5SAndroid Build Coastguard Worker
109*8975f5c5SAndroid Build Coastguard Worker      if self.fixed_port or 'in use' not in server_error:
110*8975f5c5SAndroid Build Coastguard Worker        print('Client error:', client_error)
111*8975f5c5SAndroid Build Coastguard Worker        print('Server error:', server_error)
112*8975f5c5SAndroid Build Coastguard Worker        return False
113*8975f5c5SAndroid Build Coastguard Worker      self.port = self._GetRandomPort()
114*8975f5c5SAndroid Build Coastguard Worker    return True
115*8975f5c5SAndroid Build Coastguard Worker
116*8975f5c5SAndroid Build Coastguard Worker  def ShutdownHttpServer(self):
117*8975f5c5SAndroid Build Coastguard Worker    """Shuts down our lighttpd processes."""
118*8975f5c5SAndroid Build Coastguard Worker    if self.process:
119*8975f5c5SAndroid Build Coastguard Worker      self.process.terminate()
120*8975f5c5SAndroid Build Coastguard Worker    shutil.rmtree(self.temp_dir, ignore_errors=True)
121*8975f5c5SAndroid Build Coastguard Worker
122*8975f5c5SAndroid Build Coastguard Worker  def _TestServerConnection(self):
123*8975f5c5SAndroid Build Coastguard Worker    # Wait for server to start
124*8975f5c5SAndroid Build Coastguard Worker    server_msg = ''
125*8975f5c5SAndroid Build Coastguard Worker    for timeout in range(1, 5):
126*8975f5c5SAndroid Build Coastguard Worker      client_error = None
127*8975f5c5SAndroid Build Coastguard Worker      try:
128*8975f5c5SAndroid Build Coastguard Worker        with contextlib.closing(
129*8975f5c5SAndroid Build Coastguard Worker            http.client.HTTPConnection('127.0.0.1', self.port,
130*8975f5c5SAndroid Build Coastguard Worker                                       timeout=timeout)) as http_client:
131*8975f5c5SAndroid Build Coastguard Worker          http_client.set_debuglevel(timeout > 3)
132*8975f5c5SAndroid Build Coastguard Worker          http_client.request('HEAD', '/')
133*8975f5c5SAndroid Build Coastguard Worker          r = http_client.getresponse()
134*8975f5c5SAndroid Build Coastguard Worker          r.read()
135*8975f5c5SAndroid Build Coastguard Worker          if (r.status == 200 and r.reason == 'OK' and
136*8975f5c5SAndroid Build Coastguard Worker              r.getheader('Server') == self.server_tag):
137*8975f5c5SAndroid Build Coastguard Worker            return (None, server_msg)
138*8975f5c5SAndroid Build Coastguard Worker          client_error = ('Bad response: %s %s version %s\n  ' %
139*8975f5c5SAndroid Build Coastguard Worker                          (r.status, r.reason, r.version) +
140*8975f5c5SAndroid Build Coastguard Worker                          '\n  '.join([': '.join(h) for h in r.getheaders()]))
141*8975f5c5SAndroid Build Coastguard Worker      except (http.client.HTTPException, socket.error) as client_error:
142*8975f5c5SAndroid Build Coastguard Worker        pass  # Probably too quick connecting: try again
143*8975f5c5SAndroid Build Coastguard Worker      # Check for server startup error messages
144*8975f5c5SAndroid Build Coastguard Worker      # pylint: disable=no-member
145*8975f5c5SAndroid Build Coastguard Worker      ix = self.process.expect([pexpect.TIMEOUT, pexpect.EOF, '.+'],
146*8975f5c5SAndroid Build Coastguard Worker                               timeout=timeout)
147*8975f5c5SAndroid Build Coastguard Worker      if ix == 2:  # stdout spew from the server
148*8975f5c5SAndroid Build Coastguard Worker        server_msg += self.process.match.group(0) # pylint: disable=no-member
149*8975f5c5SAndroid Build Coastguard Worker      elif ix == 1:  # EOF -- server has quit so giveup.
150*8975f5c5SAndroid Build Coastguard Worker        client_error = client_error or 'Server exited'
151*8975f5c5SAndroid Build Coastguard Worker        break
152*8975f5c5SAndroid Build Coastguard Worker    return (client_error or 'Timeout', server_msg)
153*8975f5c5SAndroid Build Coastguard Worker
154*8975f5c5SAndroid Build Coastguard Worker  @staticmethod
155*8975f5c5SAndroid Build Coastguard Worker  def _KillProcessListeningOnPort(port):
156*8975f5c5SAndroid Build Coastguard Worker    """Checks if there is a process listening on port number |port| and
157*8975f5c5SAndroid Build Coastguard Worker    terminates it if found.
158*8975f5c5SAndroid Build Coastguard Worker
159*8975f5c5SAndroid Build Coastguard Worker    Args:
160*8975f5c5SAndroid Build Coastguard Worker      port: Port number to check.
161*8975f5c5SAndroid Build Coastguard Worker    """
162*8975f5c5SAndroid Build Coastguard Worker    if subprocess.call(['fuser', '-kv', '%d/tcp' % port]) == 0:
163*8975f5c5SAndroid Build Coastguard Worker      # Give the process some time to terminate and check that it is gone.
164*8975f5c5SAndroid Build Coastguard Worker      time.sleep(2)
165*8975f5c5SAndroid Build Coastguard Worker      assert subprocess.call(['fuser', '-v', '%d/tcp' % port]) != 0, \
166*8975f5c5SAndroid Build Coastguard Worker          'Unable to kill process listening on port %d.' % port
167*8975f5c5SAndroid Build Coastguard Worker
168*8975f5c5SAndroid Build Coastguard Worker  @staticmethod
169*8975f5c5SAndroid Build Coastguard Worker  def _GetDefaultBaseConfig():
170*8975f5c5SAndroid Build Coastguard Worker    return """server.tag                  = "%(server_tag)s"
171*8975f5c5SAndroid Build Coastguard Workerserver.modules              = ( "mod_access",
172*8975f5c5SAndroid Build Coastguard Worker                                "mod_accesslog",
173*8975f5c5SAndroid Build Coastguard Worker                                "mod_alias",
174*8975f5c5SAndroid Build Coastguard Worker                                "mod_cgi",
175*8975f5c5SAndroid Build Coastguard Worker                                "mod_rewrite" )
176*8975f5c5SAndroid Build Coastguard Worker
177*8975f5c5SAndroid Build Coastguard Worker# default document root required
178*8975f5c5SAndroid Build Coastguard Worker#server.document-root = "."
179*8975f5c5SAndroid Build Coastguard Worker
180*8975f5c5SAndroid Build Coastguard Worker# files to check for if .../ is requested
181*8975f5c5SAndroid Build Coastguard Workerindex-file.names            = ( "index.php", "index.pl", "index.cgi",
182*8975f5c5SAndroid Build Coastguard Worker                                "index.html", "index.htm", "default.htm" )
183*8975f5c5SAndroid Build Coastguard Worker# mimetype mapping
184*8975f5c5SAndroid Build Coastguard Workermimetype.assign             = (
185*8975f5c5SAndroid Build Coastguard Worker  ".gif"          =>      "image/gif",
186*8975f5c5SAndroid Build Coastguard Worker  ".jpg"          =>      "image/jpeg",
187*8975f5c5SAndroid Build Coastguard Worker  ".jpeg"         =>      "image/jpeg",
188*8975f5c5SAndroid Build Coastguard Worker  ".png"          =>      "image/png",
189*8975f5c5SAndroid Build Coastguard Worker  ".svg"          =>      "image/svg+xml",
190*8975f5c5SAndroid Build Coastguard Worker  ".css"          =>      "text/css",
191*8975f5c5SAndroid Build Coastguard Worker  ".html"         =>      "text/html",
192*8975f5c5SAndroid Build Coastguard Worker  ".htm"          =>      "text/html",
193*8975f5c5SAndroid Build Coastguard Worker  ".xhtml"        =>      "application/xhtml+xml",
194*8975f5c5SAndroid Build Coastguard Worker  ".xhtmlmp"      =>      "application/vnd.wap.xhtml+xml",
195*8975f5c5SAndroid Build Coastguard Worker  ".js"           =>      "application/x-javascript",
196*8975f5c5SAndroid Build Coastguard Worker  ".log"          =>      "text/plain",
197*8975f5c5SAndroid Build Coastguard Worker  ".conf"         =>      "text/plain",
198*8975f5c5SAndroid Build Coastguard Worker  ".text"         =>      "text/plain",
199*8975f5c5SAndroid Build Coastguard Worker  ".txt"          =>      "text/plain",
200*8975f5c5SAndroid Build Coastguard Worker  ".dtd"          =>      "text/xml",
201*8975f5c5SAndroid Build Coastguard Worker  ".xml"          =>      "text/xml",
202*8975f5c5SAndroid Build Coastguard Worker  ".manifest"     =>      "text/cache-manifest",
203*8975f5c5SAndroid Build Coastguard Worker )
204*8975f5c5SAndroid Build Coastguard Worker
205*8975f5c5SAndroid Build Coastguard Worker# Use the "Content-Type" extended attribute to obtain mime type if possible
206*8975f5c5SAndroid Build Coastguard Workermimetype.use-xattr          = "enable"
207*8975f5c5SAndroid Build Coastguard Worker
208*8975f5c5SAndroid Build Coastguard Worker##
209*8975f5c5SAndroid Build Coastguard Worker# which extensions should not be handle via static-file transfer
210*8975f5c5SAndroid Build Coastguard Worker#
211*8975f5c5SAndroid Build Coastguard Worker# .php, .pl, .fcgi are most often handled by mod_fastcgi or mod_cgi
212*8975f5c5SAndroid Build Coastguard Workerstatic-file.exclude-extensions = ( ".php", ".pl", ".cgi" )
213*8975f5c5SAndroid Build Coastguard Worker
214*8975f5c5SAndroid Build Coastguard Workerserver.bind = "127.0.0.1"
215*8975f5c5SAndroid Build Coastguard Workerserver.port = %(port)s
216*8975f5c5SAndroid Build Coastguard Worker
217*8975f5c5SAndroid Build Coastguard Worker## virtual directory listings
218*8975f5c5SAndroid Build Coastguard Workerdir-listing.activate        = "enable"
219*8975f5c5SAndroid Build Coastguard Worker#dir-listing.encoding       = "iso-8859-2"
220*8975f5c5SAndroid Build Coastguard Worker#dir-listing.external-css   = "style/oldstyle.css"
221*8975f5c5SAndroid Build Coastguard Worker
222*8975f5c5SAndroid Build Coastguard Worker## enable debugging
223*8975f5c5SAndroid Build Coastguard Worker#debug.log-request-header   = "enable"
224*8975f5c5SAndroid Build Coastguard Worker#debug.log-response-header  = "enable"
225*8975f5c5SAndroid Build Coastguard Worker#debug.log-request-handling = "enable"
226*8975f5c5SAndroid Build Coastguard Worker#debug.log-file-not-found   = "enable"
227*8975f5c5SAndroid Build Coastguard Worker
228*8975f5c5SAndroid Build Coastguard Worker#### SSL engine
229*8975f5c5SAndroid Build Coastguard Worker#ssl.engine                 = "enable"
230*8975f5c5SAndroid Build Coastguard Worker#ssl.pemfile                = "server.pem"
231*8975f5c5SAndroid Build Coastguard Worker
232*8975f5c5SAndroid Build Coastguard Worker# Autogenerated test-specific config follows.
233*8975f5c5SAndroid Build Coastguard Worker
234*8975f5c5SAndroid Build Coastguard Workercgi.assign = ( ".cgi"  => "/usr/bin/env",
235*8975f5c5SAndroid Build Coastguard Worker               ".pl"   => "/usr/bin/env",
236*8975f5c5SAndroid Build Coastguard Worker               ".asis" => "/bin/cat",
237*8975f5c5SAndroid Build Coastguard Worker               ".php"  => "/usr/bin/php-cgi" )
238*8975f5c5SAndroid Build Coastguard Worker
239*8975f5c5SAndroid Build Coastguard Workerserver.errorlog = "%(error_log)s"
240*8975f5c5SAndroid Build Coastguard Workeraccesslog.filename = "%(access_log)s"
241*8975f5c5SAndroid Build Coastguard Workerserver.upload-dirs = ( "/tmp" )
242*8975f5c5SAndroid Build Coastguard Workerserver.pid-file = "%(pid_file)s"
243*8975f5c5SAndroid Build Coastguard Workerserver.document-root = "%(document_root)s"
244*8975f5c5SAndroid Build Coastguard Worker
245*8975f5c5SAndroid Build Coastguard Worker"""
246*8975f5c5SAndroid Build Coastguard Worker
247*8975f5c5SAndroid Build Coastguard Worker
248*8975f5c5SAndroid Build Coastguard Workerdef main(argv):
249*8975f5c5SAndroid Build Coastguard Worker  server = LighttpdServer(*argv[1:])
250*8975f5c5SAndroid Build Coastguard Worker  try:
251*8975f5c5SAndroid Build Coastguard Worker    if server.StartupHttpServer():
252*8975f5c5SAndroid Build Coastguard Worker      input('Server running at http://127.0.0.1:%s -'
253*8975f5c5SAndroid Build Coastguard Worker            ' press Enter to exit it.' % server.port)
254*8975f5c5SAndroid Build Coastguard Worker    else:
255*8975f5c5SAndroid Build Coastguard Worker      print('Server exit code:', server.process.exitstatus)
256*8975f5c5SAndroid Build Coastguard Worker  finally:
257*8975f5c5SAndroid Build Coastguard Worker    server.ShutdownHttpServer()
258*8975f5c5SAndroid Build Coastguard Worker
259*8975f5c5SAndroid Build Coastguard Worker
260*8975f5c5SAndroid Build Coastguard Workerif __name__ == '__main__':
261*8975f5c5SAndroid Build Coastguard Worker  sys.exit(main(sys.argv))
262