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