testing/mochitest/pywebsocket/mod_pywebsocket/_stream_hixie75.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/testing/mochitest/pywebsocket/mod_pywebsocket/_stream_hixie75.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,228 @@
     1.4 +# Copyright 2011, Google Inc.
     1.5 +# All rights reserved.
     1.6 +#
     1.7 +# Redistribution and use in source and binary forms, with or without
     1.8 +# modification, are permitted provided that the following conditions are
     1.9 +# met:
    1.10 +#
    1.11 +#     * Redistributions of source code must retain the above copyright
    1.12 +# notice, this list of conditions and the following disclaimer.
    1.13 +#     * Redistributions in binary form must reproduce the above
    1.14 +# copyright notice, this list of conditions and the following disclaimer
    1.15 +# in the documentation and/or other materials provided with the
    1.16 +# distribution.
    1.17 +#     * Neither the name of Google Inc. nor the names of its
    1.18 +# contributors may be used to endorse or promote products derived from
    1.19 +# this software without specific prior written permission.
    1.20 +#
    1.21 +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    1.22 +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    1.23 +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    1.24 +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    1.25 +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    1.26 +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    1.27 +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    1.28 +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    1.29 +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    1.30 +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    1.31 +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    1.32 +
    1.33 +
    1.34 +"""This file provides a class for parsing/building frames of the WebSocket
    1.35 +protocol version HyBi 00 and Hixie 75.
    1.36 +
    1.37 +Specification:
    1.38 +http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
    1.39 +"""
    1.40 +
    1.41 +
    1.42 +from mod_pywebsocket import common
    1.43 +from mod_pywebsocket._stream_base import BadOperationException
    1.44 +from mod_pywebsocket._stream_base import ConnectionTerminatedException
    1.45 +from mod_pywebsocket._stream_base import InvalidFrameException
    1.46 +from mod_pywebsocket._stream_base import StreamBase
    1.47 +from mod_pywebsocket._stream_base import UnsupportedFrameException
    1.48 +from mod_pywebsocket import util
    1.49 +
    1.50 +
    1.51 +class StreamHixie75(StreamBase):
    1.52 +    """A class for parsing/building frames of the WebSocket protocol version
    1.53 +    HyBi 00 and Hixie 75.
    1.54 +    """
    1.55 +
    1.56 +    def __init__(self, request, enable_closing_handshake=False):
    1.57 +        """Construct an instance.
    1.58 +
    1.59 +        Args:
    1.60 +            request: mod_python request.
    1.61 +            enable_closing_handshake: to let StreamHixie75 perform closing
    1.62 +                                      handshake as specified in HyBi 00, set
    1.63 +                                      this option to True.
    1.64 +        """
    1.65 +
    1.66 +        StreamBase.__init__(self, request)
    1.67 +
    1.68 +        self._logger = util.get_class_logger(self)
    1.69 +
    1.70 +        self._enable_closing_handshake = enable_closing_handshake
    1.71 +
    1.72 +        self._request.client_terminated = False
    1.73 +        self._request.server_terminated = False
    1.74 +
    1.75 +    def send_message(self, message, end=True, binary=False):
    1.76 +        """Send message.
    1.77 +
    1.78 +        Args:
    1.79 +            message: unicode string to send.
    1.80 +            binary: not used in hixie75.
    1.81 +
    1.82 +        Raises:
    1.83 +            BadOperationException: when called on a server-terminated
    1.84 +                connection.
    1.85 +        """
    1.86 +
    1.87 +        if not end:
    1.88 +            raise BadOperationException(
    1.89 +                'StreamHixie75 doesn\'t support send_message with end=False')
    1.90 +
    1.91 +        if binary:
    1.92 +            raise BadOperationException(
    1.93 +                'StreamHixie75 doesn\'t support send_message with binary=True')
    1.94 +
    1.95 +        if self._request.server_terminated:
    1.96 +            raise BadOperationException(
    1.97 +                'Requested send_message after sending out a closing handshake')
    1.98 +
    1.99 +        self._write(''.join(['\x00', message.encode('utf-8'), '\xff']))
   1.100 +
   1.101 +    def _read_payload_length_hixie75(self):
   1.102 +        """Reads a length header in a Hixie75 version frame with length.
   1.103 +
   1.104 +        Raises:
   1.105 +            ConnectionTerminatedException: when read returns empty string.
   1.106 +        """
   1.107 +
   1.108 +        length = 0
   1.109 +        while True:
   1.110 +            b_str = self._read(1)
   1.111 +            b = ord(b_str)
   1.112 +            length = length * 128 + (b & 0x7f)
   1.113 +            if (b & 0x80) == 0:
   1.114 +                break
   1.115 +        return length
   1.116 +
   1.117 +    def receive_message(self):
   1.118 +        """Receive a WebSocket frame and return its payload an unicode string.
   1.119 +
   1.120 +        Returns:
   1.121 +            payload unicode string in a WebSocket frame.
   1.122 +
   1.123 +        Raises:
   1.124 +            ConnectionTerminatedException: when read returns empty
   1.125 +                string.
   1.126 +            BadOperationException: when called on a client-terminated
   1.127 +                connection.
   1.128 +        """
   1.129 +
   1.130 +        if self._request.client_terminated:
   1.131 +            raise BadOperationException(
   1.132 +                'Requested receive_message after receiving a closing '
   1.133 +                'handshake')
   1.134 +
   1.135 +        while True:
   1.136 +            # Read 1 byte.
   1.137 +            # mp_conn.read will block if no bytes are available.
   1.138 +            # Timeout is controlled by TimeOut directive of Apache.
   1.139 +            frame_type_str = self.receive_bytes(1)
   1.140 +            frame_type = ord(frame_type_str)
   1.141 +            if (frame_type & 0x80) == 0x80:
   1.142 +                # The payload length is specified in the frame.
   1.143 +                # Read and discard.
   1.144 +                length = self._read_payload_length_hixie75()
   1.145 +                if length > 0:
   1.146 +                    _ = self.receive_bytes(length)
   1.147 +                # 5.3 3. 12. if /type/ is 0xFF and /length/ is 0, then set the
   1.148 +                # /client terminated/ flag and abort these steps.
   1.149 +                if not self._enable_closing_handshake:
   1.150 +                    continue
   1.151 +
   1.152 +                if frame_type == 0xFF and length == 0:
   1.153 +                    self._request.client_terminated = True
   1.154 +
   1.155 +                    if self._request.server_terminated:
   1.156 +                        self._logger.debug(
   1.157 +                            'Received ack for server-initiated closing '
   1.158 +                            'handshake')
   1.159 +                        return None
   1.160 +
   1.161 +                    self._logger.debug(
   1.162 +                        'Received client-initiated closing handshake')
   1.163 +
   1.164 +                    self._send_closing_handshake()
   1.165 +                    self._logger.debug(
   1.166 +                        'Sent ack for client-initiated closing handshake')
   1.167 +                    return None
   1.168 +            else:
   1.169 +                # The payload is delimited with \xff.
   1.170 +                bytes = self._read_until('\xff')
   1.171 +                # The WebSocket protocol section 4.4 specifies that invalid
   1.172 +                # characters must be replaced with U+fffd REPLACEMENT
   1.173 +                # CHARACTER.
   1.174 +                message = bytes.decode('utf-8', 'replace')
   1.175 +                if frame_type == 0x00:
   1.176 +                    return message
   1.177 +                # Discard data of other types.
   1.178 +
   1.179 +    def _send_closing_handshake(self):
   1.180 +        if not self._enable_closing_handshake:
   1.181 +            raise BadOperationException(
   1.182 +                'Closing handshake is not supported in Hixie 75 protocol')
   1.183 +
   1.184 +        self._request.server_terminated = True
   1.185 +
   1.186 +        # 5.3 the server may decide to terminate the WebSocket connection by
   1.187 +        # running through the following steps:
   1.188 +        # 1. send a 0xFF byte and a 0x00 byte to the client to indicate the
   1.189 +        # start of the closing handshake.
   1.190 +        self._write('\xff\x00')
   1.191 +
   1.192 +    def close_connection(self, unused_code='', unused_reason=''):
   1.193 +        """Closes a WebSocket connection.
   1.194 +
   1.195 +        Raises:
   1.196 +            ConnectionTerminatedException: when closing handshake was
   1.197 +                not successfull.
   1.198 +        """
   1.199 +
   1.200 +        if self._request.server_terminated:
   1.201 +            self._logger.debug(
   1.202 +                'Requested close_connection but server is already terminated')
   1.203 +            return
   1.204 +
   1.205 +        if not self._enable_closing_handshake:
   1.206 +            self._request.server_terminated = True
   1.207 +            self._logger.debug('Connection closed')
   1.208 +            return
   1.209 +
   1.210 +        self._send_closing_handshake()
   1.211 +        self._logger.debug('Sent server-initiated closing handshake')
   1.212 +
   1.213 +        # TODO(ukai): 2. wait until the /client terminated/ flag has been set,
   1.214 +        # or until a server-defined timeout expires.
   1.215 +        #
   1.216 +        # For now, we expect receiving closing handshake right after sending
   1.217 +        # out closing handshake, and if we couldn't receive non-handshake
   1.218 +        # frame, we take it as ConnectionTerminatedException.
   1.219 +        message = self.receive_message()
   1.220 +        if message is not None:
   1.221 +            raise ConnectionTerminatedException(
   1.222 +                'Didn\'t receive valid ack for closing handshake')
   1.223 +        # TODO: 3. close the WebSocket connection.
   1.224 +        # note: mod_python Connection (mp_conn) doesn't have close method.
   1.225 +
   1.226 +    def send_ping(self, body):
   1.227 +        raise BadOperationException(
   1.228 +            'StreamHixie75 doesn\'t support send_ping')
   1.229 +
   1.230 +
   1.231 +# vi:sts=4 sw=4 et

mercurial