Bug 570789. Add WebSocket support to mochitest. r=ted
authorJonathan Griffin <jgriffin@mozilla.com>
Wed, 16 Jun 2010 22:38:55 -0700
changeset 43718 c69b38b80f632cb62d476cabf2dc6a1e83a904ca
parent 43717 4ee79cff00249c26ed03af6dbd1e2adf81a37246
child 43719 96d248dcf4d24c9866b7720ea324b0a4bdba275f
push idunknown
push userunknown
push dateunknown
reviewersted
bugs570789
milestone1.9.3a6pre
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 570789. Add WebSocket support to mochitest. r=ted
build/automation.py.in
content/base/test/Makefile.in
testing/mochitest/Makefile.in
testing/mochitest/pywebsocket/COPYING
testing/mochitest/pywebsocket/README
testing/mochitest/pywebsocket/mod_pywebsocket/__init__.py
testing/mochitest/pywebsocket/mod_pywebsocket/dispatch.py
testing/mochitest/pywebsocket/mod_pywebsocket/handshake/__init__.py
testing/mochitest/pywebsocket/mod_pywebsocket/handshake/_base.py
testing/mochitest/pywebsocket/mod_pywebsocket/handshake/draft75.py
testing/mochitest/pywebsocket/mod_pywebsocket/handshake/handshake.py
testing/mochitest/pywebsocket/mod_pywebsocket/headerparserhandler.py
testing/mochitest/pywebsocket/mod_pywebsocket/memorizingfile.py
testing/mochitest/pywebsocket/mod_pywebsocket/msgutil.py
testing/mochitest/pywebsocket/mod_pywebsocket/util.py
testing/mochitest/pywebsocket/standalone.py
testing/mochitest/runtests.py.in
testing/mochitest/ssltunnel/ssltunnel.cpp
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -53,16 +53,18 @@ import tempfile
 
 SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0])))
 sys.path.insert(0, SCRIPT_DIR)
 import automationutils
 
 _DEFAULT_WEB_SERVER = "127.0.0.1"
 _DEFAULT_HTTP_PORT = 8888
 _DEFAULT_SSL_PORT = 4443
+_DEFAULT_WEBSOCKET_PORT = 9999
+_DEFAULT_WEBSOCKET_PROXY_PORT = 7777
 
 #expand _DIST_BIN = __XPC_BIN_PATH__
 #expand _IS_WIN32 = len("__WIN32__") != 0
 #expand _IS_MAC = __IS_MAC__ != 0
 #expand _IS_LINUX = __IS_LINUX__ != 0
 #ifdef IS_CYGWIN
 #expand _IS_CYGWIN = __IS_CYGWIN__ == 1
 #else
@@ -146,25 +148,34 @@ class Automation(object):
   IS_DEBUG_BUILD = _IS_DEBUG_BUILD
   CRASHREPORTER = _CRASHREPORTER
 
   # timeout, in seconds
   DEFAULT_TIMEOUT = 60.0
   DEFAULT_WEB_SERVER = _DEFAULT_WEB_SERVER
   DEFAULT_HTTP_PORT = _DEFAULT_HTTP_PORT
   DEFAULT_SSL_PORT = _DEFAULT_SSL_PORT
+  DEFAULT_WEBSOCKET_PORT = _DEFAULT_WEBSOCKET_PORT
+  DEFAULT_WEBSOCKET_PROXY_PORT = _DEFAULT_WEBSOCKET_PROXY_PORT
 
   def __init__(self):
     self.log = _log
     self.lastTestSeen = "automation.py"
 
-  def setServerInfo(self, webServer = _DEFAULT_WEB_SERVER, httpPort = _DEFAULT_HTTP_PORT, sslPort = _DEFAULT_SSL_PORT):
+  def setServerInfo(self, 
+                    webServer = _DEFAULT_WEB_SERVER, 
+                    httpPort = _DEFAULT_HTTP_PORT, 
+                    sslPort = _DEFAULT_SSL_PORT,
+                    webSocketPort = _DEFAULT_WEBSOCKET_PORT,
+                    webSocketProxyPort = _DEFAULT_WEBSOCKET_PROXY_PORT):
     self.webServer = webServer
     self.httpPort = httpPort
     self.sslPort = sslPort
+    self.webSocketPort = webSocketPort
+    self.webSocketProxyPort = webSocketProxyPort
 
   @property
   def __all__(self):
     return [
            "UNIXISH",
            "IS_WIN32",
            "IS_MAC",
            "log",
@@ -370,34 +381,40 @@ function FindProxyForURL(url, host)
                          '(?:[^/@]*@)?' +
                          '(.*?)' +
                          '(?::(\\\\\\\\d+))?/');
   var matches = regex.exec(url);
   if (!matches)
     return 'DIRECT';
   var isHttp = matches[1] == 'http';
   var isHttps = matches[1] == 'https';
+  var isWebSocket = matches[1] == 'ws';
   if (!matches[3])
   {
-    if (isHttp) matches[3] = '80';
+    if (isHttp | isWebSocket) matches[3] = '80';
     if (isHttps) matches[3] = '443';
   }
-    
+  if (isWebSocket)
+    matches[1] = 'http';
+
   var origin = matches[1] + '://' + matches[2] + ':' + matches[3];
   if (origins.indexOf(origin) < 0)
     return 'DIRECT';
   if (isHttp)
     return 'PROXY %(remote)s:%(httpport)s';
   if (isHttps)
     return 'PROXY %(remote)s:%(sslport)s';
