2 # Copyright (C) 2011 Google Inc. All rights reserved.
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
8 # * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 # * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
14 # * Neither the name of Google Inc. nor the names of its
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 """Base class with common routines between the Apache, Lighttpd, and websocket servers."""
40 _log = logging.getLogger(__name__)
43 class ServerError(Exception):
47 class HttpServerBase(object):
48 """A skeleton class for starting and stopping servers used by the layout tests."""
50 def __init__(self, port_obj):
51 self._executive = port_obj._executive
52 self._filesystem = port_obj._filesystem
53 self._name = '<virtual>'
57 self._port_obj = port_obj
59 # We need a non-checkout-dependent place to put lock files, etc. We
60 # don't use the Python default on the Mac because it defaults to a
61 # randomly-generated directory under /var/folders and no one would ever
63 tmpdir = tempfile.gettempdir()
64 if sys.platform == 'darwin':
67 self._runtime_path = self._filesystem.join(tmpdir, "WebKit")
68 port_obj.maybe_make_directory(self._runtime_path)
71 """Starts the server. It is an error to start an already started server.
73 This method also stops any stale servers started by a previous instance."""
74 assert not self._pid, '%s server is already running' % self._name
76 # Stop any stale servers left over from previous instances.
77 if self._filesystem.exists(self._pid_file):
78 self._pid = int(self._filesystem.read_text_file(self._pid_file))
79 self._stop_running_server()
82 self._remove_stale_logs()
83 self._prepare_config()
84 self._check_that_all_ports_are_available()
86 self._pid = self._spawn_process()
88 if self._wait_for_action(self._is_server_running_on_all_ports):
89 _log.debug("%s successfully started (pid = %d)" % (self._name, self._pid))
91 self._stop_running_server()
92 raise ServerError('Failed to start %s server' % self._name)
95 """Stops the server. Stopping a server that isn't started is harmless."""
97 if self._filesystem.exists(self._pid_file):
98 actual_pid = int(self._filesystem.read_text_file(self._pid_file))
100 self._pid = actual_pid
106 _log.warning('Failed to stop %s: pid file is missing' % self._name)
108 if self._pid != actual_pid:
109 _log.warning('Failed to stop %s: pid file contains %d, not %d' %
110 (self._name, actual_pid, self._pid))
111 # Try to kill the existing pid, anyway, in case it got orphaned.
112 self._executive.kill_process(self._pid)
116 _log.debug("Attempting to shut down %s server at pid %d" % (self._name, self._pid))
117 self._stop_running_server()
118 _log.debug("%s server at pid %d stopped" % (self._name, self._pid))
121 def _prepare_config(self):
122 """This routine can be overridden by subclasses to do any sort
123 of initialization required prior to starting the server that may fail."""
126 def _remove_stale_logs(self):
127 """This routine can be overridden by subclasses to try and remove logs
128 left over from a prior run. This routine should log warnings if the
129 files cannot be deleted, but should not fail unless failure to
130 delete the logs will actually cause start() to fail."""
133 def _spawn_process(self):
134 """This routine must be implemented by subclasses to actually start the server.
136 This routine returns the pid of the started process, and also ensures that that
137 pid has been written to self._pid_file."""
138 raise NotImplementedError()
140 def _stop_running_server(self):
141 """This routine must be implemented by subclasses to actually stop the running server listed in self._pid_file."""
142 raise NotImplementedError()
146 def _remove_log_files(self, folder, starts_with):
147 files = self._filesystem.listdir(folder)
149 if file.startswith(starts_with):
150 full_path = self._filesystem.join(folder, file)
151 self._filesystem.remove(full_path)
153 def _wait_for_action(self, action, wait_secs=20.0, sleep_secs=1.0):
154 """Repeat the action for wait_sec or until it succeeds, sleeping for sleep_secs
155 in between each attempt. Returns whether it succeeded."""
156 start_time = time.time()
157 while time.time() - start_time < wait_secs:
160 _log.debug("Waiting for action: %s" % action)
161 time.sleep(sleep_secs)
165 def _is_server_running_on_all_ports(self):
166 """Returns whether the server is running on all the desired ports."""
167 if not self._executive.check_running_pid(self._pid):
168 _log.debug("Server isn't running at all")
169 raise ServerError("Server exited")
171 for mapping in self._mappings:
173 port = mapping['port']
175 s.connect(('localhost', port))
176 _log.debug("Server running on %d" % port)
177 except socket.error, e:
178 # this branch is needed on Mac 10.5 / python 2.5
179 if e.args[0] not in (errno.ECONNREFUSED, errno.ECONNRESET):
181 _log.debug("Server NOT running on %d: %s" % (port, e))
184 if e.errno not in (errno.ECONNREFUSED, errno.ECONNRESET):
186 _log.debug("Server NOT running on %d: %s" % (port, e))
192 def _check_that_all_ports_are_available(self):
193 for mapping in self._mappings:
195 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
196 port = mapping['port']
198 s.bind(('localhost', port))
200 if e.errno in (errno.EALREADY, errno.EADDRINUSE):
201 raise ServerError('Port %d is already in use.' % port)
202 elif sys.platform == 'win32' and e.errno in (errno.WSAEACCES,):
203 raise ServerError('Port %d is already in use.' % port)