2009-10-15 Yuzo Fujishima <yuzo@google.com>
authoreric@webkit.org <eric@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 16 Oct 2009 06:42:35 +0000 (06:42 +0000)
committereric@webkit.org <eric@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 16 Oct 2009 06:42:35 +0000 (06:42 +0000)
        Reviewed by David Levin.

        Add mod_pywebsocket to test Web Sockets.
        http://code.google.com/p/pywebsocket/
        https://bugs.webkit.org/show_bug.cgi?id=27490

        * pywebsocket/COPYING: Added.
        * pywebsocket/MANIFEST.in: Added.
        * pywebsocket/README: Added.
        * pywebsocket/example/echo_client.py: Added.
        * pywebsocket/example/echo_wsh.py: Added.
        * pywebsocket/mod_pywebsocket/__init__.py: Added.
        * pywebsocket/mod_pywebsocket/dispatch.py: Added.
        * pywebsocket/mod_pywebsocket/handshake.py: Added.
        * pywebsocket/mod_pywebsocket/headerparserhandler.py: Added.
        * pywebsocket/mod_pywebsocket/msgutil.py: Added.
        * pywebsocket/mod_pywebsocket/standalone.py: Added.
        * pywebsocket/mod_pywebsocket/util.py: Added.
        * pywebsocket/setup.py: Added.
        * pywebsocket/test/config.py: Added.
        * pywebsocket/test/mock.py: Added.
        * pywebsocket/test/run_all.py: Added.
        * pywebsocket/test/test_dispatch.py: Added.
        * pywebsocket/test/test_handshake.py: Added.
        * pywebsocket/test/test_mock.py: Added.
        * pywebsocket/test/test_msgutil.py: Added.
        * pywebsocket/test/test_util.py: Added.
        * pywebsocket/test/testdata/handlers/blank_wsh.py: Added.
        * pywebsocket/test/testdata/handlers/origin_check_wsh.py: Added.
        * pywebsocket/test/testdata/handlers/sub/exception_in_transfer_wsh.py: Added.
        * pywebsocket/test/testdata/handlers/sub/no_wsh_at_the_end.py: Added.
        * pywebsocket/test/testdata/handlers/sub/non_callable_wsh.py: Added.
        * pywebsocket/test/testdata/handlers/sub/plain_wsh.py: Added.
        * pywebsocket/test/testdata/handlers/sub/wrong_handshake_sig_wsh.py: Added.
        * pywebsocket/test/testdata/handlers/sub/wrong_transfer_sig_wsh.py: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@49672 268f45cc-cd09-0410-ab3c-d52691b4dbfc

30 files changed:
WebKitTools/ChangeLog
WebKitTools/pywebsocket/COPYING [new file with mode: 0644]
WebKitTools/pywebsocket/MANIFEST.in [new file with mode: 0644]
WebKitTools/pywebsocket/README [new file with mode: 0644]
WebKitTools/pywebsocket/example/echo_client.py [new file with mode: 0644]
WebKitTools/pywebsocket/example/echo_wsh.py [new file with mode: 0644]
WebKitTools/pywebsocket/mod_pywebsocket/__init__.py [new file with mode: 0644]
WebKitTools/pywebsocket/mod_pywebsocket/dispatch.py [new file with mode: 0644]
WebKitTools/pywebsocket/mod_pywebsocket/handshake.py [new file with mode: 0644]
WebKitTools/pywebsocket/mod_pywebsocket/headerparserhandler.py [new file with mode: 0644]
WebKitTools/pywebsocket/mod_pywebsocket/msgutil.py [new file with mode: 0644]
WebKitTools/pywebsocket/mod_pywebsocket/standalone.py [new file with mode: 0644]
WebKitTools/pywebsocket/mod_pywebsocket/util.py [new file with mode: 0644]
WebKitTools/pywebsocket/setup.py [new file with mode: 0644]
WebKitTools/pywebsocket/test/config.py [new file with mode: 0644]
WebKitTools/pywebsocket/test/mock.py [new file with mode: 0644]
WebKitTools/pywebsocket/test/run_all.py [new file with mode: 0644]
WebKitTools/pywebsocket/test/test_dispatch.py [new file with mode: 0644]
WebKitTools/pywebsocket/test/test_handshake.py [new file with mode: 0644]
WebKitTools/pywebsocket/test/test_mock.py [new file with mode: 0644]
WebKitTools/pywebsocket/test/test_msgutil.py [new file with mode: 0644]
WebKitTools/pywebsocket/test/test_util.py [new file with mode: 0644]
WebKitTools/pywebsocket/test/testdata/handlers/blank_wsh.py [new file with mode: 0644]
WebKitTools/pywebsocket/test/testdata/handlers/origin_check_wsh.py [new file with mode: 0644]
WebKitTools/pywebsocket/test/testdata/handlers/sub/exception_in_transfer_wsh.py [new file with mode: 0644]
WebKitTools/pywebsocket/test/testdata/handlers/sub/no_wsh_at_the_end.py [new file with mode: 0644]
WebKitTools/pywebsocket/test/testdata/handlers/sub/non_callable_wsh.py [new file with mode: 0644]
WebKitTools/pywebsocket/test/testdata/handlers/sub/plain_wsh.py [new file with mode: 0644]
WebKitTools/pywebsocket/test/testdata/handlers/sub/wrong_handshake_sig_wsh.py [new file with mode: 0644]
WebKitTools/pywebsocket/test/testdata/handlers/sub/wrong_transfer_sig_wsh.py [new file with mode: 0644]

index 31f1508..11f2c7a 100644 (file)
@@ -1,3 +1,41 @@
+2009-10-15  Yuzo Fujishima  <yuzo@google.com>
+
+        Reviewed by David Levin.
+
+        Add mod_pywebsocket to test Web Sockets.
+        http://code.google.com/p/pywebsocket/
+        https://bugs.webkit.org/show_bug.cgi?id=27490
+
+        * pywebsocket/COPYING: Added.
+        * pywebsocket/MANIFEST.in: Added.
+        * pywebsocket/README: Added.
+        * pywebsocket/example/echo_client.py: Added.
+        * pywebsocket/example/echo_wsh.py: Added.
+        * pywebsocket/mod_pywebsocket/__init__.py: Added.
+        * pywebsocket/mod_pywebsocket/dispatch.py: Added.
+        * pywebsocket/mod_pywebsocket/handshake.py: Added.
+        * pywebsocket/mod_pywebsocket/headerparserhandler.py: Added.
+        * pywebsocket/mod_pywebsocket/msgutil.py: Added.
+        * pywebsocket/mod_pywebsocket/standalone.py: Added.
+        * pywebsocket/mod_pywebsocket/util.py: Added.
+        * pywebsocket/setup.py: Added.
+        * pywebsocket/test/config.py: Added.
+        * pywebsocket/test/mock.py: Added.
+        * pywebsocket/test/run_all.py: Added.
+        * pywebsocket/test/test_dispatch.py: Added.
+        * pywebsocket/test/test_handshake.py: Added.
+        * pywebsocket/test/test_mock.py: Added.
+        * pywebsocket/test/test_msgutil.py: Added.
+        * pywebsocket/test/test_util.py: Added.
+        * pywebsocket/test/testdata/handlers/blank_wsh.py: Added.
+        * pywebsocket/test/testdata/handlers/origin_check_wsh.py: Added.
+        * pywebsocket/test/testdata/handlers/sub/exception_in_transfer_wsh.py: Added.
+        * pywebsocket/test/testdata/handlers/sub/no_wsh_at_the_end.py: Added.
+        * pywebsocket/test/testdata/handlers/sub/non_callable_wsh.py: Added.
+        * pywebsocket/test/testdata/handlers/sub/plain_wsh.py: Added.
+        * pywebsocket/test/testdata/handlers/sub/wrong_handshake_sig_wsh.py: Added.
+        * pywebsocket/test/testdata/handlers/sub/wrong_transfer_sig_wsh.py: Added.
+
 2009-10-15  James Robinson  <jamesr@google.com>
 
         Reviewed by David Levin.
