12a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#!/usr/bin/env python
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2012 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)import BaseHTTPServer
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import imp
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import logging
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import multiprocessing
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import optparse
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import SimpleHTTPServer  # pylint: disable=W0611
132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import socket
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import sys
152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import time
162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import urlparse
172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)if sys.version_info < (2, 6, 0):
192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  sys.stderr.write("python 2.6 or later is required run this script\n")
202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  sys.exit(1)
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)NACL_SDK_ROOT = os.path.dirname(SCRIPT_DIR)
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# We only run from the examples directory so that not too much is exposed
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# via this HTTP server.  Everything in the directory is served, so there should
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# never be anything potentially sensitive in the serving directory, especially
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# if the machine might be a multi-user machine and not all users are trusted.
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# We only serve via the loopback interface.
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def SanityCheckDirectory(dirname):
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  abs_serve_dir = os.path.abspath(dirname)
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Verify we don't serve anywhere above NACL_SDK_ROOT.
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if abs_serve_dir[:len(NACL_SDK_ROOT)] == NACL_SDK_ROOT:
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  logging.error('For security, httpd.py should only be run from within the')
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  logging.error('example directory tree.')
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  logging.error('Attempting to serve from %s.' % abs_serve_dir)
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  logging.error('Run with --no_dir_check to bypass this check.')
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  sys.exit(1)
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)class PluggableHTTPServer(BaseHTTPServer.HTTPServer):
462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def __init__(self, *args, **kwargs):
472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    BaseHTTPServer.HTTPServer.__init__(self, *args)
482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.serve_dir = kwargs.get('serve_dir', '.')
492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.test_mode = kwargs.get('test_mode', False)
502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.delegate_map = {}
512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.running = True
522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.result = 0
532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def Shutdown(self, result=0):
552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.running = False
562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.result = result
572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class PluggableHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _FindDelegateAtPath(self, dirname):
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # First check the cache...
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    logging.debug('Looking for cached delegate in %s...' % dirname)
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    handler_script = os.path.join(dirname, 'handler.py')
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if dirname in self.server.delegate_map:
662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      result = self.server.delegate_map[dirname]
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if result is None:
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        logging.debug('Found None.')
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        logging.debug('Found delegate.')
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return result
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Don't have one yet, look for one.
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    delegate = None
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    logging.debug('Testing file %s for existence...' % handler_script)
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if os.path.exists(handler_script):
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.debug(
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          'File %s exists, looking for HTTPRequestHandlerDelegate.' %
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          handler_script)
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      module = imp.load_source('handler', handler_script)
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      delegate_class = getattr(module, 'HTTPRequestHandlerDelegate', None)
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      delegate = delegate_class()
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if not delegate:
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        logging.warn(
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            'Unable to find symbol HTTPRequestHandlerDelegate in module %s.' %
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            handler_script)
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return delegate
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _FindDelegateForURLRecurse(self, cur_dir, abs_root):
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    delegate = self._FindDelegateAtPath(cur_dir)
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not delegate:
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Didn't find it, try the parent directory, but stop if this is the server
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # root.
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if cur_dir != abs_root:
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        parent_dir = os.path.dirname(cur_dir)
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        delegate = self._FindDelegateForURLRecurse(parent_dir, abs_root)
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    logging.debug('Adding delegate to cache for %s.' % cur_dir)
1012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.server.delegate_map[cur_dir] = delegate
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return delegate
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _FindDelegateForURL(self, url_path):
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    path = self.translate_path(url_path)
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if os.path.isdir(path):
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      dirname = path
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      dirname = os.path.dirname(path)
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    abs_serve_dir = os.path.abspath(self.server.serve_dir)
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    delegate = self._FindDelegateForURLRecurse(dirname, abs_serve_dir)
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not delegate:
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.info('No handler found for path %s. Using default.' % url_path)
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return delegate
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def _SendNothingAndDie(self, result=0):
1182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.send_response(200, 'OK')
1192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.send_header('Content-type', 'text/html')
1202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.send_header('Content-length', '0')
1212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.end_headers()
1222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.server.Shutdown(result)
1232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def send_head(self):
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    delegate = self._FindDelegateForURL(self.path)
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if delegate:
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return delegate.send_head(self)
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self.base_send_head()
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def base_send_head(self):
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def do_GET(self):
1342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # TODO(binji): pyauto tests use the ?quit=1 method to kill the server.
1352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # Remove this when we kill the pyauto tests.
1362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    _, _, _, query, _ = urlparse.urlsplit(self.path)
1372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if query:
1382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      params = urlparse.parse_qs(query)
1392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      if '1' in params.get('quit', []):
1402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        self._SendNothingAndDie()
1412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        return
1422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    delegate = self._FindDelegateForURL(self.path)
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if delegate:
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return delegate.do_GET(self)
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self.base_do_GET()
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def base_do_GET(self):
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def do_POST(self):
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    delegate = self._FindDelegateForURL(self.path)
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if delegate:
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return delegate.do_POST(self)
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self.base_do_POST()
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def base_do_POST(self):
1582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if self.server.test_mode:
1592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      if self.path == '/ok':
1602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        self._SendNothingAndDie(0)
1612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      elif self.path == '/fail':
1622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        self._SendNothingAndDie(1)
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class LocalHTTPServer(object):
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Class to start a local HTTP server as a child process."""
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def __init__(self, dirname, port, test_mode):
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    parent_conn, child_conn = multiprocessing.Pipe()
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.process = multiprocessing.Process(
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        target=_HTTPServerProcess,
1722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        args=(child_conn, dirname, port, {
1732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          'serve_dir': dirname,
1742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          'test_mode': test_mode,
1752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        }))
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.process.start()
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if parent_conn.poll(10):  # wait 10 seconds
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.port = parent_conn.recv()
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise Exception('Unable to launch HTTP server.')
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.conn = parent_conn
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def ServeForever(self):
1852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Serve until the child HTTP process tells us to stop.
1862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    Returns:
1882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      The result from the child (as an errorcode), or 0 if the server was
1892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      killed not by the child (by KeyboardInterrupt for example).
1902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """
1912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    child_result = 0
1922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    try:
1932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      # Block on this pipe, waiting for a response from the child process.
1942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      child_result = self.conn.recv()
1952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    except KeyboardInterrupt:
1962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      pass
1972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    finally:
1982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      self.Shutdown()
1992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return child_result
2002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def ServeUntilSubprocessDies(self, process):
2022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Serve until the child HTTP process tells us to stop or |subprocess| dies.
2032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    Returns:
2052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      The result from the child (as an errorcode), or 0 if |subprocess| died,
2062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      or the server was killed some other way (by KeyboardInterrupt for
2072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      example).
2082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """
2092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    child_result = 0
2102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    try:
2112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      while True:
2122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        if process.poll() is not None:
2132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          child_result = 0
2142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          break
2152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        if self.conn.poll():
2162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          child_result = self.conn.recv()
2172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          break
2182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        time.sleep(0)
2192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    except KeyboardInterrupt:
2202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      pass
2212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    finally:
2222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      self.Shutdown()
2232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return child_result
2242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def Shutdown(self):
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Send a message to the child HTTP server process and wait for it to
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        finish."""
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.conn.send(False)
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.process.join()
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def GetURL(self, rel_url):
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Get the full url for a file on the local HTTP server.
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      rel_url: A URL fragment to convert to a full URL. For example,
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          GetURL('foobar.baz') -> 'http://localhost:1234/foobar.baz'
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return 'http://localhost:%d/%s' % (self.port, rel_url)
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def _HTTPServerProcess(conn, dirname, port, server_kwargs):
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Run a local httpserver with the given port or an ephemeral port.
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  This function assumes it is run as a child process using multiprocessing.
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Args:
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    conn: A connection to the parent process. The child process sends
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        the local port, and waits for a message from the parent to
2492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        stop serving. It also sends a "result" back to the parent -- this can
2502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        be used to allow a client-side test to notify the server of results.
2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    dirname: The directory to serve. All files are accessible through
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       http://localhost:<port>/path/to/filename.
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    port: The port to serve on. If 0, an ephemeral port will be chosen.
2542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    server_kwargs: A dict that will be passed as kwargs to the server.
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  try:
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    os.chdir(dirname)
2582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    httpd = PluggableHTTPServer(('', port), PluggableHTTPRequestHandler,
2592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                **server_kwargs)
2602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  except socket.error as e:
2612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    sys.stderr.write('Error creating HTTPServer: %s\n' % e)
2622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    sys.exit(1)
2632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  try:
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    conn.send(httpd.server_address[1])  # the chosen port number
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    httpd.timeout = 0.5  # seconds
2672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    while httpd.running:
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Flush output for MSVS Add-In.
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      sys.stdout.flush()
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      sys.stderr.flush()
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      httpd.handle_request()
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if conn.poll():
2732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        httpd.running = conn.recv()
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  except KeyboardInterrupt:
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pass
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  finally:
2772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    conn.send(httpd.result)
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    conn.close()
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def main(args):
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser = optparse.OptionParser()
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option('-C', '--serve-dir',
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      help='Serve files out of this directory.',
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      dest='serve_dir', default=os.path.abspath('.'))
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option('-p', '--port',
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      help='Run server on this port.',
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      dest='port', default=5103)
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option('--no_dir_check',
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      help='No check to ensure serving from safe directory.',
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      dest='do_safe_check', action='store_false', default=True)
2922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  parser.add_option('--test-mode',
2932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      help='Listen for posts to /ok or /fail and shut down the server with '
2942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          ' errorcodes 0 and 1 respectively.',
2952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      dest='test_mode', action='store_true')
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  options, args = parser.parse_args(args)
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if options.do_safe_check:
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    SanityCheckDirectory(options.serve_dir)
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  server = LocalHTTPServer(options.serve_dir, int(options.port),
3012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                           options.test_mode)
3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  # Serve until the client tells us to stop. When it does, it will give us an
3042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  # errorcode.
3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  print 'Serving %s on %s...' % (options.serve_dir, server.GetURL(''))
3062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return server.ServeForever()
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)if __name__ == '__main__':
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  sys.exit(main(sys.argv[1:]))
310