Extract http_server.py from android.py.

R=qsr@chromium.org

Review URL: https://codereview.chromium.org/1109493005

Cr-Mirrored-From: https://github.com/domokit/mojo
Cr-Mirrored-Commit: 542f94c289ab6e10aac7912ae02255a35de10b7c
diff --git a/pylib/android.py b/pylib/android.py
index 374ebb0..5c26ddb 100644
--- a/pylib/android.py
+++ b/pylib/android.py
@@ -3,13 +3,9 @@
 # found in the LICENSE file.
 
 import atexit
-import datetime
-import email.utils
-import hashlib
 import itertools
 import json
 import logging
-import math
 import os
 import os.path
 import random
@@ -19,8 +15,7 @@
 import time
 import urlparse
 
-import SimpleHTTPServer
-import SocketServer
+from pylib.http_server import StartHttpServer
 
 
 # Tags used by the mojo shell application logs.
@@ -39,124 +34,9 @@
 
 DEFAULT_BASE_PORT = 31337
 
-ZERO = datetime.timedelta(0)
-
-class UTC_TZINFO(datetime.tzinfo):
-  """UTC time zone representation."""
-
-  def utcoffset(self, _):
-    return ZERO
-
-  def tzname(self, _):
-    return "UTC"
-
-  def dst(self, _):
-     return ZERO
-
-UTC = UTC_TZINFO()
-
 _logger = logging.getLogger()
 
 
-class _SilentTCPServer(SocketServer.TCPServer):
-  """
-  A TCPServer that won't display any error, unless debugging is enabled. This is
-  useful because the client might stop while it is fetching an URL, which causes
-  spurious error messages.
-  """
-  def handle_error(self, request, client_address):
-    """
-    Override the base class method to have conditional logging.
-    """
-    if logging.getLogger().isEnabledFor(logging.DEBUG):
-      SocketServer.TCPServer.handle_error(self, request, client_address)
-
-
-def _GetHandlerClassForPath(base_path):
-  class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
-    """
-    Handler for SocketServer.TCPServer that will serve the files from
-    |base_path| directory over http.
-    """
-
-    def __init__(self, *args, **kwargs):
-      self.etag = None
-      SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, *args, **kwargs)
-
-    def get_etag(self):
-      if self.etag:
-        return self.etag
-
-      path = self.translate_path(self.path)
-      if not os.path.isfile(path):
-        return None
-
-      sha256 = hashlib.sha256()
-      BLOCKSIZE = 65536
-      with open(path, 'rb') as hashed:
-        buf = hashed.read(BLOCKSIZE)
-        while len(buf) > 0:
-          sha256.update(buf)
-          buf = hashed.read(BLOCKSIZE)
-      self.etag = '"%s"' % sha256.hexdigest()
-      return self.etag
-
-    def send_head(self):
-      # Always close the connection after each request, as the keep alive
-      # support from SimpleHTTPServer doesn't like when the client requests to
-      # close the connection before downloading the full response content.
-      # pylint: disable=W0201
-      self.close_connection = 1
-
-      path = self.translate_path(self.path)
-      if os.path.isfile(path):
-        # Handle If-None-Match
-        etag = self.get_etag()
-        if ('If-None-Match' in self.headers and
-            etag == self.headers['If-None-Match']):
-          self.send_response(304)
-          return None
-
-        # Handle If-Modified-Since
-        if ('If-None-Match' not in self.headers and
-            'If-Modified-Since' in self.headers):
-          last_modified = datetime.datetime.fromtimestamp(
-              math.floor(os.stat(path).st_mtime), tz=UTC)
-          ims = datetime.datetime(
-              *email.utils.parsedate(self.headers['If-Modified-Since'])[:6],
-              tzinfo=UTC)
-          if last_modified <= ims:
-            self.send_response(304)
-            return None
-
-      return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
-
-    def end_headers(self):
-      path = self.translate_path(self.path)
-
-      if os.path.isfile(path):
-        etag = self.get_etag()
-        if etag:
-          self.send_header('ETag', etag)
-          self.send_header('Cache-Control', 'must-revalidate')
-
-      return SimpleHTTPServer.SimpleHTTPRequestHandler.end_headers(self)
-
-    def translate_path(self, path):
-      path_from_current = (
-          SimpleHTTPServer.SimpleHTTPRequestHandler.translate_path(self, path))
-      return os.path.join(base_path, os.path.relpath(path_from_current))
-
-    def log_message(self, *_):
-      """
-      Override the base class method to disable logging.
-      """
-      pass
-
-  RequestHandler.protocol_version = 'HTTP/1.1'
-  return RequestHandler
-
-
 def _IsMapOrigin(arg):
   """Returns whether arg is a --map-origin argument."""
   return arg.startswith(MAPPING_PREFIX)
