forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1889 lines
		
	
	
	
		
			70 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1889 lines
		
	
	
	
		
			70 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# 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 multiplexing extension.
 | 
						|
 | 
						|
Specification:
 | 
						|
http://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-06
 | 
						|
"""
 | 
						|
 | 
						|
 | 
						|
import collections
 | 
						|
import copy
 | 
						|
import email
 | 
						|
import email.parser
 | 
						|
import logging
 | 
						|
import math
 | 
						|
import struct
 | 
						|
import threading
 | 
						|
import traceback
 | 
						|
 | 
						|
from mod_pywebsocket import common
 | 
						|
from mod_pywebsocket import handshake
 | 
						|
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_hybi import Frame
 | 
						|
from mod_pywebsocket._stream_hybi import Stream
 | 
						|
from mod_pywebsocket._stream_hybi import StreamOptions
 | 
						|
from mod_pywebsocket._stream_hybi import create_binary_frame
 | 
						|
from mod_pywebsocket._stream_hybi import create_closing_handshake_body
 | 
						|
from mod_pywebsocket._stream_hybi import create_header
 | 
						|
from mod_pywebsocket._stream_hybi import create_length_header
 | 
						|
from mod_pywebsocket._stream_hybi import parse_frame
 | 
						|
from mod_pywebsocket.handshake import hybi
 | 
						|
 | 
						|
 | 
						|
_CONTROL_CHANNEL_ID = 0
 | 
						|
_DEFAULT_CHANNEL_ID = 1
 | 
						|
 | 
						|
_MUX_OPCODE_ADD_CHANNEL_REQUEST = 0
 | 
						|
_MUX_OPCODE_ADD_CHANNEL_RESPONSE = 1
 | 
						|
_MUX_OPCODE_FLOW_CONTROL = 2
 | 
						|
_MUX_OPCODE_DROP_CHANNEL = 3
 | 
						|
_MUX_OPCODE_NEW_CHANNEL_SLOT = 4
 | 
						|
 | 
						|
_MAX_CHANNEL_ID = 2 ** 29 - 1
 | 
						|
 | 
						|
_INITIAL_NUMBER_OF_CHANNEL_SLOTS = 64
 | 
						|
_INITIAL_QUOTA_FOR_CLIENT = 8 * 1024
 | 
						|
 | 
						|
_HANDSHAKE_ENCODING_IDENTITY = 0
 | 
						|
_HANDSHAKE_ENCODING_DELTA = 1
 | 
						|
 | 
						|
# We need only these status code for now.
 | 
						|
_HTTP_BAD_RESPONSE_MESSAGES = {
 | 
						|
    common.HTTP_STATUS_BAD_REQUEST: 'Bad Request',
 | 
						|
}
 | 
						|
 | 
						|
# DropChannel reason code
 | 
						|
# TODO(bashi): Define all reason code defined in -05 draft.
 | 
						|
_DROP_CODE_NORMAL_CLOSURE = 1000
 | 
						|
 | 
						|
_DROP_CODE_INVALID_ENCAPSULATING_MESSAGE = 2001
 | 
						|
_DROP_CODE_CHANNEL_ID_TRUNCATED = 2002
 | 
						|
_DROP_CODE_ENCAPSULATED_FRAME_IS_TRUNCATED = 2003
 | 
						|
_DROP_CODE_UNKNOWN_MUX_OPCODE = 2004
 | 
						|
_DROP_CODE_INVALID_MUX_CONTROL_BLOCK = 2005
 | 
						|
_DROP_CODE_CHANNEL_ALREADY_EXISTS = 2006
 | 
						|
_DROP_CODE_NEW_CHANNEL_SLOT_VIOLATION = 2007
 | 
						|
_DROP_CODE_UNKNOWN_REQUEST_ENCODING = 2010
 | 
						|
 | 
						|
_DROP_CODE_SEND_QUOTA_VIOLATION = 3005
 | 
						|
_DROP_CODE_SEND_QUOTA_OVERFLOW = 3006
 | 
						|
_DROP_CODE_ACKNOWLEDGED = 3008
 | 
						|
_DROP_CODE_BAD_FRAGMENTATION = 3009
 | 
						|
 | 
						|
 | 
						|
class MuxUnexpectedException(Exception):
 | 
						|
    """Exception in handling multiplexing extension."""
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
# Temporary
 | 
						|
class MuxNotImplementedException(Exception):
 | 
						|
    """Raised when a flow enters unimplemented code path."""
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class LogicalConnectionClosedException(Exception):
 | 
						|
    """Raised when logical connection is gracefully closed."""
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class PhysicalConnectionError(Exception):
 | 
						|
    """Raised when there is a physical connection error."""
 | 
						|
    def __init__(self, drop_code, message=''):
 | 
						|
        super(PhysicalConnectionError, self).__init__(
 | 
						|
            'code=%d, message=%r' % (drop_code, message))
 | 
						|
        self.drop_code = drop_code
 | 
						|
        self.message = message
 | 
						|
 | 
						|
 | 
						|
class LogicalChannelError(Exception):
 | 
						|
    """Raised when there is a logical channel error."""
 | 
						|
    def __init__(self, channel_id, drop_code, message=''):
 | 
						|
        super(LogicalChannelError, self).__init__(
 | 
						|
            'channel_id=%d, code=%d, message=%r' % (
 | 
						|
                channel_id, drop_code, message))
 | 
						|
        self.channel_id = channel_id
 | 
						|
        self.drop_code = drop_code
 | 
						|
        self.message = message
 | 
						|
 | 
						|
 | 
						|
def _encode_channel_id(channel_id):
 | 
						|
    if channel_id < 0:
 | 
						|
        raise ValueError('Channel id %d must not be negative' % channel_id)
 | 
						|
 | 
						|
    if channel_id < 2 ** 7:
 | 
						|
        return chr(channel_id)
 | 
						|
    if channel_id < 2 ** 14:
 | 
						|
        return struct.pack('!H', 0x8000 + channel_id)
 | 
						|
    if channel_id < 2 ** 21:
 | 
						|
        first = chr(0xc0 + (channel_id >> 16))
 | 
						|
        return first + struct.pack('!H', channel_id & 0xffff)
 | 
						|
    if channel_id < 2 ** 29:
 | 
						|
        return struct.pack('!L', 0xe0000000 + channel_id)
 | 
						|
 | 
						|
    raise ValueError('Channel id %d is too large' % channel_id)
 | 
						|
 | 
						|
 | 
						|
def _encode_number(number):
 | 
						|
    return create_length_header(number, False)
 | 
						|
 | 
						|
 | 
						|
def _create_add_channel_response(channel_id, encoded_handshake,
 | 
						|
                                 encoding=0, rejected=False):
 | 
						|
    if encoding != 0 and encoding != 1:
 | 
						|
        raise ValueError('Invalid encoding %d' % encoding)
 | 
						|
 | 
						|
    first_byte = ((_MUX_OPCODE_ADD_CHANNEL_RESPONSE << 5) |
 | 
						|
                  (rejected << 4) | encoding)
 | 
						|
    block = (chr(first_byte) +
 | 
						|
             _encode_channel_id(channel_id) +
 | 
						|
             _encode_number(len(encoded_handshake)) +
 | 
						|
             encoded_handshake)
 | 
						|
    return block
 | 
						|
 | 
						|
 | 
						|
def _create_drop_channel(channel_id, code=None, message=''):
 | 
						|
    if len(message) > 0 and code is None:
 | 
						|
        raise ValueError('Code must be specified if message is specified')
 | 
						|
 | 
						|
    first_byte = _MUX_OPCODE_DROP_CHANNEL << 5
 | 
						|
    block = chr(first_byte) + _encode_channel_id(channel_id)
 | 
						|
    if code is None:
 | 
						|
        block += _encode_number(0) # Reason size
 | 
						|
    else:
 | 
						|
        reason = struct.pack('!H', code) + message
 | 
						|
        reason_size = _encode_number(len(reason))
 | 
						|
        block += reason_size + reason
 | 
						|
 | 
						|
    return block
 | 
						|
 | 
						|
 | 
						|
def _create_flow_control(channel_id, replenished_quota):
 | 
						|
    first_byte = _MUX_OPCODE_FLOW_CONTROL << 5
 | 
						|
    block = (chr(first_byte) +
 | 
						|
             _encode_channel_id(channel_id) +
 | 
						|
             _encode_number(replenished_quota))
 | 
						|
    return block
 | 
						|
 | 
						|
 | 
						|
def _create_new_channel_slot(slots, send_quota):
 | 
						|
    if slots < 0 or send_quota < 0:
 | 
						|
        raise ValueError('slots and send_quota must be non-negative.')
 | 
						|
    first_byte = _MUX_OPCODE_NEW_CHANNEL_SLOT << 5
 | 
						|
    block = (chr(first_byte) +
 | 
						|
             _encode_number(slots) +
 | 
						|
             _encode_number(send_quota))
 | 
						|
    return block
 | 
						|
 | 
						|
 | 
						|
def _create_fallback_new_channel_slot():
 | 
						|
    first_byte = (_MUX_OPCODE_NEW_CHANNEL_SLOT << 5) | 1 # Set the F flag
 | 
						|
    block = (chr(first_byte) + _encode_number(0) + _encode_number(0))
 | 
						|
    return block
 | 
						|
 | 
						|
 | 
						|
def _parse_request_text(request_text):
 | 
						|
    request_line, header_lines = request_text.split('\r\n', 1)
 | 
						|
 | 
						|
    words = request_line.split(' ')
 | 
						|
    if len(words) != 3:
 | 
						|
        raise ValueError('Bad Request-Line syntax %r' % request_line)
 | 
						|
    [command, path, version] = words
 | 
						|
    if version != 'HTTP/1.1':
 | 
						|
        raise ValueError('Bad request version %r' % version)
 | 
						|
 | 
						|
    # email.parser.Parser() parses RFC 2822 (RFC 822) style headers.
 | 
						|
    # RFC 6455 refers RFC 2616 for handshake parsing, and RFC 2616 refers
 | 
						|
    # RFC 822.
 | 
						|
    headers = email.parser.Parser().parsestr(header_lines)
 | 
						|
    return command, path, version, headers
 | 
						|
 | 
						|
 | 
						|
class _ControlBlock(object):
 | 
						|
    """A structure that holds parsing result of multiplexing control block.
 | 
						|
    Control block specific attributes will be added by _MuxFramePayloadParser.
 | 
						|
    (e.g. encoded_handshake will be added for AddChannelRequest and
 | 
						|
    AddChannelResponse)
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, opcode):
 | 
						|
        self.opcode = opcode
 | 
						|
 | 
						|
 | 
						|
