+###############################################################################\r
+##\r
+## Copyright 2011,2012 Tavendo GmbH\r
+##\r
+## Licensed under the Apache License, Version 2.0 (the "License");\r
+## you may not use this file except in compliance with the License.\r
+## You may obtain a copy of the License at\r
+##\r
+## http://www.apache.org/licenses/LICENSE-2.0\r
+##\r
+## Unless required by applicable law or agreed to in writing, software\r
+## distributed under the License is distributed on an "AS IS" BASIS,\r
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+## See the License for the specific language governing permissions and\r
+## limitations under the License.\r
+##\r
+###############################################################################\r
+\r
+## The Python urlparse module currently does not contain the ws/wss\r
+## schemes, so we add those dynamically (which is a hack of course).\r
+##\r
+import urlparse\r
+wsschemes = ["ws", "wss"]\r
+urlparse.uses_relative.extend(wsschemes)\r
+urlparse.uses_netloc.extend(wsschemes)\r
+urlparse.uses_params.extend(wsschemes)\r
+urlparse.uses_query.extend(wsschemes)\r
+urlparse.uses_fragment.extend(wsschemes)\r
+\r
+from twisted.internet import reactor, protocol\r
+from twisted.python import log\r
+import urllib\r
+import binascii\r
+import hashlib\r
+import base64\r
+import struct\r
+import random\r
+import os\r
+from array import array\r
+from collections import deque\r
+from utf8validator import Utf8Validator\r
+from xormasker import XorMaskerNull, XorMaskerSimple, XorMaskerShifted1\r
+from httpstatus import *\r
+import autobahn # need autobahn.version\r
+\r
+\r
+def createWsUrl(hostname, port = None, isSecure = False, path = None, params = None):\r
+ """\r
+ Create a WbeSocket URL from components.\r
+\r
+ :param hostname: WebSocket server hostname.\r
+ :type hostname: str\r
+ :param port: WebSocket service port or None (to select default ports 80/443 depending on isSecure).\r
+ :type port: int\r
+ :param isSecure: Set True for secure WebSockets ("wss" scheme).\r
+ :type isSecure: bool\r
+ :param path: Path component of addressed resource (will be properly URL escaped).\r
+ :type path: str\r
+ :param params: A dictionary of key-values to construct the query component of the addressed resource (will be properly URL escaped).\r
+ :type params: dict\r
+\r
+ :returns str -- Constructed WebSocket URL.\r
+ """\r
+ if port is not None:\r
+ netloc = "%s:%d" % (hostname, port)\r
+ else:\r
+ if isSecure:\r
+ netloc = "%s:443" % hostname\r
+ else:\r
+ netloc = "%s:80" % hostname\r
+ if isSecure:\r
+ scheme = "wss"\r
+ else:\r
+ scheme = "ws"\r
+ if path is not None:\r
+ ppath = urllib.quote(path)\r
+ else:\r
+ ppath = "/"\r
+ if params is not None:\r
+ query = urllib.urlencode(params)\r
+ else:\r
+ query = None\r
+ return urlparse.urlunparse((scheme, netloc, ppath, None, query, None))\r
+\r
+\r
+def parseWsUrl(url):\r
+ """\r
+ Parses as WebSocket URL into it's components and returns a tuple (isSecure, host, port, resource, path, params).\r
+\r
+ isSecure is a flag which is True for wss URLs.\r
+ host is the hostname or IP from the URL.\r
+ port is the port from the URL or standard port derived from scheme (ws = 80, wss = 443).\r
+ resource is the /resource name/ from the URL, the /path/ together with the (optional) /query/ component.\r
+ path is the /path/ component properly unescaped.\r
+ params is the /query) component properly unescaped and returned as dictionary.\r
+\r
+ :param url: A valid WebSocket URL, i.e. ws://localhost:9000/myresource?param1=23¶m2=666\r
+ :type url: str\r
+\r
+ :returns: tuple -- A tuple (isSecure, host, port, resource, path, params)\r
+ """\r
+ parsed = urlparse.urlparse(url)\r
+ if parsed.scheme not in ["ws", "wss"]:\r
+ raise Exception("invalid WebSocket scheme '%s'" % parsed.scheme)\r
+ if parsed.port is None or parsed.port == "":\r
+ if parsed.scheme == "ws":\r
+ port = 80\r
+ else:\r
+ port = 443\r
+ else:\r
+ port = int(parsed.port)\r
+ if parsed.fragment is not None and parsed.fragment != "":\r
+ raise Exception("invalid WebSocket URL: non-empty fragment '%s" % parsed.fragment)\r
+ if parsed.path is not None and parsed.path != "":\r
+ ppath = parsed.path\r
+ path = urllib.unquote(ppath)\r
+ else:\r
+ ppath = "/"\r
+ path = ppath\r
+ if parsed.query is not None and parsed.query != "":\r
+ resource = ppath + "?" + parsed.query\r
+ params = urlparse.parse_qs(parsed.query)\r
+ else:\r
+ resource = ppath\r
+ params = {}\r
+ return (parsed.scheme == "wss", parsed.hostname, port, resource, path, params)\r
+\r
+\r
+def connectWS(factory, contextFactory = None, timeout = 30, bindAddress = None):\r
+ """\r
+ Establish WebSockets connection to a server. The connection parameters like target\r
+ host, port, resource and others are provided via the factory.\r
+\r
+ :param factory: The WebSockets protocol factory to be used for creating client protocol instances.\r
+ :type factory: An :class:`autobahn.websocket.WebSocketClientFactory` instance.\r
+ :param contextFactory: SSL context factory, required for secure WebSockets connections ("wss").\r
+ :type contextFactory: A twisted.internet.ssl.ClientContextFactory instance.\r
+ :param timeout: Number of seconds to wait before assuming the connection has failed.\r
+ :type timeout: int\r
+ :param bindAddress: A (host, port) tuple of local address to bind to, or None.\r
+ :type bindAddress: tuple\r
+\r
+ :returns: obj -- An object which provides twisted.interface.IConnector.\r
+ """\r
+ if factory.isSecure:\r
+ if contextFactory is None:\r
+ # create default client SSL context factory when none given\r
+ from twisted.internet import ssl\r
+ contextFactory = ssl.ClientContextFactory()\r
+ conn = reactor.connectSSL(factory.host, factory.port, factory, contextFactory, timeout, bindAddress)\r
+ else:\r
+ conn = reactor.connectTCP(factory.host, factory.port, factory, timeout, bindAddress)\r
+ return conn\r
+\r
+\r
+def listenWS(factory, contextFactory = None, backlog = 50, interface = ''):\r
+ """\r
+ Listen for incoming WebSocket connections from clients. The connection parameters like\r
+ listening port and others are provided via the factory.\r
+\r
+ :param factory: The WebSockets protocol factory to be used for creating server protocol instances.\r
+ :type factory: An :class:`autobahn.websocket.WebSocketServerFactory` instance.\r
+ :param contextFactory: SSL context factory, required for secure WebSockets connections ("wss").\r
+ :type contextFactory: A twisted.internet.ssl.ContextFactory.\r
+ :param backlog: Size of the listen queue.\r
+ :type backlog: int\r
+ :param interface: The interface (derived from hostname given) to bind to, defaults to '' (all).\r
+ :type interface: str\r
+\r
+ :returns: obj -- An object that provides twisted.interface.IListeningPort.\r
+ """\r
+ if factory.isSecure:\r
+ if contextFactory is None:\r
+ raise Exception("Secure WebSocket listen requested, but no SSL context factory given")\r
+ listener = reactor.listenSSL(factory.port, factory, contextFactory, backlog, interface)\r
+ else:\r
+ listener = reactor.listenTCP(factory.port, factory, backlog, interface)\r
+ return listener\r
+\r
+\r
+class FrameHeader:\r
+ """\r
+ Thin-wrapper for storing WebSockets frame metadata.\r
+\r
+ FOR INTERNAL USE ONLY!\r
+ """\r
+\r
+ def __init__(self, opcode, fin, rsv, length, mask):\r
+ """\r
+ Constructor.\r
+\r
+ :param opcode: Frame opcode (0-15).\r
+ :type opcode: int\r
+ :param fin: Frame FIN flag.\r
+ :type fin: bool\r
+ :param rsv: Frame reserved flags (0-7).\r
+ :type rsv: int\r
+ :param length: Frame payload length.\r
+ :type length: int\r
+ :param mask: Frame mask (binary string) or None.\r
+ :type mask: str\r
+ """\r
+ self.opcode = opcode\r
+ self.fin = fin\r
+ self.rsv = rsv\r
+ self.length = length\r
+ self.mask = mask\r
+\r
+\r
+class HttpException():\r
+ """\r
+ Throw an instance of this class to deny a WebSockets connection\r
+ during handshake in :meth:`autobahn.websocket.WebSocketServerProtocol.onConnect`.\r
+ You can find definitions of HTTP status codes in module :mod:`autobahn.httpstatus`.\r
+ """\r
+\r
+ def __init__(self, code, reason):\r
+ """\r
+ Constructor.\r
+\r
+ :param code: HTTP error code.\r
+ :type code: int\r
+ :param reason: HTTP error reason.\r
+ :type reason: str\r
+ """\r
+ self.code = code\r
+ self.reason = reason\r
+\r
+\r
+class ConnectionRequest():\r
+ """\r
+ Thin-wrapper for WebSockets connection request information\r
+ provided in :meth:`autobahn.websocket.WebSocketServerProtocol.onConnect` when a WebSockets\r
+ client establishes a connection to a WebSockets server.\r
+ """\r
+ def __init__(self, peer, peerstr, headers, host, path, params, version, origin, protocols, extensions):\r
+ """\r
+ Constructor.\r
+\r
+ :param peer: IP address/port of the connecting client.\r
+ :type peer: object\r
+ :param peerstr: IP address/port of the connecting client as string.\r
+ :type peerstr: str\r
+ :param headers: HTTP headers from opening handshake request.\r
+ :type headers: dict\r
+ :param host: Host from opening handshake HTTP header.\r
+ :type host: str\r
+ :param path: Path from requested HTTP resource URI. For example, a resource URI of "/myservice?foo=23&foo=66&bar=2" will be parsed to "/myservice".\r
+ :type path: str\r
+ :param params: Query parameters (if any) from requested HTTP resource URI. For example, a resource URI of "/myservice?foo=23&foo=66&bar=2" will be parsed to {'foo': ['23', '66'], 'bar': ['2']}.\r
+ :type params: dict of arrays of strings\r
+ :param version: The WebSockets protocol version the client announced (and will be spoken, when connection is accepted).\r
+ :type version: int\r
+ :param origin: The WebSockets origin header or None. Note that this only a reliable source of information for browser clients!\r
+ :type origin: str\r
+ :param protocols: The WebSockets (sub)protocols the client announced. You must select and return one of those (or None) in :meth:`autobahn.websocket.WebSocketServerProtocol.onConnect`.\r
+ :type protocols: array of strings\r
+ :param extensions: The WebSockets extensions the client requested and the server accepted (and thus will be spoken, when WS connection is established).\r
+ :type extensions: array of strings\r
+ """\r
+ self.peer = peer\r
+ self.peerstr = peerstr\r
+ self.headers = headers\r
+ self.host = host\r
+ self.path = path\r
+ self.params = params\r
+ self.version = version\r
+ self.origin = origin\r
+ self.protocols = protocols\r
+ self.extensions = extensions\r
+\r
+\r
+class ConnectionResponse():\r
+ """\r
+ Thin-wrapper for WebSockets connection response information\r
+ provided in :meth:`autobahn.websocket.WebSocketClientProtocol.onConnect` when a WebSockets\r
+ client has established a connection to a WebSockets server.\r
+ """\r
+ def __init__(self, peer, peerstr, headers, version, protocol, extensions):\r
+ """\r
+ Constructor.\r
+\r
+ :param peer: IP address/port of the connected server.\r
+ :type peer: object\r
+ :param peerstr: IP address/port of the connected server as string.\r
+ :type peerstr: str\r
+ :param headers: HTTP headers from opening handshake response.\r
+ :type headers: dict\r
+ :param version: The WebSockets protocol version that is spoken.\r
+ :type version: int\r
+ :param protocol: The WebSockets (sub)protocol in use.\r
+ :type protocol: str\r
+ :param extensions: The WebSockets extensions in use.\r
+ :type extensions: array of strings\r
+ """\r
+ self.peer = peer\r
+ self.peerstr = peerstr\r
+ self.headers = headers\r
+ self.version = version\r
+ self.protocol = protocol\r
+ self.extensions = extensions\r
+\r
+\r
+def parseHttpHeader(data):\r
+ """\r
+ Parses the beginning of a HTTP request header (the data up to the \n\n line) into a pair\r
+ of status line and HTTP headers dictionary.\r
+ Header keys are normalized to all-lower-case.\r
+\r
+ FOR INTERNAL USE ONLY!\r
+\r
+ :param data: The HTTP header data up to the \n\n line.\r
+ :type data: str\r
+ """\r
+ raw = data.splitlines()\r
+ http_status_line = raw[0].strip()\r
+ http_headers = {}\r
+ http_headers_cnt = {}\r
+ for h in raw[1:]:\r
+ i = h.find(":")\r
+ if i > 0:\r
+ ## HTTP header keys are case-insensitive\r
+ key = h[:i].strip().lower()\r
+\r
+ ## not sure if UTF-8 is allowed for HTTP header values..\r
+ value = h[i+1:].strip().decode("utf-8")\r
+\r
+ ## handle HTTP headers split across multiple lines\r
+ if http_headers.has_key(key):\r
+ http_headers[key] += ", %s" % value\r
+ http_headers_cnt[key] += 1\r
+ else:\r
+ http_headers[key] = value\r
+ http_headers_cnt[key] = 1\r
+ else:\r
+ # skip bad HTTP header\r
+ pass\r
+ return (http_status_line, http_headers, http_headers_cnt)\r
+\r
+\r
+class WebSocketProtocol(protocol.Protocol):\r
+ """\r
+ A Twisted Protocol class for WebSockets. This class is used by both WebSocket\r
+ client and server protocol version. It is unusable standalone, for example\r
+ the WebSockets initial handshake is implemented in derived class differently\r
+ for clients and servers.\r
+ """\r
+\r
+ SUPPORTED_SPEC_VERSIONS = [0, 10, 11, 12, 13, 14, 15, 16, 17, 18]\r
+ """\r
+ WebSockets protocol spec (draft) versions supported by this implementation.\r
+ Use of version 18 indicates RFC6455. Use of versions < 18 indicate actual\r
+ draft spec versions (Hybi-Drafts). Use of version 0 indicates Hixie-76.\r
+ """\r
+\r
+ SUPPORTED_PROTOCOL_VERSIONS = [0, 8, 13]\r
+ """\r
+ WebSocket protocol versions supported by this implementation. For Hixie-76,\r
+ there is no protocol version announced in HTTP header, and we just use the\r
+ draft version (0) in this case.\r
+ """\r
+\r
+ SPEC_TO_PROTOCOL_VERSION = {0: 0, 10: 8, 11: 8, 12: 8, 13: 13, 14: 13, 15: 13, 16: 13, 17: 13, 18: 13}\r
+ """\r
+ Mapping from protocol spec (draft) version to protocol version. For Hixie-76,\r
+ there is no protocol version announced in HTTP header, and we just use the\r
+ pseudo protocol version 0 in this case.\r
+ """\r
+\r
+ PROTOCOL_TO_SPEC_VERSION = {0: 0, 8: 12, 13: 18}\r
+ """\r
+ Mapping from protocol version to the latest protocol spec (draft) version\r
+ using that protocol version. For Hixie-76, there is no protocol version\r
+ announced in HTTP header, and we just use the draft version (0) in this case.\r
+ """\r
+\r
+ DEFAULT_SPEC_VERSION = 10\r
+ """\r
+ Default WebSockets protocol spec version this implementation speaks.\r
+ We use Hybi-10, since this is what is currently targeted by widely distributed\r
+ browsers (namely Firefox 8 and the like).\r
+ """\r
+\r
+ DEFAULT_ALLOW_HIXIE76 = False\r
+ """\r
+ By default, this implementation will not allow to speak the obsoleted\r
+ Hixie-76 protocol version. That protocol version has security issues, but\r
+ is still spoken by some clients. Enable at your own risk! Enabling can be\r
+ done by using setProtocolOptions() on the factories for clients and servers.\r
+ """\r
+\r
+ WS_MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"\r
+ """\r
+ Protocol defined magic used during WebSocket handshake (used in Hybi-drafts\r
+ and final RFC6455.\r
+ """\r
+\r
+ QUEUED_WRITE_DELAY = 0.00001\r
+ """For synched/chopped writes, this is the reactor reentry delay in seconds."""\r
+\r
+ PAYLOAD_LEN_XOR_BREAKEVEN = 128\r
+ """Tuning parameter which chooses XORer used for masking/unmasking based on\r
+ payload length."""\r
+\r
+ MESSAGE_TYPE_TEXT = 1\r
+ """WebSockets text message type (UTF-8 payload)."""\r
+\r
+ MESSAGE_TYPE_BINARY = 2\r
+ """WebSockets binary message type (arbitrary binary payload)."""\r
+\r
+ ## WebSockets protocol state:\r
+ ## STATE_CONNECTING => STATE_OPEN => STATE_CLOSING => STATE_CLOSED\r
+ ##\r
+ STATE_CLOSED = 0\r
+ STATE_CONNECTING = 1\r
+ STATE_CLOSING = 2\r
+ STATE_OPEN = 3\r
+\r
+ ## Streaming Send State\r
+ SEND_STATE_GROUND = 0\r
+ SEND_STATE_MESSAGE_BEGIN = 1\r
+ SEND_STATE_INSIDE_MESSAGE = 2\r
+ SEND_STATE_INSIDE_MESSAGE_FRAME = 3\r
+\r
+ ## WebSockets protocol close codes\r
+ ##\r
+ CLOSE_STATUS_CODE_NORMAL = 1000\r
+ """Normal close of connection."""\r
+\r
+ CLOSE_STATUS_CODE_GOING_AWAY = 1001\r
+ """Going away."""\r
+\r
+ CLOSE_STATUS_CODE_PROTOCOL_ERROR = 1002\r
+ """Protocol error."""\r
+\r
+ CLOSE_STATUS_CODE_UNSUPPORTED_DATA = 1003\r
+ """Unsupported data."""\r
+\r
+ CLOSE_STATUS_CODE_RESERVED1 = 1004\r
+ """RESERVED"""\r
+\r
+ CLOSE_STATUS_CODE_NULL = 1005 # MUST NOT be set in close frame!\r
+ """No status received. (MUST NOT be used as status code when sending a close)."""\r
+\r
+ CLOSE_STATUS_CODE_ABNORMAL_CLOSE = 1006 # MUST NOT be set in close frame!\r
+ """Abnormal close of connection. (MUST NOT be used as status code when sending a close)."""\r
+\r
+ CLOSE_STATUS_CODE_INVALID_PAYLOAD = 1007\r
+ """Invalid frame payload data."""\r
+\r
+ CLOSE_STATUS_CODE_POLICY_VIOLATION = 1008\r
+ """Policy violation."""\r
+\r
+ CLOSE_STATUS_CODE_MESSAGE_TOO_BIG = 1009\r
+ """Message too big."""\r
+\r
+ CLOSE_STATUS_CODE_MANDATORY_EXTENSION = 1010\r
+ """Mandatory extension."""\r
+\r
+ CLOSE_STATUS_CODE_INTERNAL_ERROR = 1011\r
+ """The peer encountered an unexpected condition or internal error."""\r
+\r
+ CLOSE_STATUS_CODE_TLS_HANDSHAKE_FAILED = 1015 # MUST NOT be set in close frame!\r
+ """TLS handshake failed, i.e. server certificate could not be verified. (MUST NOT be used as status code when sending a close)."""\r
+\r
+ CLOSE_STATUS_CODES_ALLOWED = [CLOSE_STATUS_CODE_NORMAL,\r
+ CLOSE_STATUS_CODE_GOING_AWAY,\r
+ CLOSE_STATUS_CODE_PROTOCOL_ERROR,\r
+ CLOSE_STATUS_CODE_UNSUPPORTED_DATA,\r
+ CLOSE_STATUS_CODE_INVALID_PAYLOAD,\r
+ CLOSE_STATUS_CODE_POLICY_VIOLATION,\r
+ CLOSE_STATUS_CODE_MESSAGE_TOO_BIG,\r
+ CLOSE_STATUS_CODE_MANDATORY_EXTENSION,\r
+ CLOSE_STATUS_CODE_INTERNAL_ERROR]\r
+ """Status codes allowed to send in close."""\r
+\r
+\r
+ def onOpen(self):\r
+ """\r
+ Callback when initial WebSockets handshake was completed. Now you may send messages.\r
+ Default implementation does nothing. Override in derived class.\r
+\r
+ Modes: Hybi, Hixie\r
+ """\r
+ if self.debugCodePaths:\r
+ log.msg("WebSocketProtocol.onOpen")\r
+\r
+\r
+ def onMessageBegin(self, opcode):\r
+ """\r
+ Callback when receiving a new message has begun. Default implementation will\r
+ prepare to buffer message frames. Override in derived class.\r
+\r
+ Modes: Hybi, Hixie\r
+\r
+ :param opcode: Opcode of message.\r
+ :type opcode: int\r
+ """\r
+ self.message_opcode = opcode\r
+ self.message_data = []\r
+ self.message_data_total_length = 0\r
+\r
+\r
+ def onMessageFrameBegin(self, length, reserved):\r
+ """\r
+ Callback when receiving a new message frame has begun. Default implementation will\r
+ prepare to buffer message frame data. Override in derived class.\r
+\r
+ Modes: Hybi\r
+\r
+ :param length: Payload length of message frame which is to be received.\r
+ :type length: int\r
+ :param reserved: Reserved bits set in frame (an integer from 0 to 7).\r
+ :type reserved: int\r
+ """\r
+ self.frame_length = length\r
+ self.frame_reserved = reserved\r
+ self.frame_data = []\r
+ self.message_data_total_length += length\r
+ if not self.failedByMe:\r
+ if self.maxMessagePayloadSize > 0 and self.message_data_total_length > self.maxMessagePayloadSize:\r
+ self.wasMaxMessagePayloadSizeExceeded = True\r
+ self.failConnection(WebSocketProtocol.CLOSE_STATUS_CODE_MESSAGE_TOO_BIG, "message exceeds payload limit of %d octets" % self.maxMessagePayloadSize)\r
+ elif self.maxFramePayloadSize > 0 and length > self.maxFramePayloadSize:\r
+ self.wasMaxFramePayloadSizeExceeded = True\r
+ self.failConnection(WebSocketProtocol.CLOSE_STATUS_CODE_POLICY_VIOLATION, "frame exceeds payload limit of %d octets" % self.maxFramePayloadSize)\r
+\r
+\r
+ def onMessageFrameData(self, payload):\r
+ """\r
+ Callback when receiving data witin message frame. Default implementation will\r
+ buffer data for frame. Override in derived class.\r
+\r
+ Modes: Hybi, Hixie\r
+\r
+ Notes:\r
+ - For Hixie mode, this method is slightly misnamed for historic reasons.\r
+\r
+ :param payload: Partial payload for message frame.\r
+ :type payload: str\r
+ """\r
+ if not self.failedByMe:\r
+ if self.websocket_version == 0:\r
+ self.message_data_total_length += len(payload)\r
+ if self.maxMessagePayloadSize > 0 and self.message_data_total_length > self.maxMessagePayloadSize:\r
+ self.wasMaxMessagePayloadSizeExceeded = True\r
+ self.failConnection(WebSocketProtocol.CLOSE_STATUS_CODE_MESSAGE_TOO_BIG, "message exceeds payload limit of %d octets" % self.maxMessagePayloadSize)\r
+ self.message_data.append(payload)\r
+ else:\r
+ self.frame_data.append(payload)\r
+\r
+\r
+ def onMessageFrameEnd(self):\r
+ """\r
+ Callback when a message frame has been completely received. Default implementation\r
+ will flatten the buffered frame data and callback onMessageFrame. Override\r
+ in derived class.\r
+\r
+ Modes: Hybi\r
+ """\r
+ if not self.failedByMe:\r
+ self.onMessageFrame(self.frame_data, self.frame_reserved)\r
+\r
+ self.frame_data = None\r
+\r
+\r
+ def onMessageFrame(self, payload, reserved):\r
+ """\r
+ Callback fired when complete message frame has been received. Default implementation\r
+ will buffer frame for message. Override in derived class.\r
+\r
+ Modes: Hybi\r
+\r
+ :param payload: Message frame payload.\r
+ :type payload: list of str\r
+ :param reserved: Reserved bits set in frame (an integer from 0 to 7).\r
+ :type reserved: int\r
+ """\r
+ if not self.failedByMe:\r
+ self.message_data.extend(payload)\r
+\r
+\r
+ def onMessageEnd(self):\r
+ """\r
+ Callback when a message has been completely received. Default implementation\r
+ will flatten the buffered frames and callback onMessage. Override\r
+ in derived class.\r
+\r
+ Modes: Hybi, Hixie\r
+ """\r
+ if not self.failedByMe:\r
+ payload = ''.join(self.message_data)\r
+ self.onMessage(payload, self.message_opcode == WebSocketProtocol.MESSAGE_TYPE_BINARY)\r
+\r
+ self.message_data = None\r
+\r
+\r
+ def onMessage(self, payload, binary):\r
+ """\r
+ Callback when a complete message was received. Default implementation does nothing.\r
+ Override in derived class.\r
+\r
+ Modes: Hybi, Hixie\r
+\r
+ :param payload: Message payload (UTF-8 encoded text string or binary string). Can also be an empty string, when message contained no payload.\r
+ :type payload: str\r
+ :param binary: If True, payload is binary, otherwise text.\r
+ :type binary: bool\r
+ """\r
+ if self.debug:\r
+ log.msg("WebSocketProtocol.onMessage")\r
+\r
+\r
+ def onPing(self, payload):\r
+ """\r
+ Callback when Ping was received. Default implementation responds\r
+ with a Pong. Override in derived class.\r
+\r
+ Modes: Hybi\r
+\r
+ :param payload: Payload of Ping, when there was any. Can be arbitrary, up to 125 octets.\r
+ :type payload: str\r
+ """\r
+ if self.debug:\r
+ log.msg("WebSocketProtocol.onPing")\r
+ if self.state == WebSocketProtocol.STATE_OPEN:\r
+ self.sendPong(payload)\r
+\r
+\r
+ def onPong(self, payload):\r
+ """\r
+ Callback when Pong was received. Default implementation does nothing.\r
+ Override in derived class.\r
+\r
+ Modes: Hybi\r
+\r
+ :param payload: Payload of Pong, when there was any. Can be arbitrary, up to 125 octets.\r
+ """\r
+ if self.debug:\r
+ log.msg("WebSocketProtocol.onPong")\r
+\r
+\r
+ def onClose(self, wasClean, code, reason):\r
+ """\r
+ Callback when the connection has been closed. Override in derived class.\r
+\r
+ Modes: Hybi, Hixie\r
+\r
+ :param wasClean: True, iff the connection was closed cleanly.\r
+ :type wasClean: bool\r
+ :param code: None or close status code (sent by peer), if there was one (:class:`WebSocketProtocol`.CLOSE_STATUS_CODE_*).\r
+ :type code: int\r
+ :param reason: None or close reason (sent by peer) (when present, a status code MUST have been also be present).\r
+ :type reason: str\r
+ """\r
+ if self.debugCodePaths:\r
+ s = "WebSocketProtocol.onClose:\n"\r
+ s += "wasClean=%s\n" % wasClean\r
+ s += "code=%s\n" % code\r
+ s += "reason=%s\n" % reason\r
+ s += "self.closedByMe=%s\n" % self.closedByMe\r
+ s += "self.failedByMe=%s\n" % self.failedByMe\r
+ s += "self.droppedByMe=%s\n" % self.droppedByMe\r
+ s += "self.wasClean=%s\n" % self.wasClean\r
+ s += "self.wasNotCleanReason=%s\n" % self.wasNotCleanReason\r
+ s += "self.localCloseCode=%s\n" % self.localCloseCode\r
+ s += "self.localCloseReason=%s\n" % self.localCloseReason\r
+ s += "self.remoteCloseCode=%s\n" % self.remoteCloseCode\r
+ s += "self.remoteCloseReason=%s\n" % self.remoteCloseReason\r
+ log.msg(s)\r
+\r
+\r
+ def onCloseFrame(self, code, reasonRaw):\r
+ """\r
+ Callback when a Close frame was received. The default implementation answers by\r
+ sending a Close when no Close was sent before. Otherwise it drops\r
+ the TCP connection either immediately (when we are a server) or after a timeout\r
+ (when we are a client and expect the server to drop the TCP).\r
+\r
+ Modes: Hybi, Hixie\r
+\r
+ Notes:\r
+ - For Hixie mode, this method is slightly misnamed for historic reasons.\r
+ - For Hixie mode, code and reasonRaw are silently ignored.\r
+\r
+ :param code: None or close status code, if there was one (:class:`WebSocketProtocol`.CLOSE_STATUS_CODE_*).\r
+ :type code: int\r
+ :param reason: None or close reason (when present, a status code MUST have been also be present).\r
+ :type reason: str\r
+ """\r
+ if self.debugCodePaths:\r
+ log.msg("WebSocketProtocol.onCloseFrame")\r
+\r
+ self.remoteCloseCode = code\r
+ self.remoteCloseReason = reasonRaw\r
+\r
+ ## reserved close codes: 0-999, 1004, 1005, 1006, 1011-2999, >= 5000\r
+ ##\r
+ if code is not None and (code < 1000 or (code >= 1000 and code <= 2999 and code not in WebSocketProtocol.CLOSE_STATUS_CODES_ALLOWED) or code >= 5000):\r
+ if self.protocolViolation("invalid close code %d" % code):\r
+ return True\r
+\r
+ ## closing reason\r
+ ##\r
+ if reasonRaw is not None:\r
+ ## we use our own UTF-8 validator to get consistent and fully conformant\r
+ ## UTF-8 validation behavior\r
+ u = Utf8Validator()\r
+ val = u.validate(reasonRaw)\r
+ if not val[0]:\r
+ if self.invalidPayload("invalid close reason (non-UTF-8 payload)"):\r
+ return True\r
+\r
+ if self.state == WebSocketProtocol.STATE_CLOSING:\r
+ ## We already initiated the closing handshake, so this\r
+ ## is the peer's reply to our close frame.\r
+\r
+ ## cancel any closing HS timer if present\r
+ ##\r
+ if self.closeHandshakeTimeoutCall is not None:\r
+ if self.debugCodePaths:\r
+ log.msg("closeHandshakeTimeoutCall.cancel")\r
+ self.closeHandshakeTimeoutCall.cancel()\r
+ self.closeHandshakeTimeoutCall = None\r
+\r
+ self.wasClean = True\r
+\r
+ if self.isServer:\r
+ ## When we are a server, we immediately drop the TCP.\r
+ self.dropConnection(abort = True)\r
+ else:\r
+ ## When we are a client, the server should drop the TCP\r
+ ## If that doesn't happen, we do. And that will set wasClean = False.\r
+ if self.serverConnectionDropTimeout > 0:\r
+ self.serverConnectionDropTimeoutCall = reactor.callLater(self.serverConnectionDropTimeout, self.onServerConnectionDropTimeout)\r
+\r
+ elif self.state == WebSocketProtocol.STATE_OPEN:\r
+ ## The peer initiates a closing handshake, so we reply\r
+ ## by sending close frame.\r
+\r
+ self.wasClean = True\r
+\r
+ if self.websocket_version == 0:\r
+ self.sendCloseFrame(isReply = True)\r
+ else:\r
+ ## Either reply with same code/reason, or code == NORMAL/reason=None\r
+ if self.echoCloseCodeReason:\r
+ self.sendCloseFrame(code = code, reasonUtf8 = reason.encode("UTF-8"), isReply = True)\r
+ else:\r
+ self.sendCloseFrame(code = WebSocketProtocol.CLOSE_STATUS_CODE_NORMAL, isReply = True)\r
+\r
+ if self.isServer:\r
+ ## When we are a server, we immediately drop the TCP.\r
+ self.dropConnection(abort = False)\r
+ else:\r
+ ## When we are a client, we expect the server to drop the TCP,\r
+ ## and when the server fails to do so, a timeout in sendCloseFrame()\r
+ ## will set wasClean = False back again.\r
+ pass\r
+\r
+ else:\r
+ ## STATE_CONNECTING, STATE_CLOSED\r
+ raise Exception("logic error")\r
+\r
+\r
+ def onServerConnectionDropTimeout(self):\r
+ """\r
+ We (a client) expected the peer (a server) to drop the connection,\r
+ but it didn't (in time self.serverConnectionDropTimeout).\r
+ So we drop the connection, but set self.wasClean = False.\r
+\r
+ Modes: Hybi, Hixie\r
+ """\r
+ self.serverConnectionDropTimeoutCall = None\r
+ if self.state != WebSocketProtocol.STATE_CLOSED:\r
+ if self.debugCodePaths:\r
+ log.msg("onServerConnectionDropTimeout")\r
+ self.wasClean = False\r
+ self.wasNotCleanReason = "server did not drop TCP connection (in time)"\r
+ self.wasServerConnectionDropTimeout = True\r
+ self.dropConnection(abort = True)\r
+ else:\r
+ if self.debugCodePaths:\r
+ log.msg("skipping onServerConnectionDropTimeout since connection is already closed")\r
+\r
+\r
+ def onOpenHandshakeTimeout(self):\r
+ """\r
+ We expected the peer to complete the opening handshake with to us.\r
+ It didn't do so (in time self.openHandshakeTimeout).\r
+ So we drop the connection, but set self.wasClean = False.\r
+\r
+ Modes: Hybi, Hixie\r
+ """\r
+ self.openHandshakeTimeoutCall = None\r
+ if self.state == WebSocketProtocol.STATE_CONNECTING:\r
+ if self.debugCodePaths:\r
+ log.msg("onOpenHandshakeTimeout fired")\r
+ self.wasClean = False\r
+ self.wasNotCleanReason = "peer did not finish (in time) the opening handshake"\r
+ self.wasOpenHandshakeTimeout = True\r
+ self.dropConnection(abort = True)\r
+ elif self.state == WebSocketProtocol.STATE_OPEN:\r
+ if self.debugCodePaths:\r
+ log.msg("skipping onOpenHandshakeTimeout since WebSocket connection is open (opening handshake already finished)")\r
+ elif self.state == WebSocketProtocol.STATE_CLOSING:\r
+ if self.debugCodePaths:\r
+ log.msg("skipping onOpenHandshakeTimeout since WebSocket connection is closing")\r
+ elif self.state == WebSocketProtocol.STATE_CLOSED:\r
+ if self.debugCodePaths:\r
+ log.msg("skipping onOpenHandshakeTimeout since WebSocket connection already closed")\r
+ else:\r
+ # should not arrive here\r
+ raise Exception("logic error")\r
+\r
+\r
+ def onCloseHandshakeTimeout(self):\r
+ """\r
+ We expected the peer to respond to us initiating a close handshake. It didn't\r
+ respond (in time self.closeHandshakeTimeout) with a close response frame though.\r
+ So we drop the connection, but set self.wasClean = False.\r
+\r
+ Modes: Hybi, Hixie\r
+ """\r
+ self.closeHandshakeTimeoutCall = None\r
+ if self.state != WebSocketProtocol.STATE_CLOSED:\r
+ if self.debugCodePaths:\r
+ log.msg("onCloseHandshakeTimeout fired")\r
+ self.wasClean = False\r
+ self.wasNotCleanReason = "peer did not respond (in time) in closing handshake"\r
+ self.wasCloseHandshakeTimeout = True\r
+ self.dropConnection(abort = True)\r
+ else:\r
+ if self.debugCodePaths:\r
+ log.msg("skipping onCloseHandshakeTimeout since connection is already closed")\r
+\r
+\r
+ def dropConnection(self, abort = False):\r
+ """\r
+ Drop the underlying TCP connection. For abort parameter, see:\r
+\r
+ * http://twistedmatrix.com/documents/current/core/howto/servers.html#auto2\r
+ * https://github.com/tavendo/AutobahnPython/issues/96\r
+\r
+ Modes: Hybi, Hixie\r
+ """\r
+ if self.state != WebSocketProtocol.STATE_CLOSED:\r
+ if self.debugCodePaths:\r
+ log.msg("dropping connection")\r
+ self.droppedByMe = True\r
+ self.state = WebSocketProtocol.STATE_CLOSED\r
+\r
+ if abort:\r
+ self.transport.abortConnection()\r
+ else:\r
+ self.transport.loseConnection()\r
+ else:\r
+ if self.debugCodePaths:\r
+ log.msg("skipping dropConnection since connection is already closed")\r
+\r
+\r
+ def failConnection(self, code = CLOSE_STATUS_CODE_GOING_AWAY, reason = "Going Away"):\r
+ """\r
+ Fails the WebSockets connection.\r
+\r
+ Modes: Hybi, Hixie\r
+\r
+ Notes:\r
+ - For Hixie mode, the code and reason are silently ignored.\r
+ """\r
+ if self.state != WebSocketProtocol.STATE_CLOSED:\r
+ if self.debugCodePaths:\r
+ log.msg("Failing connection : %s - %s" % (code, reason))\r
+ self.failedByMe = True\r
+ if self.failByDrop:\r
+ ## brutally drop the TCP connection\r
+ self.wasClean = False\r
+ self.wasNotCleanReason = "I failed the WebSocket connection by dropping the TCP connection"\r
+ self.dropConnection(abort = True)\r
+ else:\r
+ ## perform WebSockets closing handshake\r
+ if self.state != WebSocketProtocol.STATE_CLOSING:\r
+ self.sendCloseFrame(code = code, reasonUtf8 = reason.encode("UTF-8"), isReply = False)\r
+ else:\r
+ if self.debugCodePaths:\r
+ log.msg("skipping failConnection since connection is already closing")\r
+ else:\r
+ if self.debugCodePaths:\r
+ log.msg("skipping failConnection since connection is already closed")\r
+\r
+\r
+ def protocolViolation(self, reason):\r
+ """\r
+ Fired when a WebSockets protocol violation/error occurs.\r
+\r
+ Modes: Hybi, Hixie\r
+\r
+ Notes:\r
+ - For Hixie mode, reason is silently ignored.\r
+\r
+ :param reason: Protocol violation that was encountered (human readable).\r
+ :type reason: str\r
+\r
+ :returns: bool -- True, when any further processing should be discontinued.\r
+ """\r
+ if self.debugCodePaths:\r
+ log.msg("Protocol violation : %s" % reason)\r
+ self.failConnection(WebSocketProtocol.CLOSE_STATUS_CODE_PROTOCOL_ERROR, reason)\r
+ if self.failByDrop:\r
+ return True\r
+ else:\r
+ ## if we don't immediately drop the TCP, we need to skip the invalid frame\r
+ ## to continue to later receive the closing handshake reply\r
+ return False\r
+\r
+\r
+ def invalidPayload(self, reason):\r
+ """\r
+ Fired when invalid payload is encountered. Currently, this only happens\r
+ for text message when payload is invalid UTF-8 or close frames with\r
+ close reason that is invalid UTF-8.\r
+\r
+ Modes: Hybi, Hixie\r
+\r
+ Notes:\r
+ - For Hixie mode, reason is silently ignored.\r
+\r
+ :param reason: What was invalid for the payload (human readable).\r
+ :type reason: str\r
+\r
+ :returns: bool -- True, when any further processing should be discontinued.\r
+ """\r
+ if self.debugCodePaths:\r
+ log.msg("Invalid payload : %s" % reason)\r
+ self.failConnection(WebSocketProtocol.CLOSE_STATUS_CODE_INVALID_PAYLOAD, reason)\r
+ if self.failByDrop:\r
+ return True\r
+ else:\r
+ ## if we don't immediately drop the TCP, we need to skip the invalid frame\r
+ ## to continue to later receive the closing handshake reply\r
+ return False\r
+\r
+\r
+ def connectionMade(self):\r
+ """\r
+ This is called by Twisted framework when a new TCP connection has been established\r
+ and handed over to a Protocol instance (an instance of this class).\r
+\r
+ Modes: Hybi, Hixie\r
+ """\r
+\r
+ ## copy default options from factory (so we are not affected by changed on those)\r
+ ##\r
+\r
+ self.debug = self.factory.debug\r
+ self.debugCodePaths = self.factory.debugCodePaths\r
+\r
+ self.logOctets = self.factory.logOctets\r
+ self.logFrames = self.factory.logFrames\r
+\r
+ self.allowHixie76 = self.factory.allowHixie76\r
+ self.utf8validateIncoming = self.factory.utf8validateIncoming\r
+ self.applyMask = self.factory.applyMask\r
+ self.maxFramePayloadSize = self.factory.maxFramePayloadSize\r
+ self.maxMessagePayloadSize = self.factory.maxMessagePayloadSize\r
+ self.autoFragmentSize = self.factory.autoFragmentSize\r
+ self.failByDrop = self.factory.failByDrop\r
+ self.echoCloseCodeReason = self.factory.echoCloseCodeReason\r
+ self.openHandshakeTimeout = self.factory.openHandshakeTimeout\r
+ self.closeHandshakeTimeout = self.factory.closeHandshakeTimeout\r
+ self.tcpNoDelay = self.factory.tcpNoDelay\r
+\r
+ if self.isServer:\r
+ self.versions = self.factory.versions\r
+ self.webStatus = self.factory.webStatus\r
+ self.requireMaskedClientFrames = self.factory.requireMaskedClientFrames\r
+ self.maskServerFrames = self.factory.maskServerFrames\r
+ else:\r
+ self.version = self.factory.version\r
+ self.acceptMaskedServerFrames = self.factory.acceptMaskedServerFrames\r
+ self.maskClientFrames = self.factory.maskClientFrames\r
+ self.serverConnectionDropTimeout = self.factory.serverConnectionDropTimeout\r
+\r
+ ## Set "Nagle"\r
+ self.transport.setTcpNoDelay(self.tcpNoDelay)\r
+\r
+ ## the peer we are connected to\r
+ self.peer = self.transport.getPeer()\r
+ self.peerstr = "%s:%d" % (self.peer.host, self.peer.port)\r
+\r
+ ## initial state\r
+ self.state = WebSocketProtocol.STATE_CONNECTING\r
+ self.send_state = WebSocketProtocol.SEND_STATE_GROUND\r
+ self.data = ""\r
+\r
+ ## for chopped/synched sends, we need to queue to maintain\r
+ ## ordering when recalling the reactor to actually "force"\r
+ ## the octets to wire (see test/trickling in the repo)\r
+ self.send_queue = deque()\r
+ self.triggered = False\r
+\r
+ ## incremental UTF8 validator\r
+ self.utf8validator = Utf8Validator()\r
+\r
+ ## track when frame/message payload sizes (incoming) were exceeded\r
+ self.wasMaxFramePayloadSizeExceeded = False\r
+ self.wasMaxMessagePayloadSizeExceeded = False\r
+\r
+ ## the following vars are related to connection close handling/tracking\r
+\r
+ # True, iff I have initiated closing HS (that is, did send close first)\r
+ self.closedByMe = False\r
+\r
+ # True, iff I have failed the WS connection (i.e. due to protocol error)\r
+ # Failing can be either by initiating close HS or brutal drop (this is\r
+ # controlled by failByDrop option)\r
+ self.failedByMe = False\r
+\r
+ # True, iff I dropped the TCP connection (called transport.loseConnection())\r
+ self.droppedByMe = False\r
+\r
+ # True, iff full WebSockets closing handshake was performed (close frame sent\r
+ # and received) _and_ the server dropped the TCP (which is its responsibility)\r
+ self.wasClean = False\r
+\r
+ # When self.wasClean = False, the reason (what happened)\r
+ self.wasNotCleanReason = None\r
+\r
+ # When we are a client, and we expected the server to drop the TCP, but that\r
+ # didn't happen in time, this gets True\r
+ self.wasServerConnectionDropTimeout = False\r
+\r
+ # When the initial WebSocket opening handshake times out, this gets True\r
+ self.wasOpenHandshakeTimeout = False\r
+\r
+ # When we initiated a closing handshake, but the peer did not respond in\r
+ # time, this gets True\r
+ self.wasCloseHandshakeTimeout = False\r
+\r
+ # The close code I sent in close frame (if any)\r
+ self.localCloseCode = None\r
+\r
+ # The close reason I sent in close frame (if any)\r
+ self.localCloseReason = None\r
+\r
+ # The close code the peer sent me in close frame (if any)\r
+ self.remoteCloseCode = None\r
+\r
+ # The close reason the peer sent me in close frame (if any)\r
+ self.remoteCloseReason = None\r
+\r
+ # timers, which might get set up later, and remembered here to get canceled\r
+ # when appropriate\r
+ if not self.isServer:\r
+ self.serverConnectionDropTimeoutCall = None\r
+ self.openHandshakeTimeoutCall = None\r
+ self.closeHandshakeTimeoutCall = None\r
+\r
+ # set opening handshake timeout handler\r
+ if self.openHandshakeTimeout > 0:\r
+ self.openHandshakeTimeoutCall = reactor.callLater(self.openHandshakeTimeout, self.onOpenHandshakeTimeout)\r
+\r
+\r
+ def connectionLost(self, reason):\r
+ """\r
+ This is called by Twisted framework when a TCP connection was lost.\r
+\r
+ Modes: Hybi, Hixie\r
+ """\r
+ ## cancel any server connection drop timer if present\r
+ ##\r
+ if not self.isServer and self.serverConnectionDropTimeoutCall is not None:\r
+ if self.debugCodePaths:\r
+ log.msg("serverConnectionDropTimeoutCall.cancel")\r
+ self.serverConnectionDropTimeoutCall.cancel()\r
+ self.serverConnectionDropTimeoutCall = None\r
+\r
+ self.state = WebSocketProtocol.STATE_CLOSED\r
+ if not self.wasClean:\r
+ if not self.droppedByMe and self.wasNotCleanReason is None:\r
+ self.wasNotCleanReason = "peer dropped the TCP connection without previous WebSocket closing handshake"\r
+ self.onClose(self.wasClean, WebSocketProtocol.CLOSE_STATUS_CODE_ABNORMAL_CLOSE, "connection was closed uncleanly (%s)" % self.wasNotCleanReason)\r
+ else:\r
+ self.onClose(self.wasClean, self.remoteCloseCode, self.remoteCloseReason)\r
+\r
+\r
+ def logRxOctets(self, data):\r
+ """\r
+ Hook fired right after raw octets have been received, but only when self.logOctets == True.\r
+\r
+ Modes: Hybi, Hixie\r
+ """\r
+ log.msg("RX Octets from %s : octets = %s" % (self.peerstr, binascii.b2a_hex(data)))\r
+\r
+\r
+ def logTxOctets(self, data, sync):\r
+ """\r
+ Hook fired right after raw octets have been sent, but only when self.logOctets == True.\r
+\r
+ Modes: Hybi, Hixie\r
+ """\r
+ log.msg("TX Octets to %s : sync = %s, octets = %s" % (self.peerstr, sync, binascii.b2a_hex(data)))\r
+\r
+\r
+ def logRxFrame(self, frameHeader, payload):\r
+ """\r
+ Hook fired right after WebSocket frame has been received and decoded, but only when self.logFrames == True.\r
+\r
+ Modes: Hybi\r
+ """\r
+ data = ''.join(payload)\r
+ info = (self.peerstr,\r
+ frameHeader.fin,\r
+ frameHeader.rsv,\r
+ frameHeader.opcode,\r
+ binascii.b2a_hex(frameHeader.mask) if frameHeader.mask else "-",\r
+ frameHeader.length,\r
+ data if frameHeader.opcode == 1 else binascii.b2a_hex(data))\r
+\r
+ log.msg("RX Frame from %s : fin = %s, rsv = %s, opcode = %s, mask = %s, length = %s, payload = %s" % info)\r
+\r
+\r
+ def logTxFrame(self, frameHeader, payload, repeatLength, chopsize, sync):\r
+ """\r
+ Hook fired right after WebSocket frame has been encoded and sent, but only when self.logFrames == True.\r
+\r
+ Modes: Hybi\r
+ """\r
+ info = (self.peerstr,\r
+ frameHeader.fin,\r
+ frameHeader.rsv,\r
+ frameHeader.opcode,\r
+ binascii.b2a_hex(frameHeader.mask) if frameHeader.mask else "-",\r
+ frameHeader.length,\r
+ repeatLength,\r
+ chopsize,\r
+ sync,\r
+ payload if frameHeader.opcode == 1 else binascii.b2a_hex(payload))\r
+\r
+ log.msg("TX Frame to %s : fin = %s, rsv = %s, opcode = %s, mask = %s, length = %s, repeat_length = %s, chopsize = %s, sync = %s, payload = %s" % info)\r
+\r
+\r
+ def dataReceived(self, data):\r
+ """\r
+ This is called by Twisted framework upon receiving data on TCP connection.\r
+\r
+ Modes: Hybi, Hixie\r
+ """\r
+ if self.logOctets:\r
+ self.logRxOctets(data)\r
+ self.data += data\r
+ self.consumeData()\r
+\r
+\r
+ def consumeData(self):\r
+ """\r
+ Consume buffered (incoming) data.\r
+\r
+ Modes: Hybi, Hixie\r
+ """\r
+\r
+ ## WebSocket is open (handshake was completed) or close was sent\r
+ ##\r
+ if self.state == WebSocketProtocol.STATE_OPEN or self.state == WebSocketProtocol.STATE_CLOSING:\r
+\r
+ ## process until no more buffered data left or WS was closed\r
+ ##\r
+ while self.processData() and self.state != WebSocketProtocol.STATE_CLOSED:\r
+ pass\r
+\r
+ ## WebSocket needs handshake\r
+ ##\r
+ elif self.state == WebSocketProtocol.STATE_CONNECTING:\r
+\r
+ ## the implementation of processHandshake() in derived\r
+ ## class needs to perform client or server handshake\r
+ ## from other party here ..\r
+ ##\r
+ self.processHandshake()\r
+\r
+ ## we failed the connection .. don't process any more data!\r
+ ##\r
+ elif self.state == WebSocketProtocol.STATE_CLOSED:\r
+\r
+ ## ignore any data received after WS was closed\r
+ ##\r
+ if self.debugCodePaths:\r
+ log.msg("received data in STATE_CLOSED")\r
+\r
+ ## should not arrive here (invalid state)\r
+ ##\r
+ else:\r
+ raise Exception("invalid state")\r
+\r
+\r
+ def processHandshake(self):\r
+ """\r
+ Process WebSockets handshake.\r
+\r
+ Modes: Hybi, Hixie\r
+ """\r
+ raise Exception("must implement handshake (client or server) in derived class")\r
+\r
+\r
+ def registerProducer(self, producer, streaming):\r
+ """\r
+ Register a Twisted producer with this protocol.\r
+\r
+ Modes: Hybi, Hixie\r
+\r
+ :param producer: A Twisted push or pull producer.\r
+ :type producer: object\r
+ :param streaming: Producer type.\r
+ :type streaming: bool\r
+ """\r
+ self.transport.registerProducer(producer, streaming)\r
+\r
+\r
+ def _trigger(self):\r
+ """\r
+ Trigger sending stuff from send queue (which is only used for chopped/synched writes).\r
+\r
+ Modes: Hybi, Hixie\r
+ """\r
+ if not self.triggered:\r
+ self.triggered = True\r
+ self._send()\r
+\r
+\r
+ def _send(self):\r
+ """\r
+ Send out stuff from send queue. For details how this works, see test/trickling\r
+ in the repo.\r
+\r
+ Modes: Hybi, Hixie\r
+ """\r
+ if len(self.send_queue) > 0:\r
+ e = self.send_queue.popleft()\r
+ if self.state != WebSocketProtocol.STATE_CLOSED:\r
+ self.transport.write(e[0])\r
+ if self.logOctets:\r
+ self.logTxOctets(e[0], e[1])\r
+ else:\r
+ if self.debugCodePaths:\r
+ log.msg("skipped delayed write, since connection is closed")\r
+ # we need to reenter the reactor to make the latter\r
+ # reenter the OS network stack, so that octets\r
+ # can get on the wire. Note: this is a "heuristic",\r
+ # since there is no (easy) way to really force out\r
+ # octets from the OS network stack to wire.\r
+ reactor.callLater(WebSocketProtocol.QUEUED_WRITE_DELAY, self._send)\r
+ else:\r
+ self.triggered = False\r
+\r
+\r
+ def sendData(self, data, sync = False, chopsize = None):\r
+ """\r
+ Wrapper for self.transport.write which allows to give a chopsize.\r
+ When asked to chop up writing to TCP stream, we write only chopsize octets\r
+ and then give up control to select() in underlying reactor so that bytes\r
+ get onto wire immediately. Note that this is different from and unrelated\r
+ to WebSockets data message fragmentation. Note that this is also different\r
+ from the TcpNoDelay option which can be set on the socket.\r
+\r
+ Modes: Hybi, Hixie\r
+ """\r
+ if chopsize and chopsize > 0:\r
+ i = 0\r
+ n = len(data)\r
+ done = False\r
+ while not done:\r
+ j = i + chopsize\r
+ if j >= n:\r
+ done = True\r
+ j = n\r
+ self.send_queue.append((data[i:j], True))\r
+ i += chopsize\r
+ self._trigger()\r
+ else:\r
+ if sync or len(self.send_queue) > 0:\r
+ self.send_queue.append((data, sync))\r
+ self._trigger()\r
+ else:\r
+ self.transport.write(data)\r
+ if self.logOctets:\r
+ self.logTxOctets(data, False)\r
+\r
+\r
+ def sendPreparedMessage(self, preparedMsg):\r
+ """\r
+ Send a message that was previously prepared with\r
+ WebSocketFactory.prepareMessage().\r
+\r
+ Modes: Hybi, Hixie\r
+ """\r
+ if self.websocket_version == 0:\r
+ self.sendData(preparedMsg.payloadHixie)\r
+ else:\r
+ self.sendData(preparedMsg.payloadHybi)\r
+\r
+\r
+ def processData(self):\r
+ """\r
+ After WebSockets handshake has been completed, this procedure will do all\r
+ subsequent processing of incoming bytes.\r
+\r
+ Modes: Hybi, Hixie\r
+ """\r
+ if self.websocket_version == 0:\r
+ return self.processDataHixie76()\r
+ else:\r
+ return self.processDataHybi()\r
+\r
+\r
+ def processDataHixie76(self):\r
+ """\r
+ Hixie-76 incoming data processing.\r
+\r
+ Modes: Hixie\r
+ """\r
+ buffered_len = len(self.data)\r
+\r
+ ## outside a message, that is we are awaiting data which starts a new message\r
+ ##\r
+ if not self.inside_message:\r
+ if buffered_len >= 2:\r
+\r
+ ## new message\r
+ ##\r
+ if self.data[0] == '\x00':\r
+\r
+ self.inside_message = True\r
+\r
+ if self.utf8validateIncoming:\r
+ self.utf8validator.reset()\r
+ self.utf8validateIncomingCurrentMessage = True\r
+ self.utf8validateLast = (True, True, 0, 0)\r
+ else:\r
+ self.utf8validateIncomingCurrentMessage = False\r
+\r
+ self.data = self.data[1:]\r
+ self.onMessageBegin(1)\r
+\r
+ ## Hixie close from peer received\r
+ ##\r
+ elif self.data[0] == '\xff' and self.data[1] == '\x00':\r
+ self.onCloseFrame()\r
+ self.data = self.data[2:]\r
+ # stop receiving/processing after having received close!\r
+ return False\r
+\r
+ ## malformed data\r
+ ##\r
+ else:\r
+ if self.protocolViolation("malformed data received"):\r
+ return False\r
+ else:\r
+ ## need more data\r
+ return False\r
+\r
+ end_index = self.data.find('\xff')\r
+ if end_index > 0:\r
+ payload = self.data[:end_index]\r
+ self.data = self.data[end_index + 1:]\r
+ else:\r
+ payload = self.data\r
+ self.data = ''\r
+\r
+ ## incrementally validate UTF-8 payload\r
+ ##\r
+ if self.utf8validateIncomingCurrentMessage:\r
+ self.utf8validateLast = self.utf8validator.validate(payload)\r
+ if not self.utf8validateLast[0]:\r
+ if self.invalidPayload("encountered invalid UTF-8 while processing text message at payload octet index %d" % self.utf8validateLast[3]):\r
+ return False\r
+\r
+ self.onMessageFrameData(payload)\r
+\r
+ if end_index > 0:\r
+ self.inside_message = False\r
+ self.onMessageEnd()\r
+\r
+ return len(self.data) > 0\r
+\r
+\r
+ def processDataHybi(self):\r
+ """\r
+ RFC6455/Hybi-Drafts incoming data processing.\r
+\r
+ Modes: Hybi\r
+ """\r
+ buffered_len = len(self.data)\r
+\r
+ ## outside a frame, that is we are awaiting data which starts a new frame\r
+ ##\r
+ if self.current_frame is None:\r
+\r
+ ## need minimum of 2 octets to for new frame\r
+ ##\r
+ if buffered_len >= 2:\r
+\r
+ ## FIN, RSV, OPCODE\r
+ ##\r
+ b = ord(self.data[0])\r
+ frame_fin = (b & 0x80) != 0\r
+ frame_rsv = (b & 0x70) >> 4\r
+ frame_opcode = b & 0x0f\r
+\r
+ ## MASK, PAYLOAD LEN 1\r
+ ##\r
+ b = ord(self.data[1])\r
+ frame_masked = (b & 0x80) != 0\r
+ frame_payload_len1 = b & 0x7f\r
+\r
+ ## MUST be 0 when no extension defining\r
+ ## the semantics of RSV has been negotiated\r
+ ##\r
+ if frame_rsv != 0:\r
+ if self.protocolViolation("RSV != 0 and no extension negotiated"):\r
+ return False\r
+\r
+ ## all client-to-server frames MUST be masked\r
+ ##\r
+ if self.isServer and self.requireMaskedClientFrames and not frame_masked:\r
+ if self.protocolViolation("unmasked client-to-server frame"):\r
+ return False\r
+\r
+ ## all server-to-client frames MUST NOT be masked\r
+ ##\r
+ if not self.isServer and not self.acceptMaskedServerFrames and frame_masked:\r
+ if self.protocolViolation("masked server-to-client frame"):\r
+ return False\r
+\r
+ ## check frame\r
+ ##\r
+ if frame_opcode > 7: # control frame (have MSB in opcode set)\r
+\r
+ ## control frames MUST NOT be fragmented\r
+ ##\r
+ if not frame_fin:\r
+ if self.protocolViolation("fragmented control frame"):\r
+ return False\r
+\r
+ ## control frames MUST have payload 125 octets or less\r
+ ##\r
+ if frame_payload_len1 > 125:\r
+ if self.protocolViolation("control frame with payload length > 125 octets"):\r
+ return False\r
+\r
+ ## check for reserved control frame opcodes\r
+ ##\r
+ if frame_opcode not in [8, 9, 10]:\r
+ if self.protocolViolation("control frame using reserved opcode %d" % frame_opcode):\r
+ return False\r
+\r
+ ## close frame : if there is a body, the first two bytes of the body MUST be a 2-byte\r
+ ## unsigned integer (in network byte order) representing a status code\r
+ ##\r
+ if frame_opcode == 8 and frame_payload_len1 == 1:\r
+ if self.protocolViolation("received close control frame with payload len 1"):\r
+ return False\r
+\r
+ else: # data frame\r
+\r
+ ## check for reserved data frame opcodes\r
+ ##\r
+ if frame_opcode not in [0, 1, 2]:\r
+ if self.protocolViolation("data frame using reserved opcode %d" % frame_opcode):\r
+ return False\r
+\r
+ ## check opcode vs message fragmentation state 1/2\r
+ ##\r
+ if not self.inside_message and frame_opcode == 0:\r
+ if self.protocolViolation("received continuation data frame outside fragmented message"):\r
+ return False\r
+\r
+ ## check opcode vs message fragmentation state 2/2\r
+ ##\r
+ if self.inside_message and frame_opcode != 0:\r
+ if self.protocolViolation("received non-continuation data frame while inside fragmented message"):\r
+ return False\r
+\r
+ ## compute complete header length\r
+ ##\r
+ if frame_masked:\r
+ mask_len = 4\r
+ else:\r
+ mask_len = 0\r
+\r
+ if frame_payload_len1 < 126:\r
+ frame_header_len = 2 + mask_len\r
+ elif frame_payload_len1 == 126:\r
+ frame_header_len = 2 + 2 + mask_len\r
+ elif frame_payload_len1 == 127:\r
+ frame_header_len = 2 + 8 + mask_len\r
+ else:\r
+ raise Exception("logic error")\r
+\r
+ ## only proceed when we have enough data buffered for complete\r
+ ## frame header (which includes extended payload len + mask)\r
+ ##\r
+ if buffered_len >= frame_header_len:\r
+\r
+ ## minimum frame header length (already consumed)\r
+ ##\r
+ i = 2\r
+\r
+ ## extract extended payload length\r
+ ##\r
+ if frame_payload_len1 == 126:\r
+ frame_payload_len = struct.unpack("!H", self.data[i:i+2])[0]\r
+ if frame_payload_len < 126:\r
+ if self.protocolViolation("invalid data frame length (not using minimal length encoding)"):\r
+ return False\r
+ i += 2\r
+ elif frame_payload_len1 == 127:\r
+ frame_payload_len = struct.unpack("!Q", self.data[i:i+8])[0]\r
+ if frame_payload_len > 0x7FFFFFFFFFFFFFFF: # 2**63\r
+ if self.protocolViolation("invalid data frame length (>2^63)"):\r
+ return False\r
+ if frame_payload_len < 65536:\r
+ if self.protocolViolation("invalid data frame length (not using minimal length encoding)"):\r
+ return False\r
+ i += 8\r
+ else:\r
+ frame_payload_len = frame_payload_len1\r
+\r
+ ## when payload is masked, extract frame mask\r
+ ##\r
+ frame_mask = None\r
+ if frame_masked:\r
+ frame_mask = self.data[i:i+4]\r
+ i += 4\r
+\r
+ if frame_masked and frame_payload_len > 0 and self.applyMask:\r
+ if frame_payload_len < WebSocketProtocol.PAYLOAD_LEN_XOR_BREAKEVEN:\r
+ self.current_frame_masker = XorMaskerSimple(frame_mask)\r
+ else:\r
+ self.current_frame_masker = XorMaskerShifted1(frame_mask)\r
+ else:\r
+ self.current_frame_masker = XorMaskerNull()\r
+\r
+\r
+ ## remember rest (payload of current frame after header and everything thereafter)\r
+ ##\r
+ self.data = self.data[i:]\r
+\r
+ ## ok, got complete frame header\r
+ ##\r
+ self.current_frame = FrameHeader(frame_opcode,\r
+ frame_fin,\r
+ frame_rsv,\r
+ frame_payload_len,\r
+ frame_mask)\r
+\r
+ ## process begin on new frame\r
+ ##\r
+ self.onFrameBegin()\r
+\r
+ ## reprocess when frame has no payload or and buffered data left\r
+ ##\r
+ return frame_payload_len == 0 or len(self.data) > 0\r
+\r
+ else:\r
+ return False # need more data\r
+ else:\r
+ return False # need more data\r
+\r
+ ## inside a started frame\r
+ ##\r
+ else:\r
+\r
+ ## cut out rest of frame payload\r
+ ##\r
+ rest = self.current_frame.length - self.current_frame_masker.pointer()\r
+ if buffered_len >= rest:\r
+ data = self.data[:rest]\r
+ length = rest\r
+ self.data = self.data[rest:]\r
+ else:\r
+ data = self.data\r
+ length = buffered_len\r
+ self.data = ""\r
+\r
+ if length > 0:\r
+ ## unmask payload\r
+ ##\r
+ payload = self.current_frame_masker.process(data)\r
+\r
+ ## process frame data\r
+ ##\r
+ fr = self.onFrameData(payload)\r
+ if fr == False:\r
+ return False\r
+\r
+ ## fire frame end handler when frame payload is complete\r
+ ##\r
+ if self.current_frame_masker.pointer() == self.current_frame.length:\r
+ fr = self.onFrameEnd()\r
+ if fr == False:\r
+ return False\r
+\r
+ ## reprocess when no error occurred and buffered data left\r
+ ##\r
+ return len(self.data) > 0\r
+\r
+\r
+ def onFrameBegin(self):\r
+ """\r
+ Begin of receive new frame.\r
+\r
+ Modes: Hybi\r
+ """\r
+ if self.current_frame.opcode > 7:\r
+ self.control_frame_data = []\r
+ else:\r
+ ## new message started\r
+ ##\r
+ if not self.inside_message:\r
+\r
+ self.inside_message = True\r
+\r
+ if self.current_frame.opcode == WebSocketProtocol.MESSAGE_TYPE_TEXT and self.utf8validateIncoming:\r
+ self.utf8validator.reset()\r
+ self.utf8validateIncomingCurrentMessage = True\r
+ self.utf8validateLast = (True, True, 0, 0)\r
+ else:\r
+ self.utf8validateIncomingCurrentMessage = False\r
+\r
+ self.onMessageBegin(self.current_frame.opcode)\r
+\r
+ self.onMessageFrameBegin(self.current_frame.length, self.current_frame.rsv)\r
+\r
+\r
+ def onFrameData(self, payload):\r
+ """\r
+ New data received within frame.\r
+\r
+ Modes: Hybi\r
+ """\r
+ if self.current_frame.opcode > 7:\r
+ self.control_frame_data.append(payload)\r
+ else:\r
+ ## incrementally validate UTF-8 payload\r
+ ##\r
+ if self.utf8validateIncomingCurrentMessage:\r
+ self.utf8validateLast = self.utf8validator.validate(payload)\r
+ if not self.utf8validateLast[0]:\r
+ if self.invalidPayload("encountered invalid UTF-8 while processing text message at payload octet index %d" % self.utf8validateLast[3]):\r
+ return False\r
+\r
+ self.onMessageFrameData(payload)\r
+\r
+\r
+ def onFrameEnd(self):\r
+ """\r
+ End of frame received.\r
+\r
+ Modes: Hybi\r
+ """\r
+ if self.current_frame.opcode > 7:\r
+ if self.logFrames:\r
+ self.logRxFrame(self.current_frame, self.control_frame_data)\r
+ self.processControlFrame()\r
+ else:\r
+ if self.logFrames:\r
+ self.logRxFrame(self.current_frame, self.frame_data)\r
+ self.onMessageFrameEnd()\r
+ if self.current_frame.fin:\r
+ if self.utf8validateIncomingCurrentMessage:\r
+ if not self.utf8validateLast[1]:\r
+ if self.invalidPayload("UTF-8 text message payload ended within Unicode code point at payload octet index %d" % self.utf8validateLast[3]):\r
+ return False\r
+ self.onMessageEnd()\r
+ self.inside_message = False\r
+ self.current_frame = None\r
+\r
+\r
+ def processControlFrame(self):\r
+ """\r
+ Process a completely received control frame.\r
+\r
+ Modes: Hybi\r
+ """\r
+\r
+ payload = ''.join(self.control_frame_data)\r
+ self.control_frame_data = None\r
+\r
+ ## CLOSE frame\r
+ ##\r
+ if self.current_frame.opcode == 8:\r
+\r
+ code = None\r
+ reasonRaw = None\r
+ ll = len(payload)\r
+ if ll > 1:\r
+ code = struct.unpack("!H", payload[0:2])[0]\r
+ if ll > 2:\r
+ reasonRaw = payload[2:]\r
+\r
+ if self.onCloseFrame(code, reasonRaw):\r
+ return False\r
+\r
+ ## PING frame\r
+ ##\r
+ elif self.current_frame.opcode == 9:\r
+ self.onPing(payload)\r
+\r
+ ## PONG frame\r
+ ##\r
+ elif self.current_frame.opcode == 10:\r
+ self.onPong(payload)\r
+\r
+ else:\r
+ ## we might arrive here, when protocolViolation\r
+ ## wants us to continue anyway\r
+ pass\r
+\r
+ return True\r
+\r
+\r
+ def sendFrame(self, opcode, payload = "", fin = True, rsv = 0, mask = None, payload_len = None, chopsize = None, sync = False):\r
+ """\r
+ Send out frame. Normally only used internally via sendMessage(), sendPing(), sendPong() and sendClose().\r
+\r
+ This method deliberately allows to send invalid frames (that is frames invalid\r
+ per-se, or frames invalid because of protocol state). Other than in fuzzing servers,\r
+ calling methods will ensure that no invalid frames are sent.\r
+\r
+ In addition, this method supports explicit specification of payload length.\r
+ When payload_len is given, it will always write that many octets to the stream.\r
+ It'll wrap within payload, resending parts of that when more octets were requested\r
+ The use case is again for fuzzing server which want to sent increasing amounts\r
+ of payload data to peers without having to construct potentially large messges\r
+ themselfes.\r
+\r
+ Modes: Hybi\r
+ """\r
+ if self.websocket_version == 0:\r
+ raise Exception("function not supported in Hixie-76 mode")\r
+\r
+ if payload_len is not None:\r
+ if len(payload) < 1:\r
+ raise Exception("cannot construct repeated payload with length %d from payload of length %d" % (payload_len, len(payload)))\r
+ l = payload_len\r
+ pl = ''.join([payload for k in range(payload_len / len(payload))]) + payload[:payload_len % len(payload)]\r
+ else:\r
+ l = len(payload)\r
+ pl = payload\r
+\r
+ ## first byte\r
+ ##\r
+ b0 = 0\r
+ if fin:\r
+ b0 |= (1 << 7)\r
+ b0 |= (rsv % 8) << 4\r
+ b0 |= opcode % 128\r
+\r
+ ## second byte, payload len bytes and mask\r
+ ##\r
+ b1 = 0\r
+ if mask or (not self.isServer and self.maskClientFrames) or (self.isServer and self.maskServerFrames):\r
+ b1 |= 1 << 7\r
+ if not mask:\r
+ mask = struct.pack("!I", random.getrandbits(32))\r
+ mv = mask\r
+ else:\r
+ mv = ""\r
+\r
+ ## mask frame payload\r
+ ##\r
+ if l > 0 and self.applyMask:\r
+ if l < WebSocketProtocol.PAYLOAD_LEN_XOR_BREAKEVEN:\r
+ masker = XorMaskerSimple(mask)\r
+ else:\r
+ masker = XorMaskerShifted1(mask)\r
+ plm = masker.process(pl)\r
+ else:\r
+ plm = pl\r
+\r
+ else:\r
+ mv = ""\r
+ plm = pl\r
+\r
+ el = ""\r
+ if l <= 125:\r
+ b1 |= l\r
+ elif l <= 0xFFFF:\r
+ b1 |= 126\r
+ el = struct.pack("!H", l)\r
+ elif l <= 0x7FFFFFFFFFFFFFFF:\r
+ b1 |= 127\r
+ el = struct.pack("!Q", l)\r
+ else:\r
+ raise Exception("invalid payload length")\r
+\r
+ raw = ''.join([chr(b0), chr(b1), el, mv, plm])\r
+\r
+ if self.logFrames:\r
+ frameHeader = FrameHeader(opcode, fin, rsv, l, mask)\r
+ self.logTxFrame(frameHeader, payload, payload_len, chopsize, sync)\r
+\r
+ ## send frame octets\r
+ ##\r
+ self.sendData(raw, sync, chopsize)\r
+\r
+\r
+ def sendPing(self, payload = None):\r
+ """\r
+ Send out Ping to peer. A peer is expected to Pong back the payload a soon\r
+ as "practical". When more than 1 Ping is outstanding at a peer, the peer may\r
+ elect to respond only to the last Ping.\r
+\r
+ Modes: Hybi\r
+\r
+ :param payload: An optional, arbitrary payload of length < 126 octets.\r
+ :type payload: str\r
+ """\r
+ if self.websocket_version == 0:\r
+ raise Exception("function not supported in Hixie-76 mode")\r
+ if self.state != WebSocketProtocol.STATE_OPEN:\r
+ return\r
+ if payload:\r
+ l = len(payload)\r
+ if l > 125:\r
+ raise Exception("invalid payload for PING (payload length must be <= 125, was %d)" % l)\r
+ self.sendFrame(opcode = 9, payload = payload)\r
+ else:\r
+ self.sendFrame(opcode = 9)\r
+\r
+\r
+ def sendPong(self, payload = None):\r
+ """\r
+ Send out Pong to peer. A Pong frame MAY be sent unsolicited.\r
+ This serves as a unidirectional heartbeat. A response to an unsolicited pong is "not expected".\r
+\r
+ Modes: Hybi\r
+\r
+ :param payload: An optional, arbitrary payload of length < 126 octets.\r
+ :type payload: str\r
+ """\r
+ if self.websocket_version == 0:\r
+ raise Exception("function not supported in Hixie-76 mode")\r
+ if self.state != WebSocketProtocol.STATE_OPEN:\r
+ return\r
+ if payload:\r
+ l = len(payload)\r
+ if l > 125:\r
+ raise Exception("invalid payload for PONG (payload length must be <= 125, was %d)" % l)\r
+ self.sendFrame(opcode = 10, payload = payload)\r
+ else:\r
+ self.sendFrame(opcode = 10)\r
+\r
+\r
+ def sendCloseFrame(self, code = None, reasonUtf8 = None, isReply = False):\r
+ """\r
+ Send a close frame and update protocol state. Note, that this is\r
+ an internal method which deliberately allows not send close\r
+ frame with invalid payload.\r
+\r
+ Modes: Hybi, Hixie\r
+\r
+ Notes:\r
+ - For Hixie mode, this method is slightly misnamed for historic reasons.\r
+ - For Hixie mode, code and reasonUtf8 will be silently ignored.\r
+ """\r
+ if self.state == WebSocketProtocol.STATE_CLOSING:\r
+ if self.debugCodePaths:\r
+ log.msg("ignoring sendCloseFrame since connection is closing")\r
+\r
+ elif self.state == WebSocketProtocol.STATE_CLOSED:\r
+ if self.debugCodePaths:\r
+ log.msg("ignoring sendCloseFrame since connection already closed")\r
+\r
+ elif self.state == WebSocketProtocol.STATE_CONNECTING:\r
+ raise Exception("cannot close a connection not yet connected")\r
+\r
+ elif self.state == WebSocketProtocol.STATE_OPEN:\r
+\r
+ if self.websocket_version == 0:\r
+ self.sendData("\xff\x00")\r
+ else:\r
+ ## construct Hybi close frame payload and send frame\r
+ payload = ""\r
+ if code is not None:\r
+ payload += struct.pack("!H", code)\r
+ if reasonUtf8 is not None:\r
+ payload += reasonUtf8\r
+ self.sendFrame(opcode = 8, payload = payload)\r
+\r
+ ## update state\r
+ self.state = WebSocketProtocol.STATE_CLOSING\r
+ self.closedByMe = not isReply\r
+\r
+ ## remember payload of close frame we sent\r
+ self.localCloseCode = code\r
+ self.localCloseReason = reasonUtf8\r
+\r
+ ## drop connection when timeout on receiving close handshake reply\r
+ if self.closedByMe and self.closeHandshakeTimeout > 0:\r
+ self.closeHandshakeTimeoutCall = reactor.callLater(self.closeHandshakeTimeout, self.onCloseHandshakeTimeout)\r
+\r
+ else:\r
+ raise Exception("logic error")\r
+\r
+\r
+ def sendClose(self, code = None, reason = None):\r
+ """\r
+ Starts a closing handshake.\r
+\r
+ Modes: Hybi, Hixie\r
+\r
+ Notes:\r
+ - For Hixie mode, code and reason will be silently ignored.\r
+\r
+ :param code: An optional close status code (:class:`WebSocketProtocol`.CLOSE_STATUS_CODE_NORMAL or 3000-4999).\r
+ :type code: int\r
+ :param reason: An optional close reason (a string that when present, a status code MUST also be present).\r
+ :type reason: str\r
+ """\r
+ if code is not None:\r
+ if type(code) != int:\r
+ raise Exception("invalid type %s for close code" % type(code))\r
+ if code != 1000 and not (code >= 3000 and code <= 4999):\r
+ raise Exception("invalid close code %d" % code)\r
+ if reason is not None:\r
+ if code is None:\r
+ raise Exception("close reason without close code")\r
+ if type(reason) not in [str, unicode]:\r
+ raise Exception("invalid type %s for close reason" % type(reason))\r
+ reasonUtf8 = reason.encode("UTF-8")\r
+ if len(reasonUtf8) + 2 > 125:\r
+ raise Exception("close reason too long (%d)" % len(reasonUtf8))\r
+ else:\r
+ reasonUtf8 = None\r
+ self.sendCloseFrame(code = code, reasonUtf8 = reasonUtf8, isReply = False)\r
+\r
+\r
+ def beginMessage(self, opcode = MESSAGE_TYPE_TEXT):\r
+ """\r
+ Begin sending new message.\r
+\r
+ Modes: Hybi, Hixie\r
+\r
+ :param opcode: Message type, normally either WebSocketProtocol.MESSAGE_TYPE_TEXT (default) or\r
+ WebSocketProtocol.MESSAGE_TYPE_BINARY (only Hybi mode).\r
+ """\r
+ if self.state != WebSocketProtocol.STATE_OPEN:\r
+ return\r
+\r
+ ## check if sending state is valid for this method\r
+ ##\r
+ if self.send_state != WebSocketProtocol.SEND_STATE_GROUND:\r
+ raise Exception("WebSocketProtocol.beginMessage invalid in current sending state")\r
+\r
+ if self.websocket_version == 0:\r
+ if opcode != 1:\r
+ raise Exception("cannot send non-text message in Hixie mode")\r
+\r
+ self.sendData('\x00')\r
+ self.send_state = WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE\r
+ else:\r
+ if opcode not in [1, 2]:\r
+ raise Exception("use of reserved opcode %d" % opcode)\r
+\r
+ ## remember opcode for later (when sending first frame)\r
+ ##\r
+ self.send_message_opcode = opcode\r
+ self.send_state = WebSocketProtocol.SEND_STATE_MESSAGE_BEGIN\r
+\r
+\r
+\r
+ def beginMessageFrame(self, length, reserved = 0, mask = None):\r
+ """\r
+ Begin sending new message frame.\r
+\r
+ Modes: Hybi\r
+\r
+ :param length: Length of frame which is started. Must be >= 0 and <= 2^63.\r
+ :type length: int\r
+ :param reserved: Reserved bits for frame (an integer from 0 to 7). Note that reserved != 0 is only legal when an extension has been negoiated which defines semantics.\r
+ :type reserved: int\r
+ :param mask: Optional frame mask. When given, this is used. When None and the peer is a client, a mask will be internally generated. For servers None is default.\r
+ :type mask: str\r
+ """\r
+ if self.websocket_version == 0:\r
+ raise Exception("function not supported in Hixie-76 mode")\r
+\r
+ if self.state != WebSocketProtocol.STATE_OPEN:\r
+ return\r
+ ## check if sending state is valid for this method\r
+ ##\r
+ if self.send_state not in [WebSocketProtocol.SEND_STATE_MESSAGE_BEGIN, WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE]:\r
+ raise Exception("WebSocketProtocol.beginMessageFrame invalid in current sending state")\r
+\r
+ if (not type(length) in [int, long]) or length < 0 or length > 0x7FFFFFFFFFFFFFFF: # 2**63\r
+ raise Exception("invalid value for message frame length")\r
+\r
+ if type(reserved) is not int or reserved < 0 or reserved > 7:\r
+ raise Exception("invalid value for reserved bits")\r
+\r
+ self.send_message_frame_length = length\r
+\r
+ if mask:\r
+ ## explicit mask given\r
+ ##\r
+ assert type(mask) == str\r
+ assert len(mask) == 4\r
+ self.send_message_frame_mask = mask\r
+\r
+ elif (not self.isServer and self.maskClientFrames) or (self.isServer and self.maskServerFrames):\r
+ ## automatic mask:\r
+ ## - client-to-server masking (if not deactivated)\r
+ ## - server-to-client masking (if activated)\r
+ ##\r
+ self.send_message_frame_mask = struct.pack("!I", random.getrandbits(32))\r
+\r
+ else:\r
+ ## no mask\r
+ ##\r
+ self.send_message_frame_mask = None\r
+\r
+ ## payload masker\r
+ ##\r
+ if self.send_message_frame_mask and length > 0 and self.applyMask:\r
+ if length < WebSocketProtocol.PAYLOAD_LEN_XOR_BREAKEVEN:\r
+ self.send_message_frame_masker = XorMaskerSimple(self.send_message_frame_mask)\r
+ else:\r
+ self.send_message_frame_masker = XorMaskerShifted1(self.send_message_frame_mask)\r
+ else:\r
+ self.send_message_frame_masker = XorMaskerNull()\r
+\r
+ ## first byte\r
+ ##\r
+ b0 = (reserved % 8) << 4 # FIN = false .. since with streaming, we don't know when message ends\r
+\r
+ if self.send_state == WebSocketProtocol.SEND_STATE_MESSAGE_BEGIN:\r
+ self.send_state = WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE\r
+ b0 |= self.send_message_opcode % 128\r
+ else:\r
+ pass # message continuation frame\r
+\r
+ ## second byte, payload len bytes and mask\r
+ ##\r
+ b1 = 0\r
+ if self.send_message_frame_mask:\r
+ b1 |= 1 << 7\r
+ mv = self.send_message_frame_mask\r
+ else:\r
+ mv = ""\r
+\r
+ el = ""\r
+ if length <= 125:\r
+ b1 |= length\r
+ elif length <= 0xFFFF:\r
+ b1 |= 126\r
+ el = struct.pack("!H", length)\r
+ elif length <= 0x7FFFFFFFFFFFFFFF:\r
+ b1 |= 127\r
+ el = struct.pack("!Q", length)\r
+ else:\r
+ raise Exception("invalid payload length")\r
+\r
+ ## write message frame header\r
+ ##\r
+ header = ''.join([chr(b0), chr(b1), el, mv])\r
+ self.sendData(header)\r
+\r
+ ## now we are inside message frame ..\r
+ ##\r
+ self.send_state = WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE_FRAME\r
+\r
+\r
+ def sendMessageFrameData(self, payload, sync = False):\r
+ """\r
+ Send out data when within message frame (message was begun, frame was begun).\r
+ Note that the frame is automatically ended when enough data has been sent\r
+ that is, there is no endMessageFrame, since you have begun the frame specifying\r
+ the frame length, which implicitly defined the frame end. This is different from\r
+ messages, which you begin and end, since a message can contain an unlimited number\r
+ of frames.\r
+\r
+ Modes: Hybi, Hixie\r
+\r
+ Notes:\r
+ - For Hixie mode, this method is slightly misnamed for historic reasons.\r
+\r
+ :param payload: Data to send.\r
+\r
+ :returns: int -- Hybi mode: when frame still incomplete, returns outstanding octets, when frame complete, returns <= 0, when < 0, the amount of unconsumed data in payload argument. Hixie mode: returns None.\r
+ """\r
+ if self.state != WebSocketProtocol.STATE_OPEN:\r
+ return\r
+\r
+ if self.websocket_version == 0:\r
+ ## Hixie Mode\r
+ ##\r
+ if self.send_state != WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE:\r
+ raise Exception("WebSocketProtocol.sendMessageFrameData invalid in current sending state")\r
+ self.sendData(payload, sync = sync)\r
+ return None\r
+\r
+ else:\r
+ ## Hybi Mode\r
+ ##\r
+ if self.send_state != WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE_FRAME:\r
+ raise Exception("WebSocketProtocol.sendMessageFrameData invalid in current sending state")\r
+\r
+ rl = len(payload)\r
+ if self.send_message_frame_masker.pointer() + rl > self.send_message_frame_length:\r
+ l = self.send_message_frame_length - self.send_message_frame_masker.pointer()\r
+ rest = -(rl - l)\r
+ pl = payload[:l]\r
+ else:\r
+ l = rl\r
+ rest = self.send_message_frame_length - self.send_message_frame_masker.pointer() - l\r
+ pl = payload\r
+\r
+ ## mask frame payload\r
+ ##\r
+ plm = self.send_message_frame_masker.process(pl)\r
+\r
+ ## send frame payload\r
+ ##\r
+ self.sendData(plm, sync = sync)\r
+\r
+ ## if we are done with frame, move back into "inside message" state\r
+ ##\r
+ if self.send_message_frame_masker.pointer() >= self.send_message_frame_length:\r
+ self.send_state = WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE\r
+\r
+ ## when =0 : frame was completed exactly\r
+ ## when >0 : frame is still uncomplete and that much amount is still left to complete the frame\r
+ ## when <0 : frame was completed and there was this much unconsumed data in payload argument\r
+ ##\r
+ return rest\r
+\r
+\r
+ def endMessage(self):\r
+ """\r
+ End a previously begun message. No more frames may be sent (for that message). You have to\r
+ begin a new message before sending again.\r
+\r
+ Modes: Hybi, Hixie\r
+ """\r
+ if self.state != WebSocketProtocol.STATE_OPEN:\r
+ return\r
+ ## check if sending state is valid for this method\r
+ ##\r
+ if self.send_state != WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE:\r
+ raise Exception("WebSocketProtocol.endMessage invalid in current sending state [%d]" % self.send_state)\r
+\r
+ if self.websocket_version == 0:\r
+ self.sendData('\x00')\r
+ else:\r
+ self.sendFrame(opcode = 0, fin = True)\r
+\r
+ self.send_state = WebSocketProtocol.SEND_STATE_GROUND\r
+\r
+\r
+ def sendMessageFrame(self, payload, reserved = 0, mask = None, sync = False):\r
+ """\r
+ When a message has begun, send a complete message frame in one go.\r
+\r
+ Modes: Hybi\r
+ """\r
+ if self.websocket_version == 0:\r
+ raise Exception("function not supported in Hixie-76 mode")\r
+\r
+ if self.state != WebSocketProtocol.STATE_OPEN:\r
+ return\r
+ if self.websocket_version == 0:\r
+ raise Exception("function not supported in Hixie-76 mode")\r
+ self.beginMessageFrame(len(payload), reserved, mask)\r
+ self.sendMessageFrameData(payload, sync)\r
+\r
+\r
+ def sendMessage(self, payload, binary = False, payload_frag_size = None, sync = False):\r
+ """\r
+ Send out a message in one go.\r
+\r
+ You can send text or binary message, and optionally specifiy a payload fragment size.\r
+ When the latter is given, the payload will be split up into frames with\r
+ payload <= the payload_frag_size given.\r
+\r
+ Modes: Hybi, Hixie\r
+ """\r
+ if self.state != WebSocketProtocol.STATE_OPEN:\r
+ return\r
+ if self.websocket_version == 0:\r
+ if binary:\r
+ raise Exception("cannot send binary message in Hixie76 mode")\r
+ if payload_frag_size:\r
+ raise Exception("cannot fragment messages in Hixie76 mode")\r
+ self.sendMessageHixie76(payload, sync)\r
+ else:\r
+ self.sendMessageHybi(payload, binary, payload_frag_size, sync)\r
+\r
+\r
+ def sendMessageHixie76(self, payload, sync = False):\r
+ """\r
+ Hixie76-Variant of sendMessage().\r
+\r
+ Modes: Hixie\r
+ """\r
+ self.sendData('\x00' + payload + '\xff', sync = sync)\r
+\r
+\r
+ def sendMessageHybi(self, payload, binary = False, payload_frag_size = None, sync = False):\r
+ """\r
+ Hybi-Variant of sendMessage().\r
+\r
+ Modes: Hybi\r
+ """\r
+ ## (initial) frame opcode\r
+ ##\r
+ if binary:\r
+ opcode = 2\r
+ else:\r
+ opcode = 1\r
+\r
+ ## explicit payload_frag_size arguments overrides autoFragmentSize setting\r
+ ##\r
+ if payload_frag_size is not None:\r
+ pfs = payload_frag_size\r
+ else:\r
+ if self.autoFragmentSize > 0:\r
+ pfs = self.autoFragmentSize\r
+ else:\r
+ pfs = None\r
+\r
+ ## send unfragmented\r
+ ##\r
+ if pfs is None or len(payload) <= pfs:\r
+ self.sendFrame(opcode = opcode, payload = payload, sync = sync)\r
+\r
+ ## send data message in fragments\r
+ ##\r
+ else:\r
+ if pfs < 1:\r
+ raise Exception("payload fragment size must be at least 1 (was %d)" % pfs)\r
+ n = len(payload)\r
+ i = 0\r
+ done = False\r
+ first = True\r
+ while not done:\r
+ j = i + pfs\r
+ if j > n:\r
+ done = True\r
+ j = n\r
+ if first:\r
+ self.sendFrame(opcode = opcode, payload = payload[i:j], fin = done, sync = sync)\r
+ first = False\r
+ else:\r
+ self.sendFrame(opcode = 0, payload = payload[i:j], fin = done, sync = sync)\r
+ i += pfs\r
+\r
+\r
+\r
+class PreparedMessage:\r
+ """\r
+ Encapsulates a prepared message to be sent later once or multiple\r
+ times. This is used for optimizing Broadcast/PubSub.\r
+\r
+ The message serialization formats currently created internally are:\r
+ * Hybi\r
+ * Hixie\r
+\r
+ The construction of different formats is needed, since we support\r
+ mixed clients (speaking different protocol versions).\r
+\r
+ It will also be the place to add a 3rd format, when we support\r
+ the deflate extension, since then, the clients will be mixed\r
+ between Hybi-Deflate-Unsupported, Hybi-Deflate-Supported and Hixie.\r
+ """\r
+\r
+ def __init__(self, payload, binary, masked):\r
+ self.initHixie(payload, binary)\r
+ self.initHybi(payload, binary, masked)\r
+\r
+\r
+ def initHixie(self, payload, binary):\r
+ if binary:\r
+ # silently filter out .. probably do something else:\r
+ # base64?\r
+ # dunno\r
+ self.payloadHixie = ''\r
+ else:\r
+ self.payloadHixie = '\x00' + payload + '\xff'\r
+\r
+\r
+ def initHybi(self, payload, binary, masked):\r
+ l = len(payload)\r
+\r
+ ## first byte\r
+ ##\r
+ b0 = ((1 << 7) | 2) if binary else ((1 << 7) | 1)\r
+\r
+ ## second byte, payload len bytes and mask\r
+ ##\r
+ if masked:\r
+ b1 = 1 << 7\r
+ mask = struct.pack("!I", random.getrandbits(32))\r
+ if l == 0:\r
+ plm = payload\r
+ elif l < WebSocketProtocol.PAYLOAD_LEN_XOR_BREAKEVEN:\r
+ plm = XorMaskerSimple(mask).process(payload)\r
+ else:\r
+ plm = XorMaskerShifted1(mask).process(payload)\r
+ else:\r
+ b1 = 0\r
+ mask = ""\r
+ plm = payload\r
+\r
+ ## payload extended length\r
+ ##\r
+ el = ""\r
+ if l <= 125:\r
+ b1 |= l\r
+ elif l <= 0xFFFF:\r
+ b1 |= 126\r
+ el = struct.pack("!H", l)\r
+ elif l <= 0x7FFFFFFFFFFFFFFF:\r
+ b1 |= 127\r
+ el = struct.pack("!Q", l)\r
+ else:\r
+ raise Exception("invalid payload length")\r
+\r
+ ## raw WS message (single frame)\r
+ ##\r
+ self.payloadHybi = ''.join([chr(b0), chr(b1), el, mask, plm])\r
+\r
+\r
+\r
+class WebSocketFactory:\r
+ """\r
+ Mixin for WebSocketClientFactory and WebSocketServerFactory.\r
+ """\r
+\r
+ def prepareMessage(self, payload, binary = False, masked = None):\r
+ """\r
+ Prepare a WebSocket message. This can be later used on multiple\r
+ instances of WebSocketProtocol using sendPreparedMessage().\r
+\r
+ By doing so, you can avoid the (small) overhead of framing the\r
+ _same_ payload into WS messages when that payload is to be sent\r
+ out on multiple connections.\r
+\r
+ Modes: Hybi, Hixie\r
+\r
+ Caveats:\r
+\r
+ 1) Only use when you know what you are doing. I.e. calling\r
+ sendPreparedMessage() on the _same_ protocol instance multiples\r
+ times with the same prepared message might break the spec.\r
+ Since i.e. the frame mask will be the same!\r
+\r
+ 2) Treat the object returned as opaque. It may change!\r
+ """\r
+ if masked is None:\r
+ masked = not self.isServer\r
+\r
+ return PreparedMessage(payload, binary, masked)\r
+\r
+\r
+\r
+class WebSocketServerProtocol(WebSocketProtocol):\r
+ """\r
+ A Twisted protocol for WebSockets servers.\r
+ """\r
+\r
+ def onConnect(self, connectionRequest):\r
+ """\r
+ Callback fired during WebSocket opening handshake when new WebSocket client\r
+ connection is about to be established.\r
+\r
+ Throw HttpException when you don't want to accept the WebSocket\r
+ connection request. For example, throw a\r
+ HttpException(httpstatus.HTTP_STATUS_CODE_UNAUTHORIZED[0], "You are not authorized for this!").\r
+\r
+ When you want to accept the connection, return the accepted protocol\r
+ from list of WebSockets (sub)protocols provided by client or None to\r
+ speak no specific one or when the client list was empty.\r
+\r
+ :param connectionRequest: WebSocket connection request information.\r
+ :type connectionRequest: instance of :class:`autobahn.websocket.ConnectionRequest`\r
+ """\r
+ return None\r
+\r
+\r
+ def connectionMade(self):\r
+ """\r
+ Called by Twisted when new TCP connection from client was accepted. Default\r
+ implementation will prepare for initial WebSocket opening handshake.\r
+ When overriding in derived class, make sure to call this base class\r
+ implementation _before_ your code.\r
+ """\r
+ self.isServer = True\r
+ WebSocketProtocol.connectionMade(self)\r
+ self.factory.countConnections += 1\r
+ if self.debug:\r
+ log.msg("connection accepted from peer %s" % self.peerstr)\r
+\r
+\r
+ def connectionLost(self, reason):\r
+ """\r
+ Called by Twisted when established TCP connection from client was lost. Default\r
+ implementation will tear down all state properly.\r
+ When overriding in derived class, make sure to call this base class\r
+ implementation _after_ your code.\r
+ """\r
+ WebSocketProtocol.connectionLost(self, reason)\r
+ self.factory.countConnections -= 1\r
+ if self.debug:\r
+ log.msg("connection from %s lost" % self.peerstr)\r
+\r
+\r
+ def parseHixie76Key(self, key):\r
+ return int(filter(lambda x: x.isdigit(), key)) / key.count(" ")\r
+\r
+\r
+ def processHandshake(self):\r
+ """\r
+ Process WebSockets opening handshake request from client.\r
+ """\r
+ ## only proceed when we have fully received the HTTP request line and all headers\r
+ ##\r
+ end_of_header = self.data.find("\x0d\x0a\x0d\x0a")\r
+ if end_of_header >= 0:\r
+\r
+ self.http_request_data = self.data[:end_of_header + 4]\r
+ if self.debug:\r
+ log.msg("received HTTP request:\n\n%s\n\n" % self.http_request_data)\r
+\r
+ ## extract HTTP status line and headers\r
+ ##\r
+ (self.http_status_line, self.http_headers, http_headers_cnt) = parseHttpHeader(self.http_request_data)\r
+\r
+ ## validate WebSocket opening handshake client request\r
+ ##\r
+ if self.debug:\r
+ log.msg("received HTTP status line in opening handshake : %s" % str(self.http_status_line))\r
+ log.msg("received HTTP headers in opening handshake : %s" % str(self.http_headers))\r
+\r
+ ## HTTP Request line : METHOD, VERSION\r
+ ##\r
+ rl = self.http_status_line.split()\r
+ if len(rl) != 3:\r
+ return self.failHandshake("Bad HTTP request status line '%s'" % self.http_status_line)\r
+ if rl[0].strip() != "GET":\r
+ return self.failHandshake("HTTP method '%s' not allowed" % rl[0], HTTP_STATUS_CODE_METHOD_NOT_ALLOWED[0])\r
+ vs = rl[2].strip().split("/")\r
+ if len(vs) != 2 or vs[0] != "HTTP" or vs[1] not in ["1.1"]:\r
+ return self.failHandshake("Unsupported HTTP version '%s'" % rl[2], HTTP_STATUS_CODE_UNSUPPORTED_HTTP_VERSION[0])\r
+\r
+ ## HTTP Request line : REQUEST-URI\r
+ ##\r
+ self.http_request_uri = rl[1].strip()\r
+ try:\r
+ (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(self.http_request_uri)\r
+\r
+ ## FIXME: check that if absolute resource URI is given,\r
+ ## the scheme/netloc matches the server\r
+ if scheme != "" or netloc != "":\r
+ pass\r
+\r
+ ## Fragment identifiers are meaningless in the context of WebSocket\r
+ ## URIs, and MUST NOT be used on these URIs.\r
+ if fragment != "":\r
+ return self.failHandshake("HTTP requested resource contains a fragment identifier '%s'" % fragment)\r
+\r
+ ## resource path and query parameters .. this will get forwarded\r
+ ## to onConnect()\r
+ self.http_request_path = path\r
+ self.http_request_params = urlparse.parse_qs(query)\r
+ except:\r
+ return self.failHandshake("Bad HTTP request resource - could not parse '%s'" % rl[1].strip())\r
+\r
+ ## Host\r
+ ##\r
+ if not self.http_headers.has_key("host"):\r
+ return self.failHandshake("HTTP Host header missing in opening handshake request")\r
+ if http_headers_cnt["host"] > 1:\r
+ return self.failHandshake("HTTP Host header appears more than once in opening handshake request")\r
+ self.http_request_host = self.http_headers["host"].strip()\r
+ if self.http_request_host.find(":") >= 0:\r
+ (h, p) = self.http_request_host.split(":")\r
+ try:\r
+ port = int(str(p.strip()))\r
+ except:\r
+ return self.failHandshake("invalid port '%s' in HTTP Host header '%s'" % (str(p.strip()), str(self.http_request_host)))\r
+ if port != self.factory.port:\r
+ return self.failHandshake("port %d in HTTP Host header '%s' does not match server listening port %s" % (port, str(self.http_request_host), self.factory.port))\r
+ self.http_request_host = h\r
+ else:\r
+ if not ((self.factory.isSecure and self.factory.port == 443) or (not self.factory.isSecure and self.factory.port == 80)):\r
+ return self.failHandshake("missing port in HTTP Host header '%s' and server runs on non-standard port %d (wss = %s)" % (str(self.http_request_host), self.factory.port, self.factory.isSecure))\r
+\r
+ ## Upgrade\r
+ ##\r
+ if not self.http_headers.has_key("upgrade"):\r
+ ## When no WS upgrade, render HTML server status page\r
+ ##\r
+ if self.webStatus:\r
+ self.sendServerStatus()\r
+ self.dropConnection(abort = False)\r
+ return\r
+ else:\r
+ return self.failHandshake("HTTP Upgrade header missing", HTTP_STATUS_CODE_UPGRADE_REQUIRED[0])\r
+ upgradeWebSocket = False\r
+ for u in self.http_headers["upgrade"].split(","):\r
+ if u.strip().lower() == "websocket":\r
+ upgradeWebSocket = True\r
+ break\r
+ if not upgradeWebSocket:\r
+ return self.failHandshake("HTTP Upgrade headers do not include 'websocket' value (case-insensitive) : %s" % self.http_headers["upgrade"])\r
+\r
+ ## Connection\r
+ ##\r
+ if not self.http_headers.has_key("connection"):\r
+ return self.failHandshake("HTTP Connection header missing")\r
+ connectionUpgrade = False\r
+ for c in self.http_headers["connection"].split(","):\r
+ if c.strip().lower() == "upgrade":\r
+ connectionUpgrade = True\r
+ break\r
+ if not connectionUpgrade:\r
+ return self.failHandshake("HTTP Connection headers do not include 'upgrade' value (case-insensitive) : %s" % self.http_headers["connection"])\r
+\r
+ ## Sec-WebSocket-Version PLUS determine mode: Hybi or Hixie\r
+ ##\r
+ if not self.http_headers.has_key("sec-websocket-version"):\r
+ if self.debugCodePaths:\r
+ log.msg("Hixie76 protocol detected")\r
+ if self.allowHixie76:\r
+ version = 0\r
+ else:\r
+ return self.failHandshake("WebSocket connection denied - Hixie76 protocol mode disabled.")\r
+ else:\r
+ if self.debugCodePaths:\r
+ log.msg("Hybi protocol detected")\r
+ if http_headers_cnt["sec-websocket-version"] > 1:\r
+ return self.failHandshake("HTTP Sec-WebSocket-Version header appears more than once in opening handshake request")\r
+ try:\r
+ version = int(self.http_headers["sec-websocket-version"])\r
+ except:\r
+ return self.failHandshake("could not parse HTTP Sec-WebSocket-Version header '%s' in opening handshake request" % self.http_headers["sec-websocket-version"])\r
+\r
+ if version not in self.versions:\r
+\r
+ ## respond with list of supported versions (descending order)\r
+ ##\r
+ sv = sorted(self.versions)\r
+ sv.reverse()\r
+ svs = ','.join([str(x) for x in sv])\r
+ return self.failHandshake("WebSocket version %d not supported (supported versions: %s)" % (version, svs),\r
+ HTTP_STATUS_CODE_BAD_REQUEST[0],\r
+ [("Sec-WebSocket-Version", svs)])\r
+ else:\r
+ ## store the protocol version we are supposed to talk\r
+ self.websocket_version = version\r
+\r
+ ## Sec-WebSocket-Protocol\r
+ ##\r
+ if self.http_headers.has_key("sec-websocket-protocol"):\r
+ protocols = [str(x.strip()) for x in self.http_headers["sec-websocket-protocol"].split(",")]\r
+ # check for duplicates in protocol header\r
+ pp = {}\r
+ for p in protocols:\r
+ if pp.has_key(p):\r
+ return self.failHandshake("duplicate protocol '%s' specified in HTTP Sec-WebSocket-Protocol header" % p)\r
+ else:\r
+ pp[p] = 1\r
+ # ok, no duplicates, save list in order the client sent it\r
+ self.websocket_protocols = protocols\r
+ else:\r
+ self.websocket_protocols = []\r
+\r
+ ## Origin / Sec-WebSocket-Origin\r
+ ## http://tools.ietf.org/html/draft-ietf-websec-origin-02\r
+ ##\r
+ if self.websocket_version < 13 and self.websocket_version != 0:\r
+ # Hybi, but only < Hybi-13\r
+ websocket_origin_header_key = 'sec-websocket-origin'\r
+ else:\r
+ # RFC6455, >= Hybi-13 and Hixie\r
+ websocket_origin_header_key = "origin"\r
+\r
+ self.websocket_origin = None\r
+ if self.http_headers.has_key(websocket_origin_header_key):\r
+ if http_headers_cnt[websocket_origin_header_key] > 1:\r
+ return self.failHandshake("HTTP Origin header appears more than once in opening handshake request")\r
+ self.websocket_origin = self.http_headers[websocket_origin_header_key].strip()\r
+ else:\r
+ # non-browser clients are allowed to omit this header\r
+ pass\r
+\r
+ ## Sec-WebSocket-Extensions\r
+ ##\r
+ ## extensions requested by client\r
+ self.websocket_extensions = []\r
+ ## extensions selected by server\r
+ self.websocket_extensions_in_use = []\r
+\r
+ if self.http_headers.has_key("sec-websocket-extensions"):\r
+ if self.websocket_version == 0:\r
+ return self.failHandshake("Sec-WebSocket-Extensions header specified for Hixie-76")\r
+ extensions = [x.strip() for x in self.http_headers["sec-websocket-extensions"].split(',')]\r
+ if len(extensions) > 0:\r
+ self.websocket_extensions = extensions\r
+ if self.debug:\r
+ log.msg("client requested extensions we don't support (%s)" % str(extensions))\r
+\r
+ ## Sec-WebSocket-Key (Hybi) or Sec-WebSocket-Key1/Sec-WebSocket-Key2 (Hixie-76)\r
+ ##\r
+ if self.websocket_version == 0:\r
+ for kk in ['Sec-WebSocket-Key1', 'Sec-WebSocket-Key2']:\r
+ k = kk.lower()\r
+ if not self.http_headers.has_key(k):\r
+ return self.failHandshake("HTTP %s header missing" % kk)\r
+ if http_headers_cnt[k] > 1:\r
+ return self.failHandshake("HTTP %s header appears more than once in opening handshake request" % kk)\r
+ try:\r
+ key1 = self.parseHixie76Key(self.http_headers["sec-websocket-key1"].strip())\r
+ key2 = self.parseHixie76Key(self.http_headers["sec-websocket-key2"].strip())\r
+ except:\r
+ return self.failHandshake("could not parse Sec-WebSocket-Key1/2")\r
+ else:\r
+ if not self.http_headers.has_key("sec-websocket-key"):\r
+ return self.failHandshake("HTTP Sec-WebSocket-Key header missing")\r
+ if http_headers_cnt["sec-websocket-key"] > 1:\r
+ return self.failHandshake("HTTP Sec-WebSocket-Key header appears more than once in opening handshake request")\r
+ key = self.http_headers["sec-websocket-key"].strip()\r
+ if len(key) != 24: # 16 bytes => (ceil(128/24)*24)/6 == 24\r
+ return self.failHandshake("bad Sec-WebSocket-Key (length must be 24 ASCII chars) '%s'" % key)\r
+ if key[-2:] != "==": # 24 - ceil(128/6) == 2\r
+ return self.failHandshake("bad Sec-WebSocket-Key (invalid base64 encoding) '%s'" % key)\r
+ for c in key[:-2]:\r
+ if c not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/":\r
+ return self.failHandshake("bad character '%s' in Sec-WebSocket-Key (invalid base64 encoding) '%s'" (c, key))\r
+\r
+ ## For Hixie-76, we need 8 octets of HTTP request body to complete HS!\r
+ ##\r
+ if self.websocket_version == 0:\r
+ if len(self.data) < end_of_header + 4 + 8:\r
+ return\r
+ else:\r
+ key3 = self.data[end_of_header + 4:end_of_header + 4 + 8]\r
+\r
+ ## Ok, got complete HS input, remember rest (if any)\r
+ ##\r
+ if self.websocket_version == 0:\r
+ self.data = self.data[end_of_header + 4 + 8:]\r
+ else:\r
+ self.data = self.data[end_of_header + 4:]\r
+\r
+ ## WebSocket handshake validated => produce opening handshake response\r
+\r
+ ## Now fire onConnect() on derived class, to give that class a chance to accept or deny\r
+ ## the connection. onConnect() may throw, in which case the connection is denied, or it\r
+ ## may return a protocol from the protocols provided by client or None.\r
+ ##\r
+ try:\r
+ connectionRequest = ConnectionRequest(self.peer,\r
+ self.peerstr,\r
+ self.http_headers,\r
+ self.http_request_host,\r
+ self.http_request_path,\r
+ self.http_request_params,\r
+ self.websocket_version,\r
+ self.websocket_origin,\r
+ self.websocket_protocols,\r
+ self.websocket_extensions)\r
+\r
+ ## onConnect() will return the selected subprotocol or None\r
+ ## or raise an HttpException\r
+ ##\r
+ protocol = self.onConnect(connectionRequest)\r
+\r
+ if protocol is not None and not (protocol in self.websocket_protocols):\r
+ raise Exception("protocol accepted must be from the list client sent or None")\r
+\r
+ self.websocket_protocol_in_use = protocol\r
+\r
+ except HttpException, e:\r
+ return self.failHandshake(e.reason, e.code)\r
+ #return self.sendHttpRequestFailure(e.code, e.reason)\r
+\r
+ except Exception, e:\r
+ log.msg("Exception raised in onConnect() - %s" % str(e))\r
+ return self.failHandshake("Internal Server Error", HTTP_STATUS_CODE_INTERNAL_SERVER_ERROR[0])\r
+\r
+\r
+ ## build response to complete WebSocket handshake\r
+ ##\r
+ response = "HTTP/1.1 %d Switching Protocols\x0d\x0a" % HTTP_STATUS_CODE_SWITCHING_PROTOCOLS[0]\r
+\r
+ if self.factory.server is not None and self.factory.server != "":\r
+ response += "Server: %s\x0d\x0a" % self.factory.server.encode("utf-8")\r
+\r
+ response += "Upgrade: WebSocket\x0d\x0a"\r
+ response += "Connection: Upgrade\x0d\x0a"\r
+\r
+ if self.websocket_protocol_in_use is not None:\r
+ response += "Sec-WebSocket-Protocol: %s\x0d\x0a" % str(self.websocket_protocol_in_use)\r
+\r
+ if self.websocket_version == 0:\r
+\r
+ if self.websocket_origin:\r
+ ## browser client provide the header, and expect it to be echo'ed\r
+ response += "Sec-WebSocket-Origin: %s\x0d\x0a" % str(self.websocket_origin)\r
+\r
+ if self.debugCodePaths:\r
+ log.msg('factory isSecure = %s port = %s' % (self.factory.isSecure, self.factory.port))\r
+\r
+ if (self.factory.isSecure and self.factory.port != 443) or ((not self.factory.isSecure) and self.factory.port != 80):\r
+ if self.debugCodePaths:\r
+ log.msg('factory running on non-default port')\r
+ response_port = ':' + str(self.factory.port)\r
+ else:\r
+ if self.debugCodePaths:\r
+ log.msg('factory running on default port')\r
+ response_port = ''\r
+\r
+ ## FIXME: check this! But see below ..\r
+ if False:\r
+ response_host = str(self.factory.host)\r
+ response_path = str(self.factory.path)\r
+ else:\r
+ response_host = str(self.http_request_host)\r
+ response_path = str(self.http_request_uri)\r
+\r
+ location = "%s://%s%s%s" % ('wss' if self.factory.isSecure else 'ws', response_host, response_port, response_path)\r
+\r
+ # Safari is very picky about this one\r
+ response += "Sec-WebSocket-Location: %s\x0d\x0a" % location\r
+\r
+ ## end of HTTP response headers\r
+ response += "\x0d\x0a"\r
+\r
+ ## compute accept body\r
+ ##\r
+ accept_val = struct.pack(">II", key1, key2) + key3\r
+ accept = hashlib.md5(accept_val).digest()\r
+ response_body = str(accept)\r
+ else:\r
+ ## compute Sec-WebSocket-Accept\r
+ ##\r
+ sha1 = hashlib.sha1()\r
+ sha1.update(key + WebSocketProtocol.WS_MAGIC)\r
+ sec_websocket_accept = base64.b64encode(sha1.digest())\r
+\r
+ response += "Sec-WebSocket-Accept: %s\x0d\x0a" % sec_websocket_accept\r
+\r
+ if len(self.websocket_extensions_in_use) > 0:\r
+ response += "Sec-WebSocket-Extensions: %s\x0d\x0a" % ','.join(self.websocket_extensions_in_use)\r
+\r
+ ## end of HTTP response headers\r
+ response += "\x0d\x0a"\r
+ response_body = ''\r
+\r
+ if self.debug:\r
+ log.msg("sending HTTP response:\n\n%s%s\n\n" % (response, binascii.b2a_hex(response_body)))\r
+\r
+ ## save and send out opening HS data\r
+ ##\r
+ self.http_response_data = response + response_body\r
+ self.sendData(self.http_response_data)\r
+\r
+ ## opening handshake completed, move WebSockets connection into OPEN state\r
+ ##\r
+ self.state = WebSocketProtocol.STATE_OPEN\r
+\r
+ ## cancel any opening HS timer if present\r
+ ##\r
+ if self.openHandshakeTimeoutCall is not None:\r
+ if self.debugCodePaths:\r
+ log.msg("openHandshakeTimeoutCall.cancel")\r
+ self.openHandshakeTimeoutCall.cancel()\r
+ self.openHandshakeTimeoutCall = None\r
+\r
+ ## init state\r
+ ##\r
+ self.inside_message = False\r
+ if self.websocket_version != 0:\r
+ self.current_frame = None\r
+\r
+ ## fire handler on derived class\r
+ ##\r
+ self.onOpen()\r
+\r
+ ## process rest, if any\r
+ ##\r
+ if len(self.data) > 0:\r
+ self.consumeData()\r
+\r
+\r
+ def failHandshake(self, reason, code = HTTP_STATUS_CODE_BAD_REQUEST[0], responseHeaders = []):\r
+ """\r
+ During opening handshake the client request was invalid, we send a HTTP\r
+ error response and then drop the connection.\r
+ """\r
+ if self.debug:\r
+ log.msg("failing WebSockets opening handshake ('%s')" % reason)\r
+ self.sendHttpErrorResponse(code, reason, responseHeaders)\r
+ self.dropConnection(abort = False)\r
+\r
+\r
+ def sendHttpErrorResponse(self, code, reason, responseHeaders = []):\r
+ """\r
+ Send out HTTP error response.\r
+ """\r
+ response = "HTTP/1.1 %d %s\x0d\x0a" % (code, reason.encode("utf-8"))\r
+ for h in responseHeaders:\r
+ response += "%s: %s\x0d\x0a" % (h[0], h[1].encode("utf-8"))\r
+ response += "\x0d\x0a"\r
+ self.sendData(response)\r
+\r
+\r
+ def sendHtml(self, html):\r
+ raw = html.encode("utf-8")\r
+ response = "HTTP/1.1 %d %s\x0d\x0a" % (HTTP_STATUS_CODE_OK[0], HTTP_STATUS_CODE_OK[1])\r
+ if self.factory.server is not None and self.factory.server != "":\r
+ response += "Server: %s\x0d\x0a" % self.factory.server.encode("utf-8")\r
+ response += "Content-Type: text/html; charset=UTF-8\x0d\x0a"\r
+ response += "Content-Length: %d\x0d\x0a" % len(raw)\r
+ response += "\x0d\x0a"\r
+ response += raw\r
+ self.sendData(response)\r
+\r
+\r
+ def sendServerStatus(self):\r
+ """\r
+ Used to send out server status/version upon receiving a HTTP/GET without\r
+ upgrade to WebSocket header (and option serverStatus is True).\r
+ """\r
+ html = """\r
+<!DOCTYPE html>\r
+<html>\r
+ <body>\r
+ <h1>Autobahn WebSockets %s</h1>\r
+ <p>\r
+ I am not Web server, but a WebSocket endpoint.\r
+ You can talk to me using the WebSocket <a href="http://tools.ietf.org/html/rfc6455">protocol</a>.\r
+ </p>\r
+ <p>\r
+ For more information, please visit <a href="http://autobahn.ws">my homepage</a>.\r
+ </p>\r
+ </body>\r
+</html>\r
+""" % str(autobahn.version)\r
+ self.sendHtml(html)\r
+\r
+\r
+class WebSocketServerFactory(protocol.ServerFactory, WebSocketFactory):\r
+ """\r
+ A Twisted factory for WebSockets server protocols.\r
+ """\r
+\r
+ protocol = WebSocketServerProtocol\r
+ """\r
+ The protocol to be spoken. Must be derived from :class:`autobahn.websocket.WebSocketServerProtocol`.\r
+ """\r
+\r
+\r
+ def __init__(self,\r
+\r
+ ## WebSockect session parameters\r
+ url = None,\r
+ protocols = [],\r
+ server = "AutobahnPython/%s" % autobahn.version,\r
+\r
+ ## debugging\r
+ debug = False,\r
+ debugCodePaths = False):\r
+ """\r
+ Create instance of WebSocket server factory.\r
+\r
+ Note that you MUST set URL either here or using setSessionParameters() _before_ the factory is started.\r
+\r
+ :param url: WebSocket listening URL - ("ws:" | "wss:") "//" host [ ":" port ] path [ "?" query ].\r
+ :type url: str\r
+ :param protocols: List of subprotocols the server supports. The subprotocol used is the first from the list of subprotocols announced by the client that is contained in this list.\r
+ :type protocols: list of strings\r
+ :param server: Server as announced in HTTP response header during opening handshake or None (default: "AutobahnWebSockets/x.x.x").\r
+ :type server: str\r
+ :param debug: Debug mode (default: False).\r
+ :type debug: bool\r
+ :param debugCodePaths: Debug code paths mode (default: False).\r
+ :type debugCodePaths: bool\r
+ """\r
+ self.debug = debug\r
+ self.debugCodePaths = debugCodePaths\r
+\r
+ self.logOctets = debug\r
+ self.logFrames = debug\r
+\r
+ self.isServer = True\r
+\r
+ ## seed RNG which is used for WS frame masks generation\r
+ random.seed()\r
+\r
+ ## default WS session parameters\r
+ ##\r
+ self.setSessionParameters(url, protocols, server)\r
+\r
+ ## default WebSocket protocol options\r
+ ##\r
+ self.resetProtocolOptions()\r
+\r
+ ## number of currently connected clients\r
+ ##\r
+ self.countConnections = 0\r
+\r
+\r
+ def setSessionParameters(self, url = None, protocols = [], server = None):\r
+ """\r
+ Set WebSocket session parameters.\r
+\r
+ :param url: WebSocket listening URL - ("ws:" | "wss:") "//" host [ ":" port ].\r
+ :type url: str\r
+ :param protocols: List of subprotocols the server supports. The subprotocol used is the first from the list of subprotocols announced by the client that is contained in this list.\r
+ :type protocols: list of strings\r
+ :param server: Server as announced in HTTP response header during opening handshake.\r
+ :type server: str\r
+ """\r
+ if url is not None:\r
+ ## parse WebSocket URI into components\r
+ (isSecure, host, port, resource, path, params) = parseWsUrl(url)\r
+ if path != "/":\r
+ raise Exception("path specified for server WebSocket URL")\r
+ if len(params) > 0:\r
+ raise Exception("query parameters specified for server WebSocket URL")\r
+ self.url = url\r
+ self.isSecure = isSecure\r
+ self.host = host\r
+ self.port = port\r
+ else:\r
+ self.url = None\r
+ self.isSecure = None\r
+ self.host = None\r
+ self.port = None\r
+\r
+ self.protocols = protocols\r
+ self.server = server\r
+\r
+\r
+ def resetProtocolOptions(self):\r
+ """\r
+ Reset all WebSocket protocol options to defaults.\r
+ """\r
+ self.versions = WebSocketProtocol.SUPPORTED_PROTOCOL_VERSIONS\r
+ self.allowHixie76 = WebSocketProtocol.DEFAULT_ALLOW_HIXIE76\r
+ self.webStatus = True\r
+ self.utf8validateIncoming = True\r
+ self.requireMaskedClientFrames = True\r
+ self.maskServerFrames = False\r
+ self.applyMask = True\r
+ self.maxFramePayloadSize = 0\r
+ self.maxMessagePayloadSize = 0\r
+ self.autoFragmentSize = 0\r
+ self.failByDrop = True\r
+ self.echoCloseCodeReason = False\r
+ self.openHandshakeTimeout = 5\r
+ self.closeHandshakeTimeout = 1\r
+ self.tcpNoDelay = True\r
+\r
+\r
+ def setProtocolOptions(self,\r
+ versions = None,\r
+ allowHixie76 = None,\r
+ webStatus = None,\r
+ utf8validateIncoming = None,\r
+ maskServerFrames = None,\r
+ requireMaskedClientFrames = None,\r
+ applyMask = None,\r
+ maxFramePayloadSize = None,\r
+ maxMessagePayloadSize = None,\r
+ autoFragmentSize = None,\r
+ failByDrop = None,\r
+ echoCloseCodeReason = None,\r
+ openHandshakeTimeout = None,\r
+ closeHandshakeTimeout = None,\r
+ tcpNoDelay = None):\r
+ """\r
+ Set WebSocket protocol options used as defaults for new protocol instances.\r
+\r
+ :param versions: The WebSockets protocol versions accepted by the server (default: WebSocketProtocol.SUPPORTED_PROTOCOL_VERSIONS).\r
+ :type versions: list of ints\r
+ :param allowHixie76: Allow to speak Hixie76 protocol version.\r
+ :type allowHixie76: bool\r
+ :param webStatus: Return server status/version on HTTP/GET without WebSocket upgrade header (default: True).\r
+ :type webStatus: bool\r
+ :param utf8validateIncoming: Validate incoming UTF-8 in text message payloads (default: True).\r
+ :type utf8validateIncoming: bool\r
+ :param maskServerFrames: Mask server-to-client frames (default: False).\r
+ :type maskServerFrames: bool\r
+ :param requireMaskedClientFrames: Require client-to-server frames to be masked (default: True).\r
+ :type requireMaskedClientFrames: bool\r
+ :param applyMask: Actually apply mask to payload when mask it present. Applies for outgoing and incoming frames (default: True).\r
+ :type applyMask: bool\r
+ :param maxFramePayloadSize: Maximum frame payload size that will be accepted when receiving or 0 for unlimited (default: 0).\r
+ :type maxFramePayloadSize: int\r
+ :param maxMessagePayloadSize: Maximum message payload size (after reassembly of fragmented messages) that will be accepted when receiving or 0 for unlimited (default: 0).\r
+ :type maxMessagePayloadSize: int\r
+ :param autoFragmentSize: Automatic fragmentation of outgoing data messages (when using the message-based API) into frames with payload length <= this size or 0 for no auto-fragmentation (default: 0).\r
+ :type autoFragmentSize: int\r
+ :param failByDrop: Fail connections by dropping the TCP connection without performaing closing handshake (default: True).\r
+ :type failbyDrop: bool\r
+ :param echoCloseCodeReason: Iff true, when receiving a close, echo back close code/reason. Otherwise reply with code == NORMAL, reason = "" (default: False).\r
+ :type echoCloseCodeReason: bool\r
+ :param openHandshakeTimeout: Opening WebSocket handshake timeout, timeout in seconds or 0 to deactivate (default: 0).\r
+ :type openHandshakeTimeout: float\r
+ :param closeHandshakeTimeout: When we expect to receive a closing handshake reply, timeout in seconds (default: 1).\r
+ :type closeHandshakeTimeout: float\r
+ :param tcpNoDelay: TCP NODELAY ("Nagle") socket option (default: True).\r
+ :type tcpNoDelay: bool\r
+ """\r
+ if allowHixie76 is not None and allowHixie76 != self.allowHixie76:\r
+ self.allowHixie76 = allowHixie76\r
+\r
+ if versions is not None:\r
+ for v in versions:\r
+ if v not in WebSocketProtocol.SUPPORTED_PROTOCOL_VERSIONS:\r
+ raise Exception("invalid WebSockets protocol version %s (allowed values: %s)" % (v, str(WebSocketProtocol.SUPPORTED_PROTOCOL_VERSIONS)))\r
+ if v == 0 and not self.allowHixie76:\r
+ raise Exception("use of Hixie-76 requires allowHixie76 == True")\r
+ if set(versions) != set(self.versions):\r
+ self.versions = versions\r
+\r
+ if webStatus is not None and webStatus != self.webStatus:\r
+ self.webStatus = webStatus\r
+\r
+ if utf8validateIncoming is not None and utf8validateIncoming != self.utf8validateIncoming:\r
+ self.utf8validateIncoming = utf8validateIncoming\r
+\r
+ if requireMaskedClientFrames is not None and requireMaskedClientFrames != self.requireMaskedClientFrames:\r
+ self.requireMaskedClientFrames = requireMaskedClientFrames\r
+\r
+ if maskServerFrames is not None and maskServerFrames != self.maskServerFrames:\r
+ self.maskServerFrames = maskServerFrames\r
+\r
+ if applyMask is not None and applyMask != self.applyMask:\r
+ self.applyMask = applyMask\r
+\r
+ if maxFramePayloadSize is not None and maxFramePayloadSize != self.maxFramePayloadSize:\r
+ self.maxFramePayloadSize = maxFramePayloadSize\r
+\r
+ if maxMessagePayloadSize is not None and maxMessagePayloadSize != self.maxMessagePayloadSize:\r
+ self.maxMessagePayloadSize = maxMessagePayloadSize\r
+\r
+ if autoFragmentSize is not None and autoFragmentSize != self.autoFragmentSize:\r
+ self.autoFragmentSize = autoFragmentSize\r
+\r
+ if failByDrop is not None and failByDrop != self.failByDrop:\r
+ self.failByDrop = failByDrop\r
+\r
+ if echoCloseCodeReason is not None and echoCloseCodeReason != self.echoCloseCodeReason:\r
+ self.echoCloseCodeReason = echoCloseCodeReason\r
+\r
+ if openHandshakeTimeout is not None and openHandshakeTimeout != self.openHandshakeTimeout:\r
+ self.openHandshakeTimeout = openHandshakeTimeout\r
+\r
+ if closeHandshakeTimeout is not None and closeHandshakeTimeout != self.closeHandshakeTimeout:\r
+ self.closeHandshakeTimeout = closeHandshakeTimeout\r
+\r
+ if tcpNoDelay is not None and tcpNoDelay != self.tcpNoDelay:\r
+ self.tcpNoDelay = tcpNoDelay\r
+\r
+\r
+ def getConnectionCount(self):\r
+ """\r
+ Get number of currently connected clients.\r
+\r
+ :returns: int -- Number of currently connected clients.\r
+ """\r
+ return self.countConnections\r
+\r
+\r
+ def startFactory(self):\r
+ """\r
+ Called by Twisted before starting to listen on port for incoming connections.\r
+ Default implementation does nothing. Override in derived class when appropriate.\r
+ """\r
+ pass\r
+\r
+\r
+ def stopFactory(self):\r
+ """\r
+ Called by Twisted before stopping to listen on port for incoming connections.\r
+ Default implementation does nothing. Override in derived class when appropriate.\r
+ """\r
+ pass\r
+\r
+\r
+class WebSocketClientProtocol(WebSocketProtocol):\r
+ """\r
+ Client protocol for WebSockets.\r
+ """\r
+\r
+ def onConnect(self, connectionResponse):\r
+ """\r
+ Callback fired directly after WebSocket opening handshake when new WebSocket server\r
+ connection was established.\r
+\r
+ :param connectionResponse: WebSocket connection response information.\r
+ :type connectionResponse: instance of :class:`autobahn.websocket.ConnectionResponse`\r
+ """\r
+ pass\r
+\r
+\r
+ def connectionMade(self):\r
+ """\r
+ Called by Twisted when new TCP connection to server was established. Default\r
+ implementation will start the initial WebSocket opening handshake.\r
+ When overriding in derived class, make sure to call this base class\r
+ implementation _before_ your code.\r
+ """\r
+ self.isServer = False\r
+ WebSocketProtocol.connectionMade(self)\r
+ if self.debug:\r
+ log.msg("connection to %s established" % self.peerstr)\r
+ self.startHandshake()\r
+\r
+\r
+ def connectionLost(self, reason):\r
+ """\r
+ Called by Twisted when established TCP connection to server was lost. Default\r
+ implementation will tear down all state properly.\r
+ When overriding in derived class, make sure to call this base class\r
+ implementation _after_ your code.\r
+ """\r
+ WebSocketProtocol.connectionLost(self, reason)\r
+ if self.debug:\r
+ log.msg("connection to %s lost" % self.peerstr)\r
+\r
+\r
+ def createHixieKey(self):\r
+ """\r
+ Supposed to implement the crack smoker algorithm below. Well, crack\r
+ probably wasn't the stuff they smoked - dog poo?\r
+\r
+ http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76#page-21\r
+ Items 16 - 22\r
+ """\r
+ spaces1 = random.randint(1, 12)\r
+ max1 = int(4294967295L / spaces1)\r
+ number1 = random.randint(0, max1)\r
+ product1 = number1 * spaces1\r
+ key1 = str(product1)\r
+ rchars = filter(lambda x: (x >= 0x21 and x <= 0x2f) or (x >= 0x3a and x <= 0x7e), range(0,127))\r
+ for i in xrange(random.randint(1, 12)):\r
+ p = random.randint(0, len(key1) - 1)\r
+ key1 = key1[:p] + chr(random.choice(rchars)) + key1[p:]\r
+ for i in xrange(spaces1):\r
+ p = random.randint(1, len(key1) - 2)\r
+ key1 = key1[:p] + ' ' + key1[p:]\r
+ return (key1, number1)\r
+\r
+\r
+ def startHandshake(self):\r
+ """\r
+ Start WebSockets opening handshake.\r
+ """\r
+\r
+ ## construct WS opening handshake HTTP header\r
+ ##\r
+ request = "GET %s HTTP/1.1\x0d\x0a" % self.factory.resource.encode("utf-8")\r
+\r
+ if self.factory.useragent is not None and self.factory.useragent != "":\r
+ request += "User-Agent: %s\x0d\x0a" % self.factory.useragent.encode("utf-8")\r
+\r
+ request += "Host: %s:%d\x0d\x0a" % (self.factory.host.encode("utf-8"), self.factory.port)\r
+ request += "Upgrade: WebSocket\x0d\x0a"\r
+ request += "Connection: Upgrade\x0d\x0a"\r
+\r
+ ## handshake random key\r
+ ##\r
+ if self.version == 0:\r
+ (self.websocket_key1, number1) = self.createHixieKey()\r
+ (self.websocket_key2, number2) = self.createHixieKey()\r
+ self.websocket_key3 = os.urandom(8)\r
+ accept_val = struct.pack(">II", number1, number2) + self.websocket_key3\r
+ self.websocket_expected_challenge_response = hashlib.md5(accept_val).digest()\r
+\r
+ request += "Sec-WebSocket-Key1: %s\x0d\x0a" % self.websocket_key1\r
+ request += "Sec-WebSocket-Key2: %s\x0d\x0a" % self.websocket_key2\r
+ else:\r
+ self.websocket_key = base64.b64encode(os.urandom(16))\r
+ request += "Sec-WebSocket-Key: %s\x0d\x0a" % self.websocket_key\r
+\r
+ ## optional origin announced\r
+ ##\r
+ if self.factory.origin:\r
+ if self.version > 10 or self.version == 0:\r
+ request += "Origin: %d\x0d\x0a" % self.factory.origin.encode("utf-8")\r
+ else:\r
+ request += "Sec-WebSocket-Origin: %d\x0d\x0a" % self.factory.origin.encode("utf-8")\r
+\r
+ ## optional list of WS subprotocols announced\r
+ ##\r
+ if len(self.factory.protocols) > 0:\r
+ request += "Sec-WebSocket-Protocol: %s\x0d\x0a" % ','.join(self.factory.protocols)\r
+\r
+ ## set WS protocol version depending on WS spec version\r
+ ##\r
+ if self.version != 0:\r
+ request += "Sec-WebSocket-Version: %d\x0d\x0a" % WebSocketProtocol.SPEC_TO_PROTOCOL_VERSION[self.version]\r
+\r
+ request += "\x0d\x0a"\r
+\r
+ if self.version == 0:\r
+ request += self.websocket_key3\r
+\r
+ self.http_request_data = request\r
+\r
+ if self.debug:\r
+ log.msg(self.http_request_data)\r
+\r
+ self.sendData(self.http_request_data)\r
+\r
+\r
+ def processHandshake(self):\r
+ """\r
+ Process WebSockets opening handshake response from server.\r
+ """\r
+ ## only proceed when we have fully received the HTTP request line and all headers\r
+ ##\r
+ end_of_header = self.data.find("\x0d\x0a\x0d\x0a")\r
+ if end_of_header >= 0:\r
+\r
+ self.http_response_data = self.data[:end_of_header + 4]\r
+ if self.debug:\r
+ log.msg("received HTTP response:\n\n%s\n\n" % self.http_response_data)\r
+\r
+ ## extract HTTP status line and headers\r
+ ##\r
+ (self.http_status_line, self.http_headers, http_headers_cnt) = parseHttpHeader(self.http_response_data)\r
+\r
+ ## validate WebSocket opening handshake server response\r
+ ##\r
+ if self.debug:\r
+ log.msg("received HTTP status line in opening handshake : %s" % str(self.http_status_line))\r
+ log.msg("received HTTP headers in opening handshake : %s" % str(self.http_headers))\r
+\r
+ ## Response Line\r
+ ##\r
+ sl = self.http_status_line.split()\r
+ if len(sl) < 2:\r
+ return self.failHandshake("Bad HTTP response status line '%s'" % self.http_status_line)\r
+\r
+ ## HTTP version\r
+ ##\r
+ http_version = sl[0].strip()\r
+ if http_version != "HTTP/1.1":\r
+ return self.failHandshake("Unsupported HTTP version ('%s')" % http_version)\r
+\r
+ ## HTTP status code\r
+ ##\r
+ try:\r
+ status_code = int(sl[1].strip())\r
+ except:\r
+ return self.failHandshake("Bad HTTP status code ('%s')" % sl[1].strip())\r
+ if status_code != HTTP_STATUS_CODE_SWITCHING_PROTOCOLS[0]:\r
+\r
+ ## FIXME: handle redirects\r
+ ## FIXME: handle authentication required\r
+\r
+ if len(sl) > 2:\r
+ reason = " - %s" % sl[2].strip()\r
+ else:\r
+ reason = ""\r
+ return self.failHandshake("WebSockets connection upgrade failed (%d%s)" % (status_code, reason))\r
+\r
+ ## Upgrade\r
+ ##\r
+ if not self.http_headers.has_key("upgrade"):\r
+ return self.failHandshake("HTTP Upgrade header missing")\r
+ if self.http_headers["upgrade"].strip().lower() != "websocket":\r
+ return self.failHandshake("HTTP Upgrade header different from 'websocket' (case-insensitive) : %s" % self.http_headers["upgrade"])\r
+\r
+ ## Connection\r
+ ##\r
+ if not self.http_headers.has_key("connection"):\r
+ return self.failHandshake("HTTP Connection header missing")\r
+ connectionUpgrade = False\r
+ for c in self.http_headers["connection"].split(","):\r
+ if c.strip().lower() == "upgrade":\r
+ connectionUpgrade = True\r
+ break\r
+ if not connectionUpgrade:\r
+ return self.failHandshake("HTTP Connection header does not include 'upgrade' value (case-insensitive) : %s" % self.http_headers["connection"])\r
+\r
+ ## compute Sec-WebSocket-Accept\r
+ ##\r
+ if self.version != 0:\r
+ if not self.http_headers.has_key("sec-websocket-accept"):\r
+ return self.failHandshake("HTTP Sec-WebSocket-Accept header missing in opening handshake reply")\r
+ else:\r
+ if http_headers_cnt["sec-websocket-accept"] > 1:\r
+ return self.failHandshake("HTTP Sec-WebSocket-Accept header appears more than once in opening handshake reply")\r
+ sec_websocket_accept_got = self.http_headers["sec-websocket-accept"].strip()\r
+\r
+ sha1 = hashlib.sha1()\r
+ sha1.update(self.websocket_key + WebSocketProtocol.WS_MAGIC)\r
+ sec_websocket_accept = base64.b64encode(sha1.digest())\r
+\r
+ if sec_websocket_accept_got != sec_websocket_accept:\r
+ return self.failHandshake("HTTP Sec-WebSocket-Accept bogus value : expected %s / got %s" % (sec_websocket_accept, sec_websocket_accept_got))\r
+\r
+ ## handle "extensions in use" - if any\r
+ ##\r
+ self.websocket_extensions_in_use = []\r
+ if self.version != 0:\r
+ if self.http_headers.has_key("sec-websocket-extensions"):\r
+ if http_headers_cnt["sec-websocket-extensions"] > 1:\r
+ return self.failHandshake("HTTP Sec-WebSocket-Extensions header appears more than once in opening handshake reply")\r
+ exts = self.http_headers["sec-websocket-extensions"].strip()\r
+ ##\r
+ ## we don't support any extension, but if we did, we needed\r
+ ## to set self.websocket_extensions_in_use here, and don't fail the handshake\r
+ ##\r
+ return self.failHandshake("server wants to use extensions (%s), but no extensions implemented" % exts)\r
+\r
+ ## handle "subprotocol in use" - if any\r
+ ##\r
+ self.websocket_protocol_in_use = None\r
+ if self.http_headers.has_key("sec-websocket-protocol"):\r
+ if http_headers_cnt["sec-websocket-protocol"] > 1:\r
+ return self.failHandshake("HTTP Sec-WebSocket-Protocol header appears more than once in opening handshake reply")\r
+ sp = str(self.http_headers["sec-websocket-protocol"].strip())\r
+ if sp != "":\r
+ if sp not in self.factory.protocols:\r
+ return self.failHandshake("subprotocol selected by server (%s) not in subprotocol list requested by client (%s)" % (sp, str(self.factory.protocols)))\r
+ else:\r
+ ## ok, subprotocol in use\r
+ ##\r
+ self.websocket_protocol_in_use = sp\r
+\r
+\r
+ ## For Hixie-76, we need 16 octets of HTTP request body to complete HS!\r
+ ##\r
+ if self.version == 0:\r
+ if len(self.data) < end_of_header + 4 + 16:\r
+ return\r
+ else:\r
+ challenge_response = self.data[end_of_header + 4:end_of_header + 4 + 16]\r
+ if challenge_response != self.websocket_expected_challenge_response:\r
+ return self.failHandshake("invalid challenge response received from server (Hixie-76)")\r
+\r
+ ## Ok, got complete HS input, remember rest (if any)\r
+ ##\r
+ if self.version == 0:\r
+ self.data = self.data[end_of_header + 4 + 16:]\r
+ else:\r
+ self.data = self.data[end_of_header + 4:]\r
+\r
+ ## opening handshake completed, move WebSockets connection into OPEN state\r
+ ##\r
+ self.state = WebSocketProtocol.STATE_OPEN\r
+ self.inside_message = False\r
+ if self.version != 0:\r
+ self.current_frame = None\r
+ self.websocket_version = self.version\r
+\r
+ ## we handle this symmetrical to server-side .. that is, give the\r
+ ## client a chance to bail out .. i.e. on no subprotocol selected\r
+ ## by server\r
+ try:\r
+ connectionResponse = ConnectionResponse(self.peer,\r
+ self.peerstr,\r
+ self.http_headers,\r
+ None, # FIXME\r
+ self.websocket_protocol_in_use,\r
+ self.websocket_extensions_in_use)\r
+\r
+ self.onConnect(connectionResponse)\r
+\r
+ except Exception, e:\r
+ ## immediately close the WS connection\r
+ ##\r
+ self.failConnection(1000, str(e))\r
+ else:\r
+ ## fire handler on derived class\r
+ ##\r
+ self.onOpen()\r
+\r
+ ## process rest, if any\r
+ ##\r
+ if len(self.data) > 0:\r
+ self.consumeData()\r
+\r
+\r
+ def failHandshake(self, reason):\r
+ """\r
+ During opening handshake the server response is invalid and we drop the\r
+ connection.\r
+ """\r
+ if self.debug:\r
+ log.msg("failing WebSockets opening handshake ('%s')" % reason)\r
+ self.dropConnection(abort = True)\r
+\r
+\r
+class WebSocketClientFactory(protocol.ClientFactory, WebSocketFactory):\r
+ """\r
+ A Twisted factory for WebSockets client protocols.\r
+ """\r
+\r
+ protocol = WebSocketClientProtocol\r
+ """\r
+ The protocol to be spoken. Must be derived from :class:`autobahn.websocket.WebSocketClientProtocol`.\r
+ """\r
+\r
+\r
+ def __init__(self,\r
+\r
+ ## WebSockect session parameters\r
+ url = None,\r
+ origin = None,\r
+ protocols = [],\r
+ useragent = "AutobahnPython/%s" % autobahn.version,\r
+\r
+ ## debugging\r
+ debug = False,\r
+ debugCodePaths = False):\r
+ """\r
+ Create instance of WebSocket client factory.\r
+\r
+ Note that you MUST set URL either here or using setSessionParameters() _before_ the factory is started.\r
+\r
+ :param url: WebSocket URL to connect to - ("ws:" | "wss:") "//" host [ ":" port ] path [ "?" query ].\r
+ :type url: str\r
+ :param origin: The origin to be sent in WebSockets opening handshake or None (default: None).\r
+ :type origin: str\r
+ :param protocols: List of subprotocols the client should announce in WebSockets opening handshake (default: []).\r
+ :type protocols: list of strings\r
+ :param useragent: User agent as announced in HTTP request header or None (default: "AutobahnWebSockets/x.x.x").\r
+ :type useragent: str\r
+ :param debug: Debug mode (default: False).\r
+ :type debug: bool\r
+ :param debugCodePaths: Debug code paths mode (default: False).\r
+ :type debugCodePaths: bool\r
+ """\r
+ self.debug = debug\r
+ self.debugCodePaths = debugCodePaths\r
+\r
+ self.logOctets = debug\r
+ self.logFrames = debug\r
+\r
+ self.isServer = False\r
+\r
+ ## seed RNG which is used for WS opening handshake key and WS frame masks generation\r
+ random.seed()\r
+\r
+ ## default WS session parameters\r
+ ##\r
+ self.setSessionParameters(url, origin, protocols, useragent)\r
+\r
+ ## default WebSocket protocol options\r
+ ##\r
+ self.resetProtocolOptions()\r
+\r
+\r
+ def setSessionParameters(self, url = None, origin = None, protocols = [], useragent = None):\r
+ """\r
+ Set WebSocket session parameters.\r
+\r
+ :param url: WebSocket URL to connect to - ("ws:" | "wss:") "//" host [ ":" port ] path [ "?" query ].\r
+ :type url: str\r
+ :param origin: The origin to be sent in opening handshake.\r
+ :type origin: str\r
+ :param protocols: List of WebSocket subprotocols the client should announce in opening handshake.\r
+ :type protocols: list of strings\r
+ :param useragent: User agent as announced in HTTP request header during opening handshake.\r
+ :type useragent: str\r
+ """\r
+ if url is not None:\r
+ ## parse WebSocket URI into components\r
+ (isSecure, host, port, resource, path, params) = parseWsUrl(url)\r
+ self.url = url\r
+ self.isSecure = isSecure\r
+ self.host = host\r
+ self.port = port\r
+ self.resource = resource\r
+ self.path = path\r
+ self.params = params\r
+ else:\r
+ self.url = None\r
+ self.isSecure = None\r
+ self.host = None\r
+ self.port = None\r
+ self.resource = None\r
+ self.path = None\r
+ self.params = None\r
+\r
+ self.origin = origin\r
+ self.protocols = protocols\r
+ self.useragent = useragent\r
+\r
+\r
+ def resetProtocolOptions(self):\r
+ """\r
+ Reset all WebSocket protocol options to defaults.\r
+ """\r
+ self.version = WebSocketProtocol.DEFAULT_SPEC_VERSION\r
+ self.allowHixie76 = WebSocketProtocol.DEFAULT_ALLOW_HIXIE76\r
+ self.utf8validateIncoming = True\r
+ self.acceptMaskedServerFrames = False\r
+ self.maskClientFrames = True\r
+ self.applyMask = True\r
+ self.maxFramePayloadSize = 0\r
+ self.maxMessagePayloadSize = 0\r
+ self.autoFragmentSize = 0\r
+ self.failByDrop = True\r
+ self.echoCloseCodeReason = False\r
+ self.serverConnectionDropTimeout = 1\r
+ self.openHandshakeTimeout = 5\r
+ self.closeHandshakeTimeout = 1\r
+ self.tcpNoDelay = True\r
+\r
+\r
+ def setProtocolOptions(self,\r
+ version = None,\r
+ allowHixie76 = None,\r
+ utf8validateIncoming = None,\r
+ acceptMaskedServerFrames = None,\r
+ maskClientFrames = None,\r
+ applyMask = None,\r
+ maxFramePayloadSize = None,\r
+ maxMessagePayloadSize = None,\r
+ autoFragmentSize = None,\r
+ failByDrop = None,\r
+ echoCloseCodeReason = None,\r
+ serverConnectionDropTimeout = None,\r
+ openHandshakeTimeout = None,\r
+ closeHandshakeTimeout = None,\r
+ tcpNoDelay = None):\r
+ """\r
+ Set WebSocket protocol options used as defaults for _new_ protocol instances.\r
+\r
+ :param version: The WebSockets protocol spec (draft) version to be used (default: WebSocketProtocol.DEFAULT_SPEC_VERSION).\r
+ :type version: int\r
+ :param allowHixie76: Allow to speak Hixie76 protocol version.\r
+ :type allowHixie76: bool\r
+ :param utf8validateIncoming: Validate incoming UTF-8 in text message payloads (default: True).\r
+ :type utf8validateIncoming: bool\r
+ :param acceptMaskedServerFrames: Accept masked server-to-client frames (default: False).\r
+ :type acceptMaskedServerFrames: bool\r
+ :param maskClientFrames: Mask client-to-server frames (default: True).\r
+ :type maskClientFrames: bool\r
+ :param applyMask: Actually apply mask to payload when mask it present. Applies for outgoing and incoming frames (default: True).\r
+ :type applyMask: bool\r
+ :param maxFramePayloadSize: Maximum frame payload size that will be accepted when receiving or 0 for unlimited (default: 0).\r
+ :type maxFramePayloadSize: int\r
+ :param maxMessagePayloadSize: Maximum message payload size (after reassembly of fragmented messages) that will be accepted when receiving or 0 for unlimited (default: 0).\r
+ :type maxMessagePayloadSize: int\r
+ :param autoFragmentSize: Automatic fragmentation of outgoing data messages (when using the message-based API) into frames with payload length <= this size or 0 for no auto-fragmentation (default: 0).\r
+ :type autoFragmentSize: int\r
+ :param failByDrop: Fail connections by dropping the TCP connection without performing closing handshake (default: True).\r
+ :type failbyDrop: bool\r
+ :param echoCloseCodeReason: Iff true, when receiving a close, echo back close code/reason. Otherwise reply with code == NORMAL, reason = "" (default: False).\r
+ :type echoCloseCodeReason: bool\r
+ :param serverConnectionDropTimeout: When the client expects the server to drop the TCP, timeout in seconds (default: 1).\r
+ :type serverConnectionDropTimeout: float\r
+ :param openHandshakeTimeout: Opening WebSocket handshake timeout, timeout in seconds or 0 to deactivate (default: 0).\r
+ :type openHandshakeTimeout: float\r
+ :param closeHandshakeTimeout: When we expect to receive a closing handshake reply, timeout in seconds (default: 1).\r
+ :type closeHandshakeTimeout: float\r
+ :param tcpNoDelay: TCP NODELAY ("Nagle") socket option (default: True).\r
+ :type tcpNoDelay: bool\r
+ """\r
+ if allowHixie76 is not None and allowHixie76 != self.allowHixie76:\r
+ self.allowHixie76 = allowHixie76\r
+\r
+ if version is not None:\r
+ if version not in WebSocketProtocol.SUPPORTED_SPEC_VERSIONS:\r
+ raise Exception("invalid WebSockets draft version %s (allowed values: %s)" % (version, str(WebSocketProtocol.SUPPORTED_SPEC_VERSIONS)))\r
+ if version == 0 and not self.allowHixie76:\r
+ raise Exception("use of Hixie-76 requires allowHixie76 == True")\r
+ if version != self.version:\r
+ self.version = version\r
+\r
+ if utf8validateIncoming is not None and utf8validateIncoming != self.utf8validateIncoming:\r
+ self.utf8validateIncoming = utf8validateIncoming\r
+\r
+ if acceptMaskedServerFrames is not None and acceptMaskedServerFrames != self.acceptMaskedServerFrames:\r
+ self.acceptMaskedServerFrames = acceptMaskedServerFrames\r
+\r
+ if maskClientFrames is not None and maskClientFrames != self.maskClientFrames:\r
+ self.maskClientFrames = maskClientFrames\r
+\r
+ if applyMask is not None and applyMask != self.applyMask:\r
+ self.applyMask = applyMask\r
+\r
+ if maxFramePayloadSize is not None and maxFramePayloadSize != self.maxFramePayloadSize:\r
+ self.maxFramePayloadSize = maxFramePayloadSize\r
+\r
+ if maxMessagePayloadSize is not None and maxMessagePayloadSize != self.maxMessagePayloadSize:\r
+ self.maxMessagePayloadSize = maxMessagePayloadSize\r
+\r
+ if autoFragmentSize is not None and autoFragmentSize != self.autoFragmentSize:\r
+ self.autoFragmentSize = autoFragmentSize\r
+\r
+ if failByDrop is not None and failByDrop != self.failByDrop:\r
+ self.failByDrop = failByDrop\r
+\r
+ if echoCloseCodeReason is not None and echoCloseCodeReason != self.echoCloseCodeReason:\r
+ self.echoCloseCodeReason = echoCloseCodeReason\r
+\r
+ if serverConnectionDropTimeout is not None and serverConnectionDropTimeout != self.serverConnectionDropTimeout:\r
+ self.serverConnectionDropTimeout = serverConnectionDropTimeout\r
+\r
+ if openHandshakeTimeout is not None and openHandshakeTimeout != self.openHandshakeTimeout:\r
+ self.openHandshakeTimeout = openHandshakeTimeout\r
+\r
+ if closeHandshakeTimeout is not None and closeHandshakeTimeout != self.closeHandshakeTimeout:\r
+ self.closeHandshakeTimeout = closeHandshakeTimeout\r
+\r
+ if tcpNoDelay is not None and tcpNoDelay != self.tcpNoDelay:\r
+ self.tcpNoDelay = tcpNoDelay\r
+\r
+\r
+ def clientConnectionFailed(self, connector, reason):\r
+ """\r
+ Called by Twisted when the connection to server has failed. Default implementation\r
+ does nothing. Override in derived class when appropriate.\r
+ """\r
+ pass\r
+\r
+\r
+ def clientConnectionLost(self, connector, reason):\r
+ """\r
+ Called by Twisted when the connection to server was lost. Default implementation\r
+ does nothing. Override in derived class when appropriate.\r
+ """\r
+ pass\r