forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			229 lines
		
	
	
	
		
			8.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			229 lines
		
	
	
	
		
			8.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# Copyright 2011, 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.
 | 
						|
 | 
						|
 | 
						|
"""This file provides a class for parsing/building frames of the WebSocket
 | 
						|
protocol version HyBi 00 and Hixie 75.
 | 
						|
 | 
						|
Specification:
 | 
						|
- HyBi 00 http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
 | 
						|
- Hixie 75 http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
 | 
						|
"""
 | 
						|
 | 
						|
 | 
						|
from mod_pywebsocket import common
 | 
						|
from mod_pywebsocket._stream_base import BadOperationException
 | 
						|
from mod_pywebsocket._stream_base import ConnectionTerminatedException
 | 
						|
from mod_pywebsocket._stream_base import InvalidFrameException
 | 
						|
from mod_pywebsocket._stream_base import StreamBase
 | 
						|
from mod_pywebsocket._stream_base import UnsupportedFrameException
 | 
						|
from mod_pywebsocket import util
 | 
						|
 | 
						|
 | 
						|
class StreamHixie75(StreamBase):
 | 
						|
    """A class for parsing/building frames of the WebSocket protocol version
 | 
						|
    HyBi 00 and Hixie 75.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, request, enable_closing_handshake=False):
 | 
						|
        """Construct an instance.
 | 
						|
 | 
						|
        Args:
 | 
						|
            request: mod_python request.
 | 
						|
            enable_closing_handshake: to let StreamHixie75 perform closing
 | 
						|
                                      handshake as specified in HyBi 00, set
 | 
						|
                                      this option to True.
 | 
						|
        """
 | 
						|
 | 
						|
        StreamBase.__init__(self, request)
 | 
						|
 | 
						|
        self._logger = util.get_class_logger(self)
 | 
						|
 | 
						|
        self._enable_closing_handshake = enable_closing_handshake
 | 
						|
 | 
						|
        self._request.client_terminated = False
 | 
						|
        self._request.server_terminated = False
 | 
						|
 | 
						|
    def send_message(self, message, end=True, binary=False):
 | 
						|
        """Send message.
 | 
						|
 | 
						|
        Args:
 | 
						|
            message: unicode string to send.
 | 
						|
            binary: not used in hixie75.
 | 
						|
 | 
						|
        Raises:
 | 
						|
            BadOperationException: when called on a server-terminated
 | 
						|
                connection.
 | 
						|
        """
 | 
						|
 | 
						|
        if not end:
 | 
						|
            raise BadOperationException(
 | 
						|
                'StreamHixie75 doesn\'t support send_message with end=False')
 | 
						|
 | 
						|
        if binary:
 | 
						|
            raise BadOperationException(
 | 
						|
                'StreamHixie75 doesn\'t support send_message with binary=True')
 | 
						|
 | 
						|
        if self._request.server_terminated:
 | 
						|
            raise BadOperationException(
 | 
						|
                'Requested send_message after sending out a closing handshake')
 | 
						|
 | 
						|
        self._write(''.join(['\x00', message.encode('utf-8'), '\xff']))
 | 
						|
 | 
						|
    def _read_payload_length_hixie75(self):
 | 
						|
        """Reads a length header in a Hixie75 version frame with length.
 | 
						|
 | 
						|
        Raises:
 | 
						|
            ConnectionTerminatedException: when read returns empty string.
 | 
						|
        """
 | 
						|
 | 
						|
        length = 0
 | 
						|
        while True:
 | 
						|
            b_str = self._read(1)
 | 
						|
            b = ord(b_str)
 | 
						|
            length = length * 128 + (b & 0x7f)
 | 
						|
            if (b & 0x80) == 0:
 | 
						|
                break
 | 
						|
        return length
 | 
						|
 | 
						|
    def receive_message(self):
 | 
						|
        """Receive a WebSocket frame and return its payload an unicode string.
 | 
						|
 | 
						|
        Returns:
 | 
						|
            payload unicode string in a WebSocket frame.
 | 
						|
 | 
						|
        Raises:
 | 
						|
            ConnectionTerminatedException: when read returns empty
 | 
						|
                string.
 | 
						|
            BadOperationException: when called on a client-terminated
 | 
						|
                connection.
 | 
						|
        """
 | 
						|
 | 
						|
        if self._request.client_terminated:
 | 
						|
            raise BadOperationException(
 | 
						|
                'Requested receive_message after receiving a closing '
 | 
						|
                'handshake')
 | 
						|
 | 
						|
        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 = self.receive_bytes(1)
 | 
						|
            frame_type = ord(frame_type_str)
 | 
						|
            if (frame_type & 0x80) == 0x80:
 | 
						|
                # The payload length is specified in the frame.
 | 
						|
                # Read and discard.
 | 
						|
                length = self._read_payload_length_hixie75()
 | 
						|
                if length > 0:
 | 
						|
                    _ = self.receive_bytes(length)
 | 
						|
                # 5.3 3. 12. if /type/ is 0xFF and /length/ is 0, then set the
 | 
						|
                # /client terminated/ flag and abort these steps.
 | 
						|
                if not self._enable_closing_handshake:
 | 
						|
                    continue
 | 
						|
 | 
						|
                if frame_type == 0xFF and length == 0:
 | 
						|
                    self._request.client_terminated = True
 | 
						|
 | 
						|
                    if self._request.server_terminated:
 | 
						|
                        self._logger.debug(
 | 
						|
                            'Received ack for server-initiated closing '
 | 
						|
                            'handshake')
 | 
						|
                        return None
 | 
						|
 | 
						|
                    self._logger.debug(
 | 
						|
                        'Received client-initiated closing handshake')
 | 
						|
 | 
						|
                    self._send_closing_handshake()
 | 
						|
                    self._logger.debug(
 | 
						|
                        'Sent ack for client-initiated closing handshake')
 | 
						|
                    return None
 | 
						|
            else:
 | 
						|
                # The payload is delimited with \xff.
 | 
						|
                bytes = self._read_until('\xff')
 | 
						|
                # The WebSocket protocol section 4.4 specifies that invalid
 | 
						|
                # characters must be replaced with U+fffd REPLACEMENT
 | 
						|
                # CHARACTER.
 | 
						|
                message = bytes.decode('utf-8', 'replace')
 | 
						|
                if frame_type == 0x00:
 | 
						|
                    return message
 | 
						|
                # Discard data of other types.
 | 
						|
 | 
						|
    def _send_closing_handshake(self):
 | 
						|
        if not self._enable_closing_handshake:
 | 
						|
            raise BadOperationException(
 | 
						|
                'Closing handshake is not supported in Hixie 75 protocol')
 | 
						|
 | 
						|
        self._request.server_terminated = True
 | 
						|
 | 
						|
        # 5.3 the server may decide to terminate the WebSocket connection by
 | 
						|
        # running through the following steps:
 | 
						|
        # 1. send a 0xFF byte and a 0x00 byte to the client to indicate the
 | 
						|
        # start of the closing handshake.
 | 
						|
        self._write('\xff\x00')
 | 
						|
 | 
						|
    def close_connection(self, unused_code='', unused_reason=''):
 | 
						|
        """Closes a WebSocket connection.
 | 
						|
 | 
						|
        Raises:
 | 
						|
            ConnectionTerminatedException: when closing handshake was
 | 
						|
                not successfull.
 | 
						|
        """
 | 
						|
 | 
						|
        if self._request.server_terminated:
 | 
						|
            self._logger.debug(
 | 
						|
                'Requested close_connection but server is already terminated')
 | 
						|
            return
 | 
						|
 | 
						|
        if not self._enable_closing_handshake:
 | 
						|
            self._request.server_terminated = True
 | 
						|
            self._logger.debug('Connection closed')
 | 
						|
            return
 | 
						|
 | 
						|
        self._send_closing_handshake()
 | 
						|
        self._logger.debug('Sent server-initiated closing handshake')
 | 
						|
 | 
						|
        # TODO(ukai): 2. wait until the /client terminated/ flag has been set,
 | 
						|
        # or until a server-defined timeout expires.
 | 
						|
        #
 | 
						|
        # For now, we expect receiving closing handshake right after sending
 | 
						|
        # out closing handshake, and if we couldn't receive non-handshake
 | 
						|
        # frame, we take it as ConnectionTerminatedException.
 | 
						|
        message = self.receive_message()
 | 
						|
        if message is not None:
 | 
						|
            raise ConnectionTerminatedException(
 | 
						|
                'Didn\'t receive valid ack for closing handshake')
 | 
						|
        # TODO: 3. close the WebSocket connection.
 | 
						|
        # note: mod_python Connection (mp_conn) doesn't have close method.
 | 
						|
 | 
						|
    def send_ping(self, body):
 | 
						|
        raise BadOperationException(
 | 
						|
            'StreamHixie75 doesn\'t support send_ping')
 | 
						|
 | 
						|
 | 
						|
# vi:sts=4 sw=4 et
 |