class _MuxFramePayloadParser(object):
 | 
						|
    """A class that parses multiplexed frame payload."""
 | 
						|
 | 
						|
    def __init__(self, payload):
 | 
						|
        self._data = payload
 | 
						|
        self._read_position = 0
 | 
						|
        self._logger = util.get_class_logger(self)
 | 
						|
 | 
						|
    def read_channel_id(self):
 | 
						|
        """Reads channel id.
 | 
						|
 | 
						|
        Raises:
 | 
						|
            ValueError: when the payload doesn't contain
 | 
						|
                valid channel id.
 | 
						|
        """
 | 
						|
 | 
						|
        remaining_length = len(self._data) - self._read_position
 | 
						|
        pos = self._read_position
 | 
						|
        if remaining_length == 0:
 | 
						|
            raise ValueError('Invalid channel id format')
 | 
						|
 | 
						|
        channel_id = ord(self._data[pos])
 | 
						|
        channel_id_length = 1
 | 
						|
        if channel_id & 0xe0 == 0xe0:
 | 
						|
            if remaining_length < 4:
 | 
						|
                raise ValueError('Invalid channel id format')
 | 
						|
            channel_id = struct.unpack('!L',
 | 
						|
                                       self._data[pos:pos+4])[0] & 0x1fffffff
 | 
						|
            channel_id_length = 4
 | 
						|
        elif channel_id & 0xc0 == 0xc0:
 | 
						|
            if remaining_length < 3:
 | 
						|
                raise ValueError('Invalid channel id format')
 | 
						|
            channel_id = (((channel_id & 0x1f) << 16) +
 | 
						|
                          struct.unpack('!H', self._data[pos+1:pos+3])[0])
 | 
						|
            channel_id_length = 3
 | 
						|
        elif channel_id & 0x80 == 0x80:
 | 
						|
            if remaining_length < 2:
 | 
						|
                raise ValueError('Invalid channel id format')
 | 
						|
            channel_id = struct.unpack('!H',
 | 
						|
                                       self._data[pos:pos+2])[0] & 0x3fff
 | 
						|
            channel_id_length = 2
 | 
						|
        self._read_position += channel_id_length
 | 
						|
 | 
						|
        return channel_id
 | 
						|
 | 
						|
    def read_inner_frame(self):
 | 
						|
        """Reads an inner frame.
 | 
						|
 | 
						|
        Raises:
 | 
						|
            PhysicalConnectionError: when the inner frame is invalid.
 | 
						|
        """
 | 
						|
 | 
						|
        if len(self._data) == self._read_position:
 | 
						|
            raise PhysicalConnectionError(
 | 
						|
                _DROP_CODE_ENCAPSULATED_FRAME_IS_TRUNCATED)
 | 
						|
 | 
						|
        bits = ord(self._data[self._read_position])
 | 
						|
        self._read_position += 1
 | 
						|
        fin = (bits & 0x80) == 0x80
 | 
						|
        rsv1 = (bits & 0x40) == 0x40
 | 
						|
        rsv2 = (bits & 0x20) == 0x20
 | 
						|
        rsv3 = (bits & 0x10) == 0x10
 | 
						|
        opcode = bits & 0xf
 | 
						|
        payload = self.remaining_data()
 | 
						|
        # Consume rest of the message which is payload data of the original
 | 
						|
        # frame.
 | 
						|
        self._read_position = len(self._data)
 | 
						|
        return fin, rsv1, rsv2, rsv3, opcode, payload
 | 
						|
 | 
						|
    def _read_number(self):
 | 
						|
        if self._read_position + 1 > len(self._data):
 | 
						|
            raise ValueError(
 | 
						|
                'Cannot read the first byte of number field')
 | 
						|
 | 
						|
        number = ord(self._data[self._read_position])
 | 
						|
        if number & 0x80 == 0x80:
 | 
						|
            raise ValueError(
 | 
						|
                'The most significant bit of the first byte of number should '
 | 
						|
                'be unset')
 | 
						|
        self._read_position += 1
 | 
						|
        pos = self._read_position
 | 
						|
        if number == 127:
 | 
						|
            if pos + 8 > len(self._data):
 | 
						|
                raise ValueError('Invalid number field')
 | 
						|
            self._read_position += 8
 | 
						|
            number = struct.unpack('!Q', self._data[pos:pos+8])[0]
 | 
						|
            if number > 0x7FFFFFFFFFFFFFFF:
 | 
						|
                raise ValueError('Encoded number(%d) >= 2^63' % number)
 | 
						|
            if number <= 0xFFFF:
 | 
						|
                raise ValueError(
 | 
						|
                    '%d should not be encoded by 9 bytes encoding' % number)
 | 
						|
            return number
 | 
						|
        if number == 126:
 | 
						|
            if pos + 2 > len(self._data):
 | 
						|
                raise ValueError('Invalid number field')
 | 
						|
            self._read_position += 2
 | 
						|
            number = struct.unpack('!H', self._data[pos:pos+2])[0]
 | 
						|
            if number <= 125:
 | 
						|
                raise ValueError(
 | 
						|
                    '%d should not be encoded by 3 bytes encoding' % number)
 | 
						|
        return number
 | 
						|
 | 
						|
    def _read_size_and_contents(self):
 | 
						|
        """Reads data that consists of followings:
 | 
						|
            - the size of the contents encoded the same way as payload length
 | 
						|
              of the WebSocket Protocol with 1 bit padding at the head.
 | 
						|
            - the contents.
 | 
						|
        """
 | 
						|
 | 
						|
        try:
 | 
						|
            size = self._read_number()
 | 
						|
        except ValueError, e:
 | 
						|
            raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
 | 
						|
                                          str(e))
 | 
						|
        pos = self._read_position
 | 
						|
        if pos + size > len(self._data):
 | 
						|
            raise PhysicalConnectionError(
 | 
						|
                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
 | 
						|
                'Cannot read %d bytes data' % size)
 | 
						|
 | 
						|
        self._read_position += size
 | 
						|
        return self._data[pos:pos+size]
 | 
						|
 | 
						|
    def _read_add_channel_request(self, first_byte, control_block):
 | 
						|
        reserved = (first_byte >> 2) & 0x7
 | 
						|
        if reserved != 0:
 | 
						|
            raise PhysicalConnectionError(
 | 
						|
                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
 | 
						|
                'Reserved bits must be unset')
 | 
						|
 | 
						|
        # Invalid encoding will be handled by MuxHandler.
 | 
						|
        encoding = first_byte & 0x3
 | 
						|
        try:
 | 
						|
            control_block.channel_id = self.read_channel_id()
 | 
						|
        except ValueError, e:
 | 
						|
            raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK)
 | 
						|
        control_block.encoding = encoding
 | 
						|
        encoded_handshake = self._read_size_and_contents()
 | 
						|
        control_block.encoded_handshake = encoded_handshake
 | 
						|
        return control_block
 | 
						|
 | 
						|
    def _read_add_channel_response(self, first_byte, control_block):
 | 
						|
        reserved = (first_byte >> 2) & 0x3
 | 
						|
        if reserved != 0:
 | 
						|
            raise PhysicalConnectionError(
 | 
						|
                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
 | 
						|
                'Reserved bits must be unset')
 | 
						|
 | 
						|
        control_block.accepted = (first_byte >> 4) & 1
 | 
						|
        control_block.encoding = first_byte & 0x3
 | 
						|
        try:
 | 
						|
            control_block.channel_id = self.read_channel_id()
 | 
						|
        except ValueError, e:
 | 
						|
            raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK)
 | 
						|
        control_block.encoded_handshake = self._read_size_and_contents()
 | 
						|
        return control_block
 | 
						|
 | 
						|
    def _read_flow_control(self, first_byte, control_block):
 | 
						|
        reserved = first_byte & 0x1f
 | 
						|
        if reserved != 0:
 | 
						|
            raise PhysicalConnectionError(
 | 
						|
                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
 | 
						|
                'Reserved bits must be unset')
 | 
						|
 | 
						|
        try:
 | 
						|
            control_block.channel_id = self.read_channel_id()
 | 
						|
            control_block.send_quota = self._read_number()
 | 
						|
        except ValueError, e:
 | 
						|
            raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
 | 
						|
                                          str(e))
 | 
						|
 | 
						|
        return control_block
 | 
						|
 | 
						|
    def _read_drop_channel(self, first_byte, control_block):
 | 
						|
        reserved = first_byte & 0x1f
 | 
						|
        if reserved != 0:
 | 
						|
            raise PhysicalConnectionError(
 | 
						|
                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
 | 
						|
                'Reserved bits must be unset')
 | 
						|
 | 
						|
        try:
 | 
						|
            control_block.channel_id = self.read_channel_id()
 | 
						|
        except ValueError, e:
 | 
						|
            raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK)
 | 
						|
        reason = self._read_size_and_contents()
 | 
						|
        if len(reason) == 0:
 | 
						|
            control_block.drop_code = None
 | 
						|
            control_block.drop_message = ''
 | 
						|
        elif len(reason) >= 2:
 | 
						|
            control_block.drop_code = struct.unpack('!H', reason[:2])[0]
 | 
						|
            control_block.drop_message = reason[2:]
 | 
						|
        else:
 | 
						|
            raise PhysicalConnectionError(
 | 
						|
                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
 | 
						|
                'Received DropChannel that conains only 1-byte reason')
 | 
						|
        return control_block
 | 
						|
 | 
						|
    def _read_new_channel_slot(self, first_byte, control_block):
 | 
						|
        reserved = first_byte & 0x1e
 | 
						|
        if reserved != 0:
 | 
						|
            raise PhysicalConnectionError(
 | 
						|
                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
 | 
						|
                'Reserved bits must be unset')
 | 
						|
        control_block.fallback = first_byte & 1
 | 
						|
        try:
 | 
						|
            control_block.slots = self._read_number()
 | 
						|
            control_block.send_quota = self._read_number()
 | 
						|
        except ValueError, e:
 | 
						|
            raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
 | 
						|
                                          str(e))
 | 
						|
        return control_block
 | 
						|
 | 
						|
    def read_control_blocks(self):
 | 
						|
        """Reads control block(s).
 | 
						|
 | 
						|
        Raises:
 | 
						|
           PhysicalConnectionError: when the payload contains invalid control
 | 
						|
               block(s).
 | 
						|
           StopIteration: when no control blocks left.
 | 
						|
        """
 | 
						|
 | 
						|
        while self._read_position < len(self._data):
 | 
						|
            first_byte = ord(self._data[self._read_position])
 | 
						|
            self._read_position += 1
 | 
						|
            opcode = (first_byte >> 5) & 0x7
 | 
						|
            control_block = _ControlBlock(opcode=opcode)
 | 
						|
            if opcode == _MUX_OPCODE_ADD_CHANNEL_REQUEST:
 | 
						|
                yield self._read_add_channel_request(first_byte, control_block)
 | 
						|
            elif opcode == _MUX_OPCODE_ADD_CHANNEL_RESPONSE:
 | 
						|
                yield self._read_add_channel_response(
 | 
						|
                    first_byte, control_block)
 | 
						|
            elif opcode == _MUX_OPCODE_FLOW_CONTROL:
 | 
						|
                yield self._read_flow_control(first_byte, control_block)
 | 
						|
            elif opcode == _MUX_OPCODE_DROP_CHANNEL:
 | 
						|
                yield self._read_drop_channel(first_byte, control_block)
 | 
						|
            elif opcode == _MUX_OPCODE_NEW_CHANNEL_SLOT:
 | 
						|
                yield self._read_new_channel_slot(first_byte, control_block)
 | 
						|
            else:
 | 
						|
                raise PhysicalConnectionError(
 | 
						|
                    _DROP_CODE_UNKNOWN_MUX_OPCODE,
 | 
						|
                    'Invalid opcode %d' % opcode)
 | 
						|
 | 
						|
        assert self._read_position == len(self._data)
 | 
						|
        raise StopIteration
 | 
						|
 | 
						|
    def remaining_data(self):
 | 
						|
        """Returns remaining data."""
 | 
						|
 | 
						|
        return self._data[self._read_position:]
 | 
						|
 | 
						|
 | 
						|