diff --git a/WebKitTools/pywebsocket/COPYING b/WebKitTools/pywebsocket/COPYING
new file mode 100644 (file)
index 0000000..ab9d52d
--- /dev/null
@@ -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.
diff --git a/WebKitTools/pywebsocket/MANIFEST.in b/WebKitTools/pywebsocket/MANIFEST.in
new file mode 100644 (file)
index 0000000..1925688
--- /dev/null
@@ -0,0 +1,6 @@
+include COPYING
+include MANIFEST.in
+include README
+recursive-include example *.py
+recursive-include mod_pywebsocket *.py
+recursive-include test *.py
diff --git a/WebKitTools/pywebsocket/README b/WebKitTools/pywebsocket/README
new file mode 100644 (file)
index 0000000..1f9f05f
--- /dev/null
@@ -0,0 +1,6 @@
+Install this package by:
+$ python setup.py build
+$ sudo python setup.py install
+
+Then read document by:
+$ pydoc mod_pywebsocket
diff --git a/WebKitTools/pywebsocket/example/echo_client.py b/WebKitTools/pywebsocket/example/echo_client.py
new file mode 100644 (file)
index 0000000..61b129c
--- /dev/null
@@ -0,0 +1,195 @@
+#!/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.
+
+
+"""Web Socket Echo client.
+
+This is an example Web Socket client that talks with echo_wsh.py.
+This may be useful for checking mod_pywebsocket installation.
+
+Note:
+This code is far from robust, e.g., we cut corners in handshake.
+"""
+
+
+import codecs
+from optparse import OptionParser
+import socket
+import sys
+
+
+_DEFAULT_PORT = 80
+_DEFAULT_SECURE_PORT = 443
+_UNDEFINED_PORT = -1
+
+_UPGRADE_HEADER = 'Upgrade: WebSocket\r\n'
+_CONNECTION_HEADER = 'Connection: Upgrade\r\n'
+_EXPECTED_RESPONSE = (
+        'HTTP/1.1 101 Web Socket Protocol Handshake\r\n' +
+        _UPGRADE_HEADER +
+        _CONNECTION_HEADER)
+
+
+def _method_line(resource):
+    return 'GET %s HTTP/1.1\r\n' % resource
+
+
+def _origin_header(origin):
+    return 'Origin: %s\r\n' % origin
+
+
+class _TLSSocket(object):
+    """Wrapper for a TLS connection."""
+
+    def __init__(self, raw_socket):
+        self._ssl = socket.ssl(raw_socket)
+
+    def send(self, bytes):
+        return self._ssl.write(bytes)
+
+    def recv(self, size=-1):
+        return self._ssl.read(size)
+
+    def close(self):
+        # Nothing to do.
+        pass
+
+
+class EchoClient(object):
+    """Web Socket echo client."""
+
+    def __init__(self, options):
+        self._options = options
+        self._socket = None
+
+    def run(self):
+        """Run the client.
+
+        Shake hands and then repeat sending message and receiving its echo.
+        """
+        self._socket = socket.socket()
+        try:
+            self._socket.connect((self._options.server_host,
+                                  self._options.server_port))
+            if self._options.use_tls:
+                self._socket = _TLSSocket(self._socket)
+            self._handshake()
+            for line in self._options.message.split(','):
+                frame = '\x00' + line.encode('utf-8') + '\xff'
+                self._socket.send(frame)
+                if self._options.verbose:
+                    print 'Send: %s' % line
+                received = self._socket.recv(len(frame))
+                if received != frame:
+                    raise Exception('Incorrect echo: %r' % received)
+                if self._options.verbose:
+                    print 'Recv: %s' % received[1:-1].decode('utf-8')
+        finally:
+            self._socket.close()
+
+    def _handshake(self):
+        self._socket.send(_method_line(self._options.resource))
+        self._socket.send(_UPGRADE_HEADER)
+        self._socket.send(_CONNECTION_HEADER)
+        self._socket.send(self._format_host_header())
+        self._socket.send(_origin_header(self._options.origin))
+        self._socket.send('\r\n')
+
+        for expected_char in _EXPECTED_RESPONSE:
+            received = self._socket.recv(1)[0]
+            if expected_char != received:
+                raise Exception('Handshake failure')
+        # We cut corners and skip other headers.
+        self._skip_headers()
+
+    def _skip_headers(self):
+        terminator = '\r\n\r\n'
+        pos = 0
+        while pos < len(terminator):
+            received = self._socket.recv(1)[0]
+            if received == terminator[pos]:
+                pos += 1
+            elif received == terminator[0]:
+                pos = 1
+            else:
+                pos = 0
+
+    def _format_host_header(self):
+        host = 'Host: ' + self._options.server_host
+        if ((not self._options.use_tls and
+             self._options.server_port != _DEFAULT_PORT) or
+            (self._options.use_tls and
+             self._options.server_port != _DEFAULT_SECURE_PORT)):
+            host += ':' + str(self._options.server_port)
+        host += '\r\n'
+        return host
+
+
+def main():
+    sys.stdout = codecs.getwriter('utf-8')(sys.stdout)
+
+    parser = OptionParser()
+    parser.add_option('-s', '--server_host', dest='server_host', type='string',
+                      default='localhost', help='server host')
+    parser.add_option('-p', '--server_port', dest='server_port', type='int',
+                      default=_UNDEFINED_PORT, help='server port')
+    parser.add_option('-o', '--origin', dest='origin', type='string',
+                      default='http://localhost/', help='origin')
+    parser.add_option('-r', '--resource', dest='resource', type='string',
+                      default='/echo', help='resource path')
+    parser.add_option('-m', '--message', dest='message', type='string',
+                      help='comma-separated messages to send')
+    parser.add_option('-q', '--quiet', dest='verbose', action='store_false',
+                      default=True, help='suppress messages')
+    parser.add_option('-t', '--tls', dest='use_tls', action='store_true',
+                      default=False, help='use TLS (wss://)')
+    (options, unused_args) = parser.parse_args()
+
+    # Default port number depends on whether TLS is used.
+    if options.server_port == _UNDEFINED_PORT:
+        if options.use_tls:
+            options.server_port = _DEFAULT_SECURE_PORT
+        else:
+            options.server_port = _DEFAULT_PORT
+
+    # optparse doesn't seem to handle non-ascii default values.
+    # Set default message here.
+    if not options.message:
+        options.message = u'Hello,\u65e5\u672c'   # "Japan" in Japanese
+
+    EchoClient(options).run()
+
+
+if __name__ == '__main__':
+    main()
+
+
+# vi:sts=4 sw=4 et
diff --git a/WebKitTools/pywebsocket/example/echo_wsh.py b/WebKitTools/pywebsocket/example/echo_wsh.py
new file mode 100644 (file)
index 0000000..f680fa5
--- /dev/null
@@ -0,0 +1,44 @@
+# 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.
+
+
+from mod_pywebsocket import msgutil
+
+
+def web_socket_do_extra_handshake(request):
+    pass  # Always accept.
+
+
+def web_socket_transfer_data(request):
+    while True:
+        line = msgutil.receive_message(request)
+        msgutil.send_message(request, line)
+
+
+# vi:sts=4 sw=4 et
diff --git a/WebKitTools/pywebsocket/mod_pywebsocket/__init__.py b/WebKitTools/pywebsocket/mod_pywebsocket/__init__.py
new file mode 100644 (file)
index 0000000..8acd133
--- /dev/null
@@ -0,0 +1,94 @@
+# 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
+
+   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.
+"""
+
+
+# vi:sts=4 sw=4 et tw=72
diff --git a/WebKitTools/pywebsocket/mod_pywebsocket/dispatch.py b/WebKitTools/pywebsocket/mod_pywebsocket/dispatch.py
new file mode 100644 (file)
index 0000000..908b0b4
--- /dev/null
@@ -0,0 +1,192 @@
+# 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
+
+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.abspath(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):
+        """Construct an instance.
+
+        Args:
+            root_dir: The directory where handler definition files are
+            placed.
+        """
+
+        self._handlers = {}
+        self._source_warnings = []
+        self._source_files_in_dir(root_dir)
+
+    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:
+            raise DispatchError('%s raised exception: %s' %
+                    (_DO_EXTRA_HANDSHAKE_HANDLER_NAME, util.get_stack_trace()))
+
+    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:
+            transfer_data_(request)
+        except Exception:
+            raise DispatchError('%s raised exception: %s' %
+                    (_TRANSFER_DATA_HANDLER_NAME, util.get_stack_trace()))
+
+    def _handler(self, request):
+        try:
+            return self._handlers[request.ws_resource]
+        except KeyError:
+            raise DispatchError('No handler for: %r' % request.ws_resource)
+
+    def _source_files_in_dir(self, root_dir):
+        """Source all the handler source files in the directory."""
+
+        to_resource = _path_to_resource_converter(root_dir)
+        for path in _source_file_paths(root_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
diff --git a/WebKitTools/pywebsocket/mod_pywebsocket/handshake.py b/WebKitTools/pywebsocket/mod_pywebsocket/handshake.py
new file mode 100644 (file)
index 0000000..a67aadd
--- /dev/null
@@ -0,0 +1,178 @@
+# 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 re
+
+
+_DEFAULT_WEB_SOCKET_PORT = 80
+_DEFAULT_WEB_SOCKET_SECURE_PORT = 443
+_WEB_SOCKET_SCHEME = 'ws'
+_WEB_SOCKET_SECURE_SCHEME = 'wss'
+
+_METHOD_LINE = re.compile(r'^GET ([^ ]+) HTTP/1.1\r\n$')
+
+_MANDATORY_HEADERS = [
+    # key, expected value or None
+    ['Upgrade', 'WebSocket'],
+    ['Connection', 'Upgrade'],
+    ['Host', None],
+    ['Origin', None],
+]
+
+
+def _default_port(is_secure):
+    if is_secure:
+        return _DEFAULT_WEB_SOCKET_SECURE_PORT
+    else:
+        return _DEFAULT_WEB_SOCKET_PORT
+
+
+class HandshakeError(Exception):
+    """Exception in Web Socket Handshake."""
+
+    pass
+
+
+def _validate_protocol(protocol):
+    """Validate WebSocket-Protocol string."""
+
+    if not protocol:
+        raise HandshakeError('Invalid WebSocket-Protocol: empty')
+    for c in protocol:
+        if not 0x21 <= ord(c) <= 0x7e:
+            raise HandshakeError('Illegal character in protocol: %r' % c)
+
+
+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._request = request
+        self._dispatcher = dispatcher
+
+    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):
+        location_parts = []
+        if self._request.is_https():
+            location_parts.append(_WEB_SOCKET_SECURE_SCHEME)
+        else:
+            location_parts.append(_WEB_SOCKET_SCHEME)
+        location_parts.append('://')
+        host, port = self._parse_host_header()
+        connection_port = self._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(self._request.is_https())):
+            location_parts.append(':')
+            location_parts.append(str(port))
+        location_parts.append(self._request.uri)
+        self._request.ws_location = ''.join(location_parts)
+
+    def _parse_host_header(self):
+        fields = self._request.headers_in['Host'].split(':', 1)
+        if len(fields) == 1:
+            return fields[0], _default_port(self._request.is_https())
+        try:
+            return fields[0], int(fields[1])
+        except ValueError, e:
+            raise HandshakeError('Invalid port number format: %r' % e)
+
+    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))
+
+
+# vi:sts=4 sw=4 et
diff --git a/WebKitTools/pywebsocket/mod_pywebsocket/headerparserhandler.py b/WebKitTools/pywebsocket/mod_pywebsocket/headerparserhandler.py
new file mode 100644 (file)
index 0000000..c38a1de
--- /dev/null
@@ -0,0 +1,92 @@
+# 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.
+"""
+
+
+from mod_python import apache
+
+import dispatch
+import handshake
+import util
+
+
+# PythonOption to specify the handler root directory.
+_PYOPT_HANDLER_ROOT = 'mod_pywebsocket.handler_root'
+
+
+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)
+    dispatcher = dispatch.Dispatcher(_HANDLER_ROOT)
+    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:
+        handshaker = handshake.Handshaker(request, _dispatcher)
+        handshaker.do_handshake()
+        request.log_error('mod_pywebsocket: resource: %r' % request.ws_resource,
+                          apache.APLOG_DEBUG)
+        _dispatcher.transfer_data(request)
+    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
diff --git a/WebKitTools/pywebsocket/mod_pywebsocket/msgutil.py b/WebKitTools/pywebsocket/mod_pywebsocket/msgutil.py
new file mode 100644 (file)
index 0000000..bdb554d
--- /dev/null
@@ -0,0 +1,223 @@
+# 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
+
+
+def send_message(request, message):
+    """Send message.
+
+    Args:
+        request: mod_python request.
+        message: unicode string to send.
+    """
+
+    request.connection.write('\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.
+    """
+
+    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 = request.connection.read(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)
+        else:
+            # The payload is delimited with \xff.
+            bytes = _read_until(request, '\xff')
+            message = bytes.decode('utf-8')
+            if frame_type == 0x00:
+                return message
+            # Discard data of other types.
+
+
+def _payload_length(request):
+    length = 0
+    while True:
+        b_str = request.connection.read(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 = request.connection.read(length)
+        bytes.append(new_bytes)
+        length -= len(new_bytes)
+    return ''.join(bytes)
+
+
+def _read_until(request, delim_char):
+    bytes = []
+    while True:
+        ch = request.connection.read(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):
+        while not self._stop_requested:
+            message = receive_message(self._request)
+            if self._onmessage:
+                self._onmessage(message)
+            else:
+                self._queue.put(message)
+
+    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
diff --git a/WebKitTools/pywebsocket/mod_pywebsocket/standalone.py b/WebKitTools/pywebsocket/mod_pywebsocket/standalone.py
new file mode 100644 (file)
index 0000000..b7874fa
--- /dev/null
@@ -0,0 +1,242 @@
+#!/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>]
+                         [-d <document_root>]
+
+<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.
+
+Note:
+This server is derived from SocketServer.ThreadingMixIn. Hence a thread is
+used for each request.
+"""
+
+import BaseHTTPServer
+import SimpleHTTPServer
+import SocketServer
+import logging
+import optparse
+import os
+import socket
+import sys
+
+_HAS_OPEN_SSL = False
+try:
+    import OpenSSL.SSL
+    _HAS_OPEN_SSL = True
+except ImportError:
+    pass
+
+import dispatch
+import handshake
+
+
+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 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)
+
+
+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_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_
+
+class WebSocketRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
+    """SimpleHTTPRequestHandler specialized for Web Socket."""
+
+    def setup(self):
+        """Override SocketServer.StreamRequestHandler.setup."""
+
+        self.connection = self.request
+        self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
+        self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
+
+    def __init__(self, *args, **keywords):
+        self._request = _StandaloneRequest(
+                self, WebSocketRequestHandler.options.use_tls)
+        self._dispatcher = dispatch.Dispatcher(
+                WebSocketRequestHandler.options.websock_handlers)
+        self._print_warnings_if_any()
+        self._handshaker = handshake.Handshaker(self._request,
+                                                self._dispatcher)
+        SimpleHTTPServer.SimpleHTTPRequestHandler.__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 = SimpleHTTPServer.SimpleHTTPRequestHandler.parse_request(self)
+        if result:
+            try:
+                self._handshaker.do_handshake()
+                self._dispatcher.transfer_data(self._request)
+                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
+        return result
+
+
+def _main():
+    logging.basicConfig()
+
+    parser = optparse.OptionParser()
+    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', dest='websock_handlers',
+                      default='.',
+                      help='Web Socket handlers root directory.')
+    parser.add_option('-d', '--document_root', dest='document_root',
+                      default='.',
+                      help='Document root directory.')
+    parser.add_option('-t', '--tls', dest='use_tls', action='store_true',
+                      default=False, help='use TLS (wss://)')
+    parser.add_option('-k', '--private_key', dest='private_key',
+                      default='', help='TLS private key file.')
+    parser.add_option('-c', '--certificate', dest='certificate',
+                      default='', help='TLS certificate file.')
+    options = parser.parse_args()[0]
+
+    if options.use_tls:
+        if not _HAS_OPEN_SSL:
+            print >>sys.stderr, 'To use TLS, install pyOpenSSL.'
+            sys.exit(1)
+        if not options.private_key or not options.certificate:
+            print >>sys.stderr, ('To use TLS, specify private_key and '
+                                 'certificate.')
+            sys.exit(1)
+
+    WebSocketRequestHandler.options = options
+    WebSocketServer.options = options
+
+    os.chdir(options.document_root)
+
+    server = WebSocketServer(('', options.port), WebSocketRequestHandler)
+    server.serve_forever()
+
+
+if __name__ == '__main__':
+    _main()
+
+
+# vi:sts=4 sw=4 et
diff --git a/WebKitTools/pywebsocket/mod_pywebsocket/util.py b/WebKitTools/pywebsocket/mod_pywebsocket/util.py
new file mode 100644 (file)
index 0000000..4835298
--- /dev/null
@@ -0,0 +1,52 @@
+# 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 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()
+
+
+# vi:sts=4 sw=4 et
diff --git a/WebKitTools/pywebsocket/setup.py b/WebKitTools/pywebsocket/setup.py
new file mode 100644 (file)
index 0000000..8309eec
--- /dev/null
@@ -0,0 +1,63 @@
+#!/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.
+
+
+"""Set up script for mod_pywebsocket.
+"""
+
+
+from distutils.core import setup
+import sys
+
+
+_PACKAGE_NAME = 'mod_pywebsocket'
+
+if sys.version < '2.3':
+    print >>sys.stderr, '%s requires Python 2.3 or later.' % _PACKAGE_NAME
+    sys.exit(1)
+
+setup(author='Yuzo Fujishima',
+      author_email='yuzo@chromium.org',
+      description='Web Socket extension for Apache HTTP Server.',
+      long_description=(
+              'mod_pywebsocket is an Apache HTTP Server extension for '
+              'Web Socket (http://tools.ietf.org/html/'
+              'draft-hixie-thewebsocketprotocol). '
+              'See mod_pywebsocket/__init__.py for more detail.'),
+      license='See COPYING',
+      name=_PACKAGE_NAME,
+      packages=[_PACKAGE_NAME],
+      url='http://code.google.com/p/pywebsocket/',
+      version='0.4.0',
+      )
+
+
+# vi:sts=4 sw=4 et
diff --git a/WebKitTools/pywebsocket/test/config.py b/WebKitTools/pywebsocket/test/config.py
new file mode 100644 (file)
index 0000000..5aaab8c
--- /dev/null
@@ -0,0 +1,45 @@
+# 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.
+
+
+"""Configuration for testing.
+
+Test files should import this module before mod_pywebsocket.
+"""
+
+
+import os
+import sys
+
+
+# Add the parent directory to sys.path to enable importing mod_pywebsocket.
+sys.path += [os.path.join(os.path.split(__file__)[0], '..')]
+
+
+# vi:sts=4 sw=4 et
diff --git a/WebKitTools/pywebsocket/test/mock.py b/WebKitTools/pywebsocket/test/mock.py
new file mode 100644 (file)
index 0000000..3b85d64
--- /dev/null
@@ -0,0 +1,205 @@
+# 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.
+
+
+"""Mocks for testing.
+"""
+
+
+import Queue
+import threading
+
+
+class _MockConnBase(object):
+    """Base class of mocks for mod_python.apache.mp_conn.
+
+    This enables tests to check what is written to a (mock) mp_conn.
+    """
+
+    def __init__(self):
+        self._write_data = []
+
+    def write(self, data):
+        """Override mod_python.apache.mp_conn.write."""
+
+        self._write_data.append(data)
+
+    def written_data(self):
+        """Get bytes written to this mock."""
+
+        return ''.join(self._write_data)
+
+
+class MockConn(_MockConnBase):
+    """Mock for mod_python.apache.mp_conn.
+
+    This enables tests to specify what should be read from a (mock) mp_conn as
+    well as to check what is written to it.
+    """
+
+    def __init__(self, read_data):
+        """Constructs an instance.
+
+        Args:
+            read_data: bytes that should be returned when read* methods are
+            called.
+        """
+
+        _MockConnBase.__init__(self)
+        self._read_data = read_data
+        self._read_pos = 0
+
+    def readline(self):
+        """Override mod_python.apache.mp_conn.readline."""
+
+        if self._read_pos >= len(self._read_data):
+            return ''
+        end_index = self._read_data.find('\n', self._read_pos) + 1
+        if not end_index:
+            end_index = len(self._read_data)
+        return self._read_up_to(end_index)
+
+    def read(self, length):
+        """Override mod_python.apache.mp_conn.read."""
+
+        if self._read_pos >= len(self._read_data):
+            return ''
+        end_index = min(len(self._read_data), self._read_pos + length)
+        return self._read_up_to(end_index)
+
+    def _read_up_to(self, end_index):
+        line = self._read_data[self._read_pos:end_index]
+        self._read_pos = end_index
+        return line
+
+
+class MockBlockingConn(_MockConnBase):
+    """Blocking mock for mod_python.apache.mp_conn.
+
+    This enables tests to specify what should be read from a (mock) mp_conn as
+    well as to check what is written to it.
+    Callers of read* methods will block if there is no bytes available.
+    """
+
+    def __init__(self):
+        _MockConnBase.__init__(self)
+        self._queue = Queue.Queue()
+
+    def readline(self):
+        """Override mod_python.apache.mp_conn.readline."""
+        line = ''
+        while True:
+            c = self._queue.get()
+            line += c
+            if c == '\n':
+                return line
+
+    def read(self, length):
+        """Override mod_python.apache.mp_conn.read."""
+
+        data = ''
+        for unused in range(length):
+            data += self._queue.get()
+        return data
+
+    def put_bytes(self, bytes):
+        """Put bytes to be read from this mock.
+
+        Args:
+            bytes: bytes to be read.
+        """
+
+        for byte in bytes:
+            self._queue.put(byte)
+
+
+class MockTable(dict):
+    """Mock table.
+
+    This mimics mod_python mp_table. Note that only the methods used by
+    tests are overridden.
+    """
+
+    def __init__(self, copy_from={}):
+        if isinstance(copy_from, dict):
+            copy_from = copy_from.items()
+        for key, value in copy_from:
+            self.__setitem__(key, value)
+
+    def __getitem__(self, key):
+        return super(MockTable, self).__getitem__(key.lower())
+
+    def __setitem__(self, key, value):
+        super(MockTable, self).__setitem__(key.lower(), value)
+
+    def get(self, key, def_value=None):
+        return super(MockTable, self).get(key.lower(), def_value)
+
+
+class MockRequest(object):
+    """Mock request.
+
+    This mimics mod_python request.
+    """
+
+    def __init__(self, uri=None, headers_in={}, connection=None,
+                 is_https=False):
+        """Construct an instance.
+
+        Arguments:
+            uri: URI of the request.
+            headers_in: Request headers.
+            connection: Connection used for the request.
+            is_https: Whether this request is over SSL.
+
+        See the document of mod_python Request for details.
+        """
+        self.uri = uri
+        self.connection = connection
+        self.headers_in = MockTable(headers_in)
+        # self.is_https_ needs to be accessible from tests.  To avoid name
+        # conflict with self.is_https(), it is named as such.
+        self.is_https_ = is_https
+
+    def is_https(self):
+        """Return whether this request is over SSL."""
+        return self.is_https_
+
+
+class MockDispatcher(object):
+    """Mock for dispatch.Dispatcher."""
+
+    def do_extra_handshake(self, conn_context):
+        pass
+
+    def transfer_data(self, conn_context):
+        pass
+
+
+# vi:sts=4 sw=4 et
diff --git a/WebKitTools/pywebsocket/test/run_all.py b/WebKitTools/pywebsocket/test/run_all.py
new file mode 100644 (file)
index 0000000..3885618
--- /dev/null
@@ -0,0 +1,64 @@
+#!/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.
+
+
+"""Run all tests in the same directory.
+"""
+
+
+import os
+import re
+import unittest
+
+
+_TEST_MODULE_PATTERN = re.compile(r'^(test_.+)\.py$')
+
+
+def _list_test_modules(directory):
+    module_names = []
+    for filename in os.listdir(directory):
+        match = _TEST_MODULE_PATTERN.search(filename)
+        if match:
+            module_names.append(match.group(1))
+    return module_names
+
+
+def _suite():
+    loader = unittest.TestLoader()
+    return loader.loadTestsFromNames(
+            _list_test_modules(os.path.join(os.path.split(__file__)[0], '.')))
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='_suite')
+
+
+# vi:sts=4 sw=4 et
diff --git a/WebKitTools/pywebsocket/test/test_dispatch.py b/WebKitTools/pywebsocket/test/test_dispatch.py
new file mode 100644 (file)
index 0000000..e2585c8
--- /dev/null
@@ -0,0 +1,190 @@
+#!/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.
+
+
+"""Tests for dispatch module."""
+
+
+
+import os
+import unittest
+
+import config  # This must be imported before mod_pywebsocket.
+from mod_pywebsocket import dispatch
+
+import mock
+
+
+_TEST_HANDLERS_DIR = os.path.join(
+        os.path.split(__file__)[0], 'testdata', 'handlers')
+
+class DispatcherTest(unittest.TestCase):
+    def test_normalize_path(self):
+        self.assertEqual(os.path.abspath('/a/b').replace('\\', '/'),
+                         dispatch._normalize_path('/a/b'))
+        self.assertEqual(os.path.abspath('/a/b').replace('\\', '/'),
+                         dispatch._normalize_path('\\a\\b'))
+        self.assertEqual(os.path.abspath('/a/b').replace('\\', '/'),
+                         dispatch._normalize_path('/a/c/../b'))
+        self.assertEqual(os.path.abspath('abc').replace('\\', '/'),
+                         dispatch._normalize_path('abc'))
+
+    def test_converter(self):
+        converter = dispatch._path_to_resource_converter('/a/b')
+        self.assertEqual('/h', converter('/a/b/h_wsh.py'))
+        self.assertEqual('/c/h', converter('/a/b/c/h_wsh.py'))
+        self.assertEqual(None, converter('/a/b/h.py'))
+        self.assertEqual(None, converter('a/b/h_wsh.py'))
+
+        converter = dispatch._path_to_resource_converter('a/b')
+        self.assertEqual('/h', converter('a/b/h_wsh.py'))
+
+        converter = dispatch._path_to_resource_converter('/a/b///')
+        self.assertEqual('/h', converter('/a/b/h_wsh.py'))
+        self.assertEqual('/h', converter('/a/b/../b/h_wsh.py'))
+
+        converter = dispatch._path_to_resource_converter('/a/../a/b/../b/')
+        self.assertEqual('/h', converter('/a/b/h_wsh.py'))
+
+        converter = dispatch._path_to_resource_converter(r'\a\b')
+        self.assertEqual('/h', converter(r'\a\b\h_wsh.py'))
+        self.assertEqual('/h', converter(r'/a/b/h_wsh.py'))
+
+    def test_source_file_paths(self):
+        paths = list(dispatch._source_file_paths(_TEST_HANDLERS_DIR))
+        paths.sort()
+        self.assertEqual(7, len(paths))
+        expected_paths = [
+                os.path.join(_TEST_HANDLERS_DIR, 'blank_wsh.py'),
+                os.path.join(_TEST_HANDLERS_DIR, 'origin_check_wsh.py'),
+                os.path.join(_TEST_HANDLERS_DIR, 'sub',
+                             'exception_in_transfer_wsh.py'),
+                os.path.join(_TEST_HANDLERS_DIR, 'sub', 'non_callable_wsh.py'),
+                os.path.join(_TEST_HANDLERS_DIR, 'sub', 'plain_wsh.py'),
+                os.path.join(_TEST_HANDLERS_DIR, 'sub',
+                             'wrong_handshake_sig_wsh.py'),
+                os.path.join(_TEST_HANDLERS_DIR, 'sub',
+                             'wrong_transfer_sig_wsh.py'),
+                ]
+        for expected, actual in zip(expected_paths, paths):
+            self.assertEqual(expected, actual)
+
+    def test_source(self):
+        self.assertRaises(dispatch.DispatchError, dispatch._source, '')
+        self.assertRaises(dispatch.DispatchError, dispatch._source, 'def')
+        self.assertRaises(dispatch.DispatchError, dispatch._source, '1/0')
+        self.failUnless(dispatch._source(
+                'def web_socket_do_extra_handshake(request):pass\n'
+                'def web_socket_transfer_data(request):pass\n'))
+
+    def test_source_warnings(self):
+        dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR)
+        warnings = dispatcher.source_warnings()
+        warnings.sort()
+        expected_warnings = [
+                (os.path.join(_TEST_HANDLERS_DIR, 'blank_wsh.py') +
+                 ': web_socket_do_extra_handshake is not defined.'),
+                (os.path.join(_TEST_HANDLERS_DIR, 'sub',
+                              'non_callable_wsh.py') +
+                 ': web_socket_do_extra_handshake is not callable.'),
+                (os.path.join(_TEST_HANDLERS_DIR, 'sub',
+                              'wrong_handshake_sig_wsh.py') +
+                 ': web_socket_do_extra_handshake is not defined.'),
+                (os.path.join(_TEST_HANDLERS_DIR, 'sub',
+                              'wrong_transfer_sig_wsh.py') +
+                 ': web_socket_transfer_data is not defined.'),
+                ]
+        self.assertEquals(4, len(warnings))
+        for expected, actual in zip(expected_warnings, warnings):
+            self.assertEquals(expected, actual)
+
+    def test_shake_hand(self):
+        dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR)
+        request = mock.MockRequest()
+        request.ws_resource = '/origin_check'
+        request.ws_origin = 'http://example.com'
+        dispatcher.do_extra_handshake(request)  # Must not raise exception.
+
+        request.ws_origin = 'http://bad.example.com'
+        self.assertRaises(dispatch.DispatchError,
+                          dispatcher.do_extra_handshake, request)
+
+    def test_transfer_data(self):
+        dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR)
+        request = mock.MockRequest(connection=mock.MockConn(''))
+        request.ws_resource = '/origin_check'
+        request.ws_protocol = 'p1'
+
+        dispatcher.transfer_data(request)
+        self.assertEqual('origin_check_wsh.py is called for /origin_check, p1',
+                         request.connection.written_data())
+
+        request = mock.MockRequest(connection=mock.MockConn(''))
+        request.ws_resource = '/sub/plain'
+        request.ws_protocol = None
+        dispatcher.transfer_data(request)
+        self.assertEqual('sub/plain_wsh.py is called for /sub/plain, None',
+                         request.connection.written_data())
+
+    def test_transfer_data_no_handler(self):
+        dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR)
+        for resource in ['/blank', '/sub/non_callable',
+                         '/sub/no_wsh_at_the_end', '/does/not/exist']:
+            request = mock.MockRequest(connection=mock.MockConn(''))
+            request.ws_resource = resource
+            request.ws_protocol = 'p2'
+            try:
+                dispatcher.transfer_data(request)
+                self.fail()
+            except dispatch.DispatchError, e:
+                self.failUnless(str(e).find('No handler') != -1)
+            except Exception:
+                self.fail()
+
+    def test_transfer_data_handler_exception(self):
+        dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR)
+        request = mock.MockRequest(connection=mock.MockConn(''))
+        request.ws_resource = '/sub/exception_in_transfer'
+        request.ws_protocol = 'p3'
+        try:
+            dispatcher.transfer_data(request)
+            self.fail()
+        except dispatch.DispatchError, e:
+            self.failUnless(str(e).find('Intentional') != -1)
+        except Exception:
+            self.fail()
+
+
+if __name__ == '__main__':
+    unittest.main()
+
+
+# vi:sts=4 sw=4 et
diff --git a/WebKitTools/pywebsocket/test/test_handshake.py b/WebKitTools/pywebsocket/test/test_handshake.py
new file mode 100644 (file)
index 0000000..dd1f65c
--- /dev/null
@@ -0,0 +1,316 @@
+#!/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.
+
+
+"""Tests for handshake module."""
+
+
+import unittest
+
+import config  # This must be imported before mod_pywebsocket.
+from mod_pywebsocket import handshake
+
+import mock
+
+
+_GOOD_REQUEST = (
+    80,
+    '/demo',
+    {
+        'Upgrade':'WebSocket',
+        'Connection':'Upgrade',
+        'Host':'example.com',
+        'Origin':'http://example.com',
+        'WebSocket-Protocol':'sample',
+    }
+)
+
+_GOOD_RESPONSE_DEFAULT_PORT = (
+    'HTTP/1.1 101 Web Socket Protocol Handshake\r\n'
+    'Upgrade: WebSocket\r\n'
+    'Connection: Upgrade\r\n'
+    'WebSocket-Origin: http://example.com\r\n'
+    'WebSocket-Location: ws://example.com/demo\r\n'
+    'WebSocket-Protocol: sample\r\n'
+    '\r\n')
+
+_GOOD_RESPONSE_SECURE = (
+    'HTTP/1.1 101 Web Socket Protocol Handshake\r\n'
+    'Upgrade: WebSocket\r\n'
+    'Connection: Upgrade\r\n'
+    'WebSocket-Origin: http://example.com\r\n'
+    'WebSocket-Location: wss://example.com/demo\r\n'
+    'WebSocket-Protocol: sample\r\n'
+    '\r\n')
+
+_GOOD_REQUEST_NONDEFAULT_PORT = (
+    8081,
+    '/demo',
+    {
+        'Upgrade':'WebSocket',
+        'Connection':'Upgrade',
+        'Host':'example.com:8081',
+        'Origin':'http://example.com',
+        'WebSocket-Protocol':'sample',
+    }
+)
+
+_GOOD_RESPONSE_NONDEFAULT_PORT = (
+    'HTTP/1.1 101 Web Socket Protocol Handshake\r\n'
+    'Upgrade: WebSocket\r\n'
+    'Connection: Upgrade\r\n'
+    'WebSocket-Origin: http://example.com\r\n'
+    'WebSocket-Location: ws://example.com:8081/demo\r\n'
+    'WebSocket-Protocol: sample\r\n'
+    '\r\n')
+
+_GOOD_RESPONSE_SECURE_NONDEF = (
+    'HTTP/1.1 101 Web Socket Protocol Handshake\r\n'
+    'Upgrade: WebSocket\r\n'
+    'Connection: Upgrade\r\n'
+    'WebSocket-Origin: http://example.com\r\n'
+    'WebSocket-Location: wss://example.com:8081/demo\r\n'
+    'WebSocket-Protocol: sample\r\n'
+    '\r\n')
+
+_GOOD_REQUEST_NO_PROTOCOL = (
+    80,
+    '/demo',
+    {
+        'Upgrade':'WebSocket',
+        'Connection':'Upgrade',
+        'Host':'example.com',
+        'Origin':'http://example.com',
+    }
+)
+
+_GOOD_RESPONSE_NO_PROTOCOL = (
+    'HTTP/1.1 101 Web Socket Protocol Handshake\r\n'
+    'Upgrade: WebSocket\r\n'
+    'Connection: Upgrade\r\n'
+    'WebSocket-Origin: http://example.com\r\n'
+    'WebSocket-Location: ws://example.com/demo\r\n'
+    '\r\n')
+
+_GOOD_REQUEST_WITH_OPTIONAL_HEADERS = (
+    80,
+    '/demo',
+    {
+        'Upgrade':'WebSocket',
+        'Connection':'Upgrade',
+        'Host':'example.com',
+        'Origin':'http://example.com',
+        'WebSocket-Protocol':'sample',
+        'AKey':'AValue',
+        'EmptyValue':'',
+    }
+)
+
+_BAD_REQUESTS = (
+    (  # HTTP request
+        80,
+        '/demo',
+        {
+            'Host':'www.google.com',
+            'User-Agent':'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5;'
+                         ' en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3'
+                         ' GTB6 GTBA',
+            'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,'
+                     '*/*;q=0.8',
+            'Accept-Language':'en-us,en;q=0.5',
+            'Accept-Encoding':'gzip,deflate',
+            'Accept-Charset':'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
+            'Keep-Alive':'300',
+            'Connection':'keep-alive',
+        }
+    ),
+    (  # Missing Upgrade
+        80,
+        '/demo',
+        {
+            'Connection':'Upgrade',
+            'Host':'example.com',
+            'Origin':'http://example.com',
+            'WebSocket-Protocol':'sample',
+        }
+    ),
+    (  # Wrong Upgrade
+        80,
+        '/demo',
+        {
+            'Upgrade':'NonWebSocket',
+            'Connection':'Upgrade',
+            'Host':'example.com',
+            'Origin':'http://example.com',
+            'WebSocket-Protocol':'sample',
+        }
+    ),
+    (  # Empty WebSocket-Protocol
+        80,
+        '/demo',
+        {
+            'Upgrade':'WebSocket',
+            'Connection':'Upgrade',
+            'Host':'example.com',
+            'Origin':'http://example.com',
+            'WebSocket-Protocol':'',
+        }
+    ),
+    (  # Wrong port number format
+        80,
+        '/demo',
+        {
+            'Upgrade':'WebSocket',
+            'Connection':'Upgrade',
+            'Host':'example.com:0x50',
+            'Origin':'http://example.com',
+            'WebSocket-Protocol':'sample',
+        }
+    ),
+    (  # Header/connection port mismatch
+        8080,
+        '/demo',
+        {
+            'Upgrade':'WebSocket',
+            'Connection':'Upgrade',
+            'Host':'example.com',
+            'Origin':'http://example.com',
+            'WebSocket-Protocol':'sample',
+        }
+    ),
+    (  # Illegal WebSocket-Protocol
+        80,
+        '/demo',
+        {
+            'Upgrade':'WebSocket',
+            'Connection':'Upgrade',
+            'Host':'example.com',
+            'Origin':'http://example.com',
+            'WebSocket-Protocol':'illegal protocol',
+        }
+    ),
+)
+
+
+def _create_request(request_def):
+    conn = mock.MockConn('')
+    conn.local_addr = ('0.0.0.0', request_def[0])
+    return mock.MockRequest(
+            uri=request_def[1],
+            headers_in=request_def[2],
+            connection=conn)
+
+
+class HandshakerTest(unittest.TestCase):
+    def test_validate_protocol(self):
+        handshake._validate_protocol('sample')  # should succeed.
+        handshake._validate_protocol('Sample')  # should succeed.
+        self.assertRaises(handshake.HandshakeError,
+                          handshake._validate_protocol,
+                          'sample protocol')
+        self.assertRaises(handshake.HandshakeError,
+                          handshake._validate_protocol,
+                          # "Japan" in Japanese
+                          u'\u65e5\u672c')
+
+    def test_good_request_default_port(self):
+        request = _create_request(_GOOD_REQUEST)
+        handshaker = handshake.Handshaker(request,
+                                          mock.MockDispatcher())
+        handshaker.do_handshake()
+        self.assertEqual(_GOOD_RESPONSE_DEFAULT_PORT,
+                         request.connection.written_data())
+        self.assertEqual('/demo', request.ws_resource)
+        self.assertEqual('http://example.com', request.ws_origin)
+        self.assertEqual('ws://example.com/demo', request.ws_location)
+        self.assertEqual('sample', request.ws_protocol)
+
+    def test_good_request_secure_default_port(self):
+        request = _create_request(_GOOD_REQUEST)
+        request.connection.local_addr = ('0.0.0.0', 443)
+        request.is_https_ = True
+        handshaker = handshake.Handshaker(request,
+                                          mock.MockDispatcher())
+        handshaker.do_handshake()
+        self.assertEqual(_GOOD_RESPONSE_SECURE,
+                         request.connection.written_data())
+        self.assertEqual('sample', request.ws_protocol)
+
+    def test_good_request_nondefault_port(self):
+        request = _create_request(_GOOD_REQUEST_NONDEFAULT_PORT)
+        handshaker = handshake.Handshaker(request,
+                                          mock.MockDispatcher())
+        handshaker.do_handshake()
+        self.assertEqual(_GOOD_RESPONSE_NONDEFAULT_PORT,
+                         request.connection.written_data())
+        self.assertEqual('sample', request.ws_protocol)
+
+    def test_good_request_secure_non_default_port(self):
+        request = _create_request(_GOOD_REQUEST_NONDEFAULT_PORT)
+        request.is_https_ = True
+        handshaker = handshake.Handshaker(request,
+                                          mock.MockDispatcher())
+        handshaker.do_handshake()
+        self.assertEqual(_GOOD_RESPONSE_SECURE_NONDEF,
+                         request.connection.written_data())
+        self.assertEqual('sample', request.ws_protocol)
+
+    def test_good_request_default_no_protocol(self):
+        request = _create_request(_GOOD_REQUEST_NO_PROTOCOL)
+        handshaker = handshake.Handshaker(request,
+                                          mock.MockDispatcher())
+        handshaker.do_handshake()
+        self.assertEqual(_GOOD_RESPONSE_NO_PROTOCOL,
+                         request.connection.written_data())
+        self.assertEqual(None, request.ws_protocol)
+
+    def test_good_request_optional_headers(self):
+        request = _create_request(_GOOD_REQUEST_WITH_OPTIONAL_HEADERS)
+        handshaker = handshake.Handshaker(request,
+                                          mock.MockDispatcher())
+        handshaker.do_handshake()
+        self.assertEqual('AValue',
+                         request.headers_in['AKey'])
+        self.assertEqual('',
+                         request.headers_in['EmptyValue'])
+
+    def test_bad_requests(self):
+        for request in map(_create_request, _BAD_REQUESTS):
+            handshaker = handshake.Handshaker(request,
+                                              mock.MockDispatcher())
+            self.assertRaises(handshake.HandshakeError, handshaker.do_handshake)
+
+
+if __name__ == '__main__':
+    unittest.main()
+
+
+# vi:sts=4 sw=4 et
diff --git a/WebKitTools/pywebsocket/test/test_mock.py b/WebKitTools/pywebsocket/test/test_mock.py
new file mode 100644 (file)
index 0000000..8b137d1
--- /dev/null
@@ -0,0 +1,126 @@
+#!/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.
+
+
+"""Tests for mock module."""
+
+
+import Queue
+import threading
+import unittest
+
+import mock
+
+
+class MockConnTest(unittest.TestCase):
+    def setUp(self):
+        self._conn = mock.MockConn('ABC\r\nDEFG\r\n\r\nHIJK')
+
+    def test_readline(self):
+        self.assertEqual('ABC\r\n', self._conn.readline())
+        self.assertEqual('DEFG\r\n', self._conn.readline())
+        self.assertEqual('\r\n', self._conn.readline())
+        self.assertEqual('HIJK', self._conn.readline())
+        self.assertEqual('', self._conn.readline())
+
+    def test_read(self):
+        self.assertEqual('ABC\r\nD', self._conn.read(6))
+        self.assertEqual('EFG\r\n\r\nHI', self._conn.read(9))
+        self.assertEqual('JK', self._conn.read(10))
+        self.assertEqual('', self._conn.read(10))
+
+    def test_read_and_readline(self):
+        self.assertEqual('ABC\r\nD', self._conn.read(6))
+        self.assertEqual('EFG\r\n', self._conn.readline())
+        self.assertEqual('\r\nHIJK', self._conn.read(9))
+        self.assertEqual('', self._conn.readline())
+
+    def test_write(self):
+        self._conn.write('Hello\r\n')
+        self._conn.write('World\r\n')
+        self.assertEqual('Hello\r\nWorld\r\n', self._conn.written_data())
+
+
+class MockBlockingConnTest(unittest.TestCase):
+    def test_read(self):
+        class LineReader(threading.Thread):
+            def __init__(self, conn, queue):
+                threading.Thread.__init__(self)
+                self._queue = queue
+                self._conn = conn
+                self.setDaemon(True)
+                self.start()
+            def run(self):
+                while True:
+                    data = self._conn.readline()
+                    self._queue.put(data)
+        conn = mock.MockBlockingConn()
+        queue = Queue.Queue()
+        reader = LineReader(conn, queue)
+        self.failUnless(queue.empty())
+        conn.put_bytes('Foo bar\r\n')
+        read = queue.get()
+        self.assertEqual('Foo bar\r\n', read)
+
+
+class MockTableTest(unittest.TestCase):
+    def test_create_from_dict(self):
+        table = mock.MockTable({'Key':'Value'})
+        self.assertEqual('Value', table.get('KEY'))
+        self.assertEqual('Value', table['key'])
+
+    def test_create_from_list(self):
+        table = mock.MockTable([('Key', 'Value')])
+        self.assertEqual('Value', table.get('KEY'))
+        self.assertEqual('Value', table['key'])
+
+    def test_create_from_tuple(self):
+        table = mock.MockTable((('Key', 'Value'),))
+        self.assertEqual('Value', table.get('KEY'))
+        self.assertEqual('Value', table['key'])
+
+    def test_set_and_get(self):
+        table = mock.MockTable()
+        self.assertEqual(None, table.get('Key'))
+        table['Key'] = 'Value'
+        self.assertEqual('Value', table.get('Key'))
+        self.assertEqual('Value', table.get('key'))
+        self.assertEqual('Value', table.get('KEY'))
+        self.assertEqual('Value', table['Key'])
+        self.assertEqual('Value', table['key'])
+        self.assertEqual('Value', table['KEY'])
+
+
+if __name__ == '__main__':
+    unittest.main()
+
+
+# vi:sts=4 sw=4 et
diff --git a/WebKitTools/pywebsocket/test/test_msgutil.py b/WebKitTools/pywebsocket/test/test_msgutil.py
new file mode 100644 (file)
index 0000000..b3ba539
--- /dev/null
@@ -0,0 +1,149 @@
+#!/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.
+
+
+"""Tests for msgutil module."""
+
+
+import Queue
+import unittest
+
+import config  # This must be imported before mod_pywebsocket.
+from mod_pywebsocket import msgutil
+
+import mock
+
+
+def _create_request(read_data):
+    return mock.MockRequest(connection=mock.MockConn(read_data))
+
+def _create_blocking_request():
+    return mock.MockRequest(connection=mock.MockBlockingConn())
+
+class MessageTest(unittest.TestCase):
+    def test_send_message(self):
+        request = _create_request('')
+        msgutil.send_message(request, 'Hello')
+        self.assertEqual('\x00Hello\xff', request.connection.written_data())
+
+    def test_send_message_unicode(self):
+        request = _create_request('')
+        msgutil.send_message(request, u'\u65e5')
+        # U+65e5 is encoded as e6,97,a5 in UTF-8
+        self.assertEqual('\x00\xe6\x97\xa5\xff',
+                         request.connection.written_data())
+
+    def test_receive_message(self):
+        request = _create_request('\x00Hello\xff\x00World!\xff')
+        self.assertEqual('Hello', msgutil.receive_message(request))
+        self.assertEqual('World!', msgutil.receive_message(request))
+
+    def test_receive_message_unicode(self):
+        request = _create_request('\x00\xe6\x9c\xac\xff')
+        # U+672c is encoded as e6,9c,ac in UTF-8
+        self.assertEqual(u'\u672c', msgutil.receive_message(request))
+
+    def test_receive_message_discard(self):
+        request = _create_request('\x80\x06IGNORE\x00Hello\xff'
+                                '\x01DISREGARD\xff\x00World!\xff')
+        self.assertEqual('Hello', msgutil.receive_message(request))
+        self.assertEqual('World!', msgutil.receive_message(request))
+
+    def test_payload_length(self):
+        for length, bytes in ((0, '\x00'), (0x7f, '\x7f'), (0x80, '\x81\x00'),
+                              (0x1234, '\x80\xa4\x34')):
+            self.assertEqual(length,
+                             msgutil._payload_length(_create_request(bytes)))
+
+    def test_receive_bytes(self):
+        request = _create_request('abcdefg')
+        self.assertEqual('abc', msgutil._receive_bytes(request, 3))
+        self.assertEqual('defg', msgutil._receive_bytes(request, 4))
+
+    def test_read_until(self):
+        request = _create_request('abcXdefgX')
+        self.assertEqual('abc', msgutil._read_until(request, 'X'))
+        self.assertEqual('defg', msgutil._read_until(request, 'X'))
+
+
+class MessageReceiverTest(unittest.TestCase):
+    def test_queue(self):
+        request = _create_blocking_request()
+        receiver = msgutil.MessageReceiver(request)
+
+        self.assertEqual(None, receiver.receive_nowait())
+
+        request.connection.put_bytes('\x00Hello!\xff')
+        self.assertEqual('Hello!', receiver.receive())
+
+    def test_onmessage(self):
+        onmessage_queue = Queue.Queue()
+        def onmessage_handler(message):
+            onmessage_queue.put(message)
+
+        request = _create_blocking_request()
+        receiver = msgutil.MessageReceiver(request, onmessage_handler)
+
+        request.connection.put_bytes('\x00Hello!\xff')
+        self.assertEqual('Hello!', onmessage_queue.get())
+
+
+class MessageSenderTest(unittest.TestCase):
+    def test_send(self):
+        request = _create_blocking_request()
+        sender = msgutil.MessageSender(request)
+
+        sender.send('World')
+        self.assertEqual('\x00World\xff', request.connection.written_data())
+
+    def test_send_nowait(self):
+        # Use a queue to check the bytes written by MessageSender.
+        # request.connection.written_data() cannot be used here because
+        # MessageSender runs in a separate thread.
+        send_queue = Queue.Queue()
+        def write(bytes):
+            send_queue.put(bytes)
+        request = _create_blocking_request()
+        request.connection.write = write
+
+        sender = msgutil.MessageSender(request)
+
+        sender.send_nowait('Hello')
+        sender.send_nowait('World')
+        self.assertEqual('\x00Hello\xff', send_queue.get())
+        self.assertEqual('\x00World\xff', send_queue.get())
+
+
+if __name__ == '__main__':
+    unittest.main()
+
+
+# vi:sts=4 sw=4 et
diff --git a/WebKitTools/pywebsocket/test/test_util.py b/WebKitTools/pywebsocket/test/test_util.py
new file mode 100644 (file)
index 0000000..8058b6d
--- /dev/null
@@ -0,0 +1,57 @@
+#!/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.
+
+
+"""Tests for util module."""
+
+
+import unittest
+
+import config  # This must be imported before mod_pywebsocket.
+from mod_pywebsocket import util
+
+
+class UtilTest(unittest.TestCase):
+    def test_get_stack_trace(self):
+        self.assertEqual('None\n', util.get_stack_trace())
+        try:
+            a = 1 / 0  # Intentionally raise exception.
+        except Exception:
+            trace = util.get_stack_trace()
+            self.failUnless(trace.startswith('Traceback'))
+            self.failUnless(trace.find('ZeroDivisionError') != -1)
+
+
+if __name__ == '__main__':
+    unittest.main()
+
+
+# vi:sts=4 sw=4 et
diff --git a/WebKitTools/pywebsocket/test/testdata/handlers/blank_wsh.py b/WebKitTools/pywebsocket/test/testdata/handlers/blank_wsh.py
new file mode 100644 (file)
index 0000000..7f87c6a
--- /dev/null
@@ -0,0 +1,31 @@
+# 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.
+
+
+# intentionally left blank
diff --git a/WebKitTools/pywebsocket/test/testdata/handlers/origin_check_wsh.py b/WebKitTools/pywebsocket/test/testdata/handlers/origin_check_wsh.py
new file mode 100644 (file)
index 0000000..2c139fa
--- /dev/null
@@ -0,0 +1,42 @@
+# 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.
+
+
+def web_socket_do_extra_handshake(request):
+    if request.ws_origin == 'http://example.com':
+        return
+    raise ValueError('Unacceptable origin: %r' % request.ws_origin)
+
+
+def web_socket_transfer_data(request):
+    request.connection.write('origin_check_wsh.py is called for %s, %s' %
+                             (request.ws_resource, request.ws_protocol))
+
+
+# vi:sts=4 sw=4 et
diff --git a/WebKitTools/pywebsocket/test/testdata/handlers/sub/exception_in_transfer_wsh.py b/WebKitTools/pywebsocket/test/testdata/handlers/sub/exception_in_transfer_wsh.py
new file mode 100644 (file)
index 0000000..b982d02
--- /dev/null
@@ -0,0 +1,44 @@
+# 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.
+
+
+"""Exception in web_socket_transfer_data().
+"""
+
+
+def web_socket_do_extra_handshake(request):
+    pass
+
+
+def web_socket_transfer_data(request):
+    raise Exception('Intentional Exception for %s, %s' %
+                    (request.ws_resource, request.ws_protocol))
+
+
+# vi:sts=4 sw=4 et
diff --git a/WebKitTools/pywebsocket/test/testdata/handlers/sub/no_wsh_at_the_end.py b/WebKitTools/pywebsocket/test/testdata/handlers/sub/no_wsh_at_the_end.py
new file mode 100644 (file)
index 0000000..17e7be1
--- /dev/null
@@ -0,0 +1,45 @@
+# 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.
+
+
+"""Correct signatures, wrong file name.
+"""
+
+
+def web_socket_do_extra_handshake(request):
+    pass
+
+
+def web_socket_transfer_data(request):
+    request.connection.write(
+            'sub/no_wsh_at_the_end.py is called for %s, %s' %
+            (request.ws_resource, request.ws_protocol))
+
+
+# vi:sts=4 sw=4 et
diff --git a/WebKitTools/pywebsocket/test/testdata/handlers/sub/non_callable_wsh.py b/WebKitTools/pywebsocket/test/testdata/handlers/sub/non_callable_wsh.py
new file mode 100644 (file)
index 0000000..26352eb
--- /dev/null
@@ -0,0 +1,39 @@
+# 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.
+
+
+"""Non-callable handlers.
+"""
+
+
+web_socket_do_extra_handshake = True
+web_socket_transfer_data = 1
+
+
+# vi:sts=4 sw=4 et
diff --git a/WebKitTools/pywebsocket/test/testdata/handlers/sub/plain_wsh.py b/WebKitTools/pywebsocket/test/testdata/handlers/sub/plain_wsh.py
new file mode 100644 (file)
index 0000000..db3ff69
--- /dev/null
@@ -0,0 +1,40 @@
+# 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.
+
+
+def web_socket_do_extra_handshake(request):
+    pass
+
+
+def web_socket_transfer_data(request):
+    request.connection.write('sub/plain_wsh.py is called for %s, %s' %
+                             (request.ws_resource, request.ws_protocol))
+
+
+# vi:sts=4 sw=4 et
diff --git a/WebKitTools/pywebsocket/test/testdata/handlers/sub/wrong_handshake_sig_wsh.py b/WebKitTools/pywebsocket/test/testdata/handlers/sub/wrong_handshake_sig_wsh.py
new file mode 100644 (file)
index 0000000..6bf659b
--- /dev/null
@@ -0,0 +1,45 @@
+# 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.
+
+
+"""Wrong web_socket_do_extra_handshake signature.
+"""
+
+
+def no_web_socket_do_extra_handshake(request):
+    pass
+
+
+def web_socket_transfer_data(request):
+    request.connection.write(
+            'sub/wrong_handshake_sig_wsh.py is called for %s, %s' %
+             (request.ws_resource, request.ws_protocol))
+
+
+# vi:sts=4 sw=4 et
diff --git a/WebKitTools/pywebsocket/test/testdata/handlers/sub/wrong_transfer_sig_wsh.py b/WebKitTools/pywebsocket/test/testdata/handlers/sub/wrong_transfer_sig_wsh.py
new file mode 100644 (file)
index 0000000..e0e2e55
--- /dev/null
@@ -0,0 +1,45 @@
+# 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.
+
+
+"""Wrong web_socket_transfer_data() signature.
+"""
+
+
+def web_socket_do_extra_handshake(request):
+    pass
+
+
+def no_web_socket_transfer_data(request):
+    request.connection.write(
+            'sub/wrong_transfer_sig_wsh.py is called for %s, %s' %
+            (request.ws_resource, request.ws_protocol))
+
+
+# vi:sts=4 sw=4 et