15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#!/usr/bin/env python
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2011 The Chromium Authors. All rights reserved.
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)"""A class to help start/stop a local apache http server."""
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import logging
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import optparse
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import subprocess
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import sys
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import time
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import urllib
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import google.path_utils
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import google.platform_utils
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class HttpdNotStarted(Exception): pass
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def UrlIsAlive(url):
2290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  """Checks to see if we get an http response from |url|.
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  We poll the url 5 times with a 1 second delay.  If we don't
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  get a reply in that time, we give up and assume the httpd
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  didn't start properly.
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Args:
282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    url: The URL to check.
292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  Return:
302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    True if the url is alive.
312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  """
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  wait_time = 5
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  while wait_time > 0:
34c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    try:
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response = urllib.urlopen(url)
36c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      # Server is up and responding.
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return True
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except IOError:
39c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      pass
4090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    wait_time -= 1
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Wait a second and try again.
4290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    time.sleep(1)
431e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return False
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def ApacheConfigDir(start_dir):
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Returns a path to the directory holding the Apache config files."""
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return google.path_utils.FindUpward(start_dir, 'tools', 'python',
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                      'google', 'httpd_config')
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
52010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)def GetCygserverPath(start_dir, apache2=False):
53010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  """Returns the path to the directory holding cygserver.exe file."""
54010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  cygserver_path = None
55010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  if apache2:
56010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    cygserver_path = google.path_utils.FindUpward(start_dir, 'third_party',
57010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)                                                  'cygwin', 'usr', 'sbin')
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return cygserver_path
591320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
601320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
61868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)def StartServer(document_root=None, output_dir=None, apache2=False):
62868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  """Starts a local server on port 8000 using the basic configuration files.
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Args:
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    document_root: If present, specifies the document root for the server;
665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        otherwise, the filesystem's root (e.g., C:/ or /) will be used.
67010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    output_dir: If present, specifies where to put server logs; otherwise,
68010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)        they'll be placed in the system's temp dir (e.g., $TEMP or /tmp).
69010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    apache2: boolean if true will cause this function to configure
70010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)             for Apache 2.x as opposed to Apache 1.3.x
71010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)
72010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  Returns: the ApacheHttpd object that was created
73010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  """
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  script_dir = google.path_utils.ScriptDir()
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  platform_util = google.platform_utils.PlatformUtility(script_dir)
762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if not output_dir:
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    output_dir = platform_util.GetTempDirectory()
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if not document_root:
791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    document_root = platform_util.GetFilesystemRoot()
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  apache_config_dir = ApacheConfigDir(script_dir)
812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if apache2:
822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    httpd_conf_path = os.path.join(apache_config_dir, 'httpd2.conf')
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else:
842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    httpd_conf_path = os.path.join(apache_config_dir, 'httpd.conf')
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  mime_types_path = os.path.join(apache_config_dir, 'mime.types')
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  start_cmd = platform_util.GetStartHttpdCommand(output_dir,
87a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch                                                 httpd_conf_path,
88a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch                                                 mime_types_path,
89a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch                                                 document_root,
90a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch                                                 apache2=apache2)
91a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  stop_cmd = platform_util.GetStopHttpdCommand()
92a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  httpd = ApacheHttpd(start_cmd, stop_cmd, [8000],
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                      cygserver_path=GetCygserverPath(script_dir, apache2))
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  httpd.StartServer()
95a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  return httpd
96a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch
97a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch
98a3f7b4e666c476898878fa745f637129375cd889Ben Murdochdef StopServers(apache2=False):
99a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  """Calls the platform's stop command on a newly created server, forcing it
100a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  to stop.
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  The details depend on the behavior of the platform stop command. For example,
103a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  it's often implemented to kill all running httpd processes, as implied by
104a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  the name of this function.
1051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
10658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)  Args:
107a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch    apache2: boolean if true will cause this function to configure
10858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)             for Apache 2.x as opposed to Apache 1.3.x
109a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  """
11058537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)  script_dir = google.path_utils.ScriptDir()
111a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  platform_util = google.platform_utils.PlatformUtility(script_dir)
11290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  httpd = ApacheHttpd('', platform_util.GetStopHttpdCommand(), [],
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                      cygserver_path=GetCygserverPath(script_dir, apache2))
114a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  httpd.StopServer(force=True)
11558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)
11658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)
11758537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)class ApacheHttpd(object):
11858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)  def __init__(self, start_command, stop_command, port_list,
11958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)               cygserver_path=None):
12058537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)    """Args:
121a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch        start_command: command list to call to start the httpd
122a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch        stop_command: command list to call to stop the httpd if one has been
123a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch            started.  May kill all httpd processes running on the machine.
124010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)        port_list: list of ports expected to respond on the local machine when
125010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)            the server has been successfully started.
126010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)        cygserver_path: Path to cygserver.exe. If specified, exe will be started
127010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)            with server as well as stopped when server is stopped.
128010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    """
129010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    self._http_server_proc = None
130010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    self._start_command = start_command
131010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    self._stop_command = stop_command
132a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch    self._port_list = port_list
133a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch    self._cygserver_path = cygserver_path
134a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch
135a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch  def StartServer(self):
136a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch    if self._http_server_proc:
137a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch      return
138a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch    if self._cygserver_path:
139a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch      cygserver_exe = os.path.join(self._cygserver_path, "cygserver.exe")
140a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch      cygbin = google.path_utils.FindUpward(cygserver_exe, 'third_party',
141e4256316f8b5e8d1ec0df1f7762771622a53fa63Ben Murdoch                                            'cygwin', 'bin')
142a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch      env = os.environ
143a3f7b4e666c476898878fa745f637129375cd889Ben Murdoch      env['PATH'] += ";" + cygbin
144e4256316f8b5e8d1ec0df1f7762771622a53fa63Ben Murdoch      subprocess.Popen(cygserver_exe, env=env)
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    logging.info('Starting http server')
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._http_server_proc = subprocess.Popen(self._start_command)
1475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
148010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    # Ensure that the server is running on all the desired ports.
149010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    for port in self._port_list:
1505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      if not UrlIsAlive('http://127.0.0.1:%s/' % str(port)):
151010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)        raise HttpdNotStarted('Failed to start httpd on port %s' % str(port))
152010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)
153010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  def StopServer(self, force=False):
1545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """If we started an httpd.exe process, or if force is True, call
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._stop_command (passed in on init so it can be platform-dependent).
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    This will presumably kill it, and may also kill any other httpd.exe
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    processes that are running.
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if force or self._http_server_proc:
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.info('Stopping http server')
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      kill_proc = subprocess.Popen(self._stop_command,
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                   stdout=subprocess.PIPE,
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                   stderr=subprocess.PIPE)
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.info('%s\n%s' % (kill_proc.stdout.read(),
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                               kill_proc.stderr.read()))
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._http_server_proc = None
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if self._cygserver_path:
168eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        subprocess.Popen(["taskkill.exe", "/f", "/im", "cygserver.exe"],
169eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                         stdout=subprocess.PIPE,
170eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                         stderr=subprocess.PIPE)
171eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
172eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
1734e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)def main():
174eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  # Provide some command line params for starting/stopping the http server
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # manually.
176eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  option_parser = optparse.OptionParser()
177eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  option_parser.add_option('-k', '--server', help='Server action (start|stop)')
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  option_parser.add_option('-r', '--root', help='Document root (optional)')
179eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  option_parser.add_option('-a', '--apache2', action='store_true',
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      default=False, help='Starts Apache 2 instead of Apache 1.3 (default). '
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          'Ignored on Mac (apache2 is used always)')
1822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  options, args = option_parser.parse_args()
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
184eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if not options.server:
1851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    print ("Usage: %s -k {start|stop} [-r document_root] [--apache2]" %
1861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci           sys.argv[0])
187eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    return 1
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  document_root = None
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if options.root:
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    document_root = options.root
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if 'start' == options.server:
1942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    StartServer(document_root, apache2=options.apache2)
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else:
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    StopServers(apache2=options.apache2)
19758537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
199eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochif '__main__' == __name__:
200010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  sys.exit(main())
2012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)