Bug 1230862 - Remove mozhttpd. r=wlach
☠☠ backed out by a85a19451da8 ☠ ☠
authorJulien Pagès <j.parkouss@gmail.com>
Tue, 09 Feb 2016 03:26:43 +0100
changeset 283784 200da85932e9d4ce3cf44ede8802ba2478a95b7d
parent 283783 4a38b481c11147a6d78361d2734d4e623d781185
child 283785 8bbaec3ed0f0bef19cbd9370f1fac92ece43ff18
push id29991
push usercbook@mozilla.com
push dateThu, 11 Feb 2016 10:52:20 +0000
treeherdermozilla-central@d4d72e7b30da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerswlach
bugs1230862
milestone47.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1230862 - Remove mozhttpd. r=wlach
build/mach_bootstrap.py
build/pgo/profileserver.py
build/valgrind/mach_commands.py
dom/media/test/external/requirements.txt
testing/config/mozbase_requirements.txt
testing/mozbase/docs/index.rst
testing/mozbase/docs/mozhttpd.rst
testing/mozbase/moz.build
testing/mozbase/mozcrash/tests/test.py
testing/mozbase/mozfile/setup.py
testing/mozbase/mozfile/tests/test_load.py
testing/mozbase/mozhttpd/mozhttpd/__init__.py
testing/mozbase/mozhttpd/mozhttpd/handlers.py
testing/mozbase/mozhttpd/mozhttpd/mozhttpd.py
testing/mozbase/mozhttpd/setup.py
testing/mozbase/mozhttpd/tests/api.py
testing/mozbase/mozhttpd/tests/baseurl.py
testing/mozbase/mozhttpd/tests/basic.py
testing/mozbase/mozhttpd/tests/filelisting.py
testing/mozbase/mozhttpd/tests/manifest.ini
testing/mozbase/mozhttpd/tests/paths.py
testing/mozbase/mozhttpd/tests/requestlog.py
testing/mozbase/mozprofile/setup.py
testing/mozbase/mozprofile/tests/test_addons.py
testing/mozbase/mozprofile/tests/test_preferences.py
testing/mozbase/packages.txt
testing/mozbase/test-manifest.ini
testing/mozharness/mozharness/mozilla/mozbase.py
testing/mozharness/scripts/marionette.py
testing/mozharness/test/pip-freeze.example.txt
testing/mozharness/test/test_base_python.py
testing/talos/requirements.txt
testing/talos/talos/run_tests.py
testing/tools/mach_test_package_bootstrap.py
testing/tps/setup.py
testing/tps/tps/testrunner.py
tools/docs/mach_commands.py
--- a/build/mach_bootstrap.py
+++ b/build/mach_bootstrap.py
@@ -75,17 +75,16 @@ SEARCH_PATHS = [
     'testing/luciddream',
     'testing/marionette/client',
     'testing/marionette/client/marionette/runner/mixins/browsermob-proxy-py',
     'testing/marionette/driver',
     'testing/mozbase/mozcrash',
     'testing/mozbase/mozdebug',
     'testing/mozbase/mozdevice',
     'testing/mozbase/mozfile',
-    'testing/mozbase/mozhttpd',
     'testing/mozbase/mozinfo',
     'testing/mozbase/mozinstall',
     'testing/mozbase/mozleak',
     'testing/mozbase/mozlog',
     'testing/mozbase/moznetwork',
     'testing/mozbase/mozprocess',
     'testing/mozbase/mozprofile',
     'testing/mozbase/mozrunner',
--- a/build/pgo/profileserver.py
+++ b/build/pgo/profileserver.py
@@ -2,17 +2,17 @@
 #
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from mozprofile import FirefoxProfile, Profile, Preferences
 from mozprofile.permissions import ServerLocations
 from mozrunner import FirefoxRunner, CLI
-from mozhttpd import MozHttpd
+from wptserve.server import WebTestHttpd
 import json
 import socket
 import threading
 import os
 import sys
 import shutil
 import tempfile
 from datetime import datetime
@@ -21,18 +21,18 @@ from buildconfig import substs
 
 PORT = 8888
 
 if __name__ == '__main__':
   cli = CLI()
   debug_args, interactive = cli.debugger_arguments()
 
   build = MozbuildObject.from_environment()
-  httpd = MozHttpd(port=PORT,
-                   docroot=os.path.join(build.topsrcdir, "build", "pgo"))
+  httpd = WebTestHttpd(port=PORT,
+                       doc_root=os.path.join(build.topsrcdir, "build", "pgo"))
   httpd.start(block=False)
 
   locations = ServerLocations()
   locations.add_host(host='127.0.0.1',
                      port=PORT,
                      options='primary,privileged')
 
   #TODO: mozfile.TemporaryDirectory
--- a/build/valgrind/mach_commands.py
+++ b/build/valgrind/mach_commands.py
@@ -42,28 +42,28 @@ class MachCommands(MachCommandBase):
             'files.')
     def valgrind_test(self, suppressions):
         import json
         import sys
         import tempfile
 
         from mozbuild.base import MozbuildObject
         from mozfile import TemporaryDirectory
-        from mozhttpd import MozHttpd
+        from wptserve.server import WebTestHttpd
         from mozprofile import FirefoxProfile, Preferences
         from mozprofile.permissions import ServerLocations
         from mozrunner import FirefoxRunner
         from mozrunner.utils import findInPath
         from valgrind.output_handler import OutputHandler
 
         build_dir = os.path.join(self.topsrcdir, 'build')
 
         # XXX: currently we just use the PGO inputs for Valgrind runs.  This may
         # change in the future.