class _LogicalRequest(object):
 | 
						|
    """Mimics mod_python request."""
 | 
						|
 | 
						|
    def __init__(self, channel_id, command, path, protocol, headers,
 | 
						|
                 connection):
 | 
						|
        """Constructs an instance.
 | 
						|
 | 
						|
        Args:
 | 
						|
            channel_id: the channel id of the logical channel.
 | 
						|
            command: HTTP request command.
 | 
						|
            path: HTTP request path.
 | 
						|
            headers: HTTP headers.
 | 
						|
            connection: _LogicalConnection instance.
 | 
						|
        """
 | 
						|
 | 
						|
        self.channel_id = channel_id
 | 
						|
        self.method = command
 | 
						|
        self.uri = path
 | 
						|
        self.protocol = protocol
 | 
						|
        self.headers_in = headers
 | 
						|
        self.connection = connection
 | 
						|
        self.server_terminated = False
 | 
						|
        self.client_terminated = False
 | 
						|
 | 
						|
    def is_https(self):
 | 
						|
        """Mimics request.is_https(). Returns False because this method is
 | 
						|
        used only by old protocols (hixie and hybi00).
 | 
						|
        """
 | 
						|
 | 
						|
        return False
 | 
						|
 | 
						|
 | 
						|
class _LogicalConnection(object):
 | 
						|
    """Mimics mod_python mp_conn."""
 | 
						|
 | 
						|
    # For details, see the comment of set_read_state().
 | 
						|
    STATE_ACTIVE = 1
 | 
						|
    STATE_GRACEFULLY_CLOSED = 2
 | 
						|
    STATE_TERMINATED = 3
 | 
						|
 | 
						|
    def __init__(self, mux_handler, channel_id):
 | 
						|
        """Constructs an instance.
 | 
						|
 | 
						|
        Args:
 | 
						|
            mux_handler: _MuxHandler instance.
 | 
						|
            channel_id: channel id of this connection.
 | 
						|
        """
 | 
						|
 | 
						|
        self._mux_handler = mux_handler
 | 
						|
        self._channel_id = channel_id
 | 
						|
        self._incoming_data = ''
 | 
						|
 | 
						|
        # - Protects _waiting_write_completion
 | 
						|
        # - Signals the thread waiting for completion of write by mux handler
 | 
						|
        self._write_condition = threading.Condition()
 | 
						|
        self._waiting_write_completion = False
 | 
						|
 | 
						|
        self._read_condition = threading.Condition()
 | 
						|
        self._read_state = self.STATE_ACTIVE
 | 
						|
 | 
						|
    def get_local_addr(self):
 | 
						|
        """Getter to mimic mp_conn.local_addr."""
 | 
						|
 | 
						|
        return self._mux_handler.physical_connection.get_local_addr()
 | 
						|
    local_addr = property(get_local_addr)
 | 
						|
 | 
						|
    def get_remote_addr(self):
 | 
						|
        """Getter to mimic mp_conn.remote_addr."""
 | 
						|
 | 
						|
        return self._mux_handler.physical_connection.get_remote_addr()
 | 
						|
    remote_addr = property(get_remote_addr)
 | 
						|
 | 
						|
    def get_memorized_lines(self):
 | 
						|
        """Gets memorized lines. Not supported."""
 | 
						|
 | 
						|
        raise MuxUnexpectedException('_LogicalConnection does not support '
 | 
						|
                                     'get_memorized_lines')
 | 
						|
 | 
						|
    def write(self, data):
 | 
						|
        """Writes data. mux_handler sends data asynchronously. The caller will
 | 
						|
        be suspended until write done.
 | 
						|
 | 
						|
        Args:
 | 
						|
            data: data to be written.
 | 
						|
 | 
						|
        Raises:
 | 
						|
            MuxUnexpectedException: when called before finishing the previous
 | 
						|
                write.
 | 
						|
        """
 | 
						|
 | 
						|
        try:
 | 
						|
            self._write_condition.acquire()
 | 
						|
            if self._waiting_write_completion:
 | 
						|
                raise MuxUnexpectedException(
 | 
						|
                    'Logical connection %d is already waiting the completion '
 | 
						|
                    'of write' % self._channel_id)
 | 
						|
 | 
						|
            self._waiting_write_completion = True
 | 
						|
            self._mux_handler.send_data(self._channel_id, data)
 | 
						|
            self._write_condition.wait()
 | 
						|
            # TODO(tyoshino): Raise an exception if woke up by on_writer_done.
 | 
						|
        finally:
 | 
						|
            self._write_condition.release()
 | 
						|
 | 
						|
    def write_control_data(self, data):
 | 
						|
        """Writes data via the control channel. Don't wait finishing write
 | 
						|
        because this method can be called by mux dispatcher.
 | 
						|
 | 
						|
        Args:
 | 
						|
            data: data to be written.
 | 
						|
        """
 | 
						|
 | 
						|
        self._mux_handler.send_control_data(data)
 | 
						|
 | 
						|
    def on_write_data_done(self):
 | 
						|
        """Called when sending data is completed."""
 | 
						|
 | 
						|
        try:
 | 
						|
            self._write_condition.acquire()
 | 
						|
            if not self._waiting_write_completion:
 | 
						|
                raise MuxUnexpectedException(
 | 
						|
                    'Invalid call of on_write_data_done for logical '
 | 
						|
                    'connection %d' % self._channel_id)
 | 
						|
            self._waiting_write_completion = False
 | 
						|
            self._write_condition.notify()
 | 
						|
        finally:
 | 
						|
            self._write_condition.release()
 | 
						|
 | 
						|
    def on_writer_done(self):
 | 
						|
        """Called by the mux handler when the writer thread has finished."""
 | 
						|
 | 
						|
        try:
 | 
						|
            self._write_condition.acquire()
 | 
						|
            self._waiting_write_completion = False
 | 
						|
            self._write_condition.notify()
 | 
						|
        finally:
 | 
						|
            self._write_condition.release()
 | 
						|
 | 
						|
 | 
						|
    def append_frame_data(self, frame_data):
 | 
						|
        """Appends incoming frame data. Called when mux_handler dispatches
 | 
						|
        frame data to the corresponding application.
 | 
						|
 | 
						|
        Args:
 | 
						|
            frame_data: incoming frame data.
 | 
						|
        """
 | 
						|
 | 
						|
        self._read_condition.acquire()
 | 
						|
        self._incoming_data += frame_data
 | 
						|
        self._read_condition.notify()
 | 
						|
        self._read_condition.release()
 | 
						|
 | 
						|
    def read(self, length):
 | 
						|
        """Reads data. Blocks until enough data has arrived via physical
 | 
						|
        connection.
 | 
						|
 | 
						|
        Args:
 | 
						|
            length: length of data to be read.
 | 
						|
        Raises:
 | 
						|
            LogicalConnectionClosedException: when closing handshake for this
 | 
						|
                logical channel has been received.
 | 
						|
            ConnectionTerminatedException: when the physical connection has
 | 
						|
                closed, or an error is caused on the reader thread.
 | 
						|
        """
 | 
						|
 | 
						|
        self._read_condition.acquire()
 | 
						|
        while (self._read_state == self.STATE_ACTIVE and
 | 
						|
               len(self._incoming_data) < length):
 | 
						|
            self._read_condition.wait()
 | 
						|
 | 
						|
        try:
 | 
						|
            if self._read_state == self.STATE_GRACEFULLY_CLOSED:
 | 
						|
                raise LogicalConnectionClosedException(
 | 
						|
                    'Logical channel %d has closed.' % self._channel_id)
 | 
						|
            elif self._read_state == self.STATE_TERMINATED:
 | 
						|
                raise ConnectionTerminatedException(
 | 
						|
                    'Receiving %d byte failed. Logical channel (%d) closed' %
 | 
						|
                    (length, self._channel_id))
 | 
						|
 | 
						|
            value = self._incoming_data[:length]
 | 
						|
            self._incoming_data = self._incoming_data[length:]
 | 
						|
        finally:
 | 
						|
            self._read_condition.release()
 | 
						|
 | 
						|
        return value
 | 
						|
 | 
						|
    def set_read_state(self, new_state):
 | 
						|
        """Sets the state of this connection. Called when an event for this
 | 
						|
        connection has occurred.
 | 
						|
 | 
						|
        Args:
 | 
						|
            new_state: state to be set. new_state must be one of followings:
 | 
						|
            - STATE_GRACEFULLY_CLOSED: when closing handshake for this
 | 
						|
                connection has been received.
 | 
						|
            - STATE_TERMINATED: when the physical connection has closed or
 | 
						|
                DropChannel of this connection has received.
 | 
						|
        """
 | 
						|
 | 
						|
        self._read_condition.acquire()
 | 
						|
        self._read_state = new_state
 | 
						|
        self._read_condition.notify()
 | 
						|
        self._read_condition.release()
 | 
						|
 | 
						|
 | 
						|
