1# Copyright (C) 2011 Google Inc. All rights reserved. 2# 3# Redistribution and use in source and binary forms, with or without 4# modification, are permitted provided that the following conditions are 5# met: 6# 7# * Redistributions of source code must retain the above copyright 8# notice, this list of conditions and the following disclaimer. 9# * Redistributions in binary form must reproduce the above 10# copyright notice, this list of conditions and the following disclaimer 11# in the documentation and/or other materials provided with the 12# distribution. 13# * Neither the name of Google Inc. nor the names of its 14# contributors may be used to endorse or promote products derived from 15# this software without specific prior written permission. 16# 17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29"""Start and stop the Apache HTTP server as it is used by the layout tests.""" 30 31import logging 32import os 33import socket 34 35from webkitpy.layout_tests.servers import server_base 36 37 38_log = logging.getLogger(__name__) 39 40 41class ApacheHTTP(server_base.ServerBase): 42 def __init__(self, port_obj, output_dir, additional_dirs, number_of_servers): 43 super(ApacheHTTP, self).__init__(port_obj, output_dir) 44 # We use the name "httpd" instead of "apache" to make our paths (e.g. the pid file: /tmp/WebKit/httpd.pid) 45 # match old-run-webkit-tests: https://bugs.webkit.org/show_bug.cgi?id=63956 46 self._name = 'httpd' 47 self._mappings = [{'port': 8000}, 48 {'port': 8080}, 49 {'port': 8443, 'sslcert': True}] 50 self._number_of_servers = number_of_servers 51 52 self._pid_file = self._filesystem.join(self._runtime_path, '%s.pid' % self._name) 53 54 executable = self._port_obj.path_to_apache() 55 server_root = self._filesystem.dirname(self._filesystem.dirname(executable)) 56 57 test_dir = self._port_obj.layout_tests_dir() 58 document_root = self._filesystem.join(test_dir, "http", "tests") 59 js_test_resources_dir = self._filesystem.join(test_dir, "resources") 60 media_resources_dir = self._filesystem.join(test_dir, "media") 61 mime_types_path = self._filesystem.join(test_dir, "http", "conf", "mime.types") 62 cert_file = self._filesystem.join(test_dir, "http", "conf", "webkit-httpd.pem") 63 64 access_log = self._filesystem.join(output_dir, "access_log.txt") 65 error_log = self._filesystem.join(output_dir, "error_log.txt") 66 67 self._is_win = self._port_obj.host.platform.is_win() 68 69 start_cmd = [executable, 70 '-f', '%s' % self._port_obj.path_to_apache_config_file(), 71 '-C', 'ServerRoot "%s"' % server_root, 72 '-C', 'DocumentRoot "%s"' % document_root, 73 '-c', 'Alias /js-test-resources "%s"' % js_test_resources_dir, 74 '-c', 'Alias /media-resources "%s"' % media_resources_dir, 75 '-c', 'TypesConfig "%s"' % mime_types_path, 76 '-c', 'CustomLog "%s" common' % access_log, 77 '-c', 'ErrorLog "%s"' % error_log, 78 '-c', 'PidFile %s' % self._pid_file, 79 '-c', 'SSLCertificateFile "%s"' % cert_file, 80 ] 81 82 if self._is_win: 83 start_cmd += ['-c', "ThreadsPerChild %d" % (self._number_of_servers * 2)] 84 else: 85 start_cmd += ['-c', "StartServers %d" % self._number_of_servers, 86 '-c', "MinSpareServers %d" % self._number_of_servers, 87 '-c', "MaxSpareServers %d" % self._number_of_servers, 88 '-C', 'User "%s"' % os.environ.get('USERNAME', os.environ.get('USER', '')), 89 '-k', 'start'] 90 91 enable_ipv6 = self._port_obj.http_server_supports_ipv6() 92 # Perform part of the checks Apache's APR does when trying to listen to 93 # a specific host/port. This allows us to avoid trying to listen to 94 # IPV6 addresses when it fails on Apache. APR itself tries to call 95 # getaddrinfo() again without AI_ADDRCONFIG if the first call fails 96 # with EBADFLAGS, but that is not how it normally fails in our use 97 # cases, so ignore that for now. 98 # See https://bugs.webkit.org/show_bug.cgi?id=98602#c7 99 try: 100 socket.getaddrinfo('::1', 0, 0, 0, 0, socket.AI_ADDRCONFIG) 101 except: 102 enable_ipv6 = False 103 104 for mapping in self._mappings: 105 port = mapping['port'] 106 107 start_cmd += ['-C', "Listen 127.0.0.1:%d" % port] 108 109 # We listen to both IPv4 and IPv6 loop-back addresses, but ignore 110 # requests to 8000 from random users on network. 111 # See https://bugs.webkit.org/show_bug.cgi?id=37104 112 if enable_ipv6: 113 start_cmd += ['-C', "Listen [::1]:%d" % port] 114 115 if additional_dirs: 116 self._start_cmd = start_cmd 117 for alias, path in additional_dirs.iteritems(): 118 start_cmd += ['-c', 'Alias %s "%s"' % (alias, path), 119 # Disable CGI handler for additional dirs. 120 '-c', '<Location %s>' % alias, 121 '-c', 'RemoveHandler .cgi .pl', 122 '-c', '</Location>'] 123 124 self._start_cmd = start_cmd 125 126 def _spawn_process(self): 127 _log.debug('Starting %s server, cmd="%s"' % (self._name, str(self._start_cmd))) 128 self._process = self._executive.popen(self._start_cmd) 129 if self._process.returncode is not None: 130 retval = self._process.returncode 131 err = self._process.stderr.read() 132 if retval or len(err): 133 raise server_base.ServerError('Failed to start %s: %s' % (self._name, err)) 134 135 # For some reason apache isn't guaranteed to have created the pid file before 136 # the process exits, so we wait a little while longer. 137 if not self._wait_for_action(lambda: self._filesystem.exists(self._pid_file)): 138 raise server_base.ServerError('Failed to start %s: no pid file found' % self._name) 139 140 return int(self._filesystem.read_text_file(self._pid_file)) 141 142 def stop(self): 143 self._stop_running_server() 144 145 def _stop_running_server(self): 146 # If apache was forcefully killed, the pid file will not have been deleted, so check 147 # that the process specified by the pid_file no longer exists before deleting the file. 148 if self._pid and not self._executive.check_running_pid(self._pid): 149 self._filesystem.remove(self._pid_file) 150 return 151 152 if self._is_win: 153 self._executive.kill_process(self._pid) 154 return 155 156 proc = self._executive.popen([self._port_obj.path_to_apache(), 157 '-f', self._port_obj.path_to_apache_config_file(), 158 '-c', 'PidFile "%s"' % self._pid_file, 159 '-k', 'stop'], stderr=self._executive.PIPE) 160 proc.wait() 161 retval = proc.returncode 162 err = proc.stderr.read() 163 if retval or len(err): 164 raise server_base.ServerError('Failed to stop %s: %s' % (self._name, err)) 165 166 # For some reason apache isn't guaranteed to have actually stopped after 167 # the stop command returns, so we wait a little while longer for the 168 # pid file to be removed. 169 if not self._wait_for_action(lambda: not self._filesystem.exists(self._pid_file)): 170 raise server_base.ServerError('Failed to stop %s: pid file still exists' % self._name) 171