-        httpd = MozHttpd(docroot=os.path.join(build_dir, 'pgo'))
+        httpd = WebTestHttpd(doc_root=os.path.join(build_dir, 'pgo'))
         httpd.start(block=False)
 
         with TemporaryDirectory() as profilePath:
             #TODO: refactor this into mozprofile
             prefpath = os.path.join(self.topsrcdir, 'testing', 'profiles', 'prefs_general.js')
             prefs = {}
             prefs.update(Preferences.read_prefs(prefpath))
             interpolation = { 'server': '%s:%d' % httpd.httpd.server_address,
--- a/dom/media/test/external/requirements.txt
+++ b/dom/media/test/external/requirements.txt
@@ -1,14 +1,13 @@
 browsermob-proxy==0.7.1
 manifestparser==1.1
 mozcrash==0.16
 mozdevice==0.48
 mozfile==1.2
-mozhttpd==0.7
 mozinfo==0.9
 # optional - mozharness install step
 mozInstall==1.12
 mozlog==3.1
 moznetwork==0.27
 mozprocess==0.22
 mozprofile==0.28
 mozrunner==6.11
--- a/testing/config/mozbase_requirements.txt
+++ b/testing/config/mozbase_requirements.txt
@@ -1,14 +1,13 @@
 ../mozbase/manifestparser
 ../mozbase/mozcrash
 ../mozbase/mozdebug
 ../mozbase/mozdevice
 ../mozbase/mozfile
-../mozbase/mozhttpd
 ../mozbase/mozinfo
 ../mozbase/mozinstall
 ../mozbase/mozleak
 ../mozbase/mozlog
 ../mozbase/moznetwork
 ../mozbase/mozprocess
 ../mozbase/mozprofile
 ../mozbase/mozrunner
--- a/testing/mozbase/docs/index.rst
+++ b/testing/mozbase/docs/index.rst
@@ -39,17 +39,16 @@ The documentation is organized by catego
 want to do then dive in!
 
 .. toctree::
    :maxdepth: 2
 
    manifestparser
    gettinginfo
    setuprunning
-   mozhttpd
    loggingreporting
    devicemanagement
 
 Indices and tables
 ==================
 
 * :ref:`genindex`
 * :ref:`modindex`
deleted file mode 100644
--- a/testing/mozbase/docs/mozhttpd.rst
+++ /dev/null
@@ -1,22 +0,0 @@
-
-Serving up content to be consumed by the browser
-================================================
-
-
-.. warning:: The mozhttpd module is considered obsolete. For new code,
-             please use wptserve_ which can do everything mozhttpd does
-             and more.
-
-.. _wptserve: https://pypi.python.org/pypi/wptserve
-
-:mod:`mozhttpd` --- Simple webserver
-------------------------------------
-
-.. automodule:: mozhttpd
-   :members:
-
-Interface
-`````````
-
-.. autoclass:: MozHttpd
-   :members:
--- a/testing/mozbase/moz.build
+++ b/testing/mozbase/moz.build
@@ -9,17 +9,16 @@ PYTHON_UNIT_TESTS += [
 ]
 
 python_modules = [
     'manifestparser',
     'mozcrash',
     'mozdebug',
     'mozdevice',
     'mozfile',
-    'mozhttpd',
     'mozinfo',
     'mozinstall',
     'mozleak',
     'mozlog',
     'moznetwork',
     'mozprocess',
     'mozprofile',
     'mozrunner',
--- a/testing/mozbase/mozcrash/tests/test.py
+++ b/testing/mozbase/mozcrash/tests/test.py
@@ -1,17 +1,18 @@
 #!/usr/bin/env python
 #
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import os, unittest, subprocess, tempfile, shutil, urlparse, zipfile, StringIO
 import mozcrash
-import mozhttpd
+from wptserve.server import WebTestHttpd
+from wptserve import handlers
 import mozlog.unstructured as mozlog
 
 # Make logs go away
 log = mozlog.getLogger("mozcrash", handler=mozlog.FileHandler(os.devnull))
 
 def popen_factory(stdouts):
     """
     Generate a class that can mock subprocess.Popen. |stdouts| is an iterable that
@@ -160,21 +161,22 @@ class TestCrash(unittest.TestCase):
         self.stdouts.append(["this is some output"])
 
         def make_zipfile():
             data = StringIO.StringIO()
             z = zipfile.ZipFile(data, 'w')
             z.writestr("symbols.txt", "abc/xyz")
             z.close()
             return data.getvalue()
-        def get_symbols(req):
-            headers = {}
-            return (200, headers, make_zipfile())
-        httpd = mozhttpd.MozHttpd(port=0,
-                                  urlhandlers=[{'method':'GET', 'path':'/symbols', 'function':get_symbols}])
+
+        @handlers.handler
+        def get_symbols(request, response):
+            return (200, [], [make_zipfile()])
+        httpd = WebTestHttpd(port=0,
+                             routes=[("GET", "/symbols", get_symbols)])
         httpd.start()
         symbol_url = urlparse.urlunsplit(('http', '%s:%d' % httpd.httpd.server_address,
                                         '/symbols','',''))
         self.assert_(mozcrash.check_for_crashes(self.tempdir,
                                                 symbol_url,
                                                 stackwalk_binary=self.stackwalk,
                                                 quiet=True))
 
--- a/testing/mozbase/mozfile/setup.py
+++ b/testing/mozbase/mozfile/setup.py
@@ -16,10 +16,10 @@ setup(name=PACKAGE_NAME,
       author='Mozilla Automation and Tools team',
       author_email='tools@lists.mozilla.org',
       url='https://wiki.mozilla.org/Auto-tools/Projects/Mozbase',
       license='MPL',
       packages=['mozfile'],
       include_package_data=True,
       zip_safe=False,
       install_requires=[],
-      tests_require=['mozhttpd']
+      tests_require=['wptserve']
       )
--- a/testing/mozbase/mozfile/tests/test_load.py
+++ b/testing/mozbase/mozfile/tests/test_load.py
@@ -1,39 +1,39 @@
 #!/usr/bin/env python
 
 """
 tests for mozfile.load
 """
 
-import mozhttpd
+from wptserve import server, handlers
 import os
 import tempfile
 import unittest
 from mozfile import load
 
 
 class TestLoad(unittest.TestCase):
     """test the load function"""
 
     def test_http(self):
-        """test with mozhttpd and a http:// URL"""
+        """test with wptserve and a http:// URL"""
 
-        def example(request):
+        @handlers.handler
+        def example(request, reponse):
             """example request handler"""
             body = 'example'
-            return (200, {'Content-type': 'text/plain',
-                          'Content-length': len(body)
-                          }, body)
+            return (200, [{'Content-type': 'text/plain',
+                           'Content-length': len(body)
+                          }], [body])
 
         host = '127.0.0.1'
-        httpd = mozhttpd.MozHttpd(host=host,
-                                  urlhandlers=[{'method': 'GET',
-                                                'path': '.*',
-                                                'function': example}])
+        httpd = server.WebTestHttpd(
+            host=host,
+            routes=[('GET', "*", example)])
         try:
             httpd.start(block=False)
             content = load(httpd.get_url()).read()
             self.assertEqual(content, 'example')
         finally:
             httpd.stop()
 
     def test_file_path(self):
deleted file mode 100644
--- a/testing/mozbase/mozhttpd/mozhttpd/__init__.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this file,
-# You can obtain one at http://mozilla.org/MPL/2.0/.
-
-"""
-Mozhttpd is a simple http webserver written in python, designed expressly
-for use in automated testing scenarios. It is designed to both serve static
-content and provide simple web services.
-
-The server is based on python standard library modules such as
-SimpleHttpServer, urlparse, etc. The ThreadingMixIn is used to
-serve each request on a discrete thread.
-
-Some existing uses of mozhttpd include Peptest_, Eideticker_, and Talos_.
-
-.. _Peptest: https://github.com/mozilla/peptest/
-
-.. _Eideticker: https://github.com/mozilla/eideticker/
-
-.. _Talos: http://hg.mozilla.org/build/
-
-The following simple example creates a basic HTTP server which serves
-content from the current directory, defines a single API endpoint
-`/api/resource/<resourceid>` and then serves requests indefinitely:
-
-::
-
-  import mozhttpd
-
-  @mozhttpd.handlers.json_response
-  def resource_get(request, objid):
-      return (200, { 'id': objid,
-                     'query': request.query })
-
-
-  httpd = mozhttpd.MozHttpd(port=8080, docroot='.',
-                            urlhandlers = [ { 'method': 'GET',
-                                              'path': '/api/resources/([^/]+)/?',
-                                              'function': resource_get } ])
-  print "Serving '%s' at %s:%s" % (httpd.docroot, httpd.host, httpd.port)
-  httpd.start(block=True)
-
-"""
-
-from mozhttpd import MozHttpd, Request, RequestHandler, main
-from handlers import json_response
deleted file mode 100644
--- a/testing/mozbase/mozhttpd/mozhttpd/handlers.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this file,
-# You can obtain one at http://mozilla.org/MPL/2.0/.
-
-import json
-
-def json_response(func):
-    """ Translates results of 'func' into a JSON response. """
-    def wrap(*a, **kw):
-        (code, data) = func(*a, **kw)
-        json_data = json.dumps(data)
-        return (code, { 'Content-type': 'application/json',
-                        'Content-Length': len(json_data) }, json_data)
-
-    return wrap
deleted file mode 100755
--- a/testing/mozbase/mozhttpd/mozhttpd/mozhttpd.py
+++ /dev/null
@@ -1,329 +0,0 @@
-#!/usr/bin/env python
-
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this file,
-# You can obtain one at http://mozilla.org/MPL/2.0/.
-
-import BaseHTTPServer
-import SimpleHTTPServer
-import errno
-import logging
-import threading
-import posixpath
-import socket
-import sys
-import os
-import urllib
-import urlparse
-import re
-import moznetwork
-import time
-from SocketServer import ThreadingMixIn
-
-class EasyServer(ThreadingMixIn, BaseHTTPServer.HTTPServer):
-    allow_reuse_address = True
-    acceptable_errors = (errno.EPIPE, errno.ECONNABORTED)
-
-    def handle_error(self, request, client_address):
-        error = sys.exc_value
-
-        if ((isinstance(error, socket.error) and
-             isinstance(error.args, tuple) and
-             error.args[0] in self.acceptable_errors)
-            or
-            (isinstance(error, IOError) and
-             error.errno in self.acceptable_errors)):
-            pass  # remote hang up before the result is sent
-        else:
-            logging.error(error)
-
-
-class Request(object):
-    """Details of a request."""
-
-    # attributes from urlsplit that this class also sets
-    uri_attrs = ('scheme', 'netloc', 'path', 'query', 'fragment')
-
-    def __init__(self, uri, headers, rfile=None):
-        self.uri = uri
-        self.headers = headers
-        parsed = urlparse.urlsplit(uri)
-        for i, attr in enumerate(self.uri_attrs):
-            setattr(self, attr, parsed[i])
-        try:
-            body_len = int(self.headers.get('Content-length', 0))
-        except ValueError:
-            body_len = 0
-        if body_len and rfile:
-            self.body = rfile.read(body_len)
-        else:
-            self.body = None
-
-
-class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
-
-    docroot = os.getcwd() # current working directory at time of import
-    proxy_host_dirs = False
-    request_log = []
-    log_requests = False
-    request = None
-
-    def __init__(self, *args, **kwargs):
-        SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, *args, **kwargs)
-        self.extensions_map['.svg'] = 'image/svg+xml'
-
-    def _try_handler(self, method):
-        if self.log_requests:
-            self.request_log.append({ 'method': method,
-                                      'path': self.request.path,
-                                      'time': time.time() })
-
-        handlers = [handler for handler in self.urlhandlers
-                    if handler['method'] == method]
-        for handler in handlers:
-            m = re.match(handler['path'], self.request.path)
-            if m:
-                (response_code, headerdict, data) = \
-                    handler['function'](self.request, *m.groups())
-                self.send_response(response_code)
-                for (keyword, value) in headerdict.iteritems():
-                    self.send_header(keyword, value)
-                self.end_headers()
-                self.wfile.write(data)
-
-                return True
-
-        return False
-
-    def _find_path(self):
-        """Find the on-disk path to serve this request from,
-        using self.path_mappings and self.docroot.
-        Return (url_path, disk_path)."""
-        path_components = filter(None, self.request.path.split('/'))
-        for prefix, disk_path in self.path_mappings.iteritems():
-            prefix_components = filter(None, prefix.split('/'))
-            if len(path_components) < len(prefix_components):
-                continue
-            if path_components[:len(prefix_components)] == prefix_components:
-                return ('/'.join(path_components[len(prefix_components):]),
-                        disk_path)
-        if self.docroot:
-            return self.request.path, self.docroot
-        return None
-
-    def parse_request(self):
-        retval = SimpleHTTPServer.SimpleHTTPRequestHandler.parse_request(self)
-        self.request = Request(self.path, self.headers, self.rfile)
-        return retval
-
-    def do_GET(self):
-        if not self._try_handler('GET'):
-            res = self._find_path()
-            if res:
-                self.path, self.disk_root = res
-                # don't include query string and fragment, and prepend
-                # host directory if required.
-                if self.request.netloc and self.proxy_host_dirs:
-                    self.path = '/' + self.request.netloc + \
-                        self.path
-                SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
-            else:
-                self.send_response(404)
-                self.end_headers()
-                self.wfile.write('')
-
-    def do_POST(self):
-        # if we don't have a match, we always fall through to 404 (this may
-        # not be "technically" correct if we have a local file at the same
-        # path as the resource but... meh)
-        if not self._try_handler('POST'):
-            self.send_response(404)
-            self.end_headers()
-            self.wfile.write('')
-
-    def do_DEL(self):
-        # if we don't have a match, we always fall through to 404 (this may
-        # not be "technically" correct if we have a local file at the same
-        # path as the resource but... meh)
-        if not self._try_handler('DEL'):
-            self.send_response(404)
-            self.end_headers()
-            self.wfile.write('')
-
-    def translate_path(self, path):
-        # this is taken from SimpleHTTPRequestHandler.translate_path(),
-        # except we serve from self.docroot instead of os.getcwd(), and
-        # parse_request()/do_GET() have already stripped the query string and
-        # fragment and mangled the path for proxying, if required.
-        path = posixpath.normpath(urllib.unquote(self.path))
-        words = path.split('/')
-        words = filter(None, words)
-        path = self.disk_root
-        for word in words:
-            drive, word = os.path.splitdrive(word)
-            head, word = os.path.split(word)
-            if word in (os.curdir, os.pardir): continue
-            path = os.path.join(path, word)
-        return path
-
-
-    # I found on my local network that calls to this were timing out
-    # I believe all of these calls are from log_message
-    def address_string(self):
-        return "a.b.c.d"
-
-    # This produces a LOT of noise
-    def log_message(self, format, *args):
-        pass
-
-
-class MozHttpd(object):
-    """
-    :param host: Host from which to serve (default 127.0.0.1)
-    :param port: Port from which to serve (default 8888)
-    :param docroot: Server root (default os.getcwd())
-    :param urlhandlers: Handlers to specify behavior against method and path match (default None)
-    :param path_mappings: A dict mapping URL prefixes to additional on-disk paths.
-    :param proxy_host_dirs: Toggle proxy behavior (default False)
-    :param log_requests: Toggle logging behavior (default False)
-
-    Very basic HTTP server class. Takes a docroot (path on the filesystem)
-    and a set of urlhandler dictionaries of the form:
-
-    ::
-
-      {
-        'method': HTTP method (string): GET, POST, or DEL,
-        'path': PATH_INFO (regular expression string),
-        'function': function of form fn(arg1, arg2, arg3, ..., request)
-      }
-
-    and serves HTTP. For each request, MozHttpd will either return a file
-    off the docroot, or dispatch to a handler function (if both path and
-    method match).
-
-    Note that one of docroot or urlhandlers may be None (in which case no
-    local files or handlers, respectively, will be used). If both docroot or
-    urlhandlers are None then MozHttpd will default to serving just the local
-    directory.
-
-    MozHttpd also handles proxy requests (i.e. with a full URI on the request
-    line).  By default files are served from docroot according to the request
-    URI's path component, but if proxy_host_dirs is True, files are served
-    from <self.docroot>/<host>/.
-
-    For example, the request "GET http://foo.bar/dir/file.html" would
-    (assuming no handlers match) serve <docroot>/dir/file.html if
-    proxy_host_dirs is False, or <docroot>/foo.bar/dir/file.html if it is
-    True.
-    """
-
-    def __init__(self,
-                 host="127.0.0.1",
-                 port=0,
-                 docroot=None,
-                 urlhandlers=None,
-                 path_mappings=None,
-                 proxy_host_dirs=False,
-                 log_requests=False):
-        self.host = host
-        self.port = int(port)
-        self.docroot = docroot
-        if not (urlhandlers or docroot or path_mappings):
-            self.docroot = os.getcwd()
-        self.proxy_host_dirs = proxy_host_dirs
-        self.httpd = None
-        self.urlhandlers = urlhandlers or []
-        self.path_mappings = path_mappings or {}
-        self.log_requests = log_requests
-        self.request_log = []
-
-        class RequestHandlerInstance(RequestHandler):
-            docroot = self.docroot
-            urlhandlers = self.urlhandlers
-            path_mappings = self.path_mappings
-            proxy_host_dirs = self.proxy_host_dirs
-            request_log = self.request_log
-            log_requests = self.log_requests
-
-        self.handler_class = RequestHandlerInstance
-
-    def start(self, block=False):
-        """
-        Starts the server.
-
-        If `block` is True, the call will not return. If `block` is False, the
-        server will be started on a separate thread that can be terminated by
-        a call to stop().
-        """
-        self.httpd = EasyServer((self.host, self.port), self.handler_class)
-        if block:
-            self.httpd.serve_forever()
-        else:
-            self.server = threading.Thread(target=self.httpd.serve_forever)
-            self.server.setDaemon(True) # don't hang on exit
-            self.server.start()
-
-    def stop(self):
-        """
-        Stops the server.
-
-        If the server is not running, this method has no effect.
-        """
-        if self.httpd:
-            ### FIXME: There is no shutdown() method in Python 2.4...
-            try:
-                self.httpd.shutdown()
-            except AttributeError:
-                pass
-        self.httpd = None
-
-    def get_url(self, path="/"):
-        """
-        Returns a URL that can be used for accessing the server (e.g. http://192.168.1.3:4321/)
-
-        :param path: Path to append to URL (e.g. if path were /foobar.html you would get a URL like
-                     http://192.168.1.3:4321/foobar.html). Default is `/`.
-        """
-        if not self.httpd:
-            return None
-
-        return "http://%s:%s%s" % (self.host, self.httpd.server_port, path)
-
-    __del__ = stop
-
-
-def main(args=sys.argv[1:]):
-
-    # parse command line options
-    from optparse import OptionParser
-    parser = OptionParser()
-    parser.add_option('-p', '--port', dest='port',
-                      type="int", default=8888,
-                      help="port to run the server on [DEFAULT: %default]")
-    parser.add_option('-H', '--host', dest='host',
-                      default='127.0.0.1',
-                      help="host [DEFAULT: %default]")
-    parser.add_option('-i', '--external-ip', action="store_true",
-                      dest='external_ip', default=False,
-                      help="find and use external ip for host")
-    parser.add_option('-d', '--docroot', dest='docroot',
-                      default=os.getcwd(),
-                      help="directory to serve files from [DEFAULT: %default]")
-    options, args = parser.parse_args(args)
-    if args:
-        parser.error("mozhttpd does not take any arguments")
-
-    if options.external_ip:
-        host = moznetwork.get_lan_ip()
-    else:
-        host = options.host
-
-    # create the server
-    server = MozHttpd(host=host, port=options.port, docroot=options.docroot)
-
-    print "Serving '%s' at %s:%s" % (server.docroot, server.host, server.port)
-    server.start(block=True)
-
-if __name__ == '__main__':
-    main()
deleted file mode 100644
--- a/testing/mozbase/mozhttpd/setup.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this file,
-# You can obtain one at http://mozilla.org/MPL/2.0/.
-
-from setuptools import setup
-
-PACKAGE_VERSION = '0.7'
-deps = ['moznetwork >= 0.24']
-
-setup(name='mozhttpd',
-      version=PACKAGE_VERSION,
-      description="Python webserver intended for use with Mozilla testing",
-      long_description="see http://mozbase.readthedocs.org/",
-      classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
-      keywords='mozilla',
-      author='Mozilla Automation and Testing Team',
-      author_email='tools@lists.mozilla.org',
-      url='https://wiki.mozilla.org/Auto-tools/Projects/Mozbase',
-      license='MPL',
-      packages=['mozhttpd'],
-      include_package_data=True,
-      zip_safe=False,
-      install_requires=deps,
-      entry_points="""
-      # -*- Entry points: -*-
-      [console_scripts]
-      mozhttpd = mozhttpd:main
-      """,
-      )
-
deleted file mode 100644
--- a/testing/mozbase/mozhttpd/tests/api.py
+++ /dev/null
@@ -1,262 +0,0 @@
-#!/usr/bin/env python
-
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this file,
-# You can obtain one at http://mozilla.org/MPL/2.0/.
-
-import mozfile
-import mozhttpd
-import urllib2
-import os
-import unittest
-import json
-import tempfile
-
-here = os.path.dirname(os.path.abspath(__file__))
-
-class ApiTest(unittest.TestCase):
-    resource_get_called = 0
-    resource_post_called = 0
-    resource_del_called = 0
-
-    @mozhttpd.handlers.json_response
-    def resource_get(self, request, objid):
-        self.resource_get_called += 1
-        return (200, { 'called': self.resource_get_called,
-                       'id': objid,
-                       'query': request.query })
-
-    @mozhttpd.handlers.json_response
-    def resource_post(self, request):
-        self.resource_post_called += 1
-        return (201, { 'called': self.resource_post_called,
-                       'data': json.loads(request.body),
-                       'query': request.query })
-
-    @mozhttpd.handlers.json_response
-    def resource_del(self, request, objid):
-        self.resource_del_called += 1
-        return (200, { 'called': self.resource_del_called,
-                       'id': objid,
-                       'query': request.query })
-
-    def get_url(self, path, server_port, querystr):
-        url = "http://127.0.0.1:%s%s" % (server_port, path)
-        if querystr:
-            url += "?%s" % querystr
-        return url
-
-    def try_get(self, server_port, querystr):
-        self.resource_get_called = 0
-
-        f = urllib2.urlopen(self.get_url('/api/resource/1', server_port, querystr))
-        try:
-            self.assertEqual(f.getcode(), 200)
-        except AttributeError:
-            pass  # python 2.4
-        self.assertEqual(json.loads(f.read()), { 'called': 1, 'id': str(1), 'query': querystr })
-        self.assertEqual(self.resource_get_called, 1)
-
-    def try_post(self, server_port, querystr):
-        self.resource_post_called = 0
-
-        postdata = { 'hamburgers': '1234' }
-        try:
-            f = urllib2.urlopen(self.get_url('/api/resource/', server_port, querystr),
-                                data=json.dumps(postdata))
-        except urllib2.HTTPError, e:
-            # python 2.4
-            self.assertEqual(e.code, 201)
-            body = e.fp.read()
-        else:
-            self.assertEqual(f.getcode(), 201)
-            body = f.read()
-        self.assertEqual(json.loads(body), { 'called': 1,
-                                             'data': postdata,
-                                             'query': querystr })
-        self.assertEqual(self.resource_post_called, 1)
-
-    def try_del(self, server_port, querystr):
-        self.resource_del_called = 0
-
-        opener = urllib2.build_opener(urllib2.HTTPHandler)
-        request = urllib2.Request(self.get_url('/api/resource/1', server_port, querystr))
-        request.get_method = lambda: 'DEL'
-        f = opener.open(request)
-
-        try:
-            self.assertEqual(f.getcode(), 200)
-        except AttributeError:
-            pass  # python 2.4
-        self.assertEqual(json.loads(f.read()), { 'called': 1, 'id': str(1), 'query': querystr })
-        self.assertEqual(self.resource_del_called, 1)
-
-    def test_api(self):
-        httpd = mozhttpd.MozHttpd(port=0,
-                                  urlhandlers = [ { 'method': 'GET',
-                                                    'path': '/api/resource/([^/]+)/?',
-                                                    'function': self.resource_get },
-                                                  { 'method': 'POST',
-                                                    'path': '/api/resource/?',
-                                                    'function': self.resource_post },
-                                                  { 'method': 'DEL',
-                                                    'path': '/api/resource/([^/]+)/?',
-                                                    'function': self.resource_del }
-                                                  ])
-        httpd.start(block=False)
-
-        server_port = httpd.httpd.server_port
-
-        # GET
-        self.try_get(server_port, '')
-        self.try_get(server_port, '?foo=bar')
-
-        # POST
-        self.try_post(server_port, '')
-        self.try_post(server_port, '?foo=bar')
-
-        # DEL
-        self.try_del(server_port, '')
-        self.try_del(server_port, '?foo=bar')
-
-        # GET: By default we don't serve any files if we just define an API
-        exception_thrown = False
-        try:
-            urllib2.urlopen(self.get_url('/', server_port, None))
-        except urllib2.HTTPError, e:
-            self.assertEqual(e.code, 404)
-            exception_thrown = True
-        self.assertTrue(exception_thrown)
-
-    def test_nonexistent_resources(self):
-        # Create a server with a placeholder handler so we don't fall back
-        # to serving local files
-        httpd = mozhttpd.MozHttpd(port=0)
-        httpd.start(block=False)
-        server_port = httpd.httpd.server_port
-
-        # GET: Return 404 for non-existent endpoint
-        exception_thrown = False
-        try:
-            urllib2.urlopen(self.get_url('/api/resource/', server_port, None))
-        except urllib2.HTTPError, e:
-            self.assertEqual(e.code, 404)
-            exception_thrown = True
-        self.assertTrue(exception_thrown)
-
-        # POST: POST should also return 404
-        exception_thrown = False
-        try:
-            urllib2.urlopen(self.get_url('/api/resource/', server_port, None),
-                            data=json.dumps({}))
-        except urllib2.HTTPError, e:
-            self.assertEqual(e.code, 404)
-            exception_thrown = True
-        self.assertTrue(exception_thrown)
-
-        # DEL: DEL should also return 404
-        exception_thrown = False
-        try:
-            opener = urllib2.build_opener(urllib2.HTTPHandler)
-            request = urllib2.Request(self.get_url('/api/resource/', server_port,
-                                                   None))
-            request.get_method = lambda: 'DEL'
-            opener.open(request)
-        except urllib2.HTTPError:
-            self.assertEqual(e.code, 404)
-            exception_thrown = True
-        self.assertTrue(exception_thrown)
-
-    def test_api_with_docroot(self):
-        httpd = mozhttpd.MozHttpd(port=0, docroot=here,
-                                  urlhandlers = [ { 'method': 'GET',
-                                                    'path': '/api/resource/([^/]+)/?',
-                                                    'function': self.resource_get } ])
-        httpd.start(block=False)
-        server_port = httpd.httpd.server_port
-
-        # We defined a docroot, so we expect a directory listing
-        f = urllib2.urlopen(self.get_url('/', server_port, None))
-        try:
-            self.assertEqual(f.getcode(), 200)
-        except AttributeError:
-            pass  # python 2.4
-        self.assertTrue('Directory listing for' in f.read())
-
-        # Make sure API methods still work
-        self.try_get(server_port, '')
-        self.try_get(server_port, '?foo=bar')
-
-class ProxyTest(unittest.TestCase):
-
-    def tearDown(self):
-        # reset proxy opener in case it changed
-        urllib2.install_opener(None)
-
-    def test_proxy(self):
-        docroot = tempfile.mkdtemp()
-        self.addCleanup(mozfile.remove, docroot)
-        hosts = ('mozilla.com', 'mozilla.org')
-        unproxied_host = 'notmozilla.org'
-        def url(host): return 'http://%s/' % host
-
-        index_filename = 'index.html'
-        def index_contents(host): return '%s index' % host
-
-        index = file(os.path.join(docroot, index_filename), 'w')
-        index.write(index_contents('*'))
-        index.close()
-
-        httpd = mozhttpd.MozHttpd(port=0, docroot=docroot)
-        httpd.start(block=False)
-        server_port = httpd.httpd.server_port
-
-        proxy_support = urllib2.ProxyHandler({'http': 'http://127.0.0.1:%d' %
-                                              server_port})
-        urllib2.install_opener(urllib2.build_opener(proxy_support))
-
-        for host in hosts:
-            f = urllib2.urlopen(url(host))
-            try:
-                self.assertEqual(f.getcode(), 200)
-            except AttributeError:
-                pass  # python 2.4
-            self.assertEqual(f.read(), index_contents('*'))
-
-        httpd.stop()
-
-        # test separate directories per host
-
-        httpd = mozhttpd.MozHttpd(port=0, docroot=docroot, proxy_host_dirs=True)
-        httpd.start(block=False)
-        server_port = httpd.httpd.server_port
-
-        proxy_support = urllib2.ProxyHandler({'http': 'http://127.0.0.1:%d' %
-                                              server_port})
-        urllib2.install_opener(urllib2.build_opener(proxy_support))
-
-        # set up dirs
-        for host in hosts:
-            os.mkdir(os.path.join(docroot, host))
-            file(os.path.join(docroot, host, index_filename), 'w') \
-                .write(index_contents(host))
-
-        for host in hosts:
-            f = urllib2.urlopen(url(host))
-            try:
-                self.assertEqual(f.getcode(), 200)
-            except AttributeError:
-                pass  # python 2.4
-            self.assertEqual(f.read(), index_contents(host))
-
-        exc = None
-        try:
-            urllib2.urlopen(url(unproxied_host))
-        except urllib2.HTTPError, e:
-            exc = e
-        self.assertNotEqual(exc, None)
-        self.assertEqual(exc.code, 404)
-
-
-if __name__ == '__main__':
-    unittest.main()
deleted file mode 100644
--- a/testing/mozbase/mozhttpd/tests/baseurl.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import mozhttpd
-import unittest
-
-class BaseUrlTest(unittest.TestCase):
-
-    def test_base_url(self):
-        httpd = mozhttpd.MozHttpd(port=0)
-        self.assertEqual(httpd.get_url(), None)
-        httpd.start(block=False)
-        self.assertEqual("http://127.0.0.1:%s/" % httpd.httpd.server_port,
-                         httpd.get_url())
-        self.assertEqual("http://127.0.0.1:%s/cheezburgers.html" % \
-                             httpd.httpd.server_port,
-                         httpd.get_url(path="/cheezburgers.html"))
-        httpd.stop()
-
-if __name__ == '__main__':
-    unittest.main()
deleted file mode 100644
--- a/testing/mozbase/mozhttpd/tests/basic.py
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/usr/bin/env python
-
-import mozhttpd
-import mozfile
-import os
-import tempfile
-import unittest
-
-
-class TestBasic(unittest.TestCase):
-    """ Test basic Mozhttpd capabilites """
-
-    def test_basic(self):
-        """ Test mozhttpd can serve files """
-
-        tempdir = tempfile.mkdtemp()
-
-        # sizes is a dict of the form: name -> [size, binary_string, filepath]
-        sizes = {'small': [128], 'large': [16384]}
-
-        for k in sizes.keys():
-            # Generate random binary string
-            sizes[k].append(os.urandom(sizes[k][0]))
-
-            # Add path of file with binary string to list
-            fpath = os.path.join(tempdir, k)
-            sizes[k].append(fpath)
-
-            # Write binary string to file
-            with open(fpath, 'wb') as f:
-                f.write(sizes[k][1])
-
-        server = mozhttpd.MozHttpd(docroot=tempdir)
-        server.start()
-        server_url = server.get_url()
-
-        # Retrieve file and check contents matchup
-        for k in sizes.keys():
-            retrieved_content = mozfile.load(server_url + k).read()
-            self.assertEqual(retrieved_content, sizes[k][1])
-
-        # Cleanup tempdir and related files
-        mozfile.rmtree(tempdir)
-
-if __name__ == '__main__':
-    unittest.main()
deleted file mode 100644
--- a/testing/mozbase/mozhttpd/tests/filelisting.py
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/usr/bin/env python
-
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this file,
-# You can obtain one at http://mozilla.org/MPL/2.0/.
-
-import mozhttpd
-import urllib2
-import os
-import unittest
-import re
-
-here = os.path.dirname(os.path.abspath(__file__))
-
-class FileListingTest(unittest.TestCase):
-
-    def check_filelisting(self, path=''):
-        filelist = os.listdir(here)
-
-        httpd = mozhttpd.MozHttpd(port=0, docroot=here)
-        httpd.start(block=False)
-        f = urllib2.urlopen("http://%s:%s/%s" % ('127.0.0.1', httpd.httpd.server_port, path))
-        for line in f.readlines():
-            webline = re.sub('\<[a-zA-Z0-9\-\_\.\=\"\'\/\\\%\!\@\#\$\^\&\*\(\) ]*\>', '', line.strip('\n')).strip('/').strip().strip('@')
-
-            if webline and not webline.startswith("Directory listing for"):
-                self.assertTrue(webline in filelist,
-                                "File %s in dir listing corresponds to a file" % webline)
-                filelist.remove(webline)
-        self.assertFalse(filelist, "Should have no items in filelist (%s) unaccounted for" % filelist)
-
-
-    def test_filelist(self):
-        self.check_filelisting()
-
-    def test_filelist_params(self):
-        self.check_filelisting('?foo=bar&fleem=&foo=fleem')
-
-
-if __name__ == '__main__':
-    unittest.main()
deleted file mode 100644
--- a/testing/mozbase/mozhttpd/tests/manifest.ini
+++ /dev/null
@@ -1,6 +0,0 @@
-[api.py]
-[baseurl.py]
-[basic.py]
-[filelisting.py]
-[paths.py]
-[requestlog.py]
deleted file mode 100644
--- a/testing/mozbase/mozhttpd/tests/paths.py
+++ /dev/null
@@ -1,75 +0,0 @@
-#!/usr/bin/env python
-
-# Any copyright is dedicated to the Public Domain.
-# http://creativecommons.org/publicdomain/zero/1.0/
-
-from mozfile import TemporaryDirectory
-import mozhttpd
-import os
-import unittest
-import urllib2
-
-class PathTest(unittest.TestCase):
-    def try_get(self, url, expected_contents):
-        f = urllib2.urlopen(url)
-        self.assertEqual(f.getcode(), 200)
-        self.assertEqual(f.read(), expected_contents)
-
-    def try_get_expect_404(self, url):
-        with self.assertRaises(urllib2.HTTPError) as cm:
-            urllib2.urlopen(url)
-        self.assertEqual(404, cm.exception.code)
-
-    def test_basic(self):
-        """Test that requests to docroot and a path mapping work as expected."""
-        with TemporaryDirectory() as d1, TemporaryDirectory() as d2:
-            open(os.path.join(d1, "test1.txt"), "w").write("test 1 contents")
-            open(os.path.join(d2, "test2.txt"), "w").write("test 2 contents")
-            httpd = mozhttpd.MozHttpd(port=0,
-                                      docroot=d1,
-                                      path_mappings={'/files': d2}
-                                      )
-            httpd.start(block=False)
-            self.try_get(httpd.get_url("/test1.txt"), "test 1 contents")
-            self.try_get(httpd.get_url("/files/test2.txt"), "test 2 contents")
-            self.try_get_expect_404(httpd.get_url("/files/test2_nope.txt"))
-            httpd.stop()
-
-    def test_substring_mappings(self):
-        """Test that a path mapping that's a substring of another works."""
-        with TemporaryDirectory() as d1, TemporaryDirectory() as d2:
-            open(os.path.join(d1, "test1.txt"), "w").write("test 1 contents")
-            open(os.path.join(d2, "test2.txt"), "w").write("test 2 contents")
-            httpd = mozhttpd.MozHttpd(port=0,
-                                      path_mappings={'/abcxyz': d1,
-                                                     '/abc': d2,}
-                                      )
-            httpd.start(block=False)
-            self.try_get(httpd.get_url("/abcxyz/test1.txt"), "test 1 contents")
-            self.try_get(httpd.get_url("/abc/test2.txt"), "test 2 contents")
-            httpd.stop()
-
-    def test_multipart_path_mapping(self):
-        """Test that a path mapping with multiple directories works."""
-        with TemporaryDirectory() as d1:
-            open(os.path.join(d1, "test1.txt"), "w").write("test 1 contents")
-            httpd = mozhttpd.MozHttpd(port=0,
-                                      path_mappings={'/abc/def/ghi': d1}
-                                      )
-            httpd.start(block=False)
-            self.try_get(httpd.get_url("/abc/def/ghi/test1.txt"), "test 1 contents")
-            self.try_get_expect_404(httpd.get_url("/abc/test1.txt"))
-            self.try_get_expect_404(httpd.get_url("/abc/def/test1.txt"))
-            httpd.stop()
-
-    def test_no_docroot(self):
-        """Test that path mappings with no docroot work."""
-        with TemporaryDirectory() as d1:
-            httpd = mozhttpd.MozHttpd(port=0,
-                                      path_mappings={'/foo': d1})
-            httpd.start(block=False)
-            self.try_get_expect_404(httpd.get_url())
-            httpd.stop()
-
-if __name__ == '__main__':
-    unittest.main()
deleted file mode 100644
--- a/testing/mozbase/mozhttpd/tests/requestlog.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this file,
-# You can obtain one at http://mozilla.org/MPL/2.0/.
-
-import mozhttpd
-import urllib2
-import os
-import unittest
-
-here = os.path.dirname(os.path.abspath(__file__))
-
-class RequestLogTest(unittest.TestCase):
-
-    def check_logging(self, log_requests=False):
-
-        httpd = mozhttpd.MozHttpd(port=0, docroot=here, log_requests=log_requests)
-        httpd.start(block=False)
-        url = "http://%s:%s/" % ('127.0.0.1', httpd.httpd.server_port)
-        f = urllib2.urlopen(url)
-        f.read()
-
-        return httpd.request_log
-
-    def test_logging_enabled(self):
-        request_log = self.check_logging(log_requests=True)
-
-        self.assertEqual(len(request_log), 1)
-
-        log_entry = request_log[0]
-        self.assertEqual(log_entry['method'], 'GET')
-        self.assertEqual(log_entry['path'], '/')
-        self.assertEqual(type(log_entry['time']), float)
-
-    def test_logging_disabled(self):
-        request_log = self.check_logging(log_requests=False)
-
-        self.assertEqual(len(request_log), 0)
-
-if __name__ == '__main__':
-    unittest.main()
--- a/testing/mozbase/mozprofile/setup.py
+++ b/testing/mozbase/mozprofile/setup.py
@@ -30,17 +30,17 @@ setup(name=PACKAGE_NAME,
       author_email='tools@lists.mozilla.org',
       url='https://wiki.mozilla.org/Auto-tools/Projects/Mozbase',
       license='MPL 2.0',
       packages=['mozprofile'],
       include_package_data=True,
       zip_safe=False,
       install_requires=deps,
       extras_require={'manifest': ['manifestparser >= 0.6']},
-      tests_require=['mozhttpd'],
+      tests_require=['wptserve'],
       entry_points="""
       # -*- Entry points: -*-
       [console_scripts]
       mozprofile = mozprofile:cli
       view-profile = mozprofile:view_profile
       diff-profiles = mozprofile:diff_profiles
       """,
     )
--- a/testing/mozbase/mozprofile/tests/test_addons.py
+++ b/testing/mozbase/mozprofile/tests/test_addons.py
@@ -7,17 +7,17 @@
 import os
 import shutil
 import tempfile
 import unittest
 import urllib2
 
 from manifestparser import ManifestParser
 import mozfile
-import mozhttpd
+from wptserve.server import WebTestHttpd
 import mozlog.unstructured as mozlog
 import mozprofile
 
 from addon_stubs import generate_addon, generate_manifest
 
 
 here = os.path.dirname(os.path.abspath(__file__))
 
@@ -58,17 +58,17 @@ class TestAddonsManager(unittest.TestCas
         # specified via XPI and folder
         self.am.install_addons([addon_folder, addon_xpi])
         self.assertEqual(len(self.am.installed_addons), 2)
         self.assertIn(addon_folder, self.am.installed_addons)
         self.assertIn(addon_xpi, self.am.installed_addons)
         self.am.clean()
 
     def test_download(self):
-        server = mozhttpd.MozHttpd(docroot=os.path.join(here, 'addons'))
+        server = WebTestHttpd(doc_root=os.path.join(here, 'addons'))
         server.start()
 
         # Download a valid add-on without a class instance to the general
         # tmp folder and clean-up
         try:
             addon = server.get_url() + 'empty.xpi'
             xpi_file = mozprofile.addons.AddonManager.download(addon)
             self.assertTrue(os.path.isfile(xpi_file))
@@ -168,17 +168,17 @@ class TestAddonsManager(unittest.TestCas
         self.am.clean()
 
         # Test forcing unpack an add-on
         self.am.install_from_path(addon_no_unpack, unpack=True)
         self.assertEqual(self.am.installed_addons, [addon_no_unpack])
         self.am.clean()
 
     def test_install_from_path_url(self):
-        server = mozhttpd.MozHttpd(docroot=os.path.join(here, 'addons'))
+        server = WebTestHttpd(doc_root=os.path.join(here, 'addons'))
         server.start()
 
         addon = server.get_url() + 'empty.xpi'
         self.am.install_from_path(addon)
 
         server.stop()
 
         self.assertEqual(len(self.am.downloaded_addons), 1)
@@ -340,17 +340,17 @@ class TestAddonsManager(unittest.TestCas
         addons_after_cleanup = [unicode(x[:-len('.xpi')]) for x in os.listdir(os.path.join(
                                 duplicate_profile.profile, 'extensions', 'staged'))]
         # New addons installed should be removed by clean_addons()
         self.assertEqual(installed_addons, addons_after_cleanup)
 
     def test_noclean(self):
         """test `restore=True/False` functionality"""
 
-        server = mozhttpd.MozHttpd(docroot=os.path.join(here, 'addons'))
+        server = WebTestHttpd(doc_root=os.path.join(here, 'addons'))
         server.start()
 
         profile = tempfile.mkdtemp()
         tmpdir = tempfile.mkdtemp()
 
         try:
             # empty initially
             self.assertFalse(bool(os.listdir(profile)))
--- a/testing/mozbase/mozprofile/tests/test_preferences.py
+++ b/testing/mozbase/mozprofile/tests/test_preferences.py
@@ -1,16 +1,16 @@
 #!/usr/bin/env python
 
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import mozfile
-import mozhttpd
+from wptserve.server import WebTestHttpd
 import os
 import shutil
 import tempfile
 import unittest
 from mozprofile.cli import MozProfileCLI
 from mozprofile.prefs import Preferences
 from mozprofile.profile import Profile
 
@@ -347,31 +347,30 @@ user_pref("webgl.force-enabled", true);
             "server": "server-name",
             "abc": "something"
             }
         path = os.path.join(here, 'files', 'prefs_with_interpolation.js')
         read_prefs = Preferences.read_prefs(path, interpolation=values)
         self.assertEqual(dict(read_prefs), expected_prefs)
 
     def test_read_prefs_ttw(self):
-        """test reading preferences through the web via mozhttpd"""
+        """test reading preferences through the web via wptserve"""
 
-        # create a MozHttpd instance
-        docroot = os.path.join(here, 'files')
-        host = '127.0.0.1'
-        port = 8888
-        httpd = mozhttpd.MozHttpd(host=host, port=port, docroot=docroot)
+        # create a server instance
+        httpd = WebTestHttpd(host='127.0.0.1', port=0,
+                             doc_root=os.path.join(here, 'files'))
 
         # create a preferences instance
         prefs = Preferences()
 
         try:
             # start server
             httpd.start(block=False)
 
             # read preferences through the web
-            read = prefs.read_prefs('http://%s:%d/prefs_with_comments.js' % (host, port))
+            read = prefs.read_prefs('http://%s:%d/prefs_with_comments.js' %
+                                    httpd.httpd.server_address)
             self.assertEqual(dict(read), self._prefs_with_comments)
         finally:
             httpd.stop()
 
 if __name__ == '__main__':
     unittest.main()
--- a/testing/mozbase/packages.txt
+++ b/testing/mozbase/packages.txt
@@ -1,15 +1,14 @@
 manifestparser.pth:testing/mozbase/manifestparser
 mozb2g.pth:testing/mozbase/mozb2g
 mozcrash.pth:testing/mozbase/mozcrash
 mozdebug.pth:testing/mozbase/mozdebug
 mozdevice.pth:testing/mozbase/mozdevice
 mozfile.pth:testing/mozbase/mozfile
-mozhttpd.pth:testing/mozbase/mozhttpd
 mozinfo.pth:testing/mozbase/mozinfo
 mozinstall.pth:testing/mozbase/mozinstall
 mozleak.pth:testing/mozbase/mozleak
 mozlog.pth:testing/mozbase/mozlog
 moznetwork.pth:testing/mozbase/moznetwork
 mozprocess.pth:testing/mozbase/mozprocess
 mozprofile.pth:testing/mozbase/mozprofile
 mozrunner.pth:testing/mozbase/mozrunner
--- a/testing/mozbase/test-manifest.ini
+++ b/testing/mozbase/test-manifest.ini
@@ -7,17 +7,16 @@
 
 # run with
 # https://github.com/mozilla/mozbase/blob/master/test.py
 
 [include:manifestparser/tests/manifest.ini]
 [include:mozcrash/tests/manifest.ini]
 [include:mozdevice/tests/manifest.ini]
 [include:mozfile/tests/manifest.ini]
-[include:mozhttpd/tests/manifest.ini]
 [include:mozinfo/tests/manifest.ini]
 [include:mozinstall/tests/manifest.ini]
 [include:mozlog/tests/manifest.ini]
 [include:moznetwork/tests/manifest.ini]
 [include:mozprocess/tests/manifest.ini]
 [include:mozprofile/tests/manifest.ini]
 [include:mozrunner/tests/manifest.ini]
 [include:moztest/tests/manifest.ini]
--- a/testing/mozharness/mozharness/mozilla/mozbase.py
+++ b/testing/mozharness/mozharness/mozilla/mozbase.py
@@ -26,14 +26,14 @@ class MozbaseMixin(object):
         # XXX Bug 908356: This block can be removed as soon as the
         # in-tree requirements files propagate to all active trees.
         mozbase_dir = os.path.join('tests', 'mozbase')
         self.register_virtualenv_module(
             'manifestparser',
             url=os.path.join(mozbase_dir, 'manifestdestiny')
         )
 
-        for m in ('mozfile', 'mozlog', 'mozinfo', 'moznetwork', 'mozhttpd',
+        for m in ('mozfile', 'mozlog', 'mozinfo', 'moznetwork',
                   'mozcrash', 'mozinstall', 'mozdevice', 'mozprofile',
                   'mozprocess', 'mozrunner'):
             self.register_virtualenv_module(
                 m, url=os.path.join(mozbase_dir, m)
             )
--- a/testing/mozharness/scripts/marionette.py
+++ b/testing/mozharness/scripts/marionette.py
@@ -238,17 +238,17 @@ class MarionetteTest(TestingMixin, Mercu
         else:
             # XXX Bug 879765: Dependent modules need to be listed before parent
             # modules, otherwise they will get installed from the pypi server.
             # XXX Bug 908356: This block can be removed as soon as the
             # in-tree requirements files propagate to all active trees.
             mozbase_dir = os.path.join('tests', 'mozbase')
             self.register_virtualenv_module(
                 'manifestparser', os.path.join(mozbase_dir, 'manifestdestiny'))
-            for m in ('mozfile', 'mozlog', 'mozinfo', 'moznetwork', 'mozhttpd',
+            for m in ('mozfile', 'mozlog', 'mozinfo', 'moznetwork',
                       'mozcrash', 'mozinstall', 'mozdevice', 'mozprofile',
                       'mozprocess', 'mozrunner'):
                 self.register_virtualenv_module(
                     m, os.path.join(mozbase_dir, m))
 
             self.register_virtualenv_module(
                 'marionette', os.path.join('tests', 'marionette'))
 
--- a/testing/mozharness/test/pip-freeze.example.txt
+++ b/testing/mozharness/test/pip-freeze.example.txt
@@ -4,16 +4,15 @@ Tempita==0.5.1
 WebOb==1.2b3
 -e hg+http://k0s.org/mozilla/hg/configuration@35416ad140982c11eba0a2d6b96d683f53429e94#egg=configuration-dev
 coverage==3.5.1
 -e hg+http://k0s.org/mozilla/hg/jetperf@4645ae34d2c41a353dcdbd856b486b6d3faabb99#egg=jetperf-dev
 logilab-astng==0.23.1
 logilab-common==0.57.1
 mozdevice==0.2
 -e hg+https://hg.mozilla.org/build/mozharness@df6b7f1e14d8c472125ef7a77b8a3b40c96ae181#egg=mozharness-jetperf
-mozhttpd==0.3
 mozinfo==0.3.3
 nose==1.1.2
 pyflakes==0.5.0
 pylint==0.25.1
 -e hg+https://hg.mozilla.org/build/talos@ee5c0b090d808e81a8fc5ba5f96b012797b3e785#egg=talos-dev
 virtualenv==1.7.1.2
 wsgiref==0.1.2
--- a/testing/mozharness/test/test_base_python.py
+++ b/testing/mozharness/test/test_base_python.py
@@ -17,17 +17,16 @@ class TestVirtualenvMixin(unittest.TestC
         expected = {'MakeItSo': '0.2.6',
                     'PyYAML': '3.10',
                     'Tempita': '0.5.1',
                     'WebOb': '1.2b3',
                     'coverage': '3.5.1',
                     'logilab-astng': '0.23.1',
                     'logilab-common': '0.57.1',
                     'mozdevice': '0.2',
-                    'mozhttpd': '0.3',
                     'mozinfo': '0.3.3',
                     'nose': '1.1.2',
                     'pyflakes': '0.5.0',
                     'pylint': '0.25.1',
                     'virtualenv': '1.7.1.2',
                     'wsgiref': '0.1.2'}
 
         self.assertEqual(packages, expected)
--- a/testing/talos/requirements.txt
+++ b/testing/talos/requirements.txt
@@ -1,9 +1,9 @@
 mozlog>=3.1
 mozcrash>=0.15
 mozfile>=1.2
-mozhttpd>=0.7
+wptserve>=1.3.0
 mozinfo>=0.8
 mozprocess>=0.22
 mozversion>=1.3
 mozprofile>=0.25
 psutil>=3.1.1
--- a/testing/talos/talos/run_tests.py
+++ b/testing/talos/talos/run_tests.py
@@ -6,18 +6,18 @@
 
 import mozversion
 import os
 import sys
 import time
 import traceback
 import urllib
 import utils
-import mozhttpd
 
+from wptserve.server import WebTestHttpd
 from mozlog import get_proxy_logger
 
 from talos.results import TalosResults
 from talos.ttest import TTest
 from talos.utils import TalosError, TalosRegression
 from talos.config import get_configs, ConfigurationError
 
 # directory of this file
@@ -63,21 +63,21 @@ def buildCommandLine(test):
 
     # XXX we should actually return the list but since we abuse
     # the url as a command line flag to pass to firefox all over the place
     # will just make a string for now
     return ' '.join(url)
 
 
 def setup_webserver(webserver):
-    """use mozhttpd to setup a webserver"""
+    """use wptserve to setup a webserver"""
     LOG.info("starting webserver on %r" % webserver)
 
     host, port = webserver.split(':')
-    return mozhttpd.MozHttpd(host=host, port=int(port), docroot=here)
+    return WebTestHttpd(host=host, port=int(port), doc_root=here)
 
 
 def run_tests(config, browser_config):
     """Runs the talos tests on the given configuration and generates a report.
     """
     # get the test data
     tests = config['tests']
     tests = useBaseTestDefaults(config.get('basetest', {}), tests)
--- a/testing/tools/mach_test_package_bootstrap.py
+++ b/testing/tools/mach_test_package_bootstrap.py
@@ -11,17 +11,16 @@ import time
 
 
 SEARCH_PATHS = [
     'mochitest',
     'mozbase/mozcrash',
     'mozbase/mozdebug',
     'mozbase/mozdevice',
     'mozbase/mozfile',
-    'mozbase/mozhttpd',
     'mozbase/mozleak',
     'mozbase/mozlog',
     'mozbase/moznetwork',
     'mozbase/mozprocess',
     'mozbase/mozprofile',
     'mozbase/mozrunner',
     'mozbase/mozsystemmonitor',
     'mozbase/mozinfo',
--- a/testing/tps/setup.py
+++ b/testing/tps/setup.py
@@ -4,17 +4,17 @@
 
 from setuptools import setup, find_packages
 import sys
 
 version = '0.5'
 
 deps = ['httplib2 == 0.7.3',
         'mozfile == 1.1',
-        'mozhttpd == 0.7',
+        'wptserve == 1.3.0',
         'mozinfo == 0.7',
         'mozinstall == 1.10',
         'mozprocess == 0.19',
         'mozprofile == 0.27',
         'mozrunner == 6.0',
         'mozversion == 1.4',
        ]
 
--- a/testing/tps/tps/testrunner.py
+++ b/testing/tps/tps/testrunner.py
@@ -6,17 +6,17 @@ import json
 import os
 import platform
 import random
 import re
 import tempfile
 import time
 import traceback
 
-from mozhttpd import MozHttpd
+from wptserve.server import WebTestHttpd
 import mozinfo
 from mozprofile import Profile
 import mozversion
 
 from .firefoxrunner import TPSFirefoxRunner
 from .phase import TPSTestPhase
 
 
@@ -430,18 +430,18 @@ class TPSTestRunner(object):
             jsondata = f.read()
             f.close()
             testfiles = json.loads(jsondata)
             testlist = testfiles['tests']
         except ValueError:
             testlist = [os.path.basename(self.testfile)]
         testdir = os.path.dirname(self.testfile)
 
-        self.mozhttpd = MozHttpd(port=4567, docroot=testdir)
-        self.mozhttpd.start()
+        self.httpd = WebTestHttpd(port=4567, doc_root=testdir)
+        self.httpd.start()
 
         # run each test, and save the results
         for test in testlist:
             result = self.run_single_test(testdir, test)
 
             if not self.productversion:
                 self.productversion = result['productversion']
             if not self.addonversion:
@@ -454,17 +454,17 @@ class TPSTestRunner(object):
             if result['state'] == 'TEST-PASS':
                 self.numpassed += 1
             else:
                 self.numfailed += 1
                 if self.stop_on_error:
                     print '\nTest failed with --stop-on-error specified; not running any more tests.\n'
                     break
 
-        self.mozhttpd.stop()
+        self.httpd.stop()
 
         # generate the postdata we'll use to post the results to the db
         self.postdata = { 'tests': self.results,
                           'os': '%s %sbit' % (mozinfo.version, mozinfo.bits),
                           'testtype': 'crossweave',
                           'productversion': self.productversion,
                           'addonversion': self.addonversion,
                           'synctype': self.synctype,
--- a/tools/docs/mach_commands.py
+++ b/tools/docs/mach_commands.py
@@ -8,17 +8,17 @@ import os
 import sys
 
 from mach.decorators import (
     Command,
     CommandArgument,
     CommandProvider,
 )
 
-import mozhttpd
+from wptserve.server import WebTestHttpd
 
 from mozbuild.base import MachCommandBase
 
 
 @CommandProvider
 class Documentation(MachCommandBase):
     """Helps manage in-tree documentation."""
 
@@ -85,17 +85,17 @@ class Documentation(MachCommandBase):
             return die('failed to generate documentation:\n%s' % '\n'.join(failed))
 
         if http is not None:
             host, port = http.split(':', 1)
             addr = (host, int(port))
             if len(addr) != 2:
                 return die('invalid address: %s' % http)
 
-            httpd = mozhttpd.MozHttpd(host=addr[0], port=addr[1], docroot=outdir)
+            httpd = WebTestHttpd(host=addr[0], port=addr[1], doc_root=outdir)
             print('listening on %s:%d' % addr)
             httpd.start(block=True)
 
     def _find_project_name(self, path):
         import imp
         path = os.path.join(path, 'conf.py')
         with open(path, 'r') as fh:
             conf = imp.load_module('doc_conf', fh, path,