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