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._log_prefixes = ('access_log', 'error_log') 48 self._mappings = [{'port': 8000}, 49 {'port': 8080}, 50 {'port': 8443, 'sslcert': True}] 51 self._number_of_servers = number_of_servers 52 53 self._pid_file = self._filesystem.join(self._runtime_path, '%s.pid' % self._name) 54 55 executable = self._port_obj.path_to_apache() 56 server_root = self._filesystem.dirname(self._filesystem.dirname(executable)) 57 58 test_dir = self._port_obj.layout_tests_dir() 59 document_root = self._filesystem.join(test_dir, "http", "tests") 60 js_test_resources_dir = self._filesystem.join(test_dir, "resources") 61 media_resources_dir = self._filesystem.join(test_dir, "media") 62 mime_types_path = self._filesystem.join(test_dir, "http", "conf", "mime.types") 63 cert_file = self._filesystem.join(test_dir, "http", "conf", "webkit-httpd.pem") 64 65 self._access_log_path = self._filesystem.join(output_dir, "access_log.txt") 66 self._error_log_path = self._filesystem.join(output_dir, "error_log.txt") 67 68 self._is_win = self._port_obj.host.platform.is_win() 69 70 start_cmd = [executable, 71 '-f', '%s' % self._port_obj.path_to_apache_config_file(), 72 '-C', 'ServerRoot "%s"' % server_root, 73 '-C', 'DocumentRoot "%s"' % document_root, 74 '-c', 'Alias /js-test-resources "%s"' % js_test_resources_dir, 75 '-c', 'Alias /media-resources "%s"' % media_resources_dir, 76 '-c', 'TypesConfig "%s"' % mime_types_path, 77 '-c', 'CustomLog "%s" common' % self._access_log_path, 78 '-c', 'ErrorLog "%s"' % self._error_log_path, 79 '-c', 'PidFile %s' % self._pid_file, 80 '-c', 'SSLCertificateFile "%s"' % cert_file, 81 ] 82 83 if self._is_win: 84 start_cmd += ['-c', "ThreadsPerChild %d" % (self._number_of_servers * 2)] 85 else: 86 start_cmd += ['-c', "StartServers %d" % self._number_of_servers, 87 '-c', "MinSpareServers %d" % self._number_of_servers, 88 '-c', "MaxSpareServers %d" % self._number_of_servers, 89 '-C', 'User "%s"' % os.environ.get('USERNAME', os.environ.get('USER', '')), 90 '-k', 'start'] 91 92 enable_ipv6 = self._port_obj.http_server_supports_ipv6() 93 # Perform part of the checks Apache's APR does when trying to listen to 94 # a specific host/port. This allows us to avoid trying to listen to 95 # IPV6 addresses when it fails on Apache. APR itself tries to call 96 # getaddrinfo() again without AI_ADDRCONFIG if the first call fails 97 # with EBADFLAGS, but that is not how it normally fails in our use 98 # cases, so ignore that for now. 99 # See https://bugs.webkit.org/show_bug.cgi?id=98602#c7 100 try: 101 socket.getaddrinfo('::1', 0, 0, 0, 0, socket.AI_ADDRCONFIG) 102 except: 103 enable_ipv6 = False 104 105 for mapping in self._mappings: 106 port = mapping['port'] 107 108 start_cmd += ['-C', "Listen 127.0.0.1:%d" % port] 109 110 # We listen to both IPv4 and IPv6 loop-back addresses, but ignore 111 # requests to 8000 from random users on network. 112 # See https://bugs.webkit.org/show_bug.cgi?id=37104 113 if enable_ipv6: 114 start_cmd += ['-C', "Listen [::1]:%d" % port] 115 116 if additional_dirs: 117 self._start_cmd = start_cmd 118 for alias, path in additional_dirs.iteritems(): 119 start_cmd += ['-c', 'Alias %s "%s"' % (alias, path), 120 # Disable CGI handler for additional dirs. 121 '-c', '<Location %s>' % alias, 122 '-c', 'RemoveHandler .cgi .pl', 123 '-c', '</Location>'] 124 125 self._start_cmd = start_cmd 126 127 def _spawn_process(self): 128 _log.debug('Starting %s server, cmd="%s"' % (self._name, str(self._start_cmd))) 129 self._process = self._executive.popen(self._start_cmd, stderr=self._executive.PIPE) 130 if self._process.returncode is not None: 131 retval = self._process.returncode 132 err = self._process.stderr.read() 133 if retval or len(err): 134 raise server_base.ServerError('Failed to start %s: %s' % (self._name, err)) 135 136 # For some reason apache isn't guaranteed to have created the pid file before 137 # the process exits, so we wait a little while longer. 138 if not self._wait_for_action(lambda: self._filesystem.exists(self._pid_file)): 139 self._log_errors_from_subprocess() 140 raise server_base.ServerError('Failed to start %s: no pid file found' % self._name) 141 142 return int(self._filesystem.read_text_file(self._pid_file)) 143 144 def stop(self): 145 self._stop_running_server() 146 147 def _stop_running_server(self): 148 # If apache was forcefully killed, the pid file will not have been deleted, so check 149 # that the process specified by the pid_file no longer exists before deleting the file. 150 if self._pid and not self._executive.check_running_pid(self._pid): 151 self._filesystem.remove(self._pid_file) 152 return 153 154 if self._is_win: 155 self._executive.kill_process(self._pid) 156 return 157 158 proc = self._executive.popen([self._port_obj.path_to_apache(), 159 '-f', self._port_obj.path_to_apache_config_file(), 160 '-c', 'PidFile "%s"' % self._pid_file, 161 '-k', 'stop'], stderr=self._executive.PIPE) 162 proc.wait() 163 retval = proc.returncode 164 err = proc.stderr.read() 165 if retval or len(err): 166 raise server_base.ServerError('Failed to stop %s: %s' % (self._name, err)) 167 168 # For some reason apache isn't guaranteed to have actually stopped after 169 # the stop command returns, so we wait a little while longer for the 170 # pid file to be removed. 171 if not self._wait_for_action(lambda: not self._filesystem.exists(self._pid_file)): 172 raise server_base.ServerError('Failed to stop %s: pid file still exists' % self._name) 173