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