#!/usr/bin/env python # Copyright (c) 2011 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """A class to help start/stop a local apache http server.""" import logging import optparse import os import subprocess import sys import time import urllib import google.path_utils import google.platform_utils class HttpdNotStarted(Exception): pass def UrlIsAlive(url): """Checks to see if we get an http response from |url|. We poll the url 5 times with a 1 second delay. If we don't get a reply in that time, we give up and assume the httpd didn't start properly. Args: url: The URL to check. Return: True if the url is alive. """ wait_time = 5 while wait_time > 0: try: response = urllib.urlopen(url) # Server is up and responding. return True except IOError: pass wait_time -= 1 # Wait a second and try again. time.sleep(1) return False def ApacheConfigDir(start_dir): """Returns a path to the directory holding the Apache config files.""" return google.path_utils.FindUpward(start_dir, 'tools', 'python', 'google', 'httpd_config') def GetCygserverPath(start_dir, apache2=False): """Returns the path to the directory holding cygserver.exe file.""" cygserver_path = None if apache2: cygserver_path = google.path_utils.FindUpward(start_dir, 'third_party', 'cygwin', 'usr', 'sbin') return cygserver_path def StartServer(document_root=None, output_dir=None, apache2=False): """Starts a local server on port 8000 using the basic configuration files. Args: document_root: If present, specifies the document root for the server; otherwise, the filesystem's root (e.g., C:/ or /) will be used. output_dir: If present, specifies where to put server logs; otherwise, they'll be placed in the system's temp dir (e.g., $TEMP or /tmp). apache2: boolean if true will cause this function to configure for Apache 2.x as opposed to Apache 1.3.x Returns: the ApacheHttpd object that was created """ script_dir = google.path_utils.ScriptDir() platform_util = google.platform_utils.PlatformUtility(script_dir) if not output_dir: output_dir = platform_util.GetTempDirectory() if not document_root: document_root = platform_util.GetFilesystemRoot() apache_config_dir = ApacheConfigDir(script_dir) if apache2: httpd_conf_path = os.path.join(apache_config_dir, 'httpd2.conf') else: httpd_conf_path = os.path.join(apache_config_dir, 'httpd.conf') mime_types_path = os.path.join(apache_config_dir, 'mime.types') start_cmd = platform_util.GetStartHttpdCommand(output_dir, httpd_conf_path, mime_types_path, document_root, apache2=apache2) stop_cmd = platform_util.GetStopHttpdCommand() httpd = ApacheHttpd(start_cmd, stop_cmd, [8000], cygserver_path=GetCygserverPath(script_dir, apache2)) httpd.StartServer() return httpd def StopServers(apache2=False): """Calls the platform's stop command on a newly created server, forcing it to stop. The details depend on the behavior of the platform stop command. For example, it's often implemented to kill all running httpd processes, as implied by the name of this function. Args: apache2: boolean if true will cause this function to configure for Apache 2.x as opposed to Apache 1.3.x """ script_dir = google.path_utils.ScriptDir() platform_util = google.platform_utils.PlatformUtility(script_dir) httpd = ApacheHttpd('', platform_util.GetStopHttpdCommand(), [], cygserver_path=GetCygserverPath(script_dir, apache2)) httpd.StopServer(force=True) class ApacheHttpd(object): def __init__(self, start_command, stop_command, port_list, cygserver_path=None): """Args: start_command: command list to call to start the httpd stop_command: command list to call to stop the httpd if one has been started. May kill all httpd processes running on the machine. port_list: list of ports expected to respond on the local machine when the server has been successfully started. cygserver_path: Path to cygserver.exe. If specified, exe will be started with server as well as stopped when server is stopped. """ self._http_server_proc = None self._start_command = start_command self._stop_command = stop_command self._port_list = port_list self._cygserver_path = cygserver_path def StartServer(self): if self._http_server_proc: return if self._cygserver_path: cygserver_exe = os.path.join(self._cygserver_path, "cygserver.exe") cygbin = google.path_utils.FindUpward(cygserver_exe, 'third_party', 'cygwin', 'bin') env = os.environ env['PATH'] += ";" + cygbin subprocess.Popen(cygserver_exe, env=env) logging.info('Starting http server') self._http_server_proc = subprocess.Popen(self._start_command) # Ensure that the server is running on all the desired ports. for port in self._port_list: if not UrlIsAlive('http://127.0.0.1:%s/' % str(port)): raise HttpdNotStarted('Failed to start httpd on port %s' % str(port)) def StopServer(self, force=False): """If we started an httpd.exe process, or if force is True, call self._stop_command (passed in on init so it can be platform-dependent). This will presumably kill it, and may also kill any other httpd.exe processes that are running. """ if force or self._http_server_proc: logging.info('Stopping http server') kill_proc = subprocess.Popen(self._stop_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) logging.info('%s\n%s' % (kill_proc.stdout.read(), kill_proc.stderr.read())) self._http_server_proc = None if self._cygserver_path: subprocess.Popen(["taskkill.exe", "/f", "/im", "cygserver.exe"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) def main(): # Provide some command line params for starting/stopping the http server # manually. option_parser = optparse.OptionParser() option_parser.add_option('-k', '--server', help='Server action (start|stop)') option_parser.add_option('-r', '--root', help='Document root (optional)') option_parser.add_option('-a', '--apache2', action='store_true', default=False, help='Starts Apache 2 instead of Apache 1.3 (default). ' 'Ignored on Mac (apache2 is used always)') options, args = option_parser.parse_args() if not options.server: print ("Usage: %s -k {start|stop} [-r document_root] [--apache2]" % sys.argv[0]) return 1 document_root = None if options.root: document_root = options.root if 'start' == options.server: StartServer(document_root, apache2=options.apache2) else: StopServers(apache2=options.apache2) if '__main__' == __name__: sys.exit(main())