1#!/usr/bin/env python
2# Copyright (c) 2011 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""A class to help start/stop a local apache http server."""
7
8import logging
9import optparse
10import os
11import subprocess
12import sys
13import time
14import urllib
15
16import google.path_utils
17import google.platform_utils
18
19class HttpdNotStarted(Exception): pass
20
21def UrlIsAlive(url):
22  """Checks to see if we get an http response from |url|.
23  We poll the url 5 times with a 1 second delay.  If we don't
24  get a reply in that time, we give up and assume the httpd
25  didn't start properly.
26
27  Args:
28    url: The URL to check.
29  Return:
30    True if the url is alive.
31  """
32  wait_time = 5
33  while wait_time > 0:
34    try:
35      response = urllib.urlopen(url)
36      # Server is up and responding.
37      return True
38    except IOError:
39      pass
40    wait_time -= 1
41    # Wait a second and try again.
42    time.sleep(1)
43
44  return False
45
46def ApacheConfigDir(start_dir):
47  """Returns a path to the directory holding the Apache config files."""
48  return google.path_utils.FindUpward(start_dir, 'tools', 'python',
49                                      'google', 'httpd_config')
50
51
52def GetCygserverPath(start_dir, apache2=False):
53  """Returns the path to the directory holding cygserver.exe file."""
54  cygserver_path = None
55  if apache2:
56    cygserver_path = google.path_utils.FindUpward(start_dir, 'third_party',
57                                                  'cygwin', 'usr', 'sbin')
58  return cygserver_path
59
60
61def StartServer(document_root=None, output_dir=None, apache2=False):
62  """Starts a local server on port 8000 using the basic configuration files.
63
64  Args:
65    document_root: If present, specifies the document root for the server;
66        otherwise, the filesystem's root (e.g., C:/ or /) will be used.
67    output_dir: If present, specifies where to put server logs; otherwise,
68        they'll be placed in the system's temp dir (e.g., $TEMP or /tmp).
69    apache2: boolean if true will cause this function to configure
70             for Apache 2.x as opposed to Apache 1.3.x
71
72  Returns: the ApacheHttpd object that was created
73  """
74  script_dir = google.path_utils.ScriptDir()
75  platform_util = google.platform_utils.PlatformUtility(script_dir)
76  if not output_dir:
77    output_dir = platform_util.GetTempDirectory()
78  if not document_root:
79    document_root = platform_util.GetFilesystemRoot()
80  apache_config_dir = ApacheConfigDir(script_dir)
81  if apache2:
82    httpd_conf_path = os.path.join(apache_config_dir, 'httpd2.conf')
83  else:
84    httpd_conf_path = os.path.join(apache_config_dir, 'httpd.conf')
85  mime_types_path = os.path.join(apache_config_dir, 'mime.types')
86  start_cmd = platform_util.GetStartHttpdCommand(output_dir,
87                                                 httpd_conf_path,
88                                                 mime_types_path,
89                                                 document_root,
90                                                 apache2=apache2)
91  stop_cmd = platform_util.GetStopHttpdCommand()
92  httpd = ApacheHttpd(start_cmd, stop_cmd, [8000],
93                      cygserver_path=GetCygserverPath(script_dir, apache2))
94  httpd.StartServer()
95  return httpd
96
97
98def StopServers(apache2=False):
99  """Calls the platform's stop command on a newly created server, forcing it
100  to stop.
101
102  The details depend on the behavior of the platform stop command. For example,
103  it's often implemented to kill all running httpd processes, as implied by
104  the name of this function.
105
106  Args:
107    apache2: boolean if true will cause this function to configure
108             for Apache 2.x as opposed to Apache 1.3.x
109  """
110  script_dir = google.path_utils.ScriptDir()
111  platform_util = google.platform_utils.PlatformUtility(script_dir)
112  httpd = ApacheHttpd('', platform_util.GetStopHttpdCommand(), [],
113                      cygserver_path=GetCygserverPath(script_dir, apache2))
114  httpd.StopServer(force=True)
115
116
117class ApacheHttpd(object):
118  def __init__(self, start_command, stop_command, port_list,
119               cygserver_path=None):
120    """Args:
121        start_command: command list to call to start the httpd
122        stop_command: command list to call to stop the httpd if one has been
123            started.  May kill all httpd processes running on the machine.
124        port_list: list of ports expected to respond on the local machine when
125            the server has been successfully started.
126        cygserver_path: Path to cygserver.exe. If specified, exe will be started
127            with server as well as stopped when server is stopped.
128    """
129    self._http_server_proc = None
130    self._start_command = start_command
131    self._stop_command = stop_command
132    self._port_list = port_list
133    self._cygserver_path = cygserver_path
134
135  def StartServer(self):
136    if self._http_server_proc:
137      return
138    if self._cygserver_path:
139      cygserver_exe = os.path.join(self._cygserver_path, "cygserver.exe")
140      cygbin = google.path_utils.FindUpward(cygserver_exe, 'third_party',
141                                            'cygwin', 'bin')
142      env = os.environ
143      env['PATH'] += ";" + cygbin
144      subprocess.Popen(cygserver_exe, env=env)
145    logging.info('Starting http server')
146    self._http_server_proc = subprocess.Popen(self._start_command)
147
148    # Ensure that the server is running on all the desired ports.
149    for port in self._port_list:
150      if not UrlIsAlive('http://127.0.0.1:%s/' % str(port)):
151        raise HttpdNotStarted('Failed to start httpd on port %s' % str(port))
152
153  def StopServer(self, force=False):
154    """If we started an httpd.exe process, or if force is True, call
155    self._stop_command (passed in on init so it can be platform-dependent).
156    This will presumably kill it, and may also kill any other httpd.exe
157    processes that are running.
158    """
159    if force or self._http_server_proc:
160      logging.info('Stopping http server')
161      kill_proc = subprocess.Popen(self._stop_command,
162                                   stdout=subprocess.PIPE,
163                                   stderr=subprocess.PIPE)
164      logging.info('%s\n%s' % (kill_proc.stdout.read(),
165                               kill_proc.stderr.read()))
166      self._http_server_proc = None
167      if self._cygserver_path:
168        subprocess.Popen(["taskkill.exe", "/f", "/im", "cygserver.exe"],
169                         stdout=subprocess.PIPE,
170                         stderr=subprocess.PIPE)
171
172
173def main():
174  # Provide some command line params for starting/stopping the http server
175  # manually.
176  option_parser = optparse.OptionParser()
177  option_parser.add_option('-k', '--server', help='Server action (start|stop)')
178  option_parser.add_option('-r', '--root', help='Document root (optional)')
179  option_parser.add_option('-a', '--apache2', action='store_true',
180      default=False, help='Starts Apache 2 instead of Apache 1.3 (default). '
181                          'Ignored on Mac (apache2 is used always)')
182  options, args = option_parser.parse_args()
183
184  if not options.server:
185    print ("Usage: %s -k {start|stop} [-r document_root] [--apache2]" %
186           sys.argv[0])
187    return 1
188
189  document_root = None
190  if options.root:
191    document_root = options.root
192
193  if 'start' == options.server:
194    StartServer(document_root, apache2=options.apache2)
195  else:
196    StopServers(apache2=options.apache2)
197
198
199if '__main__' == __name__:
200  sys.exit(main())
201