@@ -271,15 +151,10 @@
     url."""
     assert path
     print 'starting http for', path
-    httpd = _SilentTCPServer(('127.0.0.1', 0), _GetHandlerClassForPath(path))
-    atexit.register(httpd.shutdown)
+    server_address = StartHttpServer(path)
 
-    http_thread = threading.Thread(target=httpd.serve_forever)
-    http_thread.daemon = True
-    http_thread.start()
-
-    print 'local port=%d' % httpd.server_address[1]
-    return 'http://127.0.0.1:%d/' % self._MapPort(port, httpd.server_address[1])
+    print 'local port=%d' % server_address[1]
+    return 'http://127.0.0.1:%d/' % self._MapPort(port, server_address[1])
 
   def _StartHttpServerForOriginMapping(self, mapping, port):
     """If |mapping| points at a local file starts an http server to serve files
diff --git a/pylib/http_server.py b/pylib/http_server.py
new file mode 100644
index 0000000..a7296d3
--- /dev/null
+++ b/pylib/http_server.py
@@ -0,0 +1,145 @@
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import atexit
+import datetime
+import email.utils
+import hashlib
+import logging
+import math
+import os.path
+import threading
+
+import SimpleHTTPServer
+import SocketServer
+
+
+ZERO = datetime.timedelta(0)
+
+
+class UTC_TZINFO(datetime.tzinfo):
+  """UTC time zone representation."""
+
+  def utcoffset(self, _):
+    return ZERO
+
+  def tzname(self, _):
+    return "UTC"
+
+  def dst(self, _):
+     return ZERO
+
+UTC = UTC_TZINFO()
+
+
+class _SilentTCPServer(SocketServer.TCPServer):
+  """
+  A TCPServer that won't display any error, unless debugging is enabled. This is
+  useful because the client might stop while it is fetching an URL, which causes
+  spurious error messages.
+  """
+  def handle_error(self, request, client_address):
+    """
+    Override the base class method to have conditional logging.
+    """
+    if logging.getLogger().isEnabledFor(logging.DEBUG):
+      SocketServer.TCPServer.handle_error(self, request, client_address)
+
+
+def _GetHandlerClassForPath(base_path):
+  class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
+    """
+    Handler for SocketServer.TCPServer that will serve the files from
+    |base_path| directory over http.
+    """
+
+    def __init__(self, *args, **kwargs):
+      self.etag = None
+      SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, *args, **kwargs)
+
+    def get_etag(self):
+      if self.etag:
+        return self.etag
+
+      path = self.translate_path(self.path)
+      if not os.path.isfile(path):
+        return None
+
+      sha256 = hashlib.sha256()
+      BLOCKSIZE = 65536
+      with open(path, 'rb') as hashed:
+        buf = hashed.read(BLOCKSIZE)
+        while len(buf) > 0:
+          sha256.update(buf)
+          buf = hashed.read(BLOCKSIZE)
+      self.etag = '"%s"' % sha256.hexdigest()
+      return self.etag
+
+    def send_head(self):
+      # Always close the connection after each request, as the keep alive
+      # support from SimpleHTTPServer doesn't like when the client requests to
+      # close the connection before downloading the full response content.
+      # pylint: disable=W0201
+      self.close_connection = 1
+
+      path = self.translate_path(self.path)
+      if os.path.isfile(path):
+        # Handle If-None-Match
+        etag = self.get_etag()
+        if ('If-None-Match' in self.headers and
+            etag == self.headers['If-None-Match']):
+          self.send_response(304)
+          return None
+
+        # Handle If-Modified-Since
+        if ('If-None-Match' not in self.headers and
+            'If-Modified-Since' in self.headers):
+          last_modified = datetime.datetime.fromtimestamp(
+              math.floor(os.stat(path).st_mtime), tz=UTC)
+          ims = datetime.datetime(
+              *email.utils.parsedate(self.headers['If-Modified-Since'])[:6],
+              tzinfo=UTC)
+          if last_modified <= ims:
+            self.send_response(304)
+            return None
+
+      return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
+
+    def end_headers(self):
+      path = self.translate_path(self.path)
+
+      if os.path.isfile(path):
+        etag = self.get_etag()
+        if etag:
+          self.send_header('ETag', etag)
+          self.send_header('Cache-Control', 'must-revalidate')
+
+      return SimpleHTTPServer.SimpleHTTPRequestHandler.end_headers(self)
+
+    def translate_path(self, path):
+      path_from_current = (
+          SimpleHTTPServer.SimpleHTTPRequestHandler.translate_path(self, path))
+      return os.path.join(base_path, os.path.relpath(path_from_current))
+
+    def log_message(self, *_):
+      """
+      Override the base class method to disable logging.
+      """
+      pass
+
+  RequestHandler.protocol_version = 'HTTP/1.1'
+  return RequestHandler
+
+
+def StartHttpServer(path):
+  """Starts an http server serving files from |path| on random
+  (system-allocated) port. Returns the server address."""
+  assert path
+  httpd = _SilentTCPServer(('127.0.0.1', 0), _GetHandlerClassForPath(path))
+  atexit.register(httpd.shutdown)
+
+  http_thread = threading.Thread(target=httpd.serve_forever)
+  http_thread.daemon = True
+  http_thread.start()
+  return httpd.server_address