+  if (isWebSocket)
+    return 'PROXY %(remote)s:%(websocketproxyport)s';
   return 'DIRECT';
 }""" % { "origins": origins,
          "remote":  self.webServer,
          "httpport":self.httpPort,
-         "sslport": self.sslPort }
+         "sslport": self.sslPort,
+         "websocketproxyport": self.webSocketProxyPort }
       pacURL = "".join(pacURL.splitlines())
 
       part += """
 user_pref("network.proxy.type", 2);
 user_pref("network.proxy.autoconfig_url", "%(pacURL)s");
 
 user_pref("camino.use_system_proxy_settings", false); // Camino-only, harmless to others
 """ % {"pacURL": pacURL}
@@ -434,16 +451,18 @@ user_pref("camino.use_system_proxy_setti
 
     # Create head of the ssltunnel configuration file
     sslTunnelConfigPath = os.path.join(profileDir, "ssltunnel.cfg")
     sslTunnelConfig = open(sslTunnelConfigPath, "w")
   
     sslTunnelConfig.write("httpproxy:1\n")
     sslTunnelConfig.write("certdbdir:%s\n" % certPath)
     sslTunnelConfig.write("forward:127.0.0.1:%s\n" % self.httpPort)
+    sslTunnelConfig.write("proxy:%s:%s:%s\n" % 
+      (self.webSocketProxyPort, self.webServer, self.webSocketPort))
     sslTunnelConfig.write("listen:*:%s:pgo server certificate\n" % self.sslPort)
 
     # Configure automatic certificate and bind custom certificates, client authentication
     locations = self.readLocations()
     locations.pop(0)
     for loc in locations:
       if loc.scheme == "https" and "nocert" not in loc.options:
         customCertRE = re.compile("^cert=(?P<nickname>[0-9a-zA-Z_ ]+)")
--- a/content/base/test/Makefile.in
+++ b/content/base/test/Makefile.in
@@ -389,16 +389,18 @@ include $(topsrcdir)/config/rules.mk
 		test_bug562652.html \
 		test_bug562137.html \
 		file_bug562137.txt \
 		test_bug548193.html \
 		file_bug548193.sjs \
 		test_html_colors_quirks.html \
 		test_html_colors_standards.html \
 		test_bug571390.xul \
+		test_websocket_hello.html \
+		file_websocket_hello_wsh.py \
 		$(NULL)
 
 # This test fails on the Mac for some reason
 ifneq (,$(filter gtk2 windows,$(MOZ_WIDGET_TOOLKIT)))
 _TEST_FILES2 += 	test_copyimage.html \
 		$(NULL)
 endif
 
--- a/testing/mochitest/Makefile.in
+++ b/testing/mochitest/Makefile.in
@@ -77,19 +77,47 @@ include $(topsrcdir)/build/automation-bu
 		redirect-a11y.html \
 		redirect.html \
 		redirect.js \
 		$(topsrcdir)/build/pgo/server-locations.txt \
 		$(topsrcdir)/netwerk/test/httpserver/httpd.js \
 		mozprefs.js \
 		$(NULL)	
 
+_PYWEBSOCKET_FILES = \
+		pywebsocket/standalone.py \
+		$(NULL)
+
+_MOD_PYWEBSOCKET_FILES = \
+		pywebsocket/mod_pywebsocket/__init__.py \
+		pywebsocket/mod_pywebsocket/dispatch.py \
+		pywebsocket/mod_pywebsocket/util.py \
+		pywebsocket/mod_pywebsocket/msgutil.py \
+		pywebsocket/mod_pywebsocket/memorizingfile.py \
+		pywebsocket/mod_pywebsocket/headerparserhandler.py \
+		$(NULL)
+
+_HANDSHAKE_FILES = \
+		pywebsocket/mod_pywebsocket/handshake/__init__.py \
+		pywebsocket/mod_pywebsocket/handshake/_base.py \
+		pywebsocket/mod_pywebsocket/handshake/draft75.py \
+		pywebsocket/mod_pywebsocket/handshake/handshake.py \
+		$(NULL)
 
 _DEST_DIR = $(DEPTH)/_tests/$(relativesrcdir)
 
+libs:: $(_PYWEBSOCKET_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(_DEST_DIR)/pywebsocket
+
+libs:: $(_MOD_PYWEBSOCKET_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(_DEST_DIR)/pywebsocket/mod_pywebsocket
+
+libs:: $(_HANDSHAKE_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(_DEST_DIR)/pywebsocket/mod_pywebsocket/handshake
+
 runtests.py: runtests.py.in
 	$(PYTHON) $(topsrcdir)/config/Preprocessor.py \
 	$(DEFINES) $(ACDEFINES) $^ > $@
 
 GARBAGE += runtests.py
 
 libs:: $(_SERV_FILES)
 	$(INSTALL) $^ $(_DEST_DIR)
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/pywebsocket/COPYING
@@ -0,0 +1,28 @@
+Copyright 2009, Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+    * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/pywebsocket/README
@@ -0,0 +1,1 @@
+This is mod_pywebsocket 0.5, from http://code.google.com/p/pywebsocket/
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/__init__.py
@@ -0,0 +1,111 @@
+# Copyright 2009, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Web Socket extension for Apache HTTP Server.
+
+mod_pywebsocket is a Web Socket extension for Apache HTTP Server
+intended for testing or experimental purposes. mod_python is required.
+
+Installation:
+
+0. Prepare an Apache HTTP Server for which mod_python is enabled.
+
+1. Specify the following Apache HTTP Server directives to suit your
+   configuration.
+
+   If mod_pywebsocket is not in the Python path, specify the following.
+   <websock_lib> is the directory where mod_pywebsocket is installed.
+
+       PythonPath "sys.path+['<websock_lib>']"
+
+   Always specify the following. <websock_handlers> is the directory where
+   user-written Web Socket handlers are placed.
+
+       PythonOption mod_pywebsocket.handler_root <websock_handlers>
+       PythonHeaderParserHandler mod_pywebsocket.headerparserhandler
+
+   To limit the search for Web Socket handlers to a directory <scan_dir>
+   under <websock_handlers>, configure as follows:
+
+       PythonOption mod_pywebsocket.handler_scan <scan_dir>
+
+   <scan_dir> is useful in saving scan time when <websock_handlers>
+   contains many non-Web Socket handler files.
+
+   If you want to support old handshake based on
+   draft-hixie-thewebsocketprotocol-75:
+
+       PythonOption mod_pywebsocket.allow_draft75 On
+
+
+   Example snippet of httpd.conf:
+   (mod_pywebsocket is in /websock_lib, Web Socket handlers are in
+   /websock_handlers, port is 80 for ws, 443 for wss.)
+
+       <IfModule python_module>
+         PythonPath "sys.path+['/websock_lib']"
+         PythonOption mod_pywebsocket.handler_root /websock_handlers
+         PythonHeaderParserHandler mod_pywebsocket.headerparserhandler
+       </IfModule>
+
+Writing Web Socket handlers:
+
+When a Web Socket request comes in, the resource name
+specified in the handshake is considered as if it is a file path under
+<websock_handlers> and the handler defined in
+<websock_handlers>/<resource_name>_wsh.py is invoked.
+
+For example, if the resource name is /example/chat, the handler defined in
+<websock_handlers>/example/chat_wsh.py is invoked.
+
+A Web Socket handler is composed of the following two functions:
+
+    web_socket_do_extra_handshake(request)
+    web_socket_transfer_data(request)
+
+where:
+    request: mod_python request.
+
+web_socket_do_extra_handshake is called during the handshake after the
+headers are successfully parsed and Web Socket properties (ws_location,
+ws_origin, ws_protocol, and ws_resource) are added to request. A handler
+can reject the request by raising an exception.
+
+web_socket_transfer_data is called after the handshake completed
+successfully. A handler can receive/send messages from/to the client
+using request. mod_pywebsocket.msgutil module provides utilities
+for data transfer.
+
+A Web Socket handler must be thread-safe if the server (Apache or
+standalone.py) is configured to use threads.
+"""
+
+
+# vi:sts=4 sw=4 et tw=72
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/dispatch.py
@@ -0,0 +1,245 @@
+# Copyright 2009, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Dispatch Web Socket request.
+"""
+
+
+import os
+import re
+
+from mod_pywebsocket import msgutil
+from mod_pywebsocket import util
+
+
+_SOURCE_PATH_PATTERN = re.compile(r'(?i)_wsh\.py$')
+_SOURCE_SUFFIX = '_wsh.py'
+_DO_EXTRA_HANDSHAKE_HANDLER_NAME = 'web_socket_do_extra_handshake'
+_TRANSFER_DATA_HANDLER_NAME = 'web_socket_transfer_data'
+
+
+class DispatchError(Exception):
+    """Exception in dispatching Web Socket request."""
+
+    pass
+
+
+def _normalize_path(path):
+    """Normalize path.
+
+    Args:
+        path: the path to normalize.
+
+    Path is converted to the absolute path.
+    The input path can use either '\\' or '/' as the separator.
+    The normalized path always uses '/' regardless of the platform.
+    """
+
+    path = path.replace('\\', os.path.sep)
+    #path = os.path.realpath(path)
+    path = path.replace('\\', '/')
+    return path
+
+
+def _path_to_resource_converter(base_dir):
+    base_dir = _normalize_path(base_dir)
+    base_len = len(base_dir)
+    suffix_len = len(_SOURCE_SUFFIX)
+    def converter(path):
+        if not path.endswith(_SOURCE_SUFFIX):
+            return None
+        path = _normalize_path(path)
+        if not path.startswith(base_dir):
+            return None
+        return path[base_len:-suffix_len]
+    return converter
+
+
+def _source_file_paths(directory):
+    """Yield Web Socket Handler source file names in the given directory."""
+
+    for root, unused_dirs, files in os.walk(directory):
+        for base in files:
+            path = os.path.join(root, base)
+            if _SOURCE_PATH_PATTERN.search(path):
+                yield path
+
+
+def _source(source_str):
+    """Source a handler definition string."""
+
+    global_dic = {}
+    try:
+        exec source_str in global_dic
+    except Exception:
+        raise DispatchError('Error in sourcing handler:' +
+                            util.get_stack_trace())
+    return (_extract_handler(global_dic, _DO_EXTRA_HANDSHAKE_HANDLER_NAME),
+            _extract_handler(global_dic, _TRANSFER_DATA_HANDLER_NAME))
+
+
+def _extract_handler(dic, name):
+    if name not in dic:
+        raise DispatchError('%s is not defined.' % name)
+    handler = dic[name]
+    if not callable(handler):
+        raise DispatchError('%s is not callable.' % name)
+    return handler
+
+
+class Dispatcher(object):
+    """Dispatches Web Socket requests.
+
+    This class maintains a map from resource name to handlers.
+    """
+
+    def __init__(self, root_dir, scan_dir=None):
+        """Construct an instance.
+
+        Args:
+            root_dir: The directory where handler definition files are
+                      placed.
+            scan_dir: The directory where handler definition files are
+                      searched. scan_dir must be a directory under root_dir,
+                      including root_dir itself.  If scan_dir is None, root_dir
+                      is used as scan_dir. scan_dir can be useful in saving
+                      scan time when root_dir contains many subdirectories.
+        """
+
+        self._handlers = {}
+        self._source_warnings = []
+        if scan_dir is None:
+            scan_dir = root_dir
+        if not os.path.realpath(scan_dir).startswith(
+                os.path.realpath(root_dir)):
+            raise DispatchError('scan_dir:%s must be a directory under '
+                                'root_dir:%s.' % (scan_dir, root_dir))
+        self._source_files_in_dir(root_dir, scan_dir)
+
+    def add_resource_path_alias(self,
+                                alias_resource_path, existing_resource_path):
+        """Add resource path alias.
+
+        Once added, request to alias_resource_path would be handled by
+        handler registered for existing_resource_path.
+
+        Args:
+            alias_resource_path: alias resource path
+            existing_resource_path: existing resource path
+        """
+        try:
+            handler = self._handlers[existing_resource_path]
+            self._handlers[alias_resource_path] = handler
+        except KeyError:
+            raise DispatchError('No handler for: %r' % existing_resource_path)
+
+    def source_warnings(self):
+        """Return warnings in sourcing handlers."""
+
+        return self._source_warnings
+
+    def do_extra_handshake(self, request):
+        """Do extra checking in Web Socket handshake.
+
+        Select a handler based on request.uri and call its
+        web_socket_do_extra_handshake function.
+
+        Args:
+            request: mod_python request.
+        """
+
+        do_extra_handshake_, unused_transfer_data = self._handler(request)
+        try:
+            do_extra_handshake_(request)
+        except Exception, e:
+            util.prepend_message_to_exception(
+                    '%s raised exception for %s: ' % (
+                            _DO_EXTRA_HANDSHAKE_HANDLER_NAME,
+                            request.ws_resource),
+                    e)
+            raise
+
+    def transfer_data(self, request):
+        """Let a handler transfer_data with a Web Socket client.
+
+        Select a handler based on request.ws_resource and call its
+        web_socket_transfer_data function.
+
+        Args:
+            request: mod_python request.
+        """
+
+        unused_do_extra_handshake, transfer_data_ = self._handler(request)
+        try:
+            try:
+                request.client_terminated = False
+                request.server_terminated = False
+                transfer_data_(request)
+            except msgutil.ConnectionTerminatedException, e:
+                util.prepend_message_to_exception(
+                    'client initiated closing handshake for %s: ' % (
+                    request.ws_resource),
+                    e)
+                raise
+            except Exception, e:
+                print 'exception: %s' % type(e)
+                util.prepend_message_to_exception(
+                    '%s raised exception for %s: ' % (
+                    _TRANSFER_DATA_HANDLER_NAME, request.ws_resource),
+                    e)
+                raise
+        finally:
+            msgutil.close_connection(request)
+
+
+    def _handler(self, request):
+        try:
+            ws_resource_path = request.ws_resource.split('?', 1)[0]
+            return self._handlers[ws_resource_path]
+        except KeyError:
+            raise DispatchError('No handler for: %r' % request.ws_resource)
+
+    def _source_files_in_dir(self, root_dir, scan_dir):
+        """Source all the handler source files in the scan_dir directory.
+
+        The resource path is determined relative to root_dir.
+        """
+
+        to_resource = _path_to_resource_converter(root_dir)
+        for path in _source_file_paths(scan_dir):
+            try:
+                handlers = _source(open(path).read())
+            except DispatchError, e:
+                self._source_warnings.append('%s: %s' % (path, e))
+                continue
+            self._handlers[to_resource(path)] = handlers
+
+
+# vi:sts=4 sw=4 et
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/__init__.py
@@ -0,0 +1,95 @@
+# Copyright 2010, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Web Socket handshaking.
+
+Note: request.connection.write/read are used in this module, even though
+mod_python document says that they should be used only in connection handlers.
+Unfortunately, we have no other options. For example, request.write/read are
+not suitable because they don't allow direct raw bytes writing/reading.
+"""
+
+
+import logging
+import re
+
+from mod_pywebsocket.handshake import draft75
+from mod_pywebsocket.handshake import handshake
+from mod_pywebsocket.handshake._base import DEFAULT_WEB_SOCKET_PORT
+from mod_pywebsocket.handshake._base import DEFAULT_WEB_SOCKET_SECURE_PORT
+from mod_pywebsocket.handshake._base import WEB_SOCKET_SCHEME
+from mod_pywebsocket.handshake._base import WEB_SOCKET_SECURE_SCHEME
+from mod_pywebsocket.handshake._base import HandshakeError
+from mod_pywebsocket.handshake._base import validate_protocol
+
+
+class Handshaker(object):
+    """This class performs Web Socket handshake."""
+
+    def __init__(self, request, dispatcher, allowDraft75=False, strict=False):
+        """Construct an instance.
+
+        Args:
+            request: mod_python request.
+            dispatcher: Dispatcher (dispatch.Dispatcher).
+            allowDraft75: allow draft 75 handshake protocol.
+            strict: Strictly check handshake request in draft 75.
+                Default: False. If True, request.connection must provide
+                get_memorized_lines method.
+
+        Handshaker will add attributes such as ws_resource in performing
+        handshake.
+        """
+
+        self._logger = logging.getLogger("mod_pywebsocket.handshake")
+        self._request = request
+        self._dispatcher = dispatcher
+        self._strict = strict
+        self._handshaker = handshake.Handshaker(request, dispatcher)
+        self._fallbackHandshaker = None
+        if allowDraft75:
+            self._fallbackHandshaker = draft75.Handshaker(
+                request, dispatcher, strict)
+
+    def do_handshake(self):
+        """Perform Web Socket Handshake."""
+
+        try:
+            self._handshaker.do_handshake()
+        except HandshakeError, e:
+            self._logger.error('Handshake error: %s' % e)
+            if self._fallbackHandshaker:
+                self._logger.warning('fallback to old protocol')
+                self._fallbackHandshaker.do_handshake()
+                return
+            raise e
+
+
+# vi:sts=4 sw=4 et
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/_base.py
@@ -0,0 +1,101 @@
+# Copyright 2010, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Web Socket handshaking.
+
+Note: request.connection.write/read are used in this module, even though
+mod_python document says that they should be used only in connection handlers.
+Unfortunately, we have no other options. For example, request.write/read are
+not suitable because they don't allow direct raw bytes writing/reading.
+"""
+
+
+DEFAULT_WEB_SOCKET_PORT = 80
+DEFAULT_WEB_SOCKET_SECURE_PORT = 443
+WEB_SOCKET_SCHEME = 'ws'
+WEB_SOCKET_SECURE_SCHEME = 'wss'
+
+
+class HandshakeError(Exception):
+    """Exception in Web Socket Handshake."""
+
+    pass
+
+
+def default_port(is_secure):
+    if is_secure:
+        return DEFAULT_WEB_SOCKET_SECURE_PORT
+    else:
+        return DEFAULT_WEB_SOCKET_PORT
+
+
+def validate_protocol(protocol):
+    """Validate WebSocket-Protocol string."""
+
+    if not protocol:
+        raise HandshakeError('Invalid WebSocket-Protocol: empty')
+    for c in protocol:
+        if not 0x20 <= ord(c) <= 0x7e:
+            raise HandshakeError('Illegal character in protocol: %r' % c)
+
+
+def parse_host_header(request):
+    fields = request.headers_in['Host'].split(':', 1)
+    if len(fields) == 1:
+        return fields[0], default_port(request.is_https())
+    try:
+        return fields[0], int(fields[1])
+    except ValueError, e:
+        raise HandshakeError('Invalid port number format: %r' % e)
+
+
+def build_location(request):
+    """Build WebSocket location for request."""
+    location_parts = []
+    if request.is_https():
+        location_parts.append(WEB_SOCKET_SECURE_SCHEME)
+    else:
+        location_parts.append(WEB_SOCKET_SCHEME)
+    location_parts.append('://')
+    host, port = parse_host_header(request)
+    connection_port = request.connection.local_addr[1]
+    if port != connection_port:
+        raise HandshakeError('Header/connection port mismatch: %d/%d' %
+                             (port, connection_port))
+    location_parts.append(host)
+    if (port != default_port(request.is_https())):
+        location_parts.append(':')
+        location_parts.append(str(port))
+    location_parts.append(request.uri)
+    return ''.join(location_parts)
+
+
+
+# vi:sts=4 sw=4 et
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/draft75.py
@@ -0,0 +1,168 @@
+# Copyright 2010, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Web Socket handshaking defined in draft-hixie-thewebsocketprotocol-75.
+
+Note: request.connection.write/read are used in this module, even though
+mod_python document says that they should be used only in connection handlers.
+Unfortunately, we have no other options. For example, request.write/read are
+not suitable because they don't allow direct raw bytes writing/reading.
+"""
+
+
+import re
+
+from mod_pywebsocket.handshake._base import HandshakeError
+from mod_pywebsocket.handshake._base import build_location
+from mod_pywebsocket.handshake._base import validate_protocol
+
+
+_MANDATORY_HEADERS = [
+    # key, expected value or None
+    ['Upgrade', 'WebSocket'],
+    ['Connection', 'Upgrade'],
+    ['Host', None],
+    ['Origin', None],
+]
+
+_FIRST_FIVE_LINES = map(re.compile, [
+    r'^GET /[\S]* HTTP/1.1\r\n$',
+    r'^Upgrade: WebSocket\r\n$',
+    r'^Connection: Upgrade\r\n$',
+    r'^Host: [\S]+\r\n$',
+    r'^Origin: [\S]+\r\n$',
+])
+
+_SIXTH_AND_LATER = re.compile(
+    r'^'
+    r'(WebSocket-Protocol: [\x20-\x7e]+\r\n)?'
+    r'(Cookie: [^\r]*\r\n)*'
+    r'(Cookie2: [^\r]*\r\n)?'
+    r'(Cookie: [^\r]*\r\n)*'
+    r'\r\n')
+
+
+class Handshaker(object):
+    """This class performs Web Socket handshake."""
+
+    def __init__(self, request, dispatcher, strict=False):
+        """Construct an instance.
+
+        Args:
+            request: mod_python request.
+            dispatcher: Dispatcher (dispatch.Dispatcher).
+            strict: Strictly check handshake request. Default: False.
+                If True, request.connection must provide get_memorized_lines
+                method.
+
+        Handshaker will add attributes such as ws_resource in performing
+        handshake.
+        """
+
+        self._request = request
+        self._dispatcher = dispatcher
+        self._strict = strict
+
+    def do_handshake(self):
+        """Perform Web Socket Handshake."""
+
+        self._check_header_lines()
+        self._set_resource()
+        self._set_origin()
+        self._set_location()
+        self._set_protocol()
+        self._dispatcher.do_extra_handshake(self._request)
+        self._send_handshake()
+
+    def _set_resource(self):
+        self._request.ws_resource = self._request.uri
+
+    def _set_origin(self):
+        self._request.ws_origin = self._request.headers_in['Origin']
+
+    def _set_location(self):
+        self._request.ws_location = build_location(self._request)
+
+    def _set_protocol(self):
+        protocol = self._request.headers_in.get('WebSocket-Protocol')
+        if protocol is not None:
+            validate_protocol(protocol)
+        self._request.ws_protocol = protocol
+
+    def _send_handshake(self):
+        self._request.connection.write(
+                'HTTP/1.1 101 Web Socket Protocol Handshake\r\n')
+        self._request.connection.write('Upgrade: WebSocket\r\n')
+        self._request.connection.write('Connection: Upgrade\r\n')
+        self._request.connection.write('WebSocket-Origin: ')
+        self._request.connection.write(self._request.ws_origin)
+        self._request.connection.write('\r\n')
+        self._request.connection.write('WebSocket-Location: ')
+        self._request.connection.write(self._request.ws_location)
+        self._request.connection.write('\r\n')
+        if self._request.ws_protocol:
+            self._request.connection.write('WebSocket-Protocol: ')
+            self._request.connection.write(self._request.ws_protocol)
+            self._request.connection.write('\r\n')
+        self._request.connection.write('\r\n')
+
+    def _check_header_lines(self):
+        for key, expected_value in _MANDATORY_HEADERS:
+            actual_value = self._request.headers_in.get(key)
+            if not actual_value:
+                raise HandshakeError('Header %s is not defined' % key)
+            if expected_value:
+                if actual_value != expected_value:
+                    raise HandshakeError('Illegal value for header %s: %s' %
+                                         (key, actual_value))
+        if self._strict:
+            try:
+                lines = self._request.connection.get_memorized_lines()
+            except AttributeError, e:
+                raise AttributeError(
+                    'Strict handshake is specified but the connection '
+                    'doesn\'t provide get_memorized_lines()')
+            self._check_first_lines(lines)
+
+    def _check_first_lines(self, lines):
+        if len(lines) < len(_FIRST_FIVE_LINES):
+            raise HandshakeError('Too few header lines: %d' % len(lines))
+        for line, regexp in zip(lines, _FIRST_FIVE_LINES):
+            if not regexp.search(line):
+                raise HandshakeError('Unexpected header: %r doesn\'t match %r'
+                                     % (line, regexp.pattern))
+        sixth_and_later = ''.join(lines[5:])
+        if not _SIXTH_AND_LATER.search(sixth_and_later):
+            raise HandshakeError('Unexpected header: %r doesn\'t match %r'
+                                 % (sixth_and_later,
+                                    _SIXTH_AND_LATER.pattern))
+
+
+# vi:sts=4 sw=4 et
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/handshake.py
@@ -0,0 +1,208 @@
+# Copyright 2009, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Web Socket handshaking.
+
+Note: request.connection.write/read are used in this module, even though
+mod_python document says that they should be used only in connection handlers.
+Unfortunately, we have no other options. For example, request.write/read are
+not suitable because they don't allow direct raw bytes writing/reading.
+"""
+
+
+import logging
+from md5 import md5
+import re
+import struct
+
+from mod_pywebsocket.handshake._base import HandshakeError
+from mod_pywebsocket.handshake._base import build_location
+from mod_pywebsocket.handshake._base import validate_protocol
+
+
+_MANDATORY_HEADERS = [
+    # key, expected value or None
+    ['Upgrade', 'WebSocket'],
+    ['Connection', 'Upgrade'],
+]
+
+def _hexify(s):
+    return re.sub('.', lambda x: '%02x ' % ord(x.group(0)), s)
+
+class Handshaker(object):
+    """This class performs Web Socket handshake."""
+
+    def __init__(self, request, dispatcher):
+        """Construct an instance.
+
+        Args:
+            request: mod_python request.
+            dispatcher: Dispatcher (dispatch.Dispatcher).
+
+        Handshaker will add attributes such as ws_resource in performing
+        handshake.
+        """
+
+        self._logger = logging.getLogger("mod_pywebsocket.handshake")
+        self._request = request
+        self._dispatcher = dispatcher
+
+    def do_handshake(self):
+        """Perform Web Socket Handshake."""
+
+        # 5.1 Reading the client's opening handshake.
+        # dispatcher sets it in self._request.
+        self._check_header_lines()
+        self._set_resource()
+        self._set_protocol()
+        self._set_location()
+        self._set_origin()
+        self._set_challenge_response()
+        self._dispatcher.do_extra_handshake(self._request)
+        self._send_handshake()
+
+    def _check_header_lines(self):
+        # 5.1 1. The three character UTF-8 string "GET".
+        # 5.1 2. A UTF-8-encoded U+0020 SPACE character (0x20 byte).
+        if self._request.method != 'GET':
+            raise HandshakeError('Method is not GET')
+        # The expected field names, and the meaning of their corresponding
+        # values, are as follows.
+        #  |Upgrade| and |Connection|
+        for key, expected_value in _MANDATORY_HEADERS:
+            actual_value = self._request.headers_in.get(key)
+            if not actual_value:
+                raise HandshakeError('Header %s is not defined' % key)
+            if expected_value:
+                if actual_value != expected_value:
+                    raise HandshakeError('Illegal value for header %s: %s' %
+                                         (key, actual_value))
+
+    def _set_resource(self):
+        self._request.ws_resource = self._request.uri
+
+    def _set_protocol(self):
+        # |Sec-WebSocket-Protocol|
+        protocol = self._request.headers_in.get('Sec-WebSocket-Protocol')
+        if protocol is not None:
+            validate_protocol(protocol)
+        self._request.ws_protocol = protocol
+
+    def _set_location(self):
+        # |Host|
+        host = self._request.headers_in.get('Host')
+        if host is not None:
+            self._request.ws_location = build_location(self._request)
+        # TODO(ukai): check host is this host.
+
+    def _set_origin(self):
+        # |Origin|
+        origin = self._request.headers_in['Origin']
+        if origin is not None:
+            self._request.ws_origin = origin
+
+    def _set_challenge_response(self):
+        # 5.2 4-8.
+        self._request.ws_challenge = self._get_challenge()
+        # 5.2 9. let /response/ be the MD5 finterprint of /challenge/
+        self._request.ws_challenge_md5 = md5(
+            self._request.ws_challenge).digest()
+        self._logger.debug("challenge: %s" % _hexify(
+            self._request.ws_challenge))
+        self._logger.debug("response:  %s" % _hexify(
+            self._request.ws_challenge_md5))
+
+    def _get_key_value(self, key_field):
+        key_value = self._request.headers_in.get(key_field)
+        if key_value is None:
+            self._logger.debug("no %s" % key_value)
+            return None
+        try:
+            # 5.2 4. let /key-number_n/ be the digits (characters in the range
+            # U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9)) in /key_n/,
+            # interpreted as a base ten integer, ignoring all other characters
+            # in /key_n/
+            key_number = int(re.sub("\\D", "", key_value))
+            # 5.2 5. let /spaces_n/ be the number of U+0020 SPACE characters
+            # in /key_n/.
+            spaces = re.subn(" ", "", key_value)[1]
+            # 5.2 6. if /key-number_n/ is not an integral multiple of /spaces_n/
+            # then abort the WebSocket connection.
+            if key_number % spaces != 0:
+                raise handshakeError('key_number %d is not an integral '
+                                     'multiple of spaces %d' % (key_number,
+                                                                spaces))
+            # 5.2 7. let /part_n/ be /key_number_n/ divided by /spaces_n/.
+            part = key_number / spaces
+            self._logger.debug("%s: %s => %d / %d => %d" % (
+                key_field, key_value, key_number, spaces, part))
+            return part
+        except:
+            return None
+
+    def _get_challenge(self):
+        # 5.2 4-7.
+        key1 = self._get_key_value('Sec-Websocket-Key1')
+        if not key1:
+            raise HandshakeError('Sec-WebSocket-Key1 not found')
+        key2 = self._get_key_value('Sec-Websocket-Key2')
+        if not key2:
+            raise HandshakeError('Sec-WebSocket-Key2 not found')
+        # 5.2 8. let /challenge/ be the concatenation of /part_1/,
+        challenge = ""
+        challenge += struct.pack("!I", key1)  # network byteorder int
+        challenge += struct.pack("!I", key2)  # network byteorder int
+        challenge += self._request.connection.read(8)
+        return challenge
+
+    def _send_handshake(self):
+        # 5.2 10. send the following line.
+        self._request.connection.write(
+                'HTTP/1.1 101 WebSocket Protocol Handshake\r\n')
+        # 5.2 11. send the following fields to the client.
+        self._request.connection.write('Upgrade: WebSocket\r\n')
+        self._request.connection.write('Connection: Upgrade\r\n')
+        self._request.connection.write('Sec-WebSocket-Location: ')
+        self._request.connection.write(self._request.ws_location)
+        self._request.connection.write('\r\n')
+        self._request.connection.write('Sec-WebSocket-Origin: ')
+        self._request.connection.write(self._request.ws_origin)
+        self._request.connection.write('\r\n')
+        if self._request.ws_protocol:
+            self._request.connection.write('Sec-WebSocket-Protocol: ')
+            self._request.connection.write(self._request.ws_protocol)
+            self._request.connection.write('\r\n')
+        # 5.2 12. send two bytes 0x0D 0x0A.
+        self._request.connection.write('\r\n')
+        # 5.2 13. send /response/
+        self._request.connection.write(self._request.ws_challenge_md5)
+
+
+# vi:sts=4 sw=4 et
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/headerparserhandler.py
@@ -0,0 +1,138 @@
+# Copyright 2009, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""PythonHeaderParserHandler for mod_pywebsocket.
+
+Apache HTTP Server and mod_python must be configured such that this
+function is called to handle Web Socket request.
+"""
+
+import logging
+
+from mod_python import apache
+
+from mod_pywebsocket import dispatch
+from mod_pywebsocket import handshake
+from mod_pywebsocket import util
+
+
+# PythonOption to specify the handler root directory.
+_PYOPT_HANDLER_ROOT = 'mod_pywebsocket.handler_root'
+
+# PythonOption to specify the handler scan directory.
+# This must be a directory under the root directory.
+# The default is the root directory.
+_PYOPT_HANDLER_SCAN = 'mod_pywebsocket.handler_scan'
+
+# PythonOption to specify to allow draft75 handshake.
+# The default is None (Off)
+_PYOPT_ALLOW_DRAFT75 = 'mod_pywebsocket.allow_draft75'
+
+
+class ApacheLogHandler(logging.Handler):
+    """Wrapper logging.Handler to emit log message to apache's error.log"""
+    _LEVELS = {
+        logging.DEBUG: apache.APLOG_DEBUG,
+        logging.INFO: apache.APLOG_INFO,
+        logging.WARNING: apache.APLOG_WARNING,
+        logging.ERROR: apache.APLOG_ERR,
+        logging.CRITICAL: apache.APLOG_CRIT,
+        }
+    def __init__(self, request=None):
+        logging.Handler.__init__(self)
+        self.log_error = apache.log_error
+        if request is not None:
+             self.log_error = request.log_error
+
+    def emit(self, record):
+        apache_level = apache.APLOG_DEBUG
+        if record.levelno in ApacheLogHandler._LEVELS:
+            apache_level = ApacheLogHandler._LEVELS[record.levelno]
+        self.log_error(record.getMessage(), apache_level)
+
+
+logging.getLogger("mod_pywebsocket").addHandler(ApacheLogHandler())
+
+
+def _create_dispatcher():
+    _HANDLER_ROOT = apache.main_server.get_options().get(
+            _PYOPT_HANDLER_ROOT, None)
+    if not _HANDLER_ROOT:
+        raise Exception('PythonOption %s is not defined' % _PYOPT_HANDLER_ROOT,
+                        apache.APLOG_ERR)
+    _HANDLER_SCAN = apache.main_server.get_options().get(
+            _PYOPT_HANDLER_SCAN, _HANDLER_ROOT)
+    dispatcher = dispatch.Dispatcher(_HANDLER_ROOT, _HANDLER_SCAN)
+    for warning in dispatcher.source_warnings():
+        apache.log_error('mod_pywebsocket: %s' % warning, apache.APLOG_WARNING)
+    return dispatcher
+
+
+# Initialize
+_dispatcher = _create_dispatcher()
+
+
+def headerparserhandler(request):
+    """Handle request.
+
+    Args:
+        request: mod_python request.
+
+    This function is named headerparserhandler because it is the default name
+    for a PythonHeaderParserHandler.
+    """
+
+    try:
+        allowDraft75 = apache.main_server.get_options().get(
+		_PYOPT_ALLOW_DRAFT75, None)
+        handshaker = handshake.Handshaker(request, _dispatcher,
+                                          allowDraft75=allowDraft75)
+        handshaker.do_handshake()
+        request.log_error('mod_pywebsocket: resource: %r' % request.ws_resource,
+                          apache.APLOG_DEBUG)
+        try:
+            _dispatcher.transfer_data(request)
+        except Exception, e:
+            # Catch exception in transfer_data.
+            # In this case, handshake has been successful, so just log the
+            # exception and return apache.DONE
+            request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_WARNING)
+    except handshake.HandshakeError, e:
+        # Handshake for ws/wss failed.
+        # But the request can be valid http/https request.
+        request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO)
+        return apache.DECLINED
+    except dispatch.DispatchError, e:
+        request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_WARNING)
+        return apache.DECLINED
+    return apache.DONE  # Return DONE such that no other handlers are invoked.
+
+
+# vi:sts=4 sw=4 et
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/memorizingfile.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+#
+# Copyright 2009, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Memorizing file.
+
+A memorizing file wraps a file and memorizes lines read by readline.
+"""
+
+
+import sys
+
+
+class MemorizingFile(object):
+    """MemorizingFile wraps a file and memorizes lines read by readline.
+
+    Note that data read by other methods are not memorized. This behavior
+    is good enough for memorizing lines SimpleHTTPServer reads before
+    the control reaches WebSocketRequestHandler.
+    """
+    def __init__(self, file_, max_memorized_lines=sys.maxint):
+        """Construct an instance.
+
+        Args:
+            file_: the file object to wrap.
+            max_memorized_lines: the maximum number of lines to memorize.
+                Only the first max_memorized_lines are memorized.
+                Default: sys.maxint. 
+        """
+        self._file = file_
+        self._memorized_lines = []
+        self._max_memorized_lines = max_memorized_lines
+
+    def __getattribute__(self, name):
+        if name in ('_file', '_memorized_lines', '_max_memorized_lines',
+                    'readline', 'get_memorized_lines'):
+            return object.__getattribute__(self, name)
+        return self._file.__getattribute__(name)
+
+    def readline(self):
+        """Override file.readline and memorize the line read."""
+
+        line = self._file.readline()
+        if line and len(self._memorized_lines) < self._max_memorized_lines:
+            self._memorized_lines.append(line)
+        return line
+
+    def get_memorized_lines(self):
+        """Get lines memorized so far."""
+        return self._memorized_lines
+
+
+# vi:sts=4 sw=4 et
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/msgutil.py
@@ -0,0 +1,290 @@
+# Copyright 2009, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Message related utilities.
+
+Note: request.connection.write/read are used in this module, even though
+mod_python document says that they should be used only in connection handlers.
+Unfortunately, we have no other options. For example, request.write/read are
+not suitable because they don't allow direct raw bytes writing/reading.
+"""
+
+
+import Queue
+import threading
+
+from mod_pywebsocket import util
+
+
+class MsgUtilException(Exception):
+    pass
+
+
+class ConnectionTerminatedException(MsgUtilException):
+    pass
+
+
+def _read(request, length):
+    bytes = request.connection.read(length)
+    if not bytes:
+        raise MsgUtilException(
+                'Failed to receive message from %r' %
+                        (request.connection.remote_addr,))
+    return bytes
+
+
+def _write(request, bytes):
+    try:
+        request.connection.write(bytes)
+    except Exception, e:
+        util.prepend_message_to_exception(
+                'Failed to send message to %r: ' %
+                        (request.connection.remote_addr,),
+                e)
+        raise
+
+
+def close_connection(request):
+    """Close connection.
+
+    Args:
+        request: mod_python request.
+    """
+    if request.server_terminated:
+        return
+    # 5.3 the server may decide to terminate the WebSocket connection by
+    # running through the following steps:
+    # 1. send a 0xFF byte and a 0x00 byte to the client to indicate the start
+    # of the closing handshake.
+    _write(request, '\xff\x00')
+    request.server_terminated = True
+    # TODO(ukai): 2. wait until the /client terminated/ flag has been set, or
+    # until a server-defined timeout expires.
+    # TODO: 3. close the WebSocket connection.
+    # note: mod_python Connection (mp_conn) doesn't have close method.
+
+
+def send_message(request, message):
+    """Send message.
+
+    Args:
+        request: mod_python request.
+        message: unicode string to send.
+    Raises:
+        ConnectionTerminatedException: when server already terminated.
+    """
+    if request.server_terminated:
+        raise ConnectionTerminatedException
+    _write(request, '\x00' + message.encode('utf-8') + '\xff')
+
+
+def receive_message(request):
+    """Receive a Web Socket frame and return its payload as unicode string.
+
+    Args:
+        request: mod_python request.
+    Raises:
+        ConnectionTerminatedException: when client already terminated.
+    """
+
+    if request.client_terminated:
+        raise ConnectionTerminatedException
+    while True:
+        # Read 1 byte.
+        # mp_conn.read will block if no bytes are available.
+        # Timeout is controlled by TimeOut directive of Apache.
+        frame_type_str = _read(request, 1)
+        frame_type = ord(frame_type_str[0])
+        if (frame_type & 0x80) == 0x80:
+            # The payload length is specified in the frame.
+            # Read and discard.
+            length = _payload_length(request)
+            _receive_bytes(request, length)
+            # 5.3 3. 12. if /type/ is 0xFF and /length/ is 0, then set the
+            # /client terminated/ flag and abort these steps.
+            if frame_type == 0xFF and length == 0:
+                request.client_terminated = True
+                raise ConnectionTerminatedException
+        else:
+            # The payload is delimited with \xff.
+            bytes = _read_until(request, '\xff')
+            # The Web Socket protocol section 4.4 specifies that invalid
+            # characters must be replaced with U+fffd REPLACEMENT CHARACTER.
+            message = bytes.decode('utf-8', 'replace')
+            if frame_type == 0x00:
+                return message
+            # Discard data of other types.
+
+
+def _payload_length(request):
+    length = 0
+    while True:
+        b_str = _read(request, 1)
+        b = ord(b_str[0])
+        length = length * 128 + (b & 0x7f)
+        if (b & 0x80) == 0:
+            break
+    return length
+
+
+def _receive_bytes(request, length):
+    bytes = []
+    while length > 0:
+        new_bytes = _read(request, length)
+        bytes.append(new_bytes)
+        length -= len(new_bytes)
+    return ''.join(bytes)
+
+
+def _read_until(request, delim_char):
+    bytes = []
+    while True:
+        ch = _read(request, 1)
+        if ch == delim_char:
+            break
+        bytes.append(ch)
+    return ''.join(bytes)
+
+
+class MessageReceiver(threading.Thread):
+    """This class receives messages from the client.
+
+    This class provides three ways to receive messages: blocking, non-blocking,
+    and via callback. Callback has the highest precedence.
+
+    Note: This class should not be used with the standalone server for wss
+    because pyOpenSSL used by the server raises a fatal error if the socket
+    is accessed from multiple threads.
+    """
+    def __init__(self, request, onmessage=None):
+        """Construct an instance.
+
+        Args:
+            request: mod_python request.
+            onmessage: a function to be called when a message is received.
+                       May be None. If not None, the function is called on
+                       another thread. In that case, MessageReceiver.receive
+                       and MessageReceiver.receive_nowait are useless because
+                       they will never return any messages.
+        """
+        threading.Thread.__init__(self)
+        self._request = request
+        self._queue = Queue.Queue()
+        self._onmessage = onmessage
+        self._stop_requested = False
+        self.setDaemon(True)
+        self.start()
+
+    def run(self):
+        try:
+            while not self._stop_requested:
+                message = receive_message(self._request)
+                if self._onmessage:
+                    self._onmessage(message)
+                else:
+                    self._queue.put(message)
+        finally:
+            close_connection(self._request)
+
+    def receive(self):
+        """ Receive a message from the channel, blocking.
+
+        Returns:
+            message as a unicode string.
+        """
+        return self._queue.get()
+
+    def receive_nowait(self):
+        """ Receive a message from the channel, non-blocking.
+
+        Returns:
+            message as a unicode string if available. None otherwise.
+        """
+        try:
+            message = self._queue.get_nowait()
+        except Queue.Empty:
+            message = None
+        return message
+
+    def stop(self):
+        """Request to stop this instance.
+
+        The instance will be stopped after receiving the next message.
+        This method may not be very useful, but there is no clean way
+        in Python to forcefully stop a running thread.
+        """
+        self._stop_requested = True
+
+
+class MessageSender(threading.Thread):
+    """This class sends messages to the client.
+
+    This class provides both synchronous and asynchronous ways to send
+    messages.
+
+    Note: This class should not be used with the standalone server for wss
+    because pyOpenSSL used by the server raises a fatal error if the socket
+    is accessed from multiple threads.
+    """
+    def __init__(self, request):
+        """Construct an instance.
+
+        Args:
+            request: mod_python request.
+        """
+        threading.Thread.__init__(self)
+        self._request = request
+        self._queue = Queue.Queue()
+        self.setDaemon(True)
+        self.start()
+
+    def run(self):
+        while True:
+            message, condition = self._queue.get()
+            condition.acquire()
+            send_message(self._request, message)
+            condition.notify()
+            condition.release()
+
+    def send(self, message):
+        """Send a message, blocking."""
+
+        condition = threading.Condition()
+        condition.acquire()
+        self._queue.put((message, condition))
+        condition.wait()
+
+    def send_nowait(self, message):
+        """Send a message, non-blocking."""
+
+        self._queue.put((message, threading.Condition()))
+
+
+# vi:sts=4 sw=4 et
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/util.py
@@ -0,0 +1,121 @@
+# Copyright 2009, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Web Sockets utilities.
+"""
+
+
+import StringIO
+import os
+import re
+import traceback
+
+
+def get_stack_trace():
+    """Get the current stack trace as string.
+
+    This is needed to support Python 2.3.
+    TODO: Remove this when we only support Python 2.4 and above.
+          Use traceback.format_exc instead.
+    """
+
+    out = StringIO.StringIO()
+    traceback.print_exc(file=out)
+    return out.getvalue()
+
+
+def prepend_message_to_exception(message, exc):
+    """Prepend message to the exception."""
+
+    exc.args = (message + str(exc),)
+    return
+
+
+def __translate_interp(interp, cygwin_path):
+    """Translate interp program path for Win32 python to run cygwin program
+    (e.g. perl).  Note that it doesn't support path that contains space,
+    which is typically true for Unix, where #!-script is written.
+    For Win32 python, cygwin_path is a directory of cygwin binaries.
+
+    Args:
+      interp: interp command line
+      cygwin_path: directory name of cygwin binary, or None
+    Returns:
+      translated interp command line.
+    """
+    if not cygwin_path:
+        return interp
+    m = re.match("^[^ ]*/([^ ]+)( .*)?", interp)
+    if m:
+        cmd = os.path.join(cygwin_path, m.group(1))
+        return cmd + m.group(2)
+    return interp
+
+
+def get_script_interp(script_path, cygwin_path=None):
+    """Gets #!-interpreter command line from the script.
+
+    It also fixes command path.  When Cygwin Python is used, e.g. in WebKit,
+    it could run "/usr/bin/perl -wT hello.pl".
+    When Win32 Python is used, e.g. in Chromium, it couldn't.  So, fix
+    "/usr/bin/perl" to "<cygwin_path>\perl.exe".
+
+    Args:
+      script_path: pathname of the script
+      cygwin_path: directory name of cygwin binary, or None
+    Returns:
+      #!-interpreter command line, or None if it is not #!-script.
+    """
+    fp = open(script_path)
+    line = fp.readline()
+    fp.close()
+    m = re.match("^#!(.*)", line)
+    if m:
+        return __translate_interp(m.group(1), cygwin_path)
+    return None
+
+def wrap_popen3_for_win(cygwin_path):
+    """Wrap popen3 to support #!-script on Windows.
+
+    Args:
+      cygwin_path:  path for cygwin binary if command path is needed to be
+                    translated.  None if no translation required.
+    """
+    __orig_popen3 = os.popen3
+    def __wrap_popen3(cmd, mode='t', bufsize=-1):
+        cmdline = cmd.split(' ')
+        interp = get_script_interp(cmdline[0], cygwin_path)
+        if interp:
+            cmd = interp + " " + cmd
+        return __orig_popen3(cmd, mode, bufsize)
+    os.popen3 = __wrap_popen3
+
+
+# vi:sts=4 sw=4 et
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/pywebsocket/standalone.py
@@ -0,0 +1,472 @@
+#!/usr/bin/env python
+#
+# Copyright 2009, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+"""Standalone Web Socket server.
+
+Use this server to run mod_pywebsocket without Apache HTTP Server.
+
+Usage:
+    python standalone.py [-p <ws_port>] [-w <websock_handlers>]
+                         [-s <scan_dir>]
+                         [-d <document_root>]
+                         [-m <websock_handlers_map_file>]
+                         ... for other options, see _main below ...
+
+<ws_port> is the port number to use for ws:// connection.
+
+<document_root> is the path to the root directory of HTML files.
+
+<websock_handlers> is the path to the root directory of Web Socket handlers.
+See __init__.py for details of <websock_handlers> and how to write Web Socket
+handlers. If this path is relative, <document_root> is used as the base.
+
+<scan_dir> is a path under the root directory. If specified, only the handlers
+under scan_dir are scanned. This is useful in saving scan time.
+
+Note:
+This server is derived from SocketServer.ThreadingMixIn. Hence a thread is
+used for each request.
+
+SECURITY WARNING: This uses CGIHTTPServer and CGIHTTPServer is not secure.
+It may execute arbitrary Python code or external programs. It should not be
+used outside a firewall.
+"""
+
+import BaseHTTPServer
+import CGIHTTPServer
+import SimpleHTTPServer
+import SocketServer
+import logging
+import logging.handlers
+import optparse
+import os
+import re
+import socket
+import sys
+
+_HAS_OPEN_SSL = False
+try:
+    import OpenSSL.SSL
+    _HAS_OPEN_SSL = True
+except ImportError:
+    pass
+
+from mod_pywebsocket import dispatch
+from mod_pywebsocket import handshake
+from mod_pywebsocket import memorizingfile
+from mod_pywebsocket import util
+
+
+_LOG_LEVELS = {
+    'debug': logging.DEBUG,
+    'info': logging.INFO,
+    'warn': logging.WARN,
+    'error': logging.ERROR,
+    'critical': logging.CRITICAL};
+
+_DEFAULT_LOG_MAX_BYTES = 1024 * 256
+_DEFAULT_LOG_BACKUP_COUNT = 5
+
+_DEFAULT_REQUEST_QUEUE_SIZE = 128
+
+# 1024 is practically large enough to contain WebSocket handshake lines.
+_MAX_MEMORIZED_LINES = 1024
+
+def _print_warnings_if_any(dispatcher):
+    warnings = dispatcher.source_warnings()
+    if warnings:
+        for warning in warnings:
+            logging.warning('mod_pywebsocket: %s' % warning)
+
+
+class _StandaloneConnection(object):
+    """Mimic mod_python mp_conn."""
+
+    def __init__(self, request_handler):
+        """Construct an instance.
+
+        Args:
+            request_handler: A WebSocketRequestHandler instance.
+        """
+        self._request_handler = request_handler
+
+    def get_local_addr(self):
+        """Getter to mimic mp_conn.local_addr."""
+        return (self._request_handler.server.server_name,
+                self._request_handler.server.server_port)
+    local_addr = property(get_local_addr)
+
+    def get_remote_addr(self):
+        """Getter to mimic mp_conn.remote_addr.
+
+        Setting the property in __init__ won't work because the request
+        handler is not initialized yet there."""
+        return self._request_handler.client_address
+    remote_addr = property(get_remote_addr)
+
+    def write(self, data):
+        """Mimic mp_conn.write()."""
+        return self._request_handler.wfile.write(data)
+
+    def read(self, length):
+        """Mimic mp_conn.read()."""
+        return self._request_handler.rfile.read(length)
+
+    def get_memorized_lines(self):
+        """Get memorized lines."""
+        return self._request_handler.rfile.get_memorized_lines()
+
+
+class _StandaloneRequest(object):
+    """Mimic mod_python request."""
+
+    def __init__(self, request_handler, use_tls):
+        """Construct an instance.
+
+        Args:
+            request_handler: A WebSocketRequestHandler instance.
+        """
+        self._request_handler = request_handler
+        self.connection = _StandaloneConnection(request_handler)
+        self._use_tls = use_tls
+
+    def get_uri(self):
+        """Getter to mimic request.uri."""
+        return self._request_handler.path
+    uri = property(get_uri)
+
+    def get_method(self):
+        """Getter to mimic request.method."""
+        return self._request_handler.command
+    method = property(get_method)
+
+    def get_headers_in(self):
+        """Getter to mimic request.headers_in."""
+        return self._request_handler.headers
+    headers_in = property(get_headers_in)
+
+    def is_https(self):
+        """Mimic request.is_https()."""
+        return self._use_tls
+
+
+class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
+    """HTTPServer specialized for Web Socket."""
+
+    SocketServer.ThreadingMixIn.daemon_threads = True
+
+    def __init__(self, server_address, RequestHandlerClass):
+        """Override SocketServer.BaseServer.__init__."""
+
+        SocketServer.BaseServer.__init__(
+                self, server_address, RequestHandlerClass)
+        self.socket = self._create_socket()
+        self.server_bind()
+        self.server_activate()
+
+    def _create_socket(self):
+        socket_ = socket.socket(self.address_family, self.socket_type)
+        if WebSocketServer.options.use_tls:
+            ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
+            ctx.use_privatekey_file(WebSocketServer.options.private_key)
+            ctx.use_certificate_file(WebSocketServer.options.certificate)
+            socket_ = OpenSSL.SSL.Connection(ctx, socket_)
+        return socket_
+
+    def handle_error(self, rquest, client_address):
+        """Override SocketServer.handle_error."""
+
+        logging.error(
+            ('Exception in processing request from: %r' % (client_address,)) +
+            '\n' + util.get_stack_trace())
+        # Note: client_address is a tuple. To match it against %r, we need the
+        # trailing comma.
+
+
+class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
+    """CGIHTTPRequestHandler specialized for Web Socket."""
+
+    def setup(self):
+        """Override SocketServer.StreamRequestHandler.setup."""
+
+        self.connection = self.request
+        self.rfile = memorizingfile.MemorizingFile(
+                socket._fileobject(self.request, 'rb', self.rbufsize),
+                max_memorized_lines=_MAX_MEMORIZED_LINES)
+        self.wfile = socket._fileobject(self.request, 'wb', self.wbufsize)
+
+    def __init__(self, *args, **keywords):
+        self._request = _StandaloneRequest(
+                self, WebSocketRequestHandler.options.use_tls)
+        self._dispatcher = WebSocketRequestHandler.options.dispatcher
+        self._print_warnings_if_any()
+        self._handshaker = handshake.Handshaker(
+                self._request, self._dispatcher,
+                allowDraft75=WebSocketRequestHandler.options.allow_draft75,
+                strict=WebSocketRequestHandler.options.strict)
+        CGIHTTPServer.CGIHTTPRequestHandler.__init__(
+                self, *args, **keywords)
+
+    def _print_warnings_if_any(self):
+        warnings = self._dispatcher.source_warnings()
+        if warnings:
+            for warning in warnings:
+                logging.warning('mod_pywebsocket: %s' % warning)
+
+    def parse_request(self):
+        """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request.
+
+        Return True to continue processing for HTTP(S), False otherwise.
+        """
+        result = CGIHTTPServer.CGIHTTPRequestHandler.parse_request(self)
+        if result:
+            try:
+                self._handshaker.do_handshake()
+                try:
+                    self._dispatcher.transfer_data(self._request)
+                except Exception, e:
+                    # Catch exception in transfer_data.
+                    # In this case, handshake has been successful, so just log
+                    # the exception and return False.
+                    logging.info('mod_pywebsocket: %s' % e)
+                return False
+            except handshake.HandshakeError, e:
+                # Handshake for ws(s) failed. Assume http(s).
+                logging.info('mod_pywebsocket: %s' % e)
+                return True
+            except dispatch.DispatchError, e:
+                logging.warning('mod_pywebsocket: %s' % e)
+                return False
+            except Exception, e:
+                logging.warning('mod_pywebsocket: %s' % e)
+                logging.info('mod_pywebsocket: %s' % util.get_stack_trace())
+                return False
+        return result
+
+    def log_request(self, code='-', size='-'):
+        """Override BaseHTTPServer.log_request."""
+
+        logging.info('"%s" %s %s',
+                     self.requestline, str(code), str(size))
+
+    def log_error(self, *args):
+        """Override BaseHTTPServer.log_error."""
+
+        # Despite the name, this method is for warnings than for errors.
+        # For example, HTTP status code is logged by this method.
+        logging.warn('%s - %s' % (self.address_string(), (args[0] % args[1:])))
+
+    def is_cgi(self):
+        """Test whether self.path corresponds to a CGI script.
+
+        Add extra check that self.path doesn't contains ..
+        Also check if the file is a executable file or not.
+        If the file is not executable, it is handled as static file or dir
+        rather than a CGI script.
+        """
+        if CGIHTTPServer.CGIHTTPRequestHandler.is_cgi(self):
+            if '..' in self.path:
+                return False
+            # strip query parameter from request path
+            resource_name = self.path.split('?', 2)[0]
+            # convert resource_name into real path name in filesystem.
+            scriptfile = self.translate_path(resource_name)
+            if not os.path.isfile(scriptfile):
+                return False
+            if not self.is_executable(scriptfile):
+                return False
+            return True
+        return False
+
+
+def _configure_logging(options):
+    logger = logging.getLogger()
+    logger.setLevel(_LOG_LEVELS[options.log_level])
+    if options.log_file:
+        handler = logging.handlers.RotatingFileHandler(
+                options.log_file, 'a', options.log_max, options.log_count)
+    else:
+        handler = logging.StreamHandler()
+    formatter = logging.Formatter(
+            "[%(asctime)s] [%(levelname)s] %(name)s: %(message)s")
+    handler.setFormatter(formatter)
+    logger.addHandler(handler)
+
+def _alias_handlers(dispatcher, websock_handlers_map_file):
+    """Set aliases specified in websock_handler_map_file in dispatcher.
+
+    Args:
+        dispatcher: dispatch.Dispatcher instance
+        websock_handler_map_file: alias map file
+    """
+    fp = open(websock_handlers_map_file)
+    try:
+        for line in fp:
+            if line[0] == '#' or line.isspace():
+                continue
+            m = re.match('(\S+)\s+(\S+)', line)
+            if not m:
+                logging.warning('Wrong format in map file:' + line)
+                continue
+            try:
+                dispatcher.add_resource_path_alias(
+                    m.group(1), m.group(2))
+            except dispatch.DispatchError, e:
+                logging.error(str(e))
+    finally:
+        fp.close()
+
+
+
+def _main():
+    parser = optparse.OptionParser()
+    parser.add_option('-H', '--server-host', '--server_host',
+                      dest='server_host',
+                      default='',
+                      help='server hostname to listen to')
+    parser.add_option('-p', '--port', dest='port', type='int',
+                      default=handshake.DEFAULT_WEB_SOCKET_PORT,
+                      help='port to listen to')
+    parser.add_option('-w', '--websock-handlers', '--websock_handlers',
+                      dest='websock_handlers',
+                      default='.',
+                      help='Web Socket handlers root directory.')
+    parser.add_option('-m', '--websock-handlers-map-file',
+                      '--websock_handlers_map_file',
+                      dest='websock_handlers_map_file',
+                      default=None,
+                      help=('Web Socket handlers map file. '
+                            'Each line consists of alias_resource_path and '
+                            'existing_resource_path, separated by spaces.'))
+    parser.add_option('-s', '--scan-dir', '--scan_dir', dest='scan_dir',
+                      default=None,
+                      help=('Web Socket handlers scan directory. '
+                            'Must be a directory under websock_handlers.'))
+    parser.add_option('-d', '--document-root', '--document_root',
+                      dest='document_root', default='.',
+                      help='Document root directory.')
+    parser.add_option('-x', '--cgi-paths', '--cgi_paths', dest='cgi_paths',
+                      default=None,
+                      help=('CGI paths relative to document_root.'
+                            'Comma-separated. (e.g -x /cgi,/htbin) '
+                            'Files under document_root/cgi_path are handled '
+                            'as CGI programs. Must be executable.'))
+    parser.add_option('-t', '--tls', dest='use_tls', action='store_true',
+                      default=False, help='use TLS (wss://)')
+    parser.add_option('-k', '--private-key', '--private_key',
+                      dest='private_key',
+                      default='', help='TLS private key file.')
+    parser.add_option('-c', '--certificate', dest='certificate',
+                      default='', help='TLS certificate file.')
+    parser.add_option('-l', '--log-file', '--log_file', dest='log_file',
+                      default='', help='Log file.')
+    parser.add_option('--log-level', '--log_level', type='choice',
+                      dest='log_level', default='warn',
+                      choices=['debug', 'info', 'warn', 'error', 'critical'],
+                      help='Log level.')
+    parser.add_option('--log-max', '--log_max', dest='log_max', type='int',
+                      default=_DEFAULT_LOG_MAX_BYTES,
+                      help='Log maximum bytes')
+    parser.add_option('--log-count', '--log_count', dest='log_count',
+                      type='int', default=_DEFAULT_LOG_BACKUP_COUNT,
+                      help='Log backup count')
+    parser.add_option('--allow-draft75', dest='allow_draft75',
+                      action='store_true', default=False,
+                      help='Allow draft 75 handshake')
+    parser.add_option('--strict', dest='strict', action='store_true',
+                      default=False, help='Strictly check handshake request')
+    parser.add_option('-q', '--queue', dest='request_queue_size', type='int',
+                      default=_DEFAULT_REQUEST_QUEUE_SIZE,
+                      help='request queue size')
+    options = parser.parse_args()[0]
+
+    os.chdir(options.document_root)
+
+    _configure_logging(options)
+
+    SocketServer.TCPServer.request_queue_size = options.request_queue_size
+    CGIHTTPServer.CGIHTTPRequestHandler.cgi_directories = []
+
+    if options.cgi_paths:
+        CGIHTTPServer.CGIHTTPRequestHandler.cgi_directories = \
+            options.cgi_paths.split(',')
+        if sys.platform in ('cygwin', 'win32'):
+            cygwin_path = None
+            # For Win32 Python, it is expected that CYGWIN_PATH
+            # is set to a directory of cygwin binaries.
+            # For example, websocket_server.py in Chromium sets CYGWIN_PATH to
+            # full path of third_party/cygwin/bin.
+            if 'CYGWIN_PATH' in os.environ:
+                cygwin_path = os.environ['CYGWIN_PATH']
+            util.wrap_popen3_for_win(cygwin_path)
+            def __check_script(scriptpath):
+                return util.get_script_interp(scriptpath, cygwin_path)
+            CGIHTTPServer.executable = __check_script
+
+    if options.use_tls:
+        if not _HAS_OPEN_SSL:
+            logging.critical('To use TLS, install pyOpenSSL.')
+            sys.exit(1)
+        if not options.private_key or not options.certificate:
+            logging.critical(
+                    'To use TLS, specify private_key and certificate.')
+            sys.exit(1)
+
+    if not options.scan_dir:
+        options.scan_dir = options.websock_handlers
+
+    try:
+        # Share a Dispatcher among request handlers to save time for
+        # instantiation.  Dispatcher can be shared because it is thread-safe.
+        options.dispatcher = dispatch.Dispatcher(options.websock_handlers,
+                                                 options.scan_dir)
+        if options.websock_handlers_map_file:
+            _alias_handlers(options.dispatcher,
+                            options.websock_handlers_map_file)
+        _print_warnings_if_any(options.dispatcher)
+
+        WebSocketRequestHandler.options = options
+        WebSocketServer.options = options
+
+        server = WebSocketServer((options.server_host, options.port),
+                                 WebSocketRequestHandler)
+        server.serve_forever()
+    except Exception, e:
+        logging.critical(str(e))
+        sys.exit(1)
+
+
+if __name__ == '__main__':
+    _main()
+
+
+# vi:sts=4 sw=4 et
--- a/testing/mochitest/runtests.py.in
+++ b/testing/mochitest/runtests.py.in
@@ -260,16 +260,18 @@ See <http://mochikit.com/doc/html/MochiK
     options.utilityPath = mochitest.getFullPath(options.utilityPath)
     options.certPath = mochitest.getFullPath(options.certPath)
     if options.symbolsPath and not isURL(options.symbolsPath):
       options.symbolsPath = mochitest.getFullPath(options.symbolsPath)
 
     options.webServer = self._automation.DEFAULT_WEB_SERVER
     options.httpPort = self._automation.DEFAULT_HTTP_PORT
     options.sslPort = self._automation.DEFAULT_SSL_PORT
+    options.webSocketPort = self._automation.DEFAULT_WEBSOCKET_PORT
+    options.webSocketProxyPort = self._automation.DEFAULT_WEBSOCKET_PROXY_PORT
 
     if options.vmwareRecording:
       if not self._automation.IS_WIN32:
         self.error("use-vmware-recording is only supported on Windows.")
       mochitest.vmwareHelperPath = os.path.join(
         options.utilityPath, VMWARE_RECORDING_HELPER_BASENAME + ".dll")
       if not os.path.exists(mochitest.vmwareHelperPath):
         self.error("%s not found, cannot automate VMware recording." %
@@ -338,16 +340,37 @@ class MochitestServer:
     try:
       c = urllib2.urlopen(self.shutdownURL)
       c.read()
       c.close()
       self._process.wait()
     except:
       self._process.kill()
 
+class WebSocketServer(object):
+  "Class which encapsulates the mod_pywebsocket server"
+
+  def __init__(self, automation, options, scriptdir):
+    self.port = options.webSocketPort
+    self._automation = automation
+    self._scriptdir = scriptdir
+
+  def start(self):
+    script = os.path.join(self._scriptdir, 'pywebsocket/standalone.py')
+    cmd = [sys.executable, script, '-p', str(self.port), '-w', self._scriptdir, '-l', os.path.join(self._scriptdir, "websock.log"), '--log-level=debug']
+
+    self._process = self._automation.Process(cmd)
+    pid = self._process.pid
+    if pid < 0:
+      print "Error starting websocket server."
+      sys.exit(2)
+    self._automation.log.info("INFO | runtests.py | Websocket server pid: %d", pid)
+
+  def stop(self):
+    self._process.kill()
 
 class Mochitest(object):
   # Path to the test script on the server
   TEST_PATH = "/tests/"
   CHROME_PATH = "/redirect.html";
   A11Y_PATH = "/redirect-a11y.html"
   urlOpts = []
   runSSLTunnel = True
@@ -384,16 +407,30 @@ class Mochitest(object):
     elif options.a11y:
       testURL = testHost + self.A11Y_PATH
       if options.testPath:
         self.urlOpts.append("testPath=" + encodeURIComponent(options.testPath))
     elif options.browserChrome:
       testURL = "about:blank"
     return testURL
 
+  def startWebSocketServer(self, options):
+    """ Launch the websocket server """
+    if options.webServer != '127.0.0.1':
+      return
+
+    self.wsserver = WebSocketServer(self.automation, options, self.SCRIPT_DIRECTORY)
+    self.wsserver.start()
+
+  def stopWebSocketServer(self, options):
+    if options.webServer != '127.0.0.1':
+      return
+
+    self.wsserver.stop()
+
   def startWebServer(self, options):
     if options.webServer != '127.0.0.1':
       return
 
     """ Create the webserver and start it up """
     self.server = MochitestServer(self.automation, options)
     self.server.start()
 
@@ -542,17 +579,17 @@ class Mochitest(object):
     self.leak_report_file = os.path.join(options.profilePath, "runtests_leaks.log")
 
     browserEnv = self.buildBrowserEnv(options)
     if (browserEnv == None):
       return 1
 
     manifest = self.buildProfile(options)
     self.startWebServer(options)
-
+    self.startWebSocketServer(options)
 
     testURL = self.buildTestPath(options)
     self.buildURLOptions(options)
     if (len(self.urlOpts) > 0):
       testURL += "?" + "&".join(self.urlOpts)
 
     self.runExtensionRegistration(options, browserEnv)
 
@@ -582,16 +619,17 @@ class Mochitest(object):
                                 debuggerInfo=debuggerInfo,
                                 symbolsPath=options.symbolsPath,
                                 timeout = timeout)
 
     if options.vmwareRecording:
       self.stopVMwareRecording();
 
     self.stopWebServer(options)
+    self.stopWebSocketServer(options)
     processLeakLog(self.leak_report_file, options.leakThreshold)
     self.automation.log.info("\nINFO | runtests.py | Running tests: end.")
 
     self.cleanup(manifest, options)
     return status
 
   def makeTestConfig(self, options):
     "Creates a test configuration file for customizing test execution."
@@ -685,13 +723,17 @@ def main():
   if options == None:
     sys.exit(1)
 
   options.utilityPath = mochitest.getFullPath(options.utilityPath)
   options.certPath = mochitest.getFullPath(options.certPath)
   if options.symbolsPath and not isURL(options.symbolsPath):
     options.symbolsPath = mochitest.getFullPath(options.symbolsPath)
 
-  automation.setServerInfo(options.webServer, options.httpPort, options.sslPort)
+  automation.setServerInfo(options.webServer, 
+                           options.httpPort, 
+                           options.sslPort, 
+                           options.webSocketPort, 
+                           options.webSocketProxyPort)
   sys.exit(mochitest.runTests(options))
 
 if __name__ == "__main__":
   main()
--- a/testing/mochitest/ssltunnel/ssltunnel.cpp
+++ b/testing/mochitest/ssltunnel/ssltunnel.cpp
@@ -116,27 +116,36 @@ enum client_auth_option {
 };
 
 // Structs for passing data into jobs on the thread pool
 typedef struct {
   PRInt32 listen_port;
   string cert_nickname;
   PLHashTable* host_cert_table;
   PLHashTable* host_clientauth_table;
+  // If not empty, and this server is using HTTP CONNECT, connections
+  // will be proxied to this address.
+  PRNetAddr remote_addr;
+  // True if no SSL should be used for this server's connections.
+  bool http_proxy_only;
+  // The original host in the Host: header for the initial connection is
+  // stored here, for proxied connections.
+  string original_host;
 } server_info_t;
 
 typedef struct {
   PRFileDesc* client_sock;
   PRNetAddr client_addr;
   server_info_t* server_info;
 } connection_info_t;
 
 const PRInt32 BUF_SIZE = 16384;
 const PRInt32 BUF_MARGIN = 1024;
 const PRInt32 BUF_TOTAL = BUF_SIZE + BUF_MARGIN;
+const char HEADER_HOST[] = "Host:";
 
 struct relayBuffer
 {
   char *buffer, *bufferhead, *buffertail, *bufferend;
 
   relayBuffer()
   {
     // Leave 1024 bytes more for request line manipulations
@@ -249,17 +258,17 @@ bool ReadConnectRequest(server_info_t* s
   if (strncmp(buffer.buffertail-4, "\r\n\r\n", 4)) {
     printf(" !! request is not tailed with CRLFCRLF but with %x %x %x %x", 
            *(buffer.buffertail-4),
            *(buffer.buffertail-3),
            *(buffer.buffertail-2),
            *(buffer.buffertail-1));
     return false;
   }
-  
+
   printf(" parsing initial connect request, dump:\n%.*s\n", (int)buffer.present(), buffer.bufferhead);
 
   *result = 400;
 
   char* token;
   char* _caret;
   token = strtok2(buffer.bufferhead, " ", &_caret);
   if (!token) {
@@ -337,16 +346,110 @@ bool ConfigureSSLServerSocket(PRFileDesc
   }
 
   SSL_ResetHandshake(ssl_socket, PR_TRUE);
 
   return true;
 }
 
 /**
+ * This function examines the buffer for a S5ec-WebSocket-Location: field, 
+ * and if it's present, it replaces the hostname in that field with the
+ * value in the server's original_host field.  This function works
+ * in the reverse direction as AdjustHost(), replacing the real hostname
+ * of a response with the potentially fake hostname that is expected
+ * by the browser (e.g., mochi.test).
+ *
+ * @return true if the header was adjusted successfully, or not found, false
+ * if the header is present but the url is not, which should indicate
+ * that more data needs to be read from the socket
+ */
+bool AdjustWebSocketLocation(relayBuffer& buffer, server_info_t *si)
+{
+  assert(buffer.margin());
+  buffer.buffertail[1] = '\0';
+
+  char* wsloc = strstr(buffer.bufferhead, "Sec-WebSocket-Location:");
+  if (!wsloc)
+    return true;
+  // advance pointer to the start of the hostname
+  wsloc = strstr(wsloc, "ws://");
+  if (!wsloc)
+    return false;
+  wsloc += 5;
+  // find the end of the hostname
+  char* wslocend = strchr(wsloc + 1, '/');
+  if (!wslocend)
+    return false;
+  char *crlf = strstr(wsloc, "\r\n");
+  if (!crlf)
+    return false;
+  if (si->original_host.empty())
+    return true;
+
+  int diff = si->original_host.length() - (wslocend-wsloc);
+  if (diff > 0)
+    assert(size_t(diff) <= buffer.margin());
+  memmove(wslocend + diff, wslocend, buffer.buffertail - wsloc - diff);
+  buffer.buffertail += diff;
+
+  memcpy(wsloc, si->original_host.c_str(), si->original_host.length());
+  return true;
+}
+
+/**
+ * This function examines the buffer for a Host: field, and if it's present,
+ * it replaces the hostname in that field with the hostname in the server's
+ * remote_addr field.  This is needed because proxy requests may be coming
+ * from mochitest with fake hosts, like mochi.test, and these need to be
+ * replaced with the host that the destination server is actually running
+ * on.
+ */
+bool AdjustHost(relayBuffer& buffer, server_info_t *si)
+{
+  if (!si->remote_addr.inet.port)
+    return false;
+
+  assert(buffer.margin());
+
+  // Cannot use strnchr so add a null char at the end. There is always some
+  // space left because we preserve a margin.
+  buffer.buffertail[1] = '\0';
+
+  char* host = strstr(buffer.bufferhead, HEADER_HOST);
+  if (!host)
+    return false;
+  // advance pointer to beginning of hostname
+  host += strlen(HEADER_HOST);
+  host += strspn(host, " \t");
+
+  char* endhost = strstr(host, "\r\n");
+  if (!endhost)
+    return false;
+
+  // Save the original host, so we can use it later on responses from the
+  // server.
+  si->original_host.assign(host, endhost-host);
+
+  char newhost[40];
+  PR_NetAddrToString(&si->remote_addr, newhost, sizeof(newhost));
+  assert(strlen(newhost) < sizeof(newhost) - 7);
+  sprintf(newhost, "%s:%d", newhost, PR_ntohs(si->remote_addr.inet.port));
+
+  int diff = strlen(newhost) - (endhost-host);
+  if (diff > 0)
+    assert(size_t(diff) <= buffer.margin());
+  memmove(endhost + diff, endhost, buffer.buffertail - host - diff);
+  buffer.buffertail += diff;
+
+  memcpy(host, newhost, strlen(newhost));
+  return true;
+}
+
+/**
  * This function prefixes Request-URI path with a full scheme-host-port
  * string.
  */
 bool AdjustRequestURI(relayBuffer& buffer, string *host)
 {
   assert(buffer.margin());
 
   // Cannot use strnchr so add a null char at the end. There is always some space left
@@ -424,17 +527,18 @@ void HandleConnection(void* data)
   if (other_sock) 
   {
     PRInt32 numberOfSockets = 1;
 
     relayBuffer buffers[2];
 
     if (!do_http_proxy)
     {
-      if (!ConfigureSSLServerSocket(ci->client_sock, ci->server_info, certificateToUse, caNone))
+      if (!ci->server_info->http_proxy_only && 
+          !ConfigureSSLServerSocket(ci->client_sock, ci->server_info, certificateToUse, caNone))
         client_error = true;
       else if (!ConnectSocket(other_sock, &remote_addr, connect_timeout))
         client_error = true;
       else
         numberOfSockets = 2;
     }
 
     PRPollDesc sockets[2] = 
@@ -561,17 +665,20 @@ void HandleConnection(void* data)
                 sprintf(buffers[s2].buffer, "HTTP/1.1 %d ERROR\r\nConnection: close\r\n\r\n", response);
                 buffers[s2].buffertail = buffers[s2].buffer + strlen(buffers[s2].buffer);
                 break;
               }
 
               strcpy(buffers[s2].buffer, "HTTP/1.1 200 Connected\r\nConnection: keep-alive\r\n\r\n");
               buffers[s2].buffertail = buffers[s2].buffer + strlen(buffers[s2].buffer);
 
-              if (!ConnectSocket(other_sock, &remote_addr, connect_timeout))
+              PRNetAddr* addr = &remote_addr;
+              if (ci->server_info->remote_addr.inet.port > 0)
+                addr = &ci->server_info->remote_addr;
+              if (!ConnectSocket(other_sock, addr, connect_timeout))
               {
                 printf(" could not open connection to the real server\n");
                 client_error = true;
                 break;
               }
 
               printf(" accepted CONNECT request, connected to the server, sending OK to the client\n");
               // Send the response to the client socket
@@ -582,18 +689,28 @@ void HandleConnection(void* data)
             {
               // Do not poll for read when the buffer is full
               printf(" no place in our read buffer, stop reading");
               in_flags &= ~PR_POLL_READ;
             }
 
             if (ssl_updated)
             {
-              if (s == 0 && expect_request_start)
-                expect_request_start = !AdjustRequestURI(buffers[s], &fullHost);
+              if (s == 0 && expect_request_start) 
+              {
+                if (ci->server_info->http_proxy_only)
+                  expect_request_start = !AdjustHost(buffers[s], ci->server_info);
+                else
+                  expect_request_start = !AdjustRequestURI(buffers[s], &fullHost);
+              }
+              else
+              {
+                if (!AdjustWebSocketLocation(buffers[s], ci->server_info))
+                  continue;
+              }
 
               in_flags2 |= PR_POLL_WRITE;
               printf(" telling the other socket to write");
             }
             else
               printf(" we have something for the other socket to write, but ssl has not been administered on it");
           }
         } // PR_POLL_READ handling
@@ -614,34 +731,35 @@ void HandleConnection(void* data)
               // the main loop, we will never more be able to send it.
               buffers[s2].bufferhead = buffers[s2].buffertail = buffers[s2].buffer;
             }
             else
               printf(" would block");
           }
           else
           {
-            printf(", writen %d bytes", bytesWrite);
+            printf(", written %d bytes", bytesWrite);
             buffers[s2].buffertail[1] = '\0';
             printf(" dump:\n%.*s\n", bytesWrite, buffers[s2].bufferhead);
             
             buffers[s2].bufferhead += bytesWrite;
             if (buffers[s2].present())
             {
               printf(" still have to write %d bytes", (int)buffers[s2].present());
               in_flags |= PR_POLL_WRITE;
             }              
             else
             {
               if (!ssl_updated)
               {
                 printf(" proxy response sent to the client");
                 // Proxy response has just been writen, update to ssl
                 ssl_updated = true;
-                if (!ConfigureSSLServerSocket(ci->client_sock, ci->server_info, certificateToUse, clientAuth))
+                if (!ci->server_info->http_proxy_only && 
+                    !ConfigureSSLServerSocket(ci->client_sock, ci->server_info, certificateToUse, clientAuth))
                 {
                   printf(" but failed to config server socket\n");
                   client_error = true;
                   break;
                 }
 
                 printf(" client socket updated to SSL");
                 numberOfSockets = 2;
@@ -788,16 +906,45 @@ int processConfigLine(char* configLine)
       fprintf(stderr, "Invalid remote port: %s\n", serverportstring);
       return 1;
     }
     remote_addr.inet.port = PR_htons(port);
 
     return 0;
   }
 
+  if (!strcmp(keyword, "proxy"))
+  {
+    server_info_t server;
+    server.host_cert_table = PL_NewHashTable(0, PL_HashString, PL_CompareStrings, PL_CompareStrings, NULL, NULL);
+    server.host_clientauth_table = PL_NewHashTable(0, PL_HashString, PL_CompareStrings, ClientAuthValueComparator, NULL, NULL);
+    server.http_proxy_only = true;
+
+    char* listenport = strtok2(_caret, ":", &_caret);
+    server.listen_port = atoi(listenport);
+    if (server.listen_port <= 0) {
+      fprintf(stderr, "Invalid listen port in proxy config: %s\n", listenport);
+      return 1;
+    }
+    char* ipstring = strtok2(_caret, ":", &_caret);
+    if (PR_StringToNetAddr(ipstring, &server.remote_addr) != PR_SUCCESS) {
+      fprintf(stderr, "Invalid IP address in proxy config: %s\n", ipstring);
+      return 1;
+    }
+    char* remoteport = strtok2(_caret, ":", &_caret);
+    int port = atoi(remoteport);
+    if (port <= 0) {
+      fprintf(stderr, "Invalid remote port in proxy config: %s\n", remoteport);
+      return 1;
+    }
+    server.remote_addr.inet.port = PR_htons(port);
+    servers.push_back(server);
+    return 0;
+  }
+
   // Configure all listen sockets and port+certificate bindings
   if (!strcmp(keyword, "listen"))
   {
     char* hostname = strtok2(_caret, ":", &_caret);
     char* hostportstring = NULL;
     if (strcmp(hostname, "*"))
     {
       any_host_spec_config = true;
@@ -827,18 +974,20 @@ int processConfigLine(char* configLine)
       if (!entry) {
         fprintf(stderr, "Out of memory");
         return 1;
       }
     }
     else
     {
       server_info_t server;
+      memset(&server.remote_addr, 0, sizeof(PRNetAddr));
       server.cert_nickname = certnick;
       server.listen_port = port;
+      server.http_proxy_only = false;
       server.host_cert_table = PL_NewHashTable(0, PL_HashString, PL_CompareStrings, PL_CompareStrings, NULL, NULL);
       if (!server.host_cert_table)
       {
         fprintf(stderr, "Internal, could not create hash table\n");
         return 1;
       }
       server.host_clientauth_table = PL_NewHashTable(0, PL_HashString, PL_CompareStrings, ClientAuthValueComparator, NULL, NULL);
       if (!server.host_clientauth_table)
@@ -1008,17 +1157,21 @@ int main(int argc, char** argv)
       "       # This only works in httpproxy mode and has higher priority\n"
       "       # than the previous option.\n"
       "       listen:my.host.name:443:4443:a different cert\n\n"
       "       # To make a specific host require or just request a client certificate\n"
       "       # to authenticate use the following options. This can only be used\n"
       "       # in httpproxy mode and only after the 'listen' option has been\n"
       "       # specified. You also have to specify the tunnel listen port.\n"
       "       clientauth:requesting-client-cert.host.com:443:4443:request\n"
-      "       clientauth:requiring-client-cert.host.com:443:4443:require\n",
+      "       clientauth:requiring-client-cert.host.com:443:4443:require\n"
+      "       # Act as a simple proxy for incoming connections on port 7777,\n"
+      "       # tunneling them to the server at 127.0.0.1:9999. Not affected\n"
+      "       # by the 'forward' option.\n"
+      "       proxy:7777:127.0.0.1:9999\n",
       configFilePath);
     return 1;
   }
 
   // create a thread pool to handle connections
   threads = PR_CreateThreadPool(PR_MAX(INITIAL_THREADS, servers.size()*2),
                                 PR_MAX(MAX_THREADS, servers.size()*2),
                                 DEFAULT_STACKSIZE);