summaryrefslogtreecommitdiffstats
path: root/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hybi.py
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hybi.py')
-rw-r--r--Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hybi.py887
1 files changed, 0 insertions, 887 deletions
diff --git a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hybi.py b/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hybi.py
deleted file mode 100644
index a8a49e3c3..000000000
--- a/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/_stream_hybi.py
+++ /dev/null
@@ -1,887 +0,0 @@
-# Copyright 2012, 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 classes and helper functions for parsing/building frames
-of the WebSocket protocol (RFC 6455).
-
-Specification:
-http://tools.ietf.org/html/rfc6455
-"""
-
-
-from collections import deque
-import logging
-import os
-import struct
-import time
-
-from mod_pywebsocket import common
-from mod_pywebsocket import util
-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 InvalidUTF8Exception
-from mod_pywebsocket._stream_base import StreamBase
-from mod_pywebsocket._stream_base import UnsupportedFrameException
-
-
-_NOOP_MASKER = util.NoopMasker()
-
-
-class Frame(object):
-
- def __init__(self, fin=1, rsv1=0, rsv2=0, rsv3=0,
- opcode=None, payload=''):
- self.fin = fin
- self.rsv1 = rsv1
- self.rsv2 = rsv2
- self.rsv3 = rsv3
- self.opcode = opcode
- self.payload = payload
-
-
-# Helper functions made public to be used for writing unittests for WebSocket
-# clients.
-
-
-def create_length_header(length, mask):
- """Creates a length header.
-
- Args:
- length: Frame length. Must be less than 2^63.
- mask: Mask bit. Must be boolean.
-
- Raises:
- ValueError: when bad data is given.
- """
-
- if mask:
- mask_bit = 1 << 7
- else:
- mask_bit = 0
-
- if length < 0:
- raise ValueError('length must be non negative integer')
- elif length <= 125:
- return chr(mask_bit | length)
- elif length < (1 << 16):
- return chr(mask_bit | 126) + struct.pack('!H', length)
- elif length < (1 << 63):
- return chr(mask_bit | 127) + struct.pack('!Q', length)
- else:
- raise ValueError('Payload is too big for one frame')
-
-
-def create_header(opcode, payload_length, fin, rsv1, rsv2, rsv3, mask):
- """Creates a frame header.
-
- Raises:
- Exception: when bad data is given.
- """
-
- if opcode < 0 or 0xf < opcode:
- raise ValueError('Opcode out of range')
-
- if payload_length < 0 or (1 << 63) <= payload_length:
- raise ValueError('payload_length out of range')
-
- if (fin | rsv1 | rsv2 | rsv3) & ~1:
- raise ValueError('FIN bit and Reserved bit parameter must be 0 or 1')
-
- header = ''
-
- first_byte = ((fin << 7)
- | (rsv1 << 6) | (rsv2 << 5) | (rsv3 << 4)
- | opcode)
- header += chr(first_byte)
- header += create_length_header(payload_length, mask)
-
- return header
-
-
-def _build_frame(header, body, mask):
- if not mask:
- return header + body
-
- masking_nonce = os.urandom(4)
- masker = util.RepeatedXorMasker(masking_nonce)
-
- return header + masking_nonce + masker.mask(body)
-
-
-def _filter_and_format_frame_object(frame, mask, frame_filters):
- for frame_filter in frame_filters:
- frame_filter.filter(frame)
-
- header = create_header(
- frame.opcode, len(frame.payload), frame.fin,
- frame.rsv1, frame.rsv2, frame.rsv3, mask)
- return _build_frame(header, frame.payload, mask)
-
-
-def create_binary_frame(
- message, opcode=common.OPCODE_BINARY, fin=1, mask=False, frame_filters=[]):
- """Creates a simple binary frame with no extension, reserved bit."""
-
- frame = Frame(fin=fin, opcode=opcode, payload=message)
- return _filter_and_format_frame_object(frame, mask, frame_filters)
-
-
-def create_text_frame(
- message, opcode=common.OPCODE_TEXT, fin=1, mask=False, frame_filters=[]):
- """Creates a simple text frame with no extension, reserved bit."""
-
- encoded_message = message.encode('utf-8')
- return create_binary_frame(encoded_message, opcode, fin, mask,
- frame_filters)
-
-
-def parse_frame(receive_bytes, logger=None,
- ws_version=common.VERSION_HYBI_LATEST,
- unmask_receive=True):
- """Parses a frame. Returns a tuple containing each header field and
- payload.
-
- Args:
- receive_bytes: a function that reads frame data from a stream or
- something similar. The function takes length of the bytes to be
- read. The function must raise ConnectionTerminatedException if
- there is not enough data to be read.
- logger: a logging object.
- ws_version: the version of WebSocket protocol.
- unmask_receive: unmask received frames. When received unmasked
- frame, raises InvalidFrameException.
-
- Raises:
- ConnectionTerminatedException: when receive_bytes raises it.
- InvalidFrameException: when the frame contains invalid data.
- """
-
- if not logger:
- logger = logging.getLogger()
-
- logger.log(common.LOGLEVEL_FINE, 'Receive the first 2 octets of a frame')
-
- received = receive_bytes(2)
-
- first_byte = ord(received[0])
- fin = (first_byte >> 7) & 1
- rsv1 = (first_byte >> 6) & 1
- rsv2 = (first_byte >> 5) & 1
- rsv3 = (first_byte >> 4) & 1
- opcode = first_byte & 0xf
-
- second_byte = ord(received[1])
- mask = (second_byte >> 7) & 1
- payload_length = second_byte & 0x7f
-
- logger.log(common.LOGLEVEL_FINE,
- 'FIN=%s, RSV1=%s, RSV2=%s, RSV3=%s, opcode=%s, '
- 'Mask=%s, Payload_length=%s',
- fin, rsv1, rsv2, rsv3, opcode, mask, payload_length)
-
- if (mask == 1) != unmask_receive:
- raise InvalidFrameException(
- 'Mask bit on the received frame did\'nt match masking '
- 'configuration for received frames')
-
- # The HyBi and later specs disallow putting a value in 0x0-0xFFFF
- # into the 8-octet extended payload length field (or 0x0-0xFD in
- # 2-octet field).
- valid_length_encoding = True
- length_encoding_bytes = 1
- if payload_length == 127:
- logger.log(common.LOGLEVEL_FINE,
- 'Receive 8-octet extended payload length')
-
- extended_payload_length = receive_bytes(8)
- payload_length = struct.unpack(
- '!Q', extended_payload_length)[0]
- if payload_length > 0x7FFFFFFFFFFFFFFF:
- raise InvalidFrameException(
- 'Extended payload length >= 2^63')
- if ws_version >= 13 and payload_length < 0x10000:
- valid_length_encoding = False
- length_encoding_bytes = 8
-
- logger.log(common.LOGLEVEL_FINE,
- 'Decoded_payload_length=%s', payload_length)
- elif payload_length == 126:
- logger.log(common.LOGLEVEL_FINE,
- 'Receive 2-octet extended payload length')
-
- extended_payload_length = receive_bytes(2)
- payload_length = struct.unpack(
- '!H', extended_payload_length)[0]
- if ws_version >= 13 and payload_length < 126:
- valid_length_encoding = False
- length_encoding_bytes = 2
-
- logger.log(common.LOGLEVEL_FINE,
- 'Decoded_payload_length=%s', payload_length)
-
- if not valid_length_encoding:
- logger.warning(
- 'Payload length is not encoded using the minimal number of '
- 'bytes (%d is encoded using %d bytes)',
- payload_length,
- length_encoding_bytes)
-
- if mask == 1:
- logger.log(common.LOGLEVEL_FINE, 'Receive mask')
-
- masking_nonce = receive_bytes(4)
- masker = util.RepeatedXorMasker(masking_nonce)
-
- logger.log(common.LOGLEVEL_FINE, 'Mask=%r', masking_nonce)
- else:
- masker = _NOOP_MASKER
-
- logger.log(common.LOGLEVEL_FINE, 'Receive payload data')
- if logger.isEnabledFor(common.LOGLEVEL_FINE):
- receive_start = time.time()
-
- raw_payload_bytes = receive_bytes(payload_length)
-
- if logger.isEnabledFor(common.LOGLEVEL_FINE):
- logger.log(
- common.LOGLEVEL_FINE,
- 'Done receiving payload data at %s MB/s',
- payload_length / (time.time() - receive_start) / 1000 / 1000)
- logger.log(common.LOGLEVEL_FINE, 'Unmask payload data')
-
- if logger.isEnabledFor(common.LOGLEVEL_FINE):
- unmask_start = time.time()
-
- unmasked_bytes = masker.mask(raw_payload_bytes)
-
- if logger.isEnabledFor(common.LOGLEVEL_FINE):
- logger.log(
- common.LOGLEVEL_FINE,
- 'Done unmasking payload data at %s MB/s',
- payload_length / (time.time() - unmask_start) / 1000 / 1000)
-
- return opcode, unmasked_bytes, fin, rsv1, rsv2, rsv3
-
-
-class FragmentedFrameBuilder(object):
- """A stateful class to send a message as fragments."""
-
- def __init__(self, mask, frame_filters=[], encode_utf8=True):
- """Constructs an instance."""
-
- self._mask = mask
- self._frame_filters = frame_filters
- # This is for skipping UTF-8 encoding when building text type frames
- # from compressed data.
- self._encode_utf8 = encode_utf8
-
- self._started = False
-
- # Hold opcode of the first frame in messages to verify types of other
- # frames in the message are all the same.
- self._opcode = common.OPCODE_TEXT
-
- def build(self, payload_data, end, binary):
- if binary:
- frame_type = common.OPCODE_BINARY
- else:
- frame_type = common.OPCODE_TEXT
- if self._started:
- if self._opcode != frame_type:
- raise ValueError('Message types are different in frames for '
- 'the same message')
- opcode = common.OPCODE_CONTINUATION
- else:
- opcode = frame_type
- self._opcode = frame_type
-
- if end:
- self._started = False
- fin = 1
- else:
- self._started = True
- fin = 0
-
- if binary or not self._encode_utf8:
- return create_binary_frame(
- payload_data, opcode, fin, self._mask, self._frame_filters)
- else:
- return create_text_frame(
- payload_data, opcode, fin, self._mask, self._frame_filters)
-
-
-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)
-
-
-def create_ping_frame(body, mask=False, frame_filters=[]):
- return _create_control_frame(common.OPCODE_PING, body, mask, frame_filters)
-
-
-def create_pong_frame(body, mask=False, frame_filters=[]):
- return _create_control_frame(common.OPCODE_PONG, body, mask, frame_filters)
-
-
-def create_close_frame(body, mask=False, frame_filters=[]):
- return _create_control_frame(
- common.OPCODE_CLOSE, body, mask, frame_filters)
-
-
-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
-
-
-class StreamOptions(object):
- """Holds option values to configure Stream objects."""
-
- def __init__(self):
- """Constructs StreamOptions."""
-
- # Filters applied to frames.
- self.outgoing_frame_filters = []
- self.incoming_frame_filters = []
-
- # Filters applied to messages. Control frames are not affected by them.
- self.outgoing_message_filters = []
- self.incoming_message_filters = []
-
- self.encode_text_message_to_utf8 = True
- self.mask_send = False
- self.unmask_receive = True
-
-
-class Stream(StreamBase):
- """A class for parsing/building frames of the WebSocket protocol
- (RFC 6455).
- """
-
- def __init__(self, request, options):
- """Constructs an instance.
-
- Args:
- request: mod_python request.
- """
-
- StreamBase.__init__(self, request)
-
- self._logger = util.get_class_logger(self)
-
- self._options = options
-
- self._request.client_terminated = False
- self._request.server_terminated = False
-
- # Holds body of received fragments.
- self._received_fragments = []
- # Holds the opcode of the first fragment.
- self._original_opcode = None
-
- self._writer = FragmentedFrameBuilder(
- self._options.mask_send, self._options.outgoing_frame_filters,
- self._options.encode_text_message_to_utf8)
-
- self._ping_queue = deque()
-
- def _receive_frame(self):
- """Receives a frame and return data in the frame as a tuple containing
- each header field and payload separately.
-
- Raises:
- ConnectionTerminatedException: when read returns empty
- string.
- InvalidFrameException: when the frame contains invalid data.
- """
-
- def _receive_bytes(length):
- return self.receive_bytes(length)
-
- return parse_frame(receive_bytes=_receive_bytes,
- logger=self._logger,
- ws_version=self._request.ws_version,
- unmask_receive=self._options.unmask_receive)
-
- def _receive_frame_as_frame_object(self):
- opcode, unmasked_bytes, fin, rsv1, rsv2, rsv3 = self._receive_frame()
-
- return Frame(fin=fin, rsv1=rsv1, rsv2=rsv2, rsv3=rsv3,
- opcode=opcode, payload=unmasked_bytes)
-
- def receive_filtered_frame(self):
- """Receives a frame and applies frame filters and message filters.
- The frame to be received must satisfy following conditions:
- - The frame is not fragmented.
- - The opcode of the frame is TEXT or BINARY.
-
- DO NOT USE this method except for testing purpose.
- """
-
- frame = self._receive_frame_as_frame_object()
- if not frame.fin:
- raise InvalidFrameException(
- 'Segmented frames must not be received via '
- 'receive_filtered_frame()')
- if (frame.opcode != common.OPCODE_TEXT and
- frame.opcode != common.OPCODE_BINARY):
- raise InvalidFrameException(
- 'Control frames must not be received via '
- 'receive_filtered_frame()')
-
- for frame_filter in self._options.incoming_frame_filters:
- frame_filter.filter(frame)
- for message_filter in self._options.incoming_message_filters:
- frame.payload = message_filter.filter(frame.payload)
- return frame
-
- 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)
-
- def _get_message_from_frame(self, frame):
- """Gets a message from frame. If the message is composed of fragmented
- frames and the frame is not the last fragmented frame, this method
- returns None. The whole message will be returned when the last
- fragmented frame is passed to this method.
-
- Raises:
- InvalidFrameException: when the frame doesn't match defragmentation
- context, or the frame contains invalid data.
- """
-
- 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 = []
- return message
- else:
- # Intermediate frame
- self._received_fragments.append(frame.payload)
- return None
- 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
-
- self._original_opcode = frame.opcode
- return 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)
- return None
-
- def _process_close_message(self, message):
- """Processes close message.
-
- Args:
- message: close message.
-
- Raises:
- InvalidFrameException: when the message is invalid.
- """
-
- 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) == 0:
- self._logger.debug('Received close frame (empty body)')
- self._request.ws_close_code = (
- common.STATUS_NO_STATUS_RECEIVED)
- elif 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(
- 'Received close frame (code=%d, reason=%r)',
- self._request.ws_close_code,
- self._request.ws_close_reason)
-
- # As we've received a close frame, no more data is coming over the
- # socket. We can now safely close the socket without worrying about
- # RST sending.
-
- if self._request.server_terminated:
- self._logger.debug(
- 'Received ack for server-initiated closing handshake')
- return
-
- self._logger.debug(
- 'Received client-initiated closing handshake')
-
- code = common.STATUS_NORMAL_CLOSURE
- reason = ''
- if hasattr(self._request, '_dispatcher'):
- dispatcher = self._request._dispatcher
- code, reason = dispatcher.passive_closing_handshake(
- self._request)
- if code is None and reason is not None and len(reason) > 0:
- self._logger.warning(
- 'Handler specified reason despite code being None')
- reason = ''
- if reason is None:
- reason = ''
- self._send_closing_handshake(code, reason)
- self._logger.debug(
- 'Acknowledged closing handshake initiated by the peer '
- '(code=%r, reason=%r)', code, reason)
-
- def _process_ping_message(self, message):
- """Processes ping message.
-
- Args:
- message: ping message.
- """
-
- try:
- handler = self._request.on_ping_handler
- if handler:
- handler(self._request, message)
- return
- except AttributeError, e:
- pass
- self._send_pong(message)
-
- def _process_pong_message(self, message):
- """Processes pong message.
-
- Args:
- message: pong message.
- """
-
- # 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
-
- try:
- handler = self._request.on_pong_handler
- if handler:
- handler(self._request, message)
- except AttributeError, e:
- pass
-
- 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
- elif self._original_opcode == common.OPCODE_CLOSE:
- self._process_close_message(message)
- return None
- elif self._original_opcode == common.OPCODE_PING:
- self._process_ping_message(message)
- elif self._original_opcode == common.OPCODE_PONG:
- self._process_pong_message(message)
- else:
- raise UnsupportedFrameException(
- 'Opcode %d is not supported' % self._original_opcode)
-
- def _send_closing_handshake(self, code, reason):
- body = create_closing_handshake_body(code, reason)
- frame = create_close_frame(
- body, mask=self._options.mask_send,
- frame_filters=self._options.outgoing_frame_filters)
-
- self._request.server_terminated = True
-
- self._write(frame)
-
- def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason='',
- wait_response=True):
- """Closes a WebSocket connection.
-
- 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
-
- 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')
- # TODO: 3. close the WebSocket connection.
- # note: mod_python Connection (mp_conn) doesn't have close method.
-
- def send_ping(self, body=''):
- frame = create_ping_frame(
- body,
- self._options.mask_send,
- self._options.outgoing_frame_filters)
- self._write(frame)
-
- self._ping_queue.append(body)
-
- def _send_pong(self, body):
- frame = create_pong_frame(
- body,
- self._options.mask_send,
- self._options.outgoing_frame_filters)
- self._write(frame)
-
- def get_last_received_opcode(self):
- """Returns the opcode of the WebSocket message which the last received
- frame belongs to. The return value is valid iff immediately after
- receive_message call.
- """
-
- return self._original_opcode
-
-
-# vi:sts=4 sw=4 et