| #!/usr/bin/env python | |
| # Copyright 2011 The Chromium Authors | |
| # 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.""" | |
| from __future__ import print_function | |
| 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()) |