class _InnerMessage(object):
 | 
						|
    """Holds the result of _InnerMessageBuilder.build().
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, opcode, payload):
 | 
						|
        self.opcode = opcode
 | 
						|
        self.payload = payload
 | 
						|
 | 
						|
 | 
						|
class _InnerMessageBuilder(object):
 | 
						|
    """A class that holds the context of inner message fragmentation and
 | 
						|
    builds a message from fragmented inner frame(s).
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
        self._control_opcode = None
 | 
						|
        self._pending_control_fragments = []
 | 
						|
        self._message_opcode = None
 | 
						|
        self._pending_message_fragments = []
 | 
						|
        self._frame_handler = self._handle_first
 | 
						|
 | 
						|
    def _handle_first(self, frame):
 | 
						|
        if frame.opcode == common.OPCODE_CONTINUATION:
 | 
						|
            raise InvalidFrameException('Sending invalid continuation opcode')
 | 
						|
 | 
						|
        if common.is_control_opcode(frame.opcode):
 | 
						|
            return self._process_first_fragmented_control(frame)
 | 
						|
        else:
 | 
						|
            return self._process_first_fragmented_message(frame)
 | 
						|
 | 
						|
    def _process_first_fragmented_control(self, frame):
 | 
						|
        self._control_opcode = frame.opcode
 | 
						|
        self._pending_control_fragments.append(frame.payload)
 | 
						|
        if not frame.fin:
 | 
						|
            self._frame_handler = self._handle_fragmented_control
 | 
						|
            return None
 | 
						|
        return self._reassemble_fragmented_control()
 | 
						|
 | 
						|
    def _process_first_fragmented_message(self, frame):
 | 
						|
        self._message_opcode = frame.opcode
 | 
						|
        self._pending_message_fragments.append(frame.payload)
 | 
						|
        if not frame.fin:
 | 
						|
            self._frame_handler = self._handle_fragmented_message
 | 
						|
            return None
 | 
						|
        return self._reassemble_fragmented_message()
 | 
						|
 | 
						|
    def _handle_fragmented_control(self, frame):
 | 
						|
        if frame.opcode != common.OPCODE_CONTINUATION:
 | 
						|
            raise InvalidFrameException(
 | 
						|
                'Sending invalid opcode %d while sending fragmented control '
 | 
						|
                'message' % frame.opcode)
 | 
						|
        self._pending_control_fragments.append(frame.payload)
 | 
						|
        if not frame.fin:
 | 
						|
            return None
 | 
						|
        return self._reassemble_fragmented_control()
 | 
						|
 | 
						|
    def _reassemble_fragmented_control(self):
 | 
						|
        opcode = self._control_opcode
 | 
						|
        payload = ''.join(self._pending_control_fragments)
 | 
						|
        self._control_opcode = None
 | 
						|
        self._pending_control_fragments = []
 | 
						|
        if self._message_opcode is not None:
 | 
						|
            self._frame_handler = self._handle_fragmented_message
 | 
						|
        else:
 | 
						|
            self._frame_handler = self._handle_first
 | 
						|
        return _InnerMessage(opcode, payload)
 | 
						|
 | 
						|
    def _handle_fragmented_message(self, frame):
 | 
						|
        # Sender can interleave a control message while sending fragmented
 | 
						|
        # messages.
 | 
						|
        if common.is_control_opcode(frame.opcode):
 | 
						|
            if self._control_opcode is not None:
 | 
						|
                raise MuxUnexpectedException(
 | 
						|
                    'Should not reach here(Bug in builder)')
 | 
						|
            return self._process_first_fragmented_control(frame)
 | 
						|
 | 
						|
        if frame.opcode != common.OPCODE_CONTINUATION:
 | 
						|
            raise InvalidFrameException(
 | 
						|
                'Sending invalid opcode %d while sending fragmented message' %
 | 
						|
                frame.opcode)
 | 
						|
        self._pending_message_fragments.append(frame.payload)
 | 
						|
        if not frame.fin:
 | 
						|
            return None
 | 
						|
        return self._reassemble_fragmented_message()
 | 
						|
 | 
						|
    def _reassemble_fragmented_message(self):
 | 
						|
        opcode = self._message_opcode
 | 
						|
        payload = ''.join(self._pending_message_fragments)
 | 
						|
        self._message_opcode = None
 | 
						|
        self._pending_message_fragments = []
 | 
						|
        self._frame_handler = self._handle_first
 | 
						|
        return _InnerMessage(opcode, payload)
 | 
						|
 | 
						|
    def build(self, frame):
 | 
						|
        """Build an inner message. Returns an _InnerMessage instance when
 | 
						|
        the given frame is the last fragmented frame. Returns None otherwise.
 | 
						|
 | 
						|
        Args:
 | 
						|
            frame: an inner frame.
 | 
						|
        Raises:
 | 
						|
            InvalidFrameException: when received invalid opcode. (e.g.
 | 
						|
                receiving non continuation data opcode but the fin flag of
 | 
						|
                the previous inner frame was not set.)
 | 
						|
        """
 | 
						|
 | 
						|
        return self._frame_handler(frame)
 | 
						|
 | 
						|
 | 
						|
class _LogicalStream(Stream):
 | 
						|
    """Mimics the Stream class. This class interprets multiplexed WebSocket
 | 
						|
    frames.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, request, stream_options, send_quota, receive_quota):
 | 
						|
        """Constructs an instance.
 | 
						|
 | 
						|
        Args:
 | 
						|
            request: _LogicalRequest instance.
 | 
						|
            stream_options: StreamOptions instance.
 | 
						|
            send_quota: Initial send quota.
 | 
						|
            receive_quota: Initial receive quota.
 | 
						|
        """
 | 
						|
 | 
						|
        # Physical stream is responsible for masking.
 | 
						|
        stream_options.unmask_receive = False
 | 
						|
        Stream.__init__(self, request, stream_options)
 | 
						|
 | 
						|
        self._send_closed = False
 | 
						|
        self._send_quota = send_quota
 | 
						|
        # - Protects _send_closed and _send_quota
 | 
						|
        # - Signals the thread waiting for send quota replenished
 | 
						|
        self._send_condition = threading.Condition()
 | 
						|
 | 
						|
        # The opcode of the first frame in messages.
 | 
						|
        self._message_opcode = common.OPCODE_TEXT
 | 
						|
        # True when the last message was fragmented.
 | 
						|
        self._last_message_was_fragmented = False
 | 
						|
 | 
						|
        self._receive_quota = receive_quota
 | 
						|
        self._write_inner_frame_semaphore = threading.Semaphore()
 | 
						|
 | 
						|
        self._inner_message_builder = _InnerMessageBuilder()
 | 
						|
 | 
						|
    def _create_inner_frame(self, opcode, payload, end=True):
 | 
						|
        frame = Frame(fin=end, opcode=opcode, payload=payload)
 | 
						|
        for frame_filter in self._options.outgoing_frame_filters:
 | 
						|
            frame_filter.filter(frame)
 | 
						|
 | 
						|
        if len(payload) != len(frame.payload):
 | 
						|
            raise MuxUnexpectedException(
 | 
						|
                'Mux extension must not be used after extensions which change '
 | 
						|
                ' frame boundary')
 | 
						|
 | 
						|
        first_byte = ((frame.fin << 7) | (frame.rsv1 << 6) |
 | 
						|
                      (frame.rsv2 << 5) | (frame.rsv3 << 4) | frame.opcode)
 | 
						|
        return chr(first_byte) + frame.payload
 | 
						|
 | 
						|
    def _write_inner_frame(self, opcode, payload, end=True):
 | 
						|
        payload_length = len(payload)
 | 
						|
        write_position = 0
 | 
						|
 | 
						|
        try:
 | 
						|
            # An inner frame will be fragmented if there is no enough send
 | 
						|
            # quota. This semaphore ensures that fragmented inner frames are
 | 
						|
            # sent in order on the logical channel.
 | 
						|
            # Note that frames that come from other logical channels or
 | 
						|
            # multiplexing control blocks can be inserted between fragmented
 | 
						|
            # inner frames on the physical channel.
 | 
						|
            self._write_inner_frame_semaphore.acquire()
 | 
						|
 | 
						|
            # Consume an octet quota when this is the first fragmented frame.
 | 
						|
            if opcode != common.OPCODE_CONTINUATION:
 | 
						|
                try:
 | 
						|
                    self._send_condition.acquire()
 | 
						|
                    while (not self._send_closed) and self._send_quota == 0:
 | 
						|
                        self._send_condition.wait()
 | 
						|
 | 
						|
                    if self._send_closed:
 | 
						|
                        raise BadOperationException(
 | 
						|
                            'Logical connection %d is closed' %
 | 
						|
                            self._request.channel_id)
 | 
						|
 | 
						|
                    self._send_quota -= 1
 | 
						|
                finally:
 | 
						|
                    self._send_condition.release()
 | 
						|
 | 
						|
            while write_position < payload_length:
 | 
						|
                try:
 | 
						|
                    self._send_condition.acquire()
 | 
						|
                    while (not self._send_closed) and self._send_quota == 0:
 | 
						|
                        self._logger.debug(
 | 
						|
                            'No quota. Waiting FlowControl message for %d.' %
 | 
						|
                            self._request.channel_id)
 | 
						|
                        self._send_condition.wait()
 | 
						|
 | 
						|
                    if self._send_closed:
 | 
						|
                        raise BadOperationException(
 | 
						|
                            'Logical connection %d is closed' %
 | 
						|
                            self.request._channel_id)
 | 
						|
 | 
						|
                    remaining = payload_length - write_position
 | 
						|
                    write_length = min(self._send_quota, remaining)
 | 
						|
                    inner_frame_end = (
 | 
						|
                        end and
 | 
						|
                        (write_position + write_length == payload_length))
 | 
						|
 | 
						|
                    inner_frame = self._create_inner_frame(
 | 
						|
                        opcode,
 | 
						|
                        payload[write_position:write_position+write_length],
 | 
						|
                        inner_frame_end)
 | 
						|
                    self._send_quota -= write_length
 | 
						|
                    self._logger.debug('Consumed quota=%d, remaining=%d' %
 | 
						|
                                       (write_length, self._send_quota))
 | 
						|
                finally:
 | 
						|
                    self._send_condition.release()
 | 
						|
 | 
						|
                # Writing data will block the worker so we need to release
 | 
						|
                # _send_condition before writing.
 | 
						|
                self._logger.debug('Sending inner frame: %r' % inner_frame)
 | 
						|
                self._request.connection.write(inner_frame)
 | 
						|
                write_position += write_length
 | 
						|
 | 
						|
                opcode = common.OPCODE_CONTINUATION
 | 
						|
 | 
						|
        except ValueError, e:
 | 
						|
            raise BadOperationException(e)
 | 
						|
        finally:
 | 
						|
            self._write_inner_frame_semaphore.release()
 | 
						|
 | 
						|
    def replenish_send_quota(self, send_quota):
 | 
						|
        """Replenish send quota."""
 | 
						|
 | 
						|
        try:
 | 
						|
            self._send_condition.acquire()
 | 
						|
            if self._send_quota + send_quota > 0x7FFFFFFFFFFFFFFF:
 | 
						|
                self._send_quota = 0
 | 
						|
                raise LogicalChannelError(
 | 
						|
                    self._request.channel_id, _DROP_CODE_SEND_QUOTA_OVERFLOW)
 | 
						|
            self._send_quota += send_quota
 | 
						|
            self._logger.debug('Replenished send quota for channel id %d: %d' %
 | 
						|
                               (self._request.channel_id, self._send_quota))
 | 
						|
        finally:
 | 
						|
            self._send_condition.notify()
 | 
						|
            self._send_condition.release()
 | 
						|
 | 
						|
    def consume_receive_quota(self, amount):
 | 
						|
        """Consumes receive quota. Returns False on failure."""
 | 
						|
 | 
						|
        if self._receive_quota < amount:
 | 
						|
            self._logger.debug('Violate quota on channel id %d: %d < %d' %
 | 
						|
                               (self._request.channel_id,
 | 
						|
                                self._receive_quota, amount))
 | 
						|
            return False
 | 
						|
        self._receive_quota -= amount
 | 
						|
        return True
 | 
						|
 | 
						|
    def send_message(self, message, end=True, binary=False):
 | 
						|
        """Override Stream.send_message."""
 | 
						|
 | 
						|
        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')
 | 
						|
 | 
						|
        if binary:
 | 
						|
            opcode = common.OPCODE_BINARY
 | 
						|
        else:
 | 
						|
            opcode = common.OPCODE_TEXT
 | 
						|
            message = message.encode('utf-8')
 | 
						|
 | 
						|
        for message_filter in self._options.outgoing_message_filters:
 | 
						|
            message = message_filter.filter(message, end, binary)
 | 
						|
 | 
						|
        if self._last_message_was_fragmented:
 | 
						|
            if opcode != self._message_opcode:
 | 
						|
                raise BadOperationException('Message types are different in '
 | 
						|
                                            'frames for the same message')
 | 
						|
            opcode = common.OPCODE_CONTINUATION
 | 
						|
        else:
 | 
						|
            self._message_opcode = opcode
 | 
						|
 | 
						|
        self._write_inner_frame(opcode, message, end)
 | 
						|
        self._last_message_was_fragmented = not end
 | 
						|
 | 
						|
    def _receive_frame(self):
 | 
						|
        """Overrides Stream._receive_frame.
 | 
						|
 | 
						|
        In addition to call Stream._receive_frame, this method adds the amount
 | 
						|
        of payload to receiving quota and sends FlowControl to the client.
 | 
						|
        We need to do it here because Stream.receive_message() handles
 | 
						|
        control frames internally.
 | 
						|
        """
 | 
						|
 | 
						|
        opcode, payload, fin, rsv1, rsv2, rsv3 = Stream._receive_frame(self)
 | 
						|
        amount = len(payload)
 | 
						|
        # Replenish extra one octet when receiving the first fragmented frame.
 | 
						|
        if opcode != common.OPCODE_CONTINUATION:
 | 
						|
            amount += 1
 | 
						|
        self._receive_quota += amount
 | 
						|
        frame_data = _create_flow_control(self._request.channel_id,
 | 
						|
                                          amount)
 | 
						|
        self._logger.debug('Sending flow control for %d, replenished=%d' %
 | 
						|
                           (self._request.channel_id, amount))
 | 
						|
        self._request.connection.write_control_data(frame_data)
 | 
						|
        return opcode, payload, fin, rsv1, rsv2, rsv3
 | 
						|
 | 
						|
    def _get_message_from_frame(self, frame):
 | 
						|
        """Overrides Stream._get_message_from_frame.
 | 
						|
        """
 | 
						|
 | 
						|
        try:
 | 
						|
            inner_message = self._inner_message_builder.build(frame)
 | 
						|
        except InvalidFrameException:
 | 
						|
            raise LogicalChannelError(
 | 
						|
                self._request.channel_id, _DROP_CODE_BAD_FRAGMENTATION)
 | 
						|
 | 
						|
        if inner_message is None:
 | 
						|
            return None
 | 
						|
        self._original_opcode = inner_message.opcode
 | 
						|
        return inner_message.payload
 | 
						|
 | 
						|
    def receive_message(self):
 | 
						|
        """Overrides Stream.receive_message."""
 | 
						|
 | 
						|
        # Just call Stream.receive_message(), but catch
 | 
						|
        # LogicalConnectionClosedException, which is raised when the logical
 | 
						|
        # connection has closed gracefully.
 | 
						|
        try:
 | 
						|
            return Stream.receive_message(self)
 | 
						|
        except LogicalConnectionClosedException, e:
 | 
						|
            self._logger.debug('%s', e)
 | 
						|
            return None
 | 
						|
 | 
						|
    def _send_closing_handshake(self, code, reason):
 | 
						|
        """Overrides Stream._send_closing_handshake."""
 | 
						|
 | 
						|
        body = create_closing_handshake_body(code, reason)
 | 
						|
        self._logger.debug('Sending closing handshake for %d: (%r, %r)' %
 | 
						|
                           (self._request.channel_id, code, reason))
 | 
						|
        self._write_inner_frame(common.OPCODE_CLOSE, body, end=True)
 | 
						|
 | 
						|
        self._request.server_terminated = True
 | 
						|
 | 
						|
    def send_ping(self, body=''):
 | 
						|
        """Overrides Stream.send_ping"""
 | 
						|
 | 
						|
        self._logger.debug('Sending ping on logical channel %d: %r' %
 | 
						|
                           (self._request.channel_id, body))
 | 
						|
        self._write_inner_frame(common.OPCODE_PING, body, end=True)
 | 
						|
 | 
						|
        self._ping_queue.append(body)
 | 
						|
 | 
						|
    def _send_pong(self, body):
 | 
						|
        """Overrides Stream._send_pong"""
 | 
						|
 | 
						|
        self._logger.debug('Sending pong on logical channel %d: %r' %
 | 
						|
                           (self._request.channel_id, body))
 | 
						|
        self._write_inner_frame(common.OPCODE_PONG, body, end=True)
 | 
						|
 | 
						|
    def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason=''):
 | 
						|
        """Overrides Stream.close_connection."""
 | 
						|
 | 
						|
        # TODO(bashi): Implement
 | 
						|
        self._logger.debug('Closing logical connection %d' %
 | 
						|
                           self._request.channel_id)
 | 
						|
        self._request.server_terminated = True
 | 
						|
 | 
						|
    def stop_sending(self):
 | 
						|
        """Stops accepting new send operation (_write_inner_frame)."""
 | 
						|
 | 
						|
        self._send_condition.acquire()
 | 
						|
        self._send_closed = True
 | 
						|
        self._send_condition.notify()
 | 
						|
        self._send_condition.release()
 | 
						|
 | 
						|
 | 
						|
class _OutgoingData(object):
 | 
						|
    """A structure that holds data to be sent via physical connection and
 | 
						|
    origin of the data.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, channel_id, data):
 | 
						|
        self.channel_id = channel_id
 | 
						|
        self.data = data
 | 
						|
 | 
						|
 | 
						|
