146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)# Copyright 2012 The Chromium Authors. All rights reserved.
22a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
32a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# found in the LICENSE file.
44e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
52a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import BaseHTTPServer
603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)import errno
77dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdochimport gzip
82a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import mimetypes
92a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import os
102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import SimpleHTTPServer
1103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)import socket
122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import SocketServer
137dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdochimport StringIO
142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import sys
155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import urlparse
166e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)from collections import namedtuple
175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from telemetry.core import local_server
192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
20c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)ByteRange = namedtuple('ByteRange', ['from_byte', 'to_byte'])
21c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)ResourceAndRange = namedtuple('ResourceAndRange', ['resource', 'byte_range'])
22c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
23c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)class MemoryCacheHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  protocol_version = 'HTTP/1.1'  # override BaseHTTPServer setting
275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  wbufsize = -1  # override StreamRequestHandler (a base class) setting
285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  def handle(self):
3003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    try:
3103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      BaseHTTPServer.BaseHTTPRequestHandler.handle(self)
3203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    except socket.error, e:
3303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      # Connection reset errors happen all the time due to the browser closing
3403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      # without terminating the connection properly.  They can be safely
3503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      # ignored.
3603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      if e[0] != errno.ECONNRESET:
3703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        raise
3803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def do_GET(self):
402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Serve a GET request."""
41c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    resource_range = self.SendHead()
42c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
43c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    if not resource_range or not resource_range.resource:
44c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      return
45c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    response = resource_range.resource['response']
46c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
47c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    if not resource_range.byte_range:
48c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      self.wfile.write(response)
49c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      return
50c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
51c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    start_index = resource_range.byte_range.from_byte
52c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    end_index = resource_range.byte_range.to_byte
53c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    self.wfile.write(response[start_index:end_index + 1])
542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def do_HEAD(self):
562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Serve a HEAD request."""
572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.SendHead()
582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def log_error(self, fmt, *args):
605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    pass
615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def log_request(self, code='-', size='-'):
635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # Dont spam the console unless it is important.
645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    pass
655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def SendHead(self):
674e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    path = os.path.realpath(self.translate_path(self.path))
682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if path not in self.server.resource_map:
692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      self.send_error(404, 'File not found')
702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      return None
71c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    resource = self.server.resource_map[path]
73c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    total_num_of_bytes = resource['content-length']
74c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    byte_range = self.GetByteRange(total_num_of_bytes)
75c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    if byte_range:
76c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      # request specified a range, so set response code to 206.
77c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      self.send_response(206)
78c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      self.send_header('Content-Range',
79c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                       'bytes %d-%d/%d' % (byte_range.from_byte,
80c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                                           byte_range.to_byte,
81c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                                           total_num_of_bytes))
82c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      total_num_of_bytes = byte_range.to_byte - byte_range.from_byte + 1
83c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    else:
84c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      self.send_response(200)
85c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
86c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    self.send_header('Content-Length', str(total_num_of_bytes))
87c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    self.send_header('Content-Type', resource['content-type'])
882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.send_header('Last-Modified',
892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                     self.date_time_string(resource['last-modified']))
902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if resource['zipped']:
917dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      self.send_header('Content-Encoding', 'gzip')
922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.end_headers()
93c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    return ResourceAndRange(resource, byte_range)
94c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
95c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  def GetByteRange(self, total_num_of_bytes):
96c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    """Parse the header and get the range values specified.
97c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
98c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    Args:
99c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      total_num_of_bytes: Total # of bytes in requested resource,
100c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      used to calculate upper range limit.
101c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    Returns:
102c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      A ByteRange namedtuple object with the requested byte-range values.
103c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      If no Range is explicitly requested or there is a failure parsing,
104c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      return None.
1054e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      If range specified is in the format "N-", return N-END. Refer to
1064e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for details.
107c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      If upper range limit is greater than total # of bytes, return upper index.
108c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    """
109c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
110c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    range_header = self.headers.getheader('Range')
111c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    if range_header is None:
112c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      return None
113c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    if not range_header.startswith('bytes='):
114c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      return None
115c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
116c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # The range header is expected to be a string in this format:
117c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # bytes=0-1
118c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # Get the upper and lower limits of the specified byte-range.
119c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # We've already confirmed that range_header starts with 'bytes='.
120c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    byte_range_values = range_header[len('bytes='):].split('-')
121c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    from_byte = 0
122c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    to_byte = 0
123c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
124c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    if len(byte_range_values) == 2:
1254e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      # If to_range is not defined return all bytes starting from from_byte.
1264e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      to_byte = (int(byte_range_values[1]) if  byte_range_values[1]
1274e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)          else total_num_of_bytes - 1)
1284e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      # If from_range is not defined return last 'to_byte' bytes.
1294e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      from_byte = (int(byte_range_values[0]) if byte_range_values[0]
1304e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)          else total_num_of_bytes - to_byte)
131c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    else:
132c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      return None
133c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
134c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    # Do some validation.
135c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    if from_byte < 0:
136c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      return None
137c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
1384e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    # Make to_byte the end byte by default in edge cases.
1394e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    if to_byte < from_byte or to_byte >= total_num_of_bytes:
140c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      to_byte = total_num_of_bytes - 1
141c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
142c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    return ByteRange(from_byte, to_byte)
1432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class _MemoryCacheHTTPServerImpl(SocketServer.ThreadingMixIn,
1465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                 BaseHTTPServer.HTTPServer):
1472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  # Increase the request queue size. The default value, 5, is set in
1482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  # SocketServer.TCPServer (the parent of BaseHTTPServer.HTTPServer).
1492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  # Since we're intercepting many domains through this single server,
1502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  # it is quite possible to get more than 5 concurrent requests.
1512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  request_queue_size = 128
1522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
153c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  # Don't prevent python from exiting when there is thread activity.
154c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  daemon_threads = True
155c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch
156c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  def __init__(self, host_port, handler, paths):
1572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    BaseHTTPServer.HTTPServer.__init__(self, host_port, handler)
1582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self.resource_map = {}
159c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    for path in paths:
160c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      if os.path.isdir(path):
161c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        self.AddDirectoryToResourceMap(path)
162c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      else:
163c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        self.AddFileToResourceMap(path)
1642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1657d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  def AddDirectoryToResourceMap(self, directory_path):
1667d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    """Loads all files in directory_path into the in-memory resource map."""
1677d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    for root, dirs, files in os.walk(directory_path):
1682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      # Skip hidden files and folders (like .svn and .git).
1692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      files = [f for f in files if f[0] != '.']
1702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      dirs[:] = [d for d in dirs if d[0] != '.']
1712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      for f in files:
1732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        file_path = os.path.join(root, f)
1742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        if not os.path.exists(file_path):  # Allow for '.#' files
1752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)          continue
176c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        self.AddFileToResourceMap(file_path)
177c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
178c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  def AddFileToResourceMap(self, file_path):
179c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    """Loads file_path into the in-memory resource map."""
1804e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    file_path = os.path.realpath(file_path)
1814e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    if file_path in self.resource_map:
1824e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      return
1834e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
184c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    with open(file_path, 'rb') as fd:
185c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      response = fd.read()
186c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      fs = os.fstat(fd.fileno())
1874e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    content_type = mimetypes.guess_type(file_path)[0]
1884e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    zipped = False
1894e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    if content_type in ['text/html', 'text/css', 'application/javascript']:
1904e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      zipped = True
1914e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      sio = StringIO.StringIO()
1924e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      gzf = gzip.GzipFile(fileobj=sio, compresslevel=9, mode='wb')
1934e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      gzf.write(response)
1944e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      gzf.close()
1954e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      response = sio.getvalue()
1964e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      sio.close()
1974e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    self.resource_map[file_path] = {
1984e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        'content-type': content_type,
1994e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        'content-length': len(response),
2004e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        'last-modified': fs.st_mtime,
2014e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        'response': response,
2024e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        'zipped': zipped
2034e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        }
2044e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
2054e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    index = 'index.html'
2064e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    if os.path.basename(file_path) == index:
2078bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)      dir_path = os.path.dirname(file_path)
2088bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)      self.resource_map[dir_path] = self.resource_map[file_path]
2094e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
2104e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
2115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class MemoryCacheHTTPServerBackend(local_server.LocalServerBackend):
2125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def __init__(self):
2135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    super(MemoryCacheHTTPServerBackend, self).__init__()
2145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self._httpd = None
2155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def StartAndGetNamedPorts(self, args):
2175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    base_dir = args['base_dir']
2185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    os.chdir(base_dir)
2192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    paths = args['paths']
2215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    for path in paths:
2225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      if not os.path.realpath(path).startswith(os.path.realpath(os.getcwd())):
2235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        print >> sys.stderr, '"%s" is not under the cwd.' % path
2245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        sys.exit(1)
2255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    server_address = (args['host'], args['port'])
2275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    MemoryCacheHTTPRequestHandler.protocol_version = 'HTTP/1.1'
2285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self._httpd = _MemoryCacheHTTPServerImpl(
2295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        server_address, MemoryCacheHTTPRequestHandler, paths)
2305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return [local_server.NamedPort('http', self._httpd.server_address[1])]
2312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def ServeForever(self):
2335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return self._httpd.serve_forever()
2342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2354e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
2365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class MemoryCacheHTTPServer(local_server.LocalServer):
2375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def __init__(self, paths):
2385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    super(MemoryCacheHTTPServer, self).__init__(
2395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        MemoryCacheHTTPServerBackend)
2405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self._base_dir = None
241f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
2425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    for path in paths:
2435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      assert os.path.exists(path), '%s does not exist.' % path
2442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    paths = list(paths)
2465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self._paths = paths
2472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    self._paths_as_set = set(map(os.path.realpath, paths))
2495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    common_prefix = os.path.commonprefix(paths)
2515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if os.path.isdir(common_prefix):
2525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      self._base_dir = common_prefix
2535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    else:
2545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      self._base_dir = os.path.dirname(common_prefix)
2555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def GetBackendStartupArgs(self):
2575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return {'base_dir': self._base_dir,
2585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            'paths': self._paths,
2595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            'host': self.host_ip,
2605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            'port': 0}
2615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  @property
2635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def paths(self):
2645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return self._paths_as_set
2655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  @property
2675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def url(self):
2685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return self.forwarder.url
2695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  def UrlOf(self, path):
2715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    relative_path = os.path.relpath(path, self._base_dir)
2725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # Preserve trailing slash or backslash.
2735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # It doesn't matter in a file path, but it does matter in a URL.
2745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if path.endswith(os.sep) or (os.altsep and path.endswith(os.altsep)):
2755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      relative_path += '/'
2765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return urlparse.urljoin(self.url, relative_path.replace(os.sep, '/'))
277