133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck# Copyright 2012 The Chromium Authors. All rights reserved.
233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck# Use of this source code is governed by a BSD-style license that can be
333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck# found in the LICENSE file.
433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport BaseHTTPServer
633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckfrom collections import namedtuple
733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport errno
833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport gzip
933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport mimetypes
1033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport os
1133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport SimpleHTTPServer
1233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport socket
1333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport SocketServer
1433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport StringIO
1533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport sys
1633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport urlparse
1733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
1833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckfrom telemetry.core import local_server
1933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
2033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John ReckByteRange = namedtuple('ByteRange', ['from_byte', 'to_byte'])
2133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John ReckResourceAndRange = namedtuple('ResourceAndRange', ['resource', 'byte_range'])
2233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
2333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
2433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckclass MemoryCacheHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
2533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
2633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  protocol_version = 'HTTP/1.1'  # override BaseHTTPServer setting
2733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  wbufsize = -1  # override StreamRequestHandler (a base class) setting
2833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
2933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def handle(self):
3033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    try:
3133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      BaseHTTPServer.BaseHTTPRequestHandler.handle(self)
3233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    except socket.error as e:
3333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      # Connection reset errors happen all the time due to the browser closing
3433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      # without terminating the connection properly.  They can be safely
3533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      # ignored.
3633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      if e[0] != errno.ECONNRESET:
3733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        raise
3833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
3933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def do_GET(self):
4033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    """Serve a GET request."""
4133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    resource_range = self.SendHead()
4233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
4333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if not resource_range or not resource_range.resource:
4433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      return
4533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    response = resource_range.resource['response']
4633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
4733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if not resource_range.byte_range:
4833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      self.wfile.write(response)
4933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      return
5033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
5133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    start_index = resource_range.byte_range.from_byte
5233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    end_index = resource_range.byte_range.to_byte
5333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self.wfile.write(response[start_index:end_index + 1])
5433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
5533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def do_HEAD(self):
5633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    """Serve a HEAD request."""
5733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self.SendHead()
5833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
5933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def log_error(self, fmt, *args):
6033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    pass
6133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
6233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def log_request(self, code='-', size='-'):
6333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    # Don't spam the console unless it is important.
6433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    pass
6533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
6633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def SendHead(self):
6733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    path = os.path.realpath(self.translate_path(self.path))
6833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if path not in self.server.resource_map:
6933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      self.send_error(404, 'File not found')
7033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      return None
7133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
7233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    resource = self.server.resource_map[path]
7333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    total_num_of_bytes = resource['content-length']
7433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    byte_range = self.GetByteRange(total_num_of_bytes)
7533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if byte_range:
7633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      # request specified a range, so set response code to 206.
7733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      self.send_response(206)
7833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      self.send_header('Content-Range', 'bytes %d-%d/%d' %
7933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck                       (byte_range.from_byte, byte_range.to_byte,
8033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck                        total_num_of_bytes))
8133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      total_num_of_bytes = byte_range.to_byte - byte_range.from_byte + 1
8233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    else:
8333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      self.send_response(200)
8433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
8533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self.send_header('Content-Length', str(total_num_of_bytes))
8633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self.send_header('Content-Type', resource['content-type'])
8733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self.send_header('Last-Modified',
8833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck                     self.date_time_string(resource['last-modified']))
8933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if resource['zipped']:
9033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      self.send_header('Content-Encoding', 'gzip')
9133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self.end_headers()
9233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return ResourceAndRange(resource, byte_range)
9333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
9433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def GetByteRange(self, total_num_of_bytes):
9533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    """Parse the header and get the range values specified.
9633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
9733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    Args:
9833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      total_num_of_bytes: Total # of bytes in requested resource,
9933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      used to calculate upper range limit.
10033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    Returns:
10133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      A ByteRange namedtuple object with the requested byte-range values.
10233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      If no Range is explicitly requested or there is a failure parsing,
10333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      return None.
10433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      If range specified is in the format "N-", return N-END. Refer to
10533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for details.
10633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      If upper range limit is greater than total # of bytes, return upper index.
10733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    """
10833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
10933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    range_header = self.headers.getheader('Range')
11033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if range_header is None:
11133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      return None
11233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if not range_header.startswith('bytes='):
11333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      return None
11433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
11533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    # The range header is expected to be a string in this format:
11633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    # bytes=0-1
11733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    # Get the upper and lower limits of the specified byte-range.
11833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    # We've already confirmed that range_header starts with 'bytes='.
11933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    byte_range_values = range_header[len('bytes='):].split('-')
12033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    from_byte = 0
12133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    to_byte = 0
12233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
12333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if len(byte_range_values) == 2:
12433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      # If to_range is not defined return all bytes starting from from_byte.
12533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      to_byte = (int(byte_range_values[1]) if byte_range_values[1] else
12633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck                 total_num_of_bytes - 1)
12733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      # If from_range is not defined return last 'to_byte' bytes.
12833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      from_byte = (int(byte_range_values[0]) if byte_range_values[0] else
12933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck                   total_num_of_bytes - to_byte)
13033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    else:
13133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      return None
13233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
13333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    # Do some validation.
13433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if from_byte < 0:
13533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      return None
13633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
13733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    # Make to_byte the end byte by default in edge cases.
13833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if to_byte < from_byte or to_byte >= total_num_of_bytes:
13933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      to_byte = total_num_of_bytes - 1
14033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
14133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return ByteRange(from_byte, to_byte)
14233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
14333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
14433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckclass _MemoryCacheHTTPServerImpl(SocketServer.ThreadingMixIn,
14533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck                                 BaseHTTPServer.HTTPServer):
14633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  # Increase the request queue size. The default value, 5, is set in
14733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  # SocketServer.TCPServer (the parent of BaseHTTPServer.HTTPServer).
14833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  # Since we're intercepting many domains through this single server,
14933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  # it is quite possible to get more than 5 concurrent requests.
15033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  request_queue_size = 128
15133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
15233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  # Don't prevent python from exiting when there is thread activity.
15333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  daemon_threads = True
15433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
15533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def __init__(self, host_port, handler, paths):
15633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    BaseHTTPServer.HTTPServer.__init__(self, host_port, handler)
15733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self.resource_map = {}
15833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    for path in paths:
15933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      if os.path.isdir(path):
16033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        self.AddDirectoryToResourceMap(path)
16133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      else:
16233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        self.AddFileToResourceMap(path)
16333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
16433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def AddDirectoryToResourceMap(self, directory_path):
16533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    """Loads all files in directory_path into the in-memory resource map."""
16633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    for root, dirs, files in os.walk(directory_path):
16733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      # Skip hidden files and folders (like .svn and .git).
16833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      files = [f for f in files if f[0] != '.']
16933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      dirs[:] = [d for d in dirs if d[0] != '.']
17033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
17133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      for f in files:
17233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        file_path = os.path.join(root, f)
17333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        if not os.path.exists(file_path):  # Allow for '.#' files
17433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck          continue
17533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        self.AddFileToResourceMap(file_path)
17633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
17733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def AddFileToResourceMap(self, file_path):
17833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    """Loads file_path into the in-memory resource map."""
17933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    file_path = os.path.realpath(file_path)
18033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if file_path in self.resource_map:
18133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      return
18233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
18333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    with open(file_path, 'rb') as fd:
18433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      response = fd.read()
18533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      fs = os.fstat(fd.fileno())
18633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    content_type = mimetypes.guess_type(file_path)[0]
18733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    zipped = False
18833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if content_type in ['text/html', 'text/css', 'application/javascript']:
18933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      zipped = True
19033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      sio = StringIO.StringIO()
19133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      gzf = gzip.GzipFile(fileobj=sio, compresslevel=9, mode='wb')
19233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      gzf.write(response)
19333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      gzf.close()
19433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      response = sio.getvalue()
19533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      sio.close()
19633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self.resource_map[file_path] = {
19733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        'content-type': content_type,
19833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        'content-length': len(response),
19933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        'last-modified': fs.st_mtime,
20033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        'response': response,
20133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        'zipped': zipped
20233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    }
20333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
20433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    index = 'index.html'
20533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if os.path.basename(file_path) == index:
20633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      dir_path = os.path.dirname(file_path)
20733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      self.resource_map[dir_path] = self.resource_map[file_path]
20833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
20933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
21033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckclass MemoryCacheHTTPServerBackend(local_server.LocalServerBackend):
21133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
21233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def __init__(self):
21333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    super(MemoryCacheHTTPServerBackend, self).__init__()
21433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._httpd = None
21533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
21633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def StartAndGetNamedPorts(self, args):
21733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    base_dir = args['base_dir']
21833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    os.chdir(base_dir)
21933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
22033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    paths = args['paths']
22133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    for path in paths:
22233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      if not os.path.realpath(path).startswith(os.path.realpath(os.getcwd())):
22333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        print >> sys.stderr, '"%s" is not under the cwd.' % path
22433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        sys.exit(1)
22533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
22633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    server_address = (args['host'], args['port'])
22733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    MemoryCacheHTTPRequestHandler.protocol_version = 'HTTP/1.1'
22833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._httpd = _MemoryCacheHTTPServerImpl(
22933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        server_address, MemoryCacheHTTPRequestHandler, paths)
23033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return [local_server.NamedPort('http', self._httpd.server_address[1])]
23133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
23233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def ServeForever(self):
23333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return self._httpd.serve_forever()
23433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
23533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
23633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckclass MemoryCacheHTTPServer(local_server.LocalServer):
23733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
23833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def __init__(self, paths):
23933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    super(MemoryCacheHTTPServer, self).__init__(MemoryCacheHTTPServerBackend)
24033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._base_dir = None
24133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
24233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    for path in paths:
24333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      assert os.path.exists(path), '%s does not exist.' % path
24433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
24533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    paths = list(paths)
24633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._paths = paths
24733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
24833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._paths_as_set = set(map(os.path.realpath, paths))
24933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
25033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    common_prefix = os.path.commonprefix(paths)
25133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if os.path.isdir(common_prefix):
25233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      self._base_dir = common_prefix
25333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    else:
25433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      self._base_dir = os.path.dirname(common_prefix)
25533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
25633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def GetBackendStartupArgs(self):
25733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return {'base_dir': self._base_dir,
25833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck            'paths': self._paths,
25933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck            'host': self.host_ip,
26033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck            'port': 0}
26133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
26233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  @property
26333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def paths(self):
26433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return self._paths_as_set
26533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
26633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  @property
26733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def url(self):
26833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return 'http://127.0.0.1:%s' % self.port
26933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
27033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def UrlOf(self, path):
27133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if os.path.isabs(path):
27233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      relative_path = os.path.relpath(path, self._base_dir)
27333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    else:
27433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      relative_path = path
27533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    # Preserve trailing slash or backslash.
27633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    # It doesn't matter in a file path, but it does matter in a URL.
27733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if path.endswith(os.sep) or (os.altsep and path.endswith(os.altsep)):
27833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      relative_path += '/'
27933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return urlparse.urljoin(self.url, relative_path.replace(os.sep, '/'))
280