class _PhysicalConnectionWriter(threading.Thread):
 | 
						|
    """A thread that is responsible for writing data to physical connection.
 | 
						|
 | 
						|
    TODO(bashi): Make sure there is no thread-safety problem when the reader
 | 
						|
    thread reads data from the same socket at a time.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, mux_handler):
 | 
						|
        """Constructs an instance.
 | 
						|
 | 
						|
        Args:
 | 
						|
            mux_handler: _MuxHandler instance.
 | 
						|
        """
 | 
						|
 | 
						|
        threading.Thread.__init__(self)
 | 
						|
        self._logger = util.get_class_logger(self)
 | 
						|
        self._mux_handler = mux_handler
 | 
						|
        self.setDaemon(True)
 | 
						|
 | 
						|
        # When set, make this thread stop accepting new data, flush pending
 | 
						|
        # data and exit.
 | 
						|
        self._stop_requested = False
 | 
						|
        # The close code of the physical connection.
 | 
						|
        self._close_code = common.STATUS_NORMAL_CLOSURE
 | 
						|
        # Deque for passing write data. It's protected by _deque_condition
 | 
						|
        # until _stop_requested is set.
 | 
						|
        self._deque = collections.deque()
 | 
						|
        # - Protects _deque, _stop_requested and _close_code
 | 
						|
        # - Signals threads waiting for them to be available
 | 
						|
        self._deque_condition = threading.Condition()
 | 
						|
 | 
						|
    def put_outgoing_data(self, data):
 | 
						|
        """Puts outgoing data.
 | 
						|
 | 
						|
        Args:
 | 
						|
            data: _OutgoingData instance.
 | 
						|
 | 
						|
        Raises:
 | 
						|
            BadOperationException: when the thread has been requested to
 | 
						|
                terminate.
 | 
						|
        """
 | 
						|
 | 
						|
        try:
 | 
						|
            self._deque_condition.acquire()
 | 
						|
            if self._stop_requested:
 | 
						|
                raise BadOperationException('Cannot write data anymore')
 | 
						|
 | 
						|
            self._deque.append(data)
 | 
						|
            self._deque_condition.notify()
 | 
						|
        finally:
 | 
						|
            self._deque_condition.release()
 | 
						|
 | 
						|
    def _write_data(self, outgoing_data):
 | 
						|
        message = (_encode_channel_id(outgoing_data.channel_id) +
 | 
						|
                   outgoing_data.data)
 | 
						|
        try:
 | 
						|
            self._mux_handler.physical_stream.send_message(
 | 
						|
                message=message, end=True, binary=True)
 | 
						|
        except Exception, e:
 | 
						|
            util.prepend_message_to_exception(
 | 
						|
                'Failed to send message to %r: ' %
 | 
						|
                (self._mux_handler.physical_connection.remote_addr,), e)
 | 
						|
            raise
 | 
						|
 | 
						|
        # TODO(bashi): It would be better to block the thread that sends
 | 
						|
        # control data as well.
 | 
						|
        if outgoing_data.channel_id != _CONTROL_CHANNEL_ID:
 | 
						|
            self._mux_handler.notify_write_data_done(outgoing_data.channel_id)
 | 
						|
 | 
						|
    def run(self):
 | 
						|
        try:
 | 
						|
            self._deque_condition.acquire()
 | 
						|
            while not self._stop_requested:
 | 
						|
                if len(self._deque) == 0:
 | 
						|
                    self._deque_condition.wait()
 | 
						|
                    continue
 | 
						|
 | 
						|
                outgoing_data = self._deque.popleft()
 | 
						|
 | 
						|
                self._deque_condition.release()
 | 
						|
                self._write_data(outgoing_data)
 | 
						|
                self._deque_condition.acquire()
 | 
						|
 | 
						|
            # Flush deque.
 | 
						|
            #
 | 
						|
            # At this point, self._deque_condition is always acquired.
 | 
						|
            try:
 | 
						|
                while len(self._deque) > 0:
 | 
						|
                    outgoing_data = self._deque.popleft()
 | 
						|
                    self._write_data(outgoing_data)
 | 
						|
            finally:
 | 
						|
                self._deque_condition.release()
 | 
						|
 | 
						|
            # Close physical connection.
 | 
						|
            try:
 | 
						|
                # Don't wait the response here. The response will be read
 | 
						|
                # by the reader thread.
 | 
						|
                self._mux_handler.physical_stream.close_connection(
 | 
						|
                    self._close_code, wait_response=False)
 | 
						|
            except Exception, e:
 | 
						|
                util.prepend_message_to_exception(
 | 
						|
                    'Failed to close the physical connection: %r' % e)
 | 
						|
                raise
 | 
						|
        finally:
 | 
						|
            self._mux_handler.notify_writer_done()
 | 
						|
 | 
						|
    def stop(self, close_code=common.STATUS_NORMAL_CLOSURE):
 | 
						|
        """Stops the writer thread."""
 | 
						|
 | 
						|
        self._deque_condition.acquire()
 | 
						|
        self._stop_requested = True
 | 
						|
        self._close_code = close_code
 | 
						|
        self._deque_condition.notify()
 | 
						|
        self._deque_condition.release()
 | 
						|
 | 
						|
 | 
						|
class _PhysicalConnectionReader(threading.Thread):
 | 
						|
    """A thread that is responsible for reading data from physical connection.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, mux_handler):
 | 
						|
        """Constructs an instance.
 | 
						|
 | 
						|
        Args:
 | 
						|
            mux_handler: _MuxHandler instance.
 | 
						|
        """
 | 
						|
 | 
						|
        threading.Thread.__init__(self)
 | 
						|
        self._logger = util.get_class_logger(self)
 | 
						|
        self._mux_handler = mux_handler
 | 
						|
        self.setDaemon(True)
 | 
						|
 | 
						|
    def run(self):
 | 
						|
        while True:
 | 
						|
            try:
 | 
						|
                physical_stream = self._mux_handler.physical_stream
 | 
						|
                message = physical_stream.receive_message()
 | 
						|
                if message is None:
 | 
						|
                    break
 | 
						|
                # Below happens only when a data message is received.
 | 
						|
                opcode = physical_stream.get_last_received_opcode()
 | 
						|
                if opcode != common.OPCODE_BINARY:
 | 
						|
                    self._mux_handler.fail_physical_connection(
 | 
						|
                        _DROP_CODE_INVALID_ENCAPSULATING_MESSAGE,
 | 
						|
                        'Received a text message on physical connection')
 | 
						|
                    break
 | 
						|
 | 
						|
            except ConnectionTerminatedException, e:
 | 
						|
                self._logger.debug('%s', e)
 | 
						|
                break
 | 
						|
 | 
						|
            try:
 | 
						|
                self._mux_handler.dispatch_message(message)
 | 
						|
            except PhysicalConnectionError, e:
 | 
						|
                self._mux_handler.fail_physical_connection(
 | 
						|
                    e.drop_code, e.message)
 | 
						|
                break
 | 
						|
            except LogicalChannelError, e:
 | 
						|
                self._mux_handler.fail_logical_channel(
 | 
						|
                    e.channel_id, e.drop_code, e.message)
 | 
						|
            except Exception, e:
 | 
						|
                self._logger.debug(traceback.format_exc())
 | 
						|
                break
 | 
						|
 | 
						|
        self._mux_handler.notify_reader_done()
 | 
						|
 | 
						|
 | 
						|
