Home | History | Annotate | Download | only in google
      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 
      8 import logging
      9 import optparse
     10 import os
     11 import subprocess
     12 import sys
     13 import time
     14 import urllib
     15 
     16 import google.path_utils
     17 import google.platform_utils
     18 
     19 class HttpdNotStarted(Exception): pass
     20 
     21 def 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 
     46 def 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 
     52 def 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 
     61 def 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 
     98 def 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 
    117 class 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 
    173 def 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 
    199 if '__main__' == __name__:
    200   sys.exit(main())
    201