示例#1
0
    def send_message(self, message, end=True, binary=False):
        """Send message.

        Args:
            message: text in unicode or binary in str to send.
            binary: send message as binary frame.

        Raises:
            BadOperationException: when called on a server-terminated
                connection or called with inconsistent message type or binary
                parameter.
        """

        if self._request.server_terminated:
            raise BadOperationException(
                'Requested send_message after sending out a closing handshake')

        if binary and isinstance(message, unicode):
            raise BadOperationException(
                'Message for binary frame must be instance of str')

        try:
            self._write(self._writer.build(message, end, binary))
        except ValueError, e:
            raise BadOperationException(e)
示例#2
0
    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']))
示例#3
0
    def close_connection(self,
                         code=common.STATUS_NORMAL_CLOSURE,
                         reason='',
                         wait_response=True):
        """Closes a WebSocket connection. Note that this method blocks until
        it receives acknowledgement to the closing handshake.

        Args:
            code: Status code for close frame. If code is None, a close
                frame with empty body will be sent.
            reason: string representing close reason.
            wait_response: True when caller want to wait the response.
        Raises:
            BadOperationException: when reason is specified with code None
            or reason is not an instance of both str and unicode.
        """

        if self._request.server_terminated:
            self._logger.debug(
                'Requested close_connection but server is already terminated')
            return

        # When we receive a close frame, we call _process_close_message().
        # _process_close_message() immediately acknowledges to the
        # server-initiated closing handshake and sets server_terminated to
        # True. So, here we can assume that we haven't received any close
        # frame. We're initiating a closing handshake.

        if code is None:
            if reason is not None and len(reason) > 0:
                raise BadOperationException(
                    'close reason must not be specified if code is None')
            reason = ''
        else:
            if not isinstance(reason, str) and not isinstance(reason, unicode):
                raise BadOperationException(
                    'close reason must be an instance of str or unicode')

        self._send_closing_handshake(code, reason)
        self._logger.debug('Initiated closing handshake (code=%r, reason=%r)',
                           code, reason)

        if (code == common.STATUS_GOING_AWAY
                or code == common.STATUS_PROTOCOL_ERROR) or not wait_response:
            # It doesn't make sense to wait for a close frame if the reason is
            # protocol error or that the server is going away. For some of
            # other reasons, it might not make sense to wait for a close frame,
            # but it's not clear, yet.
            return

        # 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.
        message = self.receive_message()
        if message is not None:
            raise ConnectionTerminatedException(
                'Didn\'t receive valid ack for closing handshake')
示例#4
0
    def send_message(self, message, end=True, binary=False):
        """Send message.

        Args:
            message: text in unicode or binary in str to send.
            binary: send message as binary frame.

        Raises:
            BadOperationException: when called on a server-terminated
                connection or called with inconsistent message type or
                binary parameter.
        """

        if self._request.server_terminated:
            raise BadOperationException(
                'Requested send_message after sending out a closing handshake')

        if binary and isinstance(message, unicode):
            raise BadOperationException(
                'Message for binary frame must be instance of str')

        for message_filter in self._options.outgoing_message_filters:
            message = message_filter.filter(message, end, binary)

        try:
            # Set this to any positive integer to limit maximum size of data in
            # payload data of each frame.
            MAX_PAYLOAD_DATA_SIZE = -1

            if MAX_PAYLOAD_DATA_SIZE <= 0:
                self._write(self._writer.build(message, end, binary))
                return

            bytes_written = 0
            while True:
                end_for_this_frame = end
                bytes_to_write = len(message) - bytes_written
                if (MAX_PAYLOAD_DATA_SIZE > 0 and
                    bytes_to_write > MAX_PAYLOAD_DATA_SIZE):
                    end_for_this_frame = False
                    bytes_to_write = MAX_PAYLOAD_DATA_SIZE

                frame = self._writer.build(
                    message[bytes_written:bytes_written + bytes_to_write],
                    end_for_this_frame,
                    binary)
                self._write(frame)

                bytes_written += bytes_to_write

                # This if must be placed here (the end of while block) so that
                # at least one frame is sent.
                if len(message) <= bytes_written:
                    break
        except ValueError, e:
            raise BadOperationException(e)