class _Worker(threading.Thread):
 | 
						|
    """A thread that is responsible for running the corresponding application
 | 
						|
    handler.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, mux_handler, request):
 | 
						|
        """Constructs an instance.
 | 
						|
 | 
						|
        Args:
 | 
						|
            mux_handler: _MuxHandler instance.
 | 
						|
            request: _LogicalRequest instance.
 | 
						|
        """
 | 
						|
 | 
						|
        threading.Thread.__init__(self)
 | 
						|
        self._logger = util.get_class_logger(self)
 | 
						|
        self._mux_handler = mux_handler
 | 
						|
        self._request = request
 | 
						|
        self.setDaemon(True)
 | 
						|
 | 
						|
    def run(self):
 | 
						|
        self._logger.debug('Logical channel worker started. (id=%d)' %
 | 
						|
                           self._request.channel_id)
 | 
						|
        try:
 | 
						|
            # Non-critical exceptions will be handled by dispatcher.
 | 
						|
            self._mux_handler.dispatcher.transfer_data(self._request)
 | 
						|
        except LogicalChannelError, e:
 | 
						|
            self._mux_handler.fail_logical_channel(
 | 
						|
                e.channel_id, e.drop_code, e.message)
 | 
						|
        finally:
 | 
						|
            self._mux_handler.notify_worker_done(self._request.channel_id)
 | 
						|
 | 
						|
 | 
						|
class _MuxHandshaker(hybi.Handshaker):
 | 
						|
    """Opening handshake processor for multiplexing."""
 | 
						|
 | 
						|
    _DUMMY_WEBSOCKET_KEY = 'dGhlIHNhbXBsZSBub25jZQ=='
 | 
						|
 | 
						|
    def __init__(self, request, dispatcher, send_quota, receive_quota):
 | 
						|
        """Constructs an instance.
 | 
						|
        Args:
 | 
						|
            request: _LogicalRequest instance.
 | 
						|
            dispatcher: Dispatcher instance (dispatch.Dispatcher).
 | 
						|
            send_quota: Initial send quota.
 | 
						|
            receive_quota: Initial receive quota.
 | 
						|
        """
 | 
						|
 | 
						|
        hybi.Handshaker.__init__(self, request, dispatcher)
 | 
						|
        self._send_quota = send_quota
 | 
						|
        self._receive_quota = receive_quota
 | 
						|
 | 
						|
        # Append headers which should not be included in handshake field of
 | 
						|
        # AddChannelRequest.
 | 
						|
        # TODO(bashi): Make sure whether we should raise exception when
 | 
						|
        #     these headers are included already.
 | 
						|
        request.headers_in[common.UPGRADE_HEADER] = (
 | 
						|
            common.WEBSOCKET_UPGRADE_TYPE)
 | 
						|
        request.headers_in[common.SEC_WEBSOCKET_VERSION_HEADER] = (
 | 
						|
            str(common.VERSION_HYBI_LATEST))
 | 
						|
        request.headers_in[common.SEC_WEBSOCKET_KEY_HEADER] = (
 | 
						|
            self._DUMMY_WEBSOCKET_KEY)
 | 
						|
 | 
						|
    def _create_stream(self, stream_options):
 | 
						|
        """Override hybi.Handshaker._create_stream."""
 | 
						|
 | 
						|
        self._logger.debug('Creating logical stream for %d' %
 | 
						|
                           self._request.channel_id)
 | 
						|
        return _LogicalStream(
 | 
						|
            self._request, stream_options, self._send_quota,
 | 
						|
            self._receive_quota)
 | 
						|
 | 
						|
    def _create_handshake_response(self, accept):
 | 
						|
        """Override hybi._create_handshake_response."""
 | 
						|
 | 
						|
        response = []
 | 
						|
 | 
						|
        response.append('HTTP/1.1 101 Switching Protocols\r\n')
 | 
						|
 | 
						|
        # Upgrade and Sec-WebSocket-Accept should be excluded.
 | 
						|
        response.append('%s: %s\r\n' % (
 | 
						|
            common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE))
 | 
						|
        if self._request.ws_protocol is not None:
 | 
						|
            response.append('%s: %s\r\n' % (
 | 
						|
                common.SEC_WEBSOCKET_PROTOCOL_HEADER,
 | 
						|
                self._request.ws_protocol))
 | 
						|
        if (self._request.ws_extensions is not None and
 | 
						|
            len(self._request.ws_extensions) != 0):
 | 
						|
            response.append('%s: %s\r\n' % (
 | 
						|
                common.SEC_WEBSOCKET_EXTENSIONS_HEADER,
 | 
						|
                common.format_extensions(self._request.ws_extensions)))
 | 
						|
        response.append('\r\n')
 | 
						|
 | 
						|
        return ''.join(response)
 | 
						|
 | 
						|
    def _send_handshake(self, accept):
 | 
						|
        """Override hybi.Handshaker._send_handshake."""
 | 
						|
 | 
						|
        # Don't send handshake response for the default channel
 | 
						|
        if self._request.channel_id == _DEFAULT_CHANNEL_ID:
 | 
						|
            return
 | 
						|
 | 
						|
        handshake_response = self._create_handshake_response(accept)
 | 
						|
        frame_data = _create_add_channel_response(
 | 
						|
                         self._request.channel_id,
 | 
						|
                         handshake_response)
 | 
						|
        self._logger.debug('Sending handshake response for %d: %r' %
 | 
						|
                           (self._request.channel_id, frame_data))
 | 
						|
        self._request.connection.write_control_data(frame_data)
 | 
						|
 | 
						|
 | 
						|
class _LogicalChannelData(object):
 | 
						|
    """A structure that holds information about logical channel.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, request, worker):
 | 
						|
        self.request = request
 | 
						|
        self.worker = worker
 | 
						|
        self.drop_code = _DROP_CODE_NORMAL_CLOSURE
 | 
						|
        self.drop_message = ''
 | 
						|
 | 
						|
 | 
						|
class _HandshakeDeltaBase(object):
 | 
						|
    """A class that holds information for delta-encoded handshake."""
 | 
						|
 | 
						|
    def __init__(self, headers):
 | 
						|
        self._headers = headers
 | 
						|
 | 
						|
    def create_headers(self, delta=None):
 | 
						|
        """Creates request headers for an AddChannelRequest that has
 | 
						|
        delta-encoded handshake.
 | 
						|
 | 
						|
        Args:
 | 
						|
            delta: headers should be overridden.
 | 
						|
        """
 | 
						|
 | 
						|
        headers = copy.copy(self._headers)
 | 
						|
        if delta:
 | 
						|
            for key, value in delta.items():
 | 
						|
                # The spec requires that a header with an empty value is
 | 
						|
                # removed from the delta base.
 | 
						|
                if len(value) == 0 and headers.has_key(key):
 | 
						|
                    del headers[key]
 | 
						|
                else:
 | 
						|
                    headers[key] = value
 | 
						|
        return headers
 | 
						|
 | 
						|
 | 
						|
class _MuxHandler(object):
 | 
						|
    """Multiplexing handler. When a handler starts, it launches three
 | 
						|
    threads; the reader thread, the writer thread, and a worker thread.
 | 
						|
 | 
						|
    The reader thread reads data from the physical stream, i.e., the
 | 
						|
    ws_stream object of the underlying websocket connection. The reader
 | 
						|
    thread interprets multiplexed frames and dispatches them to logical
 | 
						|
    channels. Methods of this class are mostly called by the reader thread.
 | 
						|
 | 
						|
    The writer thread sends multiplexed frames which are created by
 | 
						|
    logical channels via the physical connection.
 | 
						|
 | 
						|
    The worker thread launched at the starting point handles the
 | 
						|
    "Implicitly Opened Connection". If multiplexing handler receives
 | 
						|
    an AddChannelRequest and accepts it, the handler will launch a new worker
 | 
						|
    thread and dispatch the request to it.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, request, dispatcher):
 | 
						|
        """Constructs an instance.
 | 
						|
 | 
						|
        Args:
 | 
						|
            request: mod_python request of the physical connection.
 | 
						|
            dispatcher: Dispatcher instance (dispatch.Dispatcher).
 | 
						|
        """
 | 
						|
 | 
						|
        self.original_request = request
 | 
						|
        self.dispatcher = dispatcher
 | 
						|
        self.physical_connection = request.connection
 | 
						|
        self.physical_stream = request.ws_stream
 | 
						|
        self._logger = util.get_class_logger(self)
 | 
						|
        self._logical_channels = {}
 | 
						|
        self._logical_channels_condition = threading.Condition()
 | 
						|
        # Holds client's initial quota
 | 
						|
        self._channel_slots = collections.deque()
 | 
						|
        self._handshake_base = None
 | 
						|
        self._worker_done_notify_received = False
 | 
						|
        self._reader = None
 | 
						|
        self._writer = None
 | 
						|
 | 
						|
    def start(self):
 | 
						|
        """Starts the handler.
 | 
						|
 | 
						|
        Raises:
 | 
						|
            MuxUnexpectedException: when the handler already started, or when
 | 
						|
                opening handshake of the default channel fails.
 | 
						|
        """
 | 
						|
 | 
						|
        if self._reader or self._writer:
 | 
						|
            raise MuxUnexpectedException('MuxHandler already started')
 | 
						|
 | 
						|
        self._reader = _PhysicalConnectionReader(self)
 | 
						|
        self._writer = _PhysicalConnectionWriter(self)
 | 
						|
        self._reader.start()
 | 
						|
        self._writer.start()
 | 
						|
 | 
						|
        # Create "Implicitly Opened Connection".
 | 
						|
        logical_connection = _LogicalConnection(self, _DEFAULT_CHANNEL_ID)
 | 
						|
        headers = copy.copy(self.original_request.headers_in)
 | 
						|
        # Add extensions for logical channel.
 | 
						|
        headers[common.SEC_WEBSOCKET_EXTENSIONS_HEADER] = (
 | 
						|
            common.format_extensions(
 | 
						|
                self.original_request.mux_processor.extensions()))
 | 
						|
        self._handshake_base = _HandshakeDeltaBase(headers)
 | 
						|
        logical_request = _LogicalRequest(
 | 
						|
            _DEFAULT_CHANNEL_ID,
 | 
						|
            self.original_request.method,
 | 
						|
            self.original_request.uri,
 | 
						|
            self.original_request.protocol,
 | 
						|
            self._handshake_base.create_headers(),
 | 
						|
            logical_connection)
 | 
						|
        # Client's send quota for the implicitly opened connection is zero,
 | 
						|
        # but we will send FlowControl later so set the initial quota to
 | 
						|
        # _INITIAL_QUOTA_FOR_CLIENT.
 | 
						|
        self._channel_slots.append(_INITIAL_QUOTA_FOR_CLIENT)
 | 
						|
        send_quota = self.original_request.mux_processor.quota()
 | 
						|
        if not self._do_handshake_for_logical_request(
 | 
						|
            logical_request, send_quota=send_quota):
 | 
						|
            raise MuxUnexpectedException(
 | 
						|
                'Failed handshake on the default channel id')
 | 
						|
        self._add_logical_channel(logical_request)
 | 
						|
 | 
						|
        # Send FlowControl for the implicitly opened connection.
 | 
						|
        frame_data = _create_flow_control(_DEFAULT_CHANNEL_ID,
 | 
						|
                                          _INITIAL_QUOTA_FOR_CLIENT)
 | 
						|
        logical_request.connection.write_control_data(frame_data)
 | 
						|
 | 
						|
    def add_channel_slots(self, slots, send_quota):
 | 
						|
        """Adds channel slots.
 | 
						|
 | 
						|
        Args:
 | 
						|
            slots: number of slots to be added.
 | 
						|
            send_quota: initial send quota for slots.
 | 
						|
        """
 | 
						|
 | 
						|
        self._channel_slots.extend([send_quota] * slots)
 | 
						|
        # Send NewChannelSlot to client.
 | 
						|
        frame_data = _create_new_channel_slot(slots, send_quota)
 | 
						|
        self.send_control_data(frame_data)
 | 
						|
 | 
						|
    def wait_until_done(self, timeout=None):
 | 
						|
        """Waits until all workers are done. Returns False when timeout has
 | 
						|
        occurred. Returns True on success.
 | 
						|
 | 
						|
        Args:
 | 
						|
            timeout: timeout in sec.
 | 
						|
        """
 | 
						|
 | 
						|
        self._logical_channels_condition.acquire()
 | 
						|
        try:
 | 
						|
            while len(self._logical_channels) > 0:
 | 
						|
                self._logger.debug('Waiting workers(%d)...' %
 | 
						|
                                   len(self._logical_channels))
 | 
						|
                self._worker_done_notify_received = False
 | 
						|
                self._logical_channels_condition.wait(timeout)
 | 
						|
                if not self._worker_done_notify_received:
 | 
						|
                    self._logger.debug('Waiting worker(s) timed out')
 | 
						|
                    return False
 | 
						|
        finally:
 | 
						|
            self._logical_channels_condition.release()
 | 
						|
 | 
						|
        # Flush pending outgoing data
 | 
						|
        self._writer.stop()
 | 
						|
        self._writer.join()
 | 
						|
 | 
						|
        return True
 | 
						|
 | 
						|
    def notify_write_data_done(self, channel_id):
 | 
						|
        """Called by the writer thread when a write operation has done.
 | 
						|
 | 
						|
        Args:
 | 
						|
            channel_id: objective channel id.
 | 
						|
        """
 | 
						|
 | 
						|
        try:
 | 
						|
            self._logical_channels_condition.acquire()
 | 
						|
            if channel_id in self._logical_channels:
 | 
						|
                channel_data = self._logical_channels[channel_id]
 | 
						|
                channel_data.request.connection.on_write_data_done()
 | 
						|
            else:
 | 
						|
                self._logger.debug('Seems that logical channel for %d has gone'
 | 
						|
                                   % channel_id)
 | 
						|
        finally:
 | 
						|
            self._logical_channels_condition.release()
 | 
						|
 | 
						|
    def send_control_data(self, data):
 | 
						|
        """Sends data via the control channel.
 | 
						|
 | 
						|
        Args:
 | 
						|
            data: data to be sent.
 | 
						|
        """
 | 
						|
 | 
						|
        self._writer.put_outgoing_data(_OutgoingData(
 | 
						|
                channel_id=_CONTROL_CHANNEL_ID, data=data))
 | 
						|
 | 
						|
    def send_data(self, channel_id, data):
 | 
						|
        """Sends data via given logical channel. This method is called by
 | 
						|
        worker threads.
 | 
						|
 | 
						|
        Args:
 | 
						|
            channel_id: objective channel id.
 | 
						|
            data: data to be sent.
 | 
						|
        """
 | 
						|
 | 
						|
        self._writer.put_outgoing_data(_OutgoingData(
 | 
						|
                channel_id=channel_id, data=data))
 | 
						|
 | 
						|
    def _send_drop_channel(self, channel_id, code=None, message=''):
 | 
						|
        frame_data = _create_drop_channel(channel_id, code, message)
 | 
						|
        self._logger.debug(
 | 
						|
            'Sending drop channel for channel id %d' % channel_id)
 | 
						|
        self.send_control_data(frame_data)
 | 
						|
 | 
						|
    def _send_error_add_channel_response(self, channel_id, status=None):
 | 
						|
        if status is None:
 | 
						|
            status = common.HTTP_STATUS_BAD_REQUEST
 | 
						|
 | 
						|
        if status in _HTTP_BAD_RESPONSE_MESSAGES:
 | 
						|
            message = _HTTP_BAD_RESPONSE_MESSAGES[status]
 | 
						|
        else:
 | 
						|
            self._logger.debug('Response message for %d is not found' % status)
 | 
						|
            message = '???'
 | 
						|
 | 
						|
        response = 'HTTP/1.1 %d %s\r\n\r\n' % (status, message)
 | 
						|
        frame_data = _create_add_channel_response(channel_id,
 | 
						|
                                                  encoded_handshake=response,
 | 
						|
                                                  encoding=0, rejected=True)
 | 
						|
        self.send_control_data(frame_data)
 | 
						|
 | 
						|
    def _create_logical_request(self, block):
 | 
						|
        if block.channel_id == _CONTROL_CHANNEL_ID:
 | 
						|
            # TODO(bashi): Raise PhysicalConnectionError with code 2006
 | 
						|
            # instead of MuxUnexpectedException.
 | 
						|
            raise MuxUnexpectedException(
 | 
						|
                'Received the control channel id (0) as objective channel '
 | 
						|
                'id for AddChannel')
 | 
						|
 | 
						|
        if block.encoding > _HANDSHAKE_ENCODING_DELTA:
 | 
						|
            raise PhysicalConnectionError(
 | 
						|
                _DROP_CODE_UNKNOWN_REQUEST_ENCODING)
 | 
						|
 | 
						|
        method, path, version, headers = _parse_request_text(
 | 
						|
            block.encoded_handshake)
 | 
						|
        if block.encoding == _HANDSHAKE_ENCODING_DELTA:
 | 
						|
            headers = self._handshake_base.create_headers(headers)
 | 
						|
 | 
						|
        connection = _LogicalConnection(self, block.channel_id)
 | 
						|
        request = _LogicalRequest(block.channel_id, method, path, version,
 | 
						|
                                  headers, connection)
 | 
						|
        return request
 | 
						|
 | 
						|
    def _do_handshake_for_logical_request(self, request, send_quota=0):
 | 
						|
        try:
 | 
						|
            receive_quota = self._channel_slots.popleft()
 | 
						|
        except IndexError:
 | 
						|
            raise LogicalChannelError(
 | 
						|
                request.channel_id, _DROP_CODE_NEW_CHANNEL_SLOT_VIOLATION)
 | 
						|
 | 
						|
        handshaker = _MuxHandshaker(request, self.dispatcher,
 | 
						|
                                    send_quota, receive_quota)
 | 
						|
        try:
 | 
						|
            handshaker.do_handshake()
 | 
						|
        except handshake.VersionException, e:
 | 
						|
            self._logger.info('%s', e)
 | 
						|
            self._send_error_add_channel_response(
 | 
						|
                request.channel_id, status=common.HTTP_STATUS_BAD_REQUEST)
 | 
						|
            return False
 | 
						|
        except handshake.HandshakeException, e:
 | 
						|
            # TODO(bashi): Should we _Fail the Logical Channel_ with 3001
 | 
						|
            # instead?
 | 
						|
            self._logger.info('%s', e)
 | 
						|
            self._send_error_add_channel_response(request.channel_id,
 | 
						|
                                                  status=e.status)
 | 
						|
            return False
 | 
						|
        except handshake.AbortedByUserException, e:
 | 
						|
            self._logger.info('%s', e)
 | 
						|
            self._send_error_add_channel_response(request.channel_id)
 | 
						|
            return False
 | 
						|
 | 
						|
        return True
 | 
						|
 | 
						|
    def _add_logical_channel(self, logical_request):
 | 
						|
        try:
 | 
						|
            self._logical_channels_condition.acquire()
 | 
						|
            if logical_request.channel_id in self._logical_channels:
 | 
						|
                self._logger.debug('Channel id %d already exists' %
 | 
						|
                                   logical_request.channel_id)
 | 
						|
                raise PhysicalConnectionError(
 | 
						|
                    _DROP_CODE_CHANNEL_ALREADY_EXISTS,
 | 
						|
                    'Channel id %d already exists' %
 | 
						|
                    logical_request.channel_id)
 | 
						|
            worker = _Worker(self, logical_request)
 | 
						|
            channel_data = _LogicalChannelData(logical_request, worker)
 | 
						|
            self._logical_channels[logical_request.channel_id] = channel_data
 | 
						|
            worker.start()
 | 
						|
        finally:
 | 
						|
            self._logical_channels_condition.release()
 | 
						|
 | 
						|
    def _process_add_channel_request(self, block):
 | 
						|
        try:
 | 
						|
            logical_request = self._create_logical_request(block)
 | 
						|
        except ValueError, e:
 | 
						|
            self._logger.debug('Failed to create logical request: %r' % e)
 | 
						|
            self._send_error_add_channel_response(
 | 
						|
                block.channel_id, status=common.HTTP_STATUS_BAD_REQUEST)
 | 
						|
            return
 | 
						|
        if self._do_handshake_for_logical_request(logical_request):
 | 
						|
            if block.encoding == _HANDSHAKE_ENCODING_IDENTITY:
 | 
						|
                # Update handshake base.
 | 
						|
                # TODO(bashi): Make sure this is the right place to update
 | 
						|
                # handshake base.
 | 
						|
                self._handshake_base = _HandshakeDeltaBase(
 | 
						|
                    logical_request.headers_in)
 | 
						|
            self._add_logical_channel(logical_request)
 | 
						|
        else:
 | 
						|
            self._send_error_add_channel_response(
 | 
						|
                block.channel_id, status=common.HTTP_STATUS_BAD_REQUEST)
 | 
						|
 | 
						|
    def _process_flow_control(self, block):
 | 
						|
        try:
 | 
						|
            self._logical_channels_condition.acquire()
 | 
						|
            if not block.channel_id in self._logical_channels:
 | 
						|
                return
 | 
						|
            channel_data = self._logical_channels[block.channel_id]
 | 
						|
            channel_data.request.ws_stream.replenish_send_quota(
 | 
						|
                block.send_quota)
 | 
						|
        finally:
 | 
						|
            self._logical_channels_condition.release()
 | 
						|
 | 
						|
    def _process_drop_channel(self, block):
 | 
						|
        self._logger.debug(
 | 
						|
            'DropChannel received for %d: code=%r, reason=%r' %
 | 
						|
            (block.channel_id, block.drop_code, block.drop_message))
 | 
						|
        try:
 | 
						|
            self._logical_channels_condition.acquire()
 | 
						|
            if not block.channel_id in self._logical_channels:
 | 
						|
                return
 | 
						|
            channel_data = self._logical_channels[block.channel_id]
 | 
						|
            channel_data.drop_code = _DROP_CODE_ACKNOWLEDGED
 | 
						|
 | 
						|
            # Close the logical channel
 | 
						|
            channel_data.request.connection.set_read_state(
 | 
						|
                _LogicalConnection.STATE_TERMINATED)
 | 
						|
            channel_data.request.ws_stream.stop_sending()
 | 
						|
        finally:
 | 
						|
            self._logical_channels_condition.release()
 | 
						|
 | 
						|
    def _process_control_blocks(self, parser):
 | 
						|
        for control_block in parser.read_control_blocks():
 | 
						|
            opcode = control_block.opcode
 | 
						|
            self._logger.debug('control block received, opcode: %d' % opcode)
 | 
						|
            if opcode == _MUX_OPCODE_ADD_CHANNEL_REQUEST:
 | 
						|
                self._process_add_channel_request(control_block)
 | 
						|
            elif opcode == _MUX_OPCODE_ADD_CHANNEL_RESPONSE:
 | 
						|
                raise PhysicalConnectionError(
 | 
						|
                    _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
 | 
						|
                    'Received AddChannelResponse')
 | 
						|
            elif opcode == _MUX_OPCODE_FLOW_CONTROL:
 | 
						|
                self._process_flow_control(control_block)
 | 
						|
            elif opcode == _MUX_OPCODE_DROP_CHANNEL:
 | 
						|
                self._process_drop_channel(control_block)
 | 
						|
            elif opcode == _MUX_OPCODE_NEW_CHANNEL_SLOT:
 | 
						|
                raise PhysicalConnectionError(
 | 
						|
                    _DROP_CODE_INVALID_MUX_CONTROL_BLOCK,
 | 
						|
                    'Received NewChannelSlot')
 | 
						|
            else:
 | 
						|
                raise MuxUnexpectedException(
 | 
						|
                    'Unexpected opcode %r' % opcode)
 | 
						|
 | 
						|
    def _process_logical_frame(self, channel_id, parser):
 | 
						|
        self._logger.debug('Received a frame. channel id=%d' % channel_id)
 | 
						|
        try:
 | 
						|
            self._logical_channels_condition.acquire()
 | 
						|
            if not channel_id in self._logical_channels:
 | 
						|
                # We must ignore the message for an inactive channel.
 | 
						|
                return
 | 
						|
            channel_data = self._logical_channels[channel_id]
 | 
						|
            fin, rsv1, rsv2, rsv3, opcode, payload = parser.read_inner_frame()
 | 
						|
            consuming_byte = len(payload)
 | 
						|
            if opcode != common.OPCODE_CONTINUATION:
 | 
						|
                consuming_byte += 1
 | 
						|
            if not channel_data.request.ws_stream.consume_receive_quota(
 | 
						|
                consuming_byte):
 | 
						|
                # The client violates quota. Close logical channel.
 | 
						|
                raise LogicalChannelError(
 | 
						|
                    channel_id, _DROP_CODE_SEND_QUOTA_VIOLATION)
 | 
						|
            header = create_header(opcode, len(payload), fin, rsv1, rsv2, rsv3,
 | 
						|
                                   mask=False)
 | 
						|
            frame_data = header + payload
 | 
						|
            channel_data.request.connection.append_frame_data(frame_data)
 | 
						|
        finally:
 | 
						|
            self._logical_channels_condition.release()
 | 
						|
 | 
						|
    def dispatch_message(self, message):
 | 
						|
        """Dispatches message. The reader thread calls this method.
 | 
						|
 | 
						|
        Args:
 | 
						|
            message: a message that contains encapsulated frame.
 | 
						|
        Raises:
 | 
						|
            PhysicalConnectionError: if the message contains physical
 | 
						|
                connection level errors.
 | 
						|
            LogicalChannelError: if the message contains logical channel
 | 
						|
                level errors.
 | 
						|
        """
 | 
						|
 | 
						|
        parser = _MuxFramePayloadParser(message)
 | 
						|
        try:
 | 
						|
            channel_id = parser.read_channel_id()
 | 
						|
        except ValueError, e:
 | 
						|
            raise PhysicalConnectionError(_DROP_CODE_CHANNEL_ID_TRUNCATED)
 | 
						|
        if channel_id == _CONTROL_CHANNEL_ID:
 | 
						|
            self._process_control_blocks(parser)
 | 
						|
        else:
 | 
						|
            self._process_logical_frame(channel_id, parser)
 | 
						|
 | 
						|
    def notify_worker_done(self, channel_id):
 | 
						|
        """Called when a worker has finished.
 | 
						|
 | 
						|
        Args:
 | 
						|
            channel_id: channel id corresponded with the worker.
 | 
						|
        """
 | 
						|
 | 
						|
        self._logger.debug('Worker for channel id %d terminated' % channel_id)
 | 
						|
        try:
 | 
						|
            self._logical_channels_condition.acquire()
 | 
						|
            if not channel_id in self._logical_channels:
 | 
						|
                raise MuxUnexpectedException(
 | 
						|
                    'Channel id %d not found' % channel_id)
 | 
						|
            channel_data = self._logical_channels.pop(channel_id)
 | 
						|
        finally:
 | 
						|
            self._worker_done_notify_received = True
 | 
						|
            self._logical_channels_condition.notify()
 | 
						|
            self._logical_channels_condition.release()
 | 
						|
 | 
						|
        if not channel_data.request.server_terminated:
 | 
						|
            self._send_drop_channel(
 | 
						|
                channel_id, code=channel_data.drop_code,
 | 
						|
                message=channel_data.drop_message)
 | 
						|
 | 
						|
    def notify_reader_done(self):
 | 
						|
        """This method is called by the reader thread when the reader has
 | 
						|
        finished.
 | 
						|
        """
 | 
						|
 | 
						|
        self._logger.debug(
 | 
						|
            'Termiating all logical connections waiting for incoming data '
 | 
						|
            '...')
 | 
						|
        self._logical_channels_condition.acquire()
 | 
						|
        for channel_data in self._logical_channels.values():
 | 
						|
            try:
 | 
						|
                channel_data.request.connection.set_read_state(
 | 
						|
                    _LogicalConnection.STATE_TERMINATED)
 | 
						|
            except Exception:
 | 
						|
                self._logger.debug(traceback.format_exc())
 | 
						|
        self._logical_channels_condition.release()
 | 
						|
 | 
						|
    def notify_writer_done(self):
 | 
						|
        """This method is called by the writer thread when the writer has
 | 
						|
        finished.
 | 
						|
        """
 | 
						|
 | 
						|
        self._logger.debug(
 | 
						|
            'Termiating all logical connections waiting for write '
 | 
						|
            'completion ...')
 | 
						|
        self._logical_channels_condition.acquire()
 | 
						|
        for channel_data in self._logical_channels.values():
 | 
						|
            try:
 | 
						|
                channel_data.request.connection.on_writer_done()
 | 
						|
            except Exception:
 | 
						|
                self._logger.debug(traceback.format_exc())
 | 
						|
        self._logical_channels_condition.release()
 | 
						|
 | 
						|
    def fail_physical_connection(self, code, message):
 | 
						|
        """Fail the physical connection.
 | 
						|
 | 
						|
        Args:
 | 
						|
            code: drop reason code.
 | 
						|
            message: drop message.
 | 
						|
        """
 | 
						|
 | 
						|
        self._logger.debug('Failing the physical connection...')
 | 
						|
        self._send_drop_channel(_CONTROL_CHANNEL_ID, code, message)
 | 
						|
        self._writer.stop(common.STATUS_INTERNAL_ENDPOINT_ERROR)
 | 
						|
 | 
						|
    def fail_logical_channel(self, channel_id, code, message):
 | 
						|
        """Fail a logical channel.
 | 
						|
 | 
						|
        Args:
 | 
						|
            channel_id: channel id.
 | 
						|
            code: drop reason code.
 | 
						|
            message: drop message.
 | 
						|
        """
 | 
						|
 | 
						|
        self._logger.debug('Failing logical channel %d...' % channel_id)
 | 
						|
        try:
 | 
						|
            self._logical_channels_condition.acquire()
 | 
						|
            if channel_id in self._logical_channels:
 | 
						|
                channel_data = self._logical_channels[channel_id]
 | 
						|
                # Close the logical channel. notify_worker_done() will be
 | 
						|
                # called later and it will send DropChannel.
 | 
						|
                channel_data.drop_code = code
 | 
						|
                channel_data.drop_message = message
 | 
						|
 | 
						|
                channel_data.request.connection.set_read_state(
 | 
						|
                    _LogicalConnection.STATE_TERMINATED)
 | 
						|
                channel_data.request.ws_stream.stop_sending()
 | 
						|
            else:
 | 
						|
                self._send_drop_channel(channel_id, code, message)
 | 
						|
        finally:
 | 
						|
            self._logical_channels_condition.release()
 | 
						|
 | 
						|
 | 
						|
def use_mux(request):
 | 
						|
    return hasattr(request, 'mux_processor') and (
 | 
						|
        request.mux_processor.is_active())
 | 
						|
 | 
						|
 | 
						|
def start(request, dispatcher):
 | 
						|
    mux_handler = _MuxHandler(request, dispatcher)
 | 
						|
    mux_handler.start()
 | 
						|
 | 
						|
    mux_handler.add_channel_slots(_INITIAL_NUMBER_OF_CHANNEL_SLOTS,
 | 
						|
                                  _INITIAL_QUOTA_FOR_CLIENT)
 | 
						|
 | 
						|
    mux_handler.wait_until_done()
 | 
						|
 | 
						|
 | 
						|
# vi:sts=4 sw=4 et
 |