15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2011 The Chromium Authors. All rights reserved. 25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be 35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file. 45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import BaseHTTPServer 65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import cgi 75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import mimetypes 85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os 95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os.path 105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import posixpath 115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import SimpleHTTPServer 125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import SocketServer 135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import threading 145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import time 155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import urllib 165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import urlparse 175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): 195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def NormalizePath(self, path): 215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) path = path.split('?', 1)[0] 225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) path = path.split('#', 1)[0] 235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) path = posixpath.normpath(urllib.unquote(path)) 245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) words = path.split('/') 255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) bad = set((os.curdir, os.pardir, '')) 275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) words = [word for word in words if word not in bad] 285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # The path of the request should always use POSIX-style path separators, so 295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # that the filename input of --map_file can be a POSIX-style path and still 305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # match correctly in translate_path(). 315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return '/'.join(words) 325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def translate_path(self, path): 345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) path = self.NormalizePath(path) 355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if path in self.server.file_mapping: 365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return self.server.file_mapping[path] 375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for extra_dir in self.server.serving_dirs: 385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # TODO(halyavin): set allowed paths in another parameter? 395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) full_path = os.path.join(extra_dir, os.path.basename(path)) 405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if os.path.isfile(full_path): 415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return full_path 427dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch 437dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch # Try the complete relative path, not just a basename. This allows the 447dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch # user to serve everything recursively under extra_dir, not just one 457dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch # level deep. 467dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch # 477dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch # One use case for this is the Native Client SDK examples. The examples 487dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch # expect to be able to access files as relative paths from the root of 497dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch # the example directory. 507dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch # Sometimes two subdirectories contain files with the same name, so 517dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch # including all subdirectories in self.server.serving_dirs will not do 527dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch # the correct thing; (i.e. the wrong file will be chosen, even though the 537dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch # correct path was given). 547dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch full_path = os.path.join(extra_dir, path) 557dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch if os.path.isfile(full_path): 567dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch return full_path 575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if not path.endswith('favicon.ico') and not self.server.allow_404: 585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.server.listener.ServerError('Cannot find file \'%s\'' % path) 595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return path 605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def guess_type(self, path): 625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # We store the extension -> MIME type mapping in the server instead of the 635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # request handler so we that can add additional mapping entries via the 645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # command line. 655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) base, ext = posixpath.splitext(path) 665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if ext in self.server.extensions_mapping: 675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return self.server.extensions_mapping[ext] 685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ext = ext.lower() 695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if ext in self.server.extensions_mapping: 705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return self.server.extensions_mapping[ext] 715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else: 725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return self.server.extensions_mapping[''] 735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def SendRPCResponse(self, response): 755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.send_response(200) 765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.send_header("Content-type", "text/plain") 775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.send_header("Content-length", str(len(response))) 785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.end_headers() 795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.wfile.write(response) 805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # shut down the connection 825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.wfile.flush() 835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.connection.shutdown(1) 845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def HandleRPC(self, name, query): 865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) kargs = {} 875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for k, v in query.iteritems(): 885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) assert len(v) == 1, k 895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) kargs[k] = v[0] 905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) l = self.server.listener 925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try: 935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) response = getattr(l, name)(**kargs) 945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) except Exception, e: 955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.SendRPCResponse('%r' % (e,)) 965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise 975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else: 985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.SendRPCResponse(response) 995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # For Last-Modified-based caching, the timestamp needs to be old enough 1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # for the browser cache to be used (at least 60 seconds). 1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html 1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Often we clobber and regenerate files for testing, so this is needed 1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # to actually use the browser cache. 1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def send_header(self, keyword, value): 1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if keyword == 'Last-Modified': 1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) last_mod_format = '%a, %d %b %Y %H:%M:%S GMT' 1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) old_value_as_t = time.strptime(value, last_mod_format) 1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) old_value_in_secs = time.mktime(old_value_as_t) 1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) new_value_in_secs = old_value_in_secs - 360 1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) value = time.strftime(last_mod_format, 1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) time.localtime(new_value_in_secs)) 1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) SimpleHTTPServer.SimpleHTTPRequestHandler.send_header(self, 1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) keyword, 1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) value) 1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def do_POST(self): 1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Backwards compatible - treat result as tuple without named fields. 1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) _, _, path, _, query, _ = urlparse.urlparse(self.path) 1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.server.listener.Log('POST %s (%s)' % (self.path, path)) 1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if path == '/echo': 1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.send_response(200) 1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.end_headers() 1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) data = self.rfile.read(int(self.headers.getheader('content-length'))) 1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.wfile.write(data) 127d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) elif self.server.output_dir is not None: 128d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) # Try to write the file to disk. 129d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) path = self.NormalizePath(path) 130d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) output_path = os.path.join(self.server.output_dir, path) 131d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) try: 132d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) outfile = open(output_path, 'w') 133d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) except IOError: 134d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) error_message = 'File not found: %r' % output_path 135d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) self.server.listener.ServerError(error_message) 136d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) self.send_error(404, error_message) 137d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) return 138d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 139d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) try: 140d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) data = self.rfile.read(int(self.headers.getheader('content-length'))) 141d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) outfile.write(data) 142d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) except IOError, e: 143d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) outfile.close() 144d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) try: 145d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) os.remove(output_path) 146d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) except OSError: 147d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) # Oh, well. 148d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) pass 149d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) error_message = 'Can\'t write file: %r\n' % output_path 150d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) error_message += 'Exception:\n%s' % str(e) 151d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) self.server.listener.ServerError(error_message) 152d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) self.send_error(500, error_message) 153d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) return 154d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 155d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) outfile.close() 156d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 157d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) # Send a success response. 158d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) self.send_response(200) 159d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) self.end_headers() 1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else: 161d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) error_message = 'File not found: %r' % path 162d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) self.server.listener.ServerError(error_message) 163d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) self.send_error(404, error_message) 1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.server.ResetTimeout() 1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def do_GET(self): 1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Backwards compatible - treat result as tuple without named fields. 1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) _, _, path, _, query, _ = urlparse.urlparse(self.path) 1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) tester = '/TESTER/' 1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if path.startswith(tester): 1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # If the path starts with '/TESTER/', the GET is an RPC call. 1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) name = path[len(tester):] 1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Supporting Python 2.5 prevents us from using urlparse.parse_qs 1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) query = cgi.parse_qs(query, True) 1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.server.rpc_lock.acquire() 1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try: 1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.HandleRPC(name, query) 1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) finally: 1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.server.rpc_lock.release() 1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Don't reset the timeout. This is not "part of the test", rather it's 1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # used to tell us if the renderer process is still alive. 1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if name == 'JavaScriptIsAlive': 1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.server.JavaScriptIsAlive() 1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return 1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) elif path in self.server.redirect_mapping: 1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) dest = self.server.redirect_mapping[path] 1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.send_response(301, 'Moved') 1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.send_header('Location', dest) 1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.end_headers() 1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.wfile.write(self.error_message_format % 1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) {'code': 301, 1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 'message': 'Moved', 1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 'explain': 'Object moved permanently'}) 1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.server.listener.Log('REDIRECT %s (%s -> %s)' % 2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) (self.path, path, dest)) 2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else: 2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.server.listener.Log('GET %s (%s)' % (self.path, path)) 2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # A normal GET request for transferring files, etc. 2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) f = self.send_head() 2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if f: 2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.copyfile(f, self.wfile) 2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) f.close() 2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.server.ResetTimeout() 2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def copyfile(self, source, outputfile): 2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Bandwidth values <= 0.0 are considered infinite 2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if self.server.bandwidth <= 0.0: 2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return SimpleHTTPServer.SimpleHTTPRequestHandler.copyfile( 2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self, source, outputfile) 2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.server.listener.Log('Simulating %f mbps server BW' % 2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.server.bandwidth) 2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) chunk_size = 1500 # What size to use? 2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) bits_per_sec = self.server.bandwidth * 1000000 2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) start_time = time.time() 2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) data_sent = 0 2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) while True: 2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) chunk = source.read(chunk_size) 2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if len(chunk) == 0: 2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) break 2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) cur_elapsed = time.time() - start_time 2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) target_elapsed = (data_sent + len(chunk)) * 8 / bits_per_sec 2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (cur_elapsed < target_elapsed): 2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) time.sleep(target_elapsed - cur_elapsed) 2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) outputfile.write(chunk) 2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) data_sent += len(chunk) 2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.server.listener.Log('Streamed %d bytes in %f s' % 2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) (data_sent, time.time() - start_time)) 2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Disable the built-in logging 2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def log_message(self, format, *args): 2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) pass 2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# The ThreadingMixIn allows the server to handle multiple requests 2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# concurently (or at least as concurently as Python allows). This is desirable 2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# because server sockets only allow a limited "backlog" of pending connections 2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# and in the worst case the browser could make multiple connections and exceed 2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# this backlog - causing the server to drop requests. Using ThreadingMixIn 2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# helps reduce the chance this will happen. 2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# There were apparently some problems using this Mixin with Python 2.5, but we 2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# are no longer using anything older than 2.6. 2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Server(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): 2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def Configure( 252d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) self, file_mapping, redirect_mapping, extensions_mapping, allow_404, 253d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) bandwidth, listener, serving_dirs=[], output_dir=None): 2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.file_mapping = file_mapping 2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.redirect_mapping = redirect_mapping 2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.extensions_mapping.update(extensions_mapping) 2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.allow_404 = allow_404 2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.bandwidth = bandwidth 2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.listener = listener 2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.rpc_lock = threading.Lock() 2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.serving_dirs = serving_dirs 262d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) self.output_dir = output_dir 2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def TestingBegun(self, timeout): 2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.test_in_progress = True 2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # self.timeout does not affect Python 2.5. 2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.timeout = timeout 2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.ResetTimeout() 2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.JavaScriptIsAlive() 2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Have we seen any requests from the browser? 2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.received_request = False 2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def ResetTimeout(self): 2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.last_activity = time.time() 2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.received_request = True 2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def JavaScriptIsAlive(self): 2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.last_js_activity = time.time() 2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def TimeSinceJSHeartbeat(self): 2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return time.time() - self.last_js_activity 2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def TestingEnded(self): 2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.test_in_progress = False 2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def TimedOut(self, total_time): 2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return (total_time >= 0.0 and 2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) (time.time() - self.last_activity) >= total_time) 2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def Create(host, port): 2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) server = Server((host, port), RequestHandler) 2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) server.extensions_mapping = mimetypes.types_map.copy() 2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) server.extensions_mapping.update({ 2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) '': 'application/octet-stream' # Default 2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }) 2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return server 298