示例#5
0
def create_closing_handshake_body(code, reason):
    body = ''
    if code is not None:
        if (code > common.STATUS_USER_PRIVATE_MAX
                or code < common.STATUS_NORMAL_CLOSURE):
            raise BadOperationException('Status code is out of range')
        if (code == common.STATUS_NO_STATUS_RECEIVED
                or code == common.STATUS_ABNORMAL_CLOSURE
                or code == common.STATUS_TLS_HANDSHAKE):
            raise BadOperationException('Status code is reserved pseudo '
                                        'code')
        encoded_reason = reason.encode('utf-8')
        body = struct.pack('!H', code) + encoded_reason
    return body
示例#6
0
    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
示例#7
0
    def _send_closing_handshake(self, code, reason):
        if code >= (1 << 16) or code < 0:
            raise BadOperationException('Status code is out of range')

        encoded_reason = reason.encode('utf-8')
        if len(encoded_reason) + 2 > 125:
            raise BadOperationException(
                'Application data size of close frames must be 125 bytes or '
                'less')

        frame = create_close_frame(
            struct.pack('!H', code) + encoded_reason, self._options.mask_send)

        self._request.server_terminated = True

        self._write(frame)
示例#8
0
    def _send_closing_handshake(self, code, reason):
        body = ''
        if code is not None:
            if (code > common.STATUS_USER_PRIVATE_MAX
                    or code < common.STATUS_NORMAL_CLOSURE):
                raise BadOperationException('Status code is out of range')
            if (code == common.STATUS_NO_STATUS_RECEIVED
                    or code == common.STATUS_ABNORMAL_CLOSURE
                    or code == common.STATUS_TLS_HANDSHAKE):
                raise BadOperationException('Status code is reserved pseudo '
                                            'code')
            encoded_reason = reason.encode('utf-8')
            body = struct.pack('!H', code) + encoded_reason

        frame = create_close_frame(body, self._options.mask_send,
                                   self._options.outgoing_frame_filters)

        self._request.server_terminated = True

        self._write(frame)
示例#9
0
    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')
示例#10
0
def _create_control_frame(opcode, body, mask, frame_filters):
    frame = Frame(opcode=opcode, payload=body)

    for frame_filter in frame_filters:
        frame_filter.filter(frame)

    if len(frame.payload) > 125:
        raise BadOperationException(
            'Payload data size of control frames must be 125 bytes or less')

    header = create_header(frame.opcode, len(frame.payload), frame.fin,
                           frame.rsv1, frame.rsv2, frame.rsv3, mask)
    return _build_frame(header, frame.payload, mask)
示例#11
0
    def send_message(self, message, end=True):
        """Send message.

        Args:
            message: unicode string to send.

        Raises:
            BadOperationException: when called on a server-terminated
                connection.
        """

        if self._request.server_terminated:
            raise BadOperationException(
                'Requested send_message after sending out a closing handshake')

        self._write(self._writer.build(message, end))
示例#12
0
 def send_ping(self, body):
     raise BadOperationException(
         'StreamHixie75 doesn\'t support send_ping')
示例#13
0
    def receive_message(self):
        """Receive a WebSocket frame and return its payload as a text in
        unicode or a binary in str.

        Returns:
            payload data of the frame
            - as unicode instance if received text frame
            - as str instance if received binary frame
            or None iff received closing handshake.
        Raises:
            BadOperationException: when called on a client-terminated
                connection.
            ConnectionTerminatedException: when read returns empty
                string.
            InvalidFrameException: when the frame contains invalid
                data.
            UnsupportedFrameException: when the received frame has
                flags, opcode we cannot handle. You can ignore this
                exception and continue receiving the next frame.
        """

        if self._request.client_terminated:
            raise BadOperationException(
                'Requested receive_message after receiving a closing '
                'handshake')

        while True:
            # mp_conn.read will block if no bytes are available.
            # Timeout is controlled by TimeOut directive of Apache.

            frame = self._receive_frame_as_frame_object()

            # Check the constraint on the payload size for control frames
            # before extension processes the frame.
            # See also http://tools.ietf.org/html/rfc6455#section-5.5
            if (common.is_control_opcode(frame.opcode)
                    and len(frame.payload) > 125):
                raise InvalidFrameException(
                    'Payload data size of control frames must be 125 bytes or '
                    'less')

            for frame_filter in self._options.incoming_frame_filters:
                frame_filter.filter(frame)

            if frame.rsv1 or frame.rsv2 or frame.rsv3:
                raise UnsupportedFrameException(
                    'Unsupported flag is set (rsv = %d%d%d)' %
                    (frame.rsv1, frame.rsv2, frame.rsv3))

            message = self._get_message_from_frame(frame)
            if message is None:
                continue

            for message_filter in self._options.incoming_message_filters:
                message = message_filter.filter(message)

            if self._original_opcode == common.OPCODE_TEXT:
                # The WebSocket protocol section 4.4 specifies that invalid
                # characters must be replaced with U+fffd REPLACEMENT
                # CHARACTER.
                try:
                    return message.decode('utf-8')
                except UnicodeDecodeError, e:
                    raise InvalidUTF8Exception(e)
            elif self._original_opcode == common.OPCODE_BINARY:
                return message
