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