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