示例#14
0
    def receive_message(self):
        """Receive a WebSocket frame and return its payload an unicode string.

        Returns:
            payload unicode string in a WebSocket frame. None iff received
            closing handshake.
        Raises:
            BadOperationException: when called on a client-terminated
                connection.
            ConnectionTerminatedException: when read returns empty
                string.
            InvalidFrameException: when the frame contains invalid
                data.
            UnsupportedFrameException: when the received frame has
                flags, opcode we cannot handle. You can ignore this exception
                and continue receiving the next frame.
        """

        if self._request.client_terminated:
            raise BadOperationException(
                'Requested receive_message after receiving a closing '
                'handshake')

        while True:
            # mp_conn.read will block if no bytes are available.
            # Timeout is controlled by TimeOut directive of Apache.

            opcode, bytes, fin, rsv1, rsv2, rsv3 = self._receive_frame()
            if rsv1 or rsv2 or rsv3:
                raise UnsupportedFrameException(
                    'Unsupported flag is set (rsv = %d%d%d)' %
                    (rsv1, rsv2, rsv3))

            if opcode == common.OPCODE_CONTINUATION:
                if not self._received_fragments:
                    if fin:
                        raise InvalidFrameException(
                            'Received a termination frame but fragmentation '
                            'not started')
                    else:
                        raise InvalidFrameException(
                            'Received an intermediate frame but '
                            'fragmentation not started')

                if fin:
                    # End of fragmentation frame
                    self._received_fragments.append(bytes)
                    message = ''.join(self._received_fragments)
                    self._received_fragments = []
                else:
                    # Intermediate frame
                    self._received_fragments.append(bytes)
                    continue
            else:
                if self._received_fragments:
                    if fin:
                        raise InvalidFrameException(
                            'Received an unfragmented frame without '
                            'terminating existing fragmentation')
                    else:
                        raise InvalidFrameException(
                            'New fragmentation started without terminating '
                            'existing fragmentation')

                if fin:
                    # Unfragmented frame
                    self._original_opcode = opcode
                    message = bytes

                    if is_control_opcode(opcode) and len(message) > 125:
                        raise InvalidFrameException(
                            'Application data size of control frames must be '
                            '125 bytes or less')
                else:
                    # Start of fragmentation frame

                    if is_control_opcode(opcode):
                        raise InvalidFrameException(
                            'Control frames must not be fragmented')

                    self._original_opcode = opcode
                    self._received_fragments.append(bytes)
                    continue

            if self._original_opcode == common.OPCODE_TEXT:
                # The WebSocket protocol section 4.4 specifies that invalid
                # characters must be replaced with U+fffd REPLACEMENT
                # CHARACTER.
                return message.decode('utf-8', 'replace')
            elif self._original_opcode == common.OPCODE_CLOSE:
                self._request.client_terminated = True

                # Status code is optional. We can have status reason only if we
                # have status code. Status reason can be empty string. So,
                # allowed cases are
                # - no application data: no code no reason
                # - 2 octet of application data: has code but no reason
                # - 3 or more octet of application data: both code and reason
                if len(message) == 1:
                    raise InvalidFrameException(
                        'If a close frame has status code, the length of '
                        'status code must be 2 octet')
                elif len(message) >= 2:
                    self._request.ws_close_code = struct.unpack(
                        '!H', message[0:2])[0]
                    self._request.ws_close_reason = message[2:].decode(
                        'utf-8', 'replace')

                self._logger.debug('Initiated flush read')
                self.flushread()

                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(common.STATUS_NORMAL, '')
                self._logger.debug(
                    'Sent ack for client-initiated closing handshake')
                return None
            elif self._original_opcode == common.OPCODE_PING:
                try:
                    handler = self._request.on_ping_handler
                    if handler:
                        handler(self._request, message)
                        continue
                except AttributeError, e:
                    pass
                self._send_pong(message)
            elif self._original_opcode == common.OPCODE_PONG:
                # TODO(tyoshino): Add ping timeout handling.

                inflight_pings = deque()

                while True:
                    try:
                        expected_body = self._ping_queue.popleft()
                        if expected_body == message:
                            # inflight_pings contains pings ignored by the
                            # other peer. Just forget them.
                            self._logger.debug(
                                'Ping %r is acked (%d pings were ignored)',
                                expected_body, len(inflight_pings))
                            break
                        else:
                            inflight_pings.append(expected_body)
                    except IndexError, e:
                        # The received pong was unsolicited pong. Keep the
                        # ping queue as is.
                        self._ping_queue = inflight_pings
                        self._logger.debug('Received a unsolicited pong')
                        break
示例#15
0
    def receive_message(self):
        """Receive a WebSocket frame and return its payload as a text in
        unicode or a binary in str.

        Returns:
            payload data of the frame
            - as unicode instance if received text frame
            - as str instance if received binary frame
            or None iff received closing handshake.
        Raises:
            BadOperationException: when called on a client-terminated
                connection.
            ConnectionTerminatedException: when read returns empty
                string.
            InvalidFrameException: when the frame contains invalid
                data.
            UnsupportedFrameException: when the received frame has
                flags, opcode we cannot handle. You can ignore this
                exception and continue receiving the next frame.
        """

        if self._request.client_terminated:
            raise BadOperationException(
                'Requested receive_message after receiving a closing '
                'handshake')

        while True:
            # mp_conn.read will block if no bytes are available.
            # Timeout is controlled by TimeOut directive of Apache.

            frame = self._receive_frame_as_frame_object()

            for frame_filter in self._options.incoming_frame_filters:
                frame_filter.filter(frame)

            if frame.rsv1 or frame.rsv2 or frame.rsv3:
                raise UnsupportedFrameException(
                    'Unsupported flag is set (rsv = %d%d%d)' %
                    (frame.rsv1, frame.rsv2, frame.rsv3))

            if frame.opcode == common.OPCODE_CONTINUATION:
                if not self._received_fragments:
                    if frame.fin:
                        raise InvalidFrameException(
                            'Received a termination frame but fragmentation '
                            'not started')
                    else:
                        raise InvalidFrameException(
                            'Received an intermediate frame but '
                            'fragmentation not started')

                if frame.fin:
                    # End of fragmentation frame
                    self._received_fragments.append(frame.payload)
                    message = ''.join(self._received_fragments)
                    self._received_fragments = []
                else:
                    # Intermediate frame
                    self._received_fragments.append(frame.payload)
                    continue
            else:
                if self._received_fragments:
                    if frame.fin:
                        raise InvalidFrameException(
                            'Received an unfragmented frame without '
                            'terminating existing fragmentation')
                    else:
                        raise InvalidFrameException(
                            'New fragmentation started without terminating '
                            'existing fragmentation')

                if frame.fin:
                    # Unfragmented frame

                    if (common.is_control_opcode(frame.opcode)
                            and len(frame.payload) > 125):
                        raise InvalidFrameException(
                            'Application data size of control frames must be '
                            '125 bytes or less')

                    self._original_opcode = frame.opcode
                    message = frame.payload
                else:
                    # Start of fragmentation frame

                    if common.is_control_opcode(frame.opcode):
                        raise InvalidFrameException(
                            'Control frames must not be fragmented')

                    self._original_opcode = frame.opcode
                    self._received_fragments.append(frame.payload)
                    continue

            if self._original_opcode == common.OPCODE_TEXT:
                # The WebSocket protocol section 4.4 specifies that invalid
                # characters must be replaced with U+fffd REPLACEMENT
                # CHARACTER.
                try:
                    return message.decode('utf-8')
                except UnicodeDecodeError, e:
                    raise InvalidUTF8Exception(e)
            elif self._original_opcode == common.OPCODE_BINARY:
                return message