From feb7aeec48a2bacbba9fdd9c5e6789d37afc66b3 Mon Sep 17 00:00:00 2001 From: HawkOwl Date: Tue, 15 Sep 2015 13:50:50 +0800 Subject: [PATCH 01/59] polish up some spacings --- autobahn/websocket/protocol.py | 78 +++++++++++++++++----------------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/autobahn/websocket/protocol.py b/autobahn/websocket/protocol.py index 38a831de..17661a5b 100755 --- a/autobahn/websocket/protocol.py +++ b/autobahn/websocket/protocol.py @@ -486,65 +486,67 @@ class WebSocketProtocol(object): SUPPORTED_SPEC_VERSIONS = [0, 10, 11, 12, 13, 14, 15, 16, 17, 18] """ - WebSocket protocol spec (draft) versions supported by this implementation. - Use of version 18 indicates RFC6455. Use of versions < 18 indicate actual - draft spec versions (Hybi-Drafts). Use of version 0 indicates Hixie-76. - """ + WebSocket protocol spec (draft) versions supported by this implementation. + Use of version 18 indicates RFC6455. Use of versions < 18 indicate actual + draft spec versions (Hybi-Drafts). Use of version 0 indicates Hixie-76. + """ SUPPORTED_PROTOCOL_VERSIONS = [0, 8, 13] """ - WebSocket protocol versions supported by this implementation. For Hixie-76, - there is no protocol version announced in HTTP header, and we just use the - draft version (0) in this case. - """ + WebSocket protocol versions supported by this implementation. For Hixie-76, + there is no protocol version announced in HTTP header, and we just use the + draft version (0) in this case. + """ SPEC_TO_PROTOCOL_VERSION = {0: 0, 10: 8, 11: 8, 12: 8, 13: 13, 14: 13, 15: 13, 16: 13, 17: 13, 18: 13} """ - Mapping from protocol spec (draft) version to protocol version. For Hixie-76, - there is no protocol version announced in HTTP header, and we just use the - pseudo protocol version 0 in this case. - """ + Mapping from protocol spec (draft) version to protocol version. For + Hixie-76, there is no protocol version announced in HTTP header, and we + just use the pseudo protocol version 0 in this case. + """ PROTOCOL_TO_SPEC_VERSION = {0: 0, 8: 12, 13: 18} """ - Mapping from protocol version to the latest protocol spec (draft) version - using that protocol version. For Hixie-76, there is no protocol version - announced in HTTP header, and we just use the draft version (0) in this case. - """ + Mapping from protocol version to the latest protocol spec (draft) version + using that protocol version. For Hixie-76, there is no protocol version + announced in HTTP header, and we just use the draft version (0) in this case. + """ DEFAULT_SPEC_VERSION = 18 """ - Default WebSocket protocol spec version this implementation speaks: final RFC6455. - """ + Default WebSocket protocol spec version this implementation speaks: final + RFC6455. + """ DEFAULT_ALLOW_HIXIE76 = False """ - By default, this implementation will not allow to speak the obsoleted - Hixie-76 protocol version. That protocol version has security issues, but - is still spoken by some clients. Enable at your own risk! Enabling can be - done by using setProtocolOptions() on the factories for clients and servers. - """ + By default, this implementation will not allow to speak the obsoleted + Hixie-76 protocol version. That protocol version has security issues, but + is still spoken by some clients. Enable at your own risk! Enabling can be + done by using setProtocolOptions() on the factories for clients and + servers. + """ _WS_MAGIC = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11" """ - Protocol defined magic used during WebSocket handshake (used in Hybi-drafts - and final RFC6455. - """ + Protocol defined magic used during WebSocket handshake (used in Hybi-drafts + and final RFC6455. + """ _QUEUED_WRITE_DELAY = 0.00001 """ - For synched/chopped writes, this is the reactor reentry delay in seconds. - """ + For synched/chopped writes, this is the reactor reentry delay in seconds. + """ MESSAGE_TYPE_TEXT = 1 """ - WebSocket text message type (UTF-8 payload). - """ + WebSocket text message type (UTF-8 payload). + """ MESSAGE_TYPE_BINARY = 2 """ - WebSocket binary message type (arbitrary binary payload). - """ + WebSocket binary message type (arbitrary binary payload). + """ # WebSocket protocol state: # (STATE_PROXY_CONNECTING) => STATE_CONNECTING => STATE_OPEN => STATE_CLOSING => STATE_CLOSED @@ -633,8 +635,8 @@ class WebSocketProtocol(object): 'autoPingTimeout', 'autoPingSize'] """ - Configuration attributes common to servers and clients. - """ + Configuration attributes common to servers and clients. + """ CONFIG_ATTRS_SERVER = ['versions', 'webStatus', @@ -647,8 +649,8 @@ class WebSocketProtocol(object): 'allowedOriginsPatterns', 'maxConnections'] """ - Configuration attributes specific to servers. - """ + Configuration attributes specific to servers. + """ CONFIG_ATTRS_CLIENT = ['version', 'acceptMaskedServerFrames', @@ -657,8 +659,8 @@ class WebSocketProtocol(object): 'perMessageCompressionOffers', 'perMessageCompressionAccept'] """ - Configuration attributes specific to clients. - """ + Configuration attributes specific to clients. + """ def __init__(self): #: a Future/Deferred that fires when we hit STATE_CLOSED From ab19bc4aa44dc4c11a4a80c603b4e05b764b95d1 Mon Sep 17 00:00:00 2001 From: HawkOwl Date: Tue, 15 Sep 2015 14:16:29 +0800 Subject: [PATCH 02/59] remove hixie --- autobahn/websocket/protocol.py | 684 ++++++++------------------------- 1 file changed, 159 insertions(+), 525 deletions(-) diff --git a/autobahn/websocket/protocol.py b/autobahn/websocket/protocol.py index 17661a5b..10470b90 100755 --- a/autobahn/websocket/protocol.py +++ b/autobahn/websocket/protocol.py @@ -484,32 +484,27 @@ class WebSocketProtocol(object): * :class:`autobahn.websocket.interfaces.IWebSocketChannelStreamingApi` """ - SUPPORTED_SPEC_VERSIONS = [0, 10, 11, 12, 13, 14, 15, 16, 17, 18] + SUPPORTED_SPEC_VERSIONS = [10, 11, 12, 13, 14, 15, 16, 17, 18] """ WebSocket protocol spec (draft) versions supported by this implementation. Use of version 18 indicates RFC6455. Use of versions < 18 indicate actual - draft spec versions (Hybi-Drafts). Use of version 0 indicates Hixie-76. + draft spec versions (Hybi-Drafts). """ - SUPPORTED_PROTOCOL_VERSIONS = [0, 8, 13] + SUPPORTED_PROTOCOL_VERSIONS = [8, 13] """ - WebSocket protocol versions supported by this implementation. For Hixie-76, - there is no protocol version announced in HTTP header, and we just use the - draft version (0) in this case. + WebSocket protocol versions supported by this implementation. """ - SPEC_TO_PROTOCOL_VERSION = {0: 0, 10: 8, 11: 8, 12: 8, 13: 13, 14: 13, 15: 13, 16: 13, 17: 13, 18: 13} + SPEC_TO_PROTOCOL_VERSION = {10: 8, 11: 8, 12: 8, 13: 13, 14: 13, 15: 13, 16: 13, 17: 13, 18: 13} """ - Mapping from protocol spec (draft) version to protocol version. For - Hixie-76, there is no protocol version announced in HTTP header, and we - just use the pseudo protocol version 0 in this case. + Mapping from protocol spec (draft) version to protocol version. """ - PROTOCOL_TO_SPEC_VERSION = {0: 0, 8: 12, 13: 18} + PROTOCOL_TO_SPEC_VERSION = {8: 12, 13: 18} """ Mapping from protocol version to the latest protocol spec (draft) version - using that protocol version. For Hixie-76, there is no protocol version - announced in HTTP header, and we just use the draft version (0) in this case. + using that protocol version. """ DEFAULT_SPEC_VERSION = 18 @@ -518,15 +513,6 @@ class WebSocketProtocol(object): RFC6455. """ - DEFAULT_ALLOW_HIXIE76 = False - """ - By default, this implementation will not allow to speak the obsoleted - Hixie-76 protocol version. That protocol version has security issues, but - is still spoken by some clients. Enable at your own risk! Enabling can be - done by using setProtocolOptions() on the factories for clients and - servers. - """ - _WS_MAGIC = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11" """ Protocol defined magic used during WebSocket handshake (used in Hybi-drafts @@ -620,7 +606,6 @@ class WebSocketProtocol(object): 'logOctets', 'logFrames', 'trackTimings', - 'allowHixie76', 'utf8validateIncoming', 'applyMask', 'maxFramePayloadSize', @@ -788,11 +773,7 @@ class WebSocketProtocol(object): the TCP connection either immediately (when we are a server) or after a timeout (when we are a client and expect the server to drop the TCP). - Modes: Hybi, Hixie - - Notes: - - For Hixie mode, this method is slightly misnamed for historic reasons. - - For Hixie mode, code and reasonRaw are silently ignored. + Modes: Hybi :param code: Close status code, if there was one (:class:`WebSocketProtocol`.CLOSE_STATUS_CODE_*). :type code: int or None @@ -891,7 +872,7 @@ class WebSocketProtocol(object): but it didn't (in time self.serverConnectionDropTimeout). So we drop the connection, but set self.wasClean = False. - Modes: Hybi, Hixie + Modes: Hybi """ self.serverConnectionDropTimeoutCall = None if self.state != WebSocketProtocol.STATE_CLOSED: @@ -911,7 +892,7 @@ class WebSocketProtocol(object): It didn't do so (in time self.openHandshakeTimeout). So we drop the connection, but set self.wasClean = False. - Modes: Hybi, Hixie + Modes: Hybi """ self.openHandshakeTimeoutCall = None if self.state in [WebSocketProtocol.STATE_CONNECTING, WebSocketProtocol.STATE_PROXY_CONNECTING]: @@ -940,7 +921,7 @@ class WebSocketProtocol(object): respond (in time self.closeHandshakeTimeout) with a close response frame though. So we drop the connection, but set self.wasClean = False. - Modes: Hybi, Hixie + Modes: Hybi """ self.closeHandshakeTimeoutCall = None if self.state != WebSocketProtocol.STATE_CLOSED: @@ -969,7 +950,7 @@ class WebSocketProtocol(object): """ Drop the underlying TCP connection. - Modes: Hybi, Hixie + Modes: Hybi """ if self.state != WebSocketProtocol.STATE_CLOSED: if self.debugCodePaths: @@ -991,10 +972,7 @@ class WebSocketProtocol(object): """ Fails the WebSocket connection. - Modes: Hybi, Hixie - - Notes: - - For Hixie mode, the code and reason are silently ignored. + Modes: Hybi """ if self.state != WebSocketProtocol.STATE_CLOSED: if self.debugCodePaths: @@ -1025,10 +1003,7 @@ class WebSocketProtocol(object): """ Fired when a WebSocket protocol violation/error occurs. - Modes: Hybi, Hixie - - Notes: - - For Hixie mode, reason is silently ignored. + Modes: Hybi :param reason: Protocol violation that was encountered (human readable). :type reason: str @@ -1051,10 +1026,7 @@ class WebSocketProtocol(object): for text message when payload is invalid UTF-8 or close frames with close reason that is invalid UTF-8. - Modes: Hybi, Hixie - - Notes: - - For Hixie mode, reason is silently ignored. + Modes: Hybi :param reason: What was invalid for the payload (human readable). :type reason: str @@ -1090,7 +1062,7 @@ class WebSocketProtocol(object): This is called by network framework when a new TCP connection has been established and handed over to a Protocol instance (an instance of this class). - Modes: Hybi, Hixie + Modes: Hybi """ # copy default options from factory (so we are not affected by changed on @@ -1207,7 +1179,7 @@ class WebSocketProtocol(object): """ This is called by network framework when a transport connection was lost. - Modes: Hybi, Hixie + Modes: Hybi """ # cancel any server connection drop timer if present # @@ -1252,7 +1224,7 @@ class WebSocketProtocol(object): """ Hook fired right after raw octets have been received, but only when self.logOctets == True. - Modes: Hybi, Hixie + Modes: Hybi """ self.factory.log.debug("RX Octets from %s : octets = %s" % (self.peer, binascii.b2a_hex(data))) @@ -1260,7 +1232,7 @@ class WebSocketProtocol(object): """ Hook fired right after raw octets have been sent, but only when self.logOctets == True. - Modes: Hybi, Hixie + Modes: Hybi """ self.factory.log.debug("TX Octets to %s : sync = %s, octets = %s" % (self.peer, sync, binascii.b2a_hex(data))) @@ -1304,7 +1276,7 @@ class WebSocketProtocol(object): """ This is called by network framework upon receiving data on transport connection. - Modes: Hybi, Hixie + Modes: Hybi """ if self.state == WebSocketProtocol.STATE_OPEN: self.trafficStats.incomingOctetsWireLevel += len(data) @@ -1320,7 +1292,7 @@ class WebSocketProtocol(object): """ Consume buffered (incoming) data. - Modes: Hybi, Hixie + Modes: Hybi """ # WebSocket is open (handshake was completed) or close was sent @@ -1366,7 +1338,7 @@ class WebSocketProtocol(object): """ Process proxy connect. - Modes: Hybi, Hixie + Modes: Hybi """ raise Exception("must implement proxy connect (client or server) in derived class") @@ -1374,7 +1346,7 @@ class WebSocketProtocol(object): """ Process WebSocket handshake. - Modes: Hybi, Hixie + Modes: Hybi """ raise Exception("must implement handshake (client or server) in derived class") @@ -1382,7 +1354,7 @@ class WebSocketProtocol(object): """ Trigger sending stuff from send queue (which is only used for chopped/synched writes). - Modes: Hybi, Hixie + Modes: Hybi """ if not self.triggered: self.triggered = True @@ -1393,7 +1365,7 @@ class WebSocketProtocol(object): Send out stuff from send queue. For details how this works, see test/trickling in the repo. - Modes: Hybi, Hixie + Modes: Hybi """ if len(self.send_queue) > 0: e = self.send_queue.popleft() @@ -1430,7 +1402,7 @@ class WebSocketProtocol(object): to WebSocket data message fragmentation. Note that this is also different from the TcpNoDelay option which can be set on the socket. - Modes: Hybi, Hixie + Modes: Hybi """ if chopsize and chopsize > 0: i = 0 @@ -1463,101 +1435,15 @@ class WebSocketProtocol(object): """ Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.sendPreparedMessage` """ - if self.websocket_version != 0: - if self._perMessageCompress is None or preparedMsg.doNotCompress: - self.sendData(preparedMsg.payloadHybi) - else: - self.sendMessage(preparedMsg.payload, preparedMsg.binary) + if self._perMessageCompress is None or preparedMsg.doNotCompress: + self.sendData(preparedMsg.payloadHybi) else: - self.sendData(preparedMsg.payloadHixie) + self.sendMessage(preparedMsg.payload, preparedMsg.binary) def processData(self): """ - After WebSocket handshake has been completed, this procedure will do all - subsequent processing of incoming bytes. - - Modes: Hybi, Hixie - """ - if self.websocket_version == 0: - return self.processDataHixie76() - else: - return self.processDataHybi() - - def processDataHixie76(self): - """ - Hixie-76 incoming data processing. - - Modes: Hixie - """ - buffered_len = len(self.data) - - # outside a message, that is we are awaiting data which starts a new message - # - if not self.inside_message: - if buffered_len >= 2: - - # new message - # - if self.data[0] == b'\x00': - - self.inside_message = True - - if self.utf8validateIncoming: - self.utf8validator.reset() - self.utf8validateIncomingCurrentMessage = True - self.utf8validateLast = (True, True, 0, 0) - else: - self.utf8validateIncomingCurrentMessage = False - - self.data = self.data[1:] - if self.trackedTimings: - self.trackedTimings.track("onMessageBegin") - self._onMessageBegin(False) - - # Hixie close from peer received - # - elif self.data[0] == b'\xff' and self.data[1] == b'\x00': - self.onCloseFrame(None, None) - self.data = self.data[2:] - # stop receiving/processing after having received close! - return False - - # malformed data - # - else: - if self.protocolViolation("malformed data received"): - return False - else: - # need more data - return False - - end_index = self.data.find(b'\xff') - if end_index > 0: - payload = self.data[:end_index] - self.data = self.data[end_index + 1:] - else: - payload = self.data - self.data = b'' - - # incrementally validate UTF-8 payload - # - if self.utf8validateIncomingCurrentMessage: - self.utf8validateLast = self.utf8validator.validate(payload) - if not self.utf8validateLast[0]: - if self.invalidPayload("encountered invalid UTF-8 while processing text message at payload octet index %d" % self.utf8validateLast[3]): - return False - - self._onMessageFrameData(payload) - - if end_index > 0: - self.inside_message = False - self._onMessageEnd() - - return len(self.data) > 0 - - def processDataHybi(self): - """ - RFC6455/Hybi-Drafts incoming data processing. + After WebSocket handshake has been completed, this procedure will do + all subsequent processing of incoming bytes. Modes: Hybi """ @@ -2017,9 +1903,6 @@ class WebSocketProtocol(object): Modes: Hybi """ - if self.websocket_version == 0: - raise Exception("function not supported in Hixie-76 mode") - if payload_len is not None: if len(payload) < 1: raise Exception("cannot construct repeated payload with length %d from payload of length %d" % (payload_len, len(payload))) @@ -2092,8 +1975,6 @@ class WebSocketProtocol(object): """ Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.sendPing` """ - if self.websocket_version == 0: - raise Exception("function not supported in Hixie-76 mode") if self.state != WebSocketProtocol.STATE_OPEN: return if payload: @@ -2124,8 +2005,6 @@ class WebSocketProtocol(object): """ Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.sendPong` """ - if self.websocket_version == 0: - raise Exception("function not supported in Hixie-76 mode") if self.state != WebSocketProtocol.STATE_OPEN: return if payload: @@ -2142,11 +2021,7 @@ class WebSocketProtocol(object): an internal method which deliberately allows not send close frame with invalid payload. - Modes: Hybi, Hixie - - Notes: - - For Hixie mode, this method is slightly misnamed for historic reasons. - - For Hixie mode, code and reasonUtf8 will be silently ignored. + Modes: Hybi """ if self.state == WebSocketProtocol.STATE_CLOSING: if self.debugCodePaths: @@ -2161,16 +2036,13 @@ class WebSocketProtocol(object): elif self.state == WebSocketProtocol.STATE_OPEN: - if self.websocket_version == 0: - self.sendData("\xff\x00") - else: - # construct Hybi close frame payload and send frame - payload = b'' - if code is not None: - payload += struct.pack("!H", code) - if reasonUtf8 is not None: - payload += reasonUtf8 - self.sendFrame(opcode=8, payload=payload) + # construct Hybi close frame payload and send frame + payload = b'' + if code is not None: + payload += struct.pack("!H", code) + if reasonUtf8 is not None: + payload += reasonUtf8 + self.sendFrame(opcode=8, payload=payload) # update state self.state = WebSocketProtocol.STATE_CLOSING @@ -2231,23 +2103,16 @@ class WebSocketProtocol(object): if self.send_state != WebSocketProtocol.SEND_STATE_GROUND: raise Exception("WebSocketProtocol.beginMessage invalid in current sending state") - if self.websocket_version == 0: - if isBinary: - raise Exception("cannot send binary message in Hixie76 mode") + self.send_message_opcode = WebSocketProtocol.MESSAGE_TYPE_BINARY if isBinary else WebSocketProtocol.MESSAGE_TYPE_TEXT + self.send_state = WebSocketProtocol.SEND_STATE_MESSAGE_BEGIN - self.sendData(b'\x00') - self.send_state = WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE + # setup compressor + # + if self._perMessageCompress is not None and not doNotCompress: + self.send_compressed = True + self._perMessageCompress.startCompressMessage() else: - self.send_message_opcode = WebSocketProtocol.MESSAGE_TYPE_BINARY if isBinary else WebSocketProtocol.MESSAGE_TYPE_TEXT - self.send_state = WebSocketProtocol.SEND_STATE_MESSAGE_BEGIN - - # setup compressor - # - if self._perMessageCompress is not None and not doNotCompress: - self.send_compressed = True - self._perMessageCompress.startCompressMessage() - else: - self.send_compressed = False + self.send_compressed = False self.trafficStats.outgoingWebSocketMessages += 1 @@ -2255,9 +2120,6 @@ class WebSocketProtocol(object): """ Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.beginMessageFrame` """ - if self.websocket_version == 0: - raise Exception("function not supported in Hixie-76 mode") - if self.state != WebSocketProtocol.STATE_OPEN: return @@ -2352,48 +2214,37 @@ class WebSocketProtocol(object): self.trafficStats.outgoingOctetsAppLevel += len(payload) self.trafficStats.outgoingOctetsWebSocketLevel += len(payload) - if self.websocket_version == 0: - # Hixie Mode - # - if self.send_state != WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE: - raise Exception("WebSocketProtocol.sendMessageFrameData invalid in current sending state") - self.sendData(payload, sync=sync) - return None + if self.send_state != WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE_FRAME: + raise Exception("WebSocketProtocol.sendMessageFrameData invalid in current sending state") + rl = len(payload) + if self.send_message_frame_masker.pointer() + rl > self.send_message_frame_length: + l = self.send_message_frame_length - self.send_message_frame_masker.pointer() + rest = -(rl - l) + pl = payload[:l] else: - # Hybi Mode - # - if self.send_state != WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE_FRAME: - raise Exception("WebSocketProtocol.sendMessageFrameData invalid in current sending state") + l = rl + rest = self.send_message_frame_length - self.send_message_frame_masker.pointer() - l + pl = payload - rl = len(payload) - if self.send_message_frame_masker.pointer() + rl > self.send_message_frame_length: - l = self.send_message_frame_length - self.send_message_frame_masker.pointer() - rest = -(rl - l) - pl = payload[:l] - else: - l = rl - rest = self.send_message_frame_length - self.send_message_frame_masker.pointer() - l - pl = payload + # mask frame payload + # + plm = self.send_message_frame_masker.process(pl) - # mask frame payload - # - plm = self.send_message_frame_masker.process(pl) + # send frame payload + # + self.sendData(plm, sync=sync) - # send frame payload - # - self.sendData(plm, sync=sync) + # if we are done with frame, move back into "inside message" state + # + if self.send_message_frame_masker.pointer() >= self.send_message_frame_length: + self.send_state = WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE - # if we are done with frame, move back into "inside message" state - # - if self.send_message_frame_masker.pointer() >= self.send_message_frame_length: - self.send_state = WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE - - # when =0 : frame was completed exactly - # when >0 : frame is still uncomplete and that much amount is still left to complete the frame - # when <0 : frame was completed and there was this much unconsumed data in payload argument - # - return rest + # when =0 : frame was completed exactly + # when >0 : frame is still uncomplete and that much amount is still left to complete the frame + # when <0 : frame was completed and there was this much unconsumed data in payload argument + # + return rest def endMessage(self): """ @@ -2407,16 +2258,13 @@ class WebSocketProtocol(object): # if self.send_state != WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE: # raise Exception("WebSocketProtocol.endMessage invalid in current sending state [%d]" % self.send_state) - if self.websocket_version == 0: - self.sendData(b'\x00') + if self.send_compressed: + payload = self._perMessageCompress.endCompressMessage() + self.trafficStats.outgoingOctetsWebSocketLevel += len(payload) else: - if self.send_compressed: - payload = self._perMessageCompress.endCompressMessage() - self.trafficStats.outgoingOctetsWebSocketLevel += len(payload) - else: - # send continuation frame with empty payload and FIN set to end message - payload = b'' - self.sendFrame(opcode=0, payload=payload, fin=True) + # send continuation frame with empty payload and FIN set to end message + payload = b'' + self.sendFrame(opcode=0, payload=payload, fin=True) self.send_state = WebSocketProtocol.SEND_STATE_GROUND @@ -2424,9 +2272,6 @@ class WebSocketProtocol(object): """ Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.sendMessageFrame` """ - if self.websocket_version == 0: - raise Exception("function not supported in Hixie-76 mode") - if self.state != WebSocketProtocol.STATE_OPEN: return @@ -2454,34 +2299,6 @@ class WebSocketProtocol(object): if self.trackedTimings: self.trackedTimings.track("sendMessage") - if self.websocket_version == 0: - if isBinary: - raise Exception("cannot send binary message in Hixie76 mode") - if fragmentSize: - raise Exception("cannot fragment messages in Hixie76 mode") - self.sendMessageHixie76(payload, sync) - else: - self.sendMessageHybi(payload, isBinary, fragmentSize, sync, doNotCompress) - - def sendMessageHixie76(self, payload, sync=False): - """ - Hixie76-Variant of sendMessage(). - - Modes: Hixie - """ - self.sendData(b'\x00' + payload + b'\xff', sync=sync) - - def sendMessageHybi(self, - payload, - isBinary=False, - fragmentSize=None, - sync=False, - doNotCompress=False): - """ - Hybi-Variant of sendMessage(). - - Modes: Hybi - """ # (initial) frame opcode # if isBinary: @@ -2620,22 +2437,6 @@ class PreparedMessage(object): self.binary = isBinary self.doNotCompress = doNotCompress - # store pre-framed octets to be sent to Hixie-76 peers - self._initHixie(payload, isBinary) - - # store pre-framed octets to be sent to Hybi peers - self._initHybi(payload, isBinary, applyMask) - - def _initHixie(self, payload, binary): - if binary: - # silently filter out .. probably do something else: - # base64? - # dunno - self.payloadHixie = b'' - else: - self.payloadHixie = b'\x00' + payload + b'\xff' - - def _initHybi(self, payload, binary, masked): l = len(payload) # first byte @@ -2763,12 +2564,6 @@ class WebSocketServerProtocol(WebSocketProtocol): def processProxyConnect(self): raise Exception("Autobahn isn't a proxy server") - def parseHixie76Key(self, key): - """ - Parse Hixie76 opening handshake key provided by client. - """ - return int(filter(lambda x: x.isdigit(), key)) / key.count(" ") - def processHandshake(self): """ Process WebSocket opening handshake request from client. @@ -2927,10 +2722,7 @@ class WebSocketServerProtocol(WebSocketProtocol): if 'sec-websocket-version' not in self.http_headers: if self.debugCodePaths: self.factory.log.debug("Hixie76 protocol detected") - if self.allowHixie76: - version = 0 - else: - return self.failHandshake("WebSocket connection denied - Hixie76 protocol mode disabled.") + return self.failHandshake("WebSocket connection denied - Hixie76 protocol not supported.") else: if self.debugCodePaths: self.factory.log.debug("Hybi protocol detected") @@ -2974,11 +2766,11 @@ class WebSocketServerProtocol(WebSocketProtocol): # Origin / Sec-WebSocket-Origin # http://tools.ietf.org/html/draft-ietf-websec-origin-02 # - if self.websocket_version < 13 and self.websocket_version != 0: + if self.websocket_version < 13: # Hybi, but only < Hybi-13 websocket_origin_header_key = 'sec-websocket-origin' else: - # RFC6455, >= Hybi-13 and Hixie + # RFC6455, >= Hybi-13 websocket_origin_header_key = "origin" self.websocket_origin = "" @@ -3000,74 +2792,40 @@ class WebSocketServerProtocol(WebSocketProtocol): if not origin_is_allowed: return self.failHandshake("WebSocket connection denied: origin '{0}' not allowed".format(self.websocket_origin)) - # Sec-WebSocket-Key (Hybi) or Sec-WebSocket-Key1/Sec-WebSocket-Key2 (Hixie-76) + # Sec-WebSocket-Key # - if self.websocket_version == 0: - for kk in ['Sec-WebSocket-Key1', 'Sec-WebSocket-Key2']: - k = kk.lower() - if k not in self.http_headers: - return self.failHandshake("HTTP %s header missing" % kk) - if http_headers_cnt[k] > 1: - return self.failHandshake("HTTP %s header appears more than once in opening handshake request" % kk) - try: - key1 = self.parseHixie76Key(self.http_headers["sec-websocket-key1"].strip()) - key2 = self.parseHixie76Key(self.http_headers["sec-websocket-key2"].strip()) - except: - return self.failHandshake("could not parse Sec-WebSocket-Key1/2") - else: - if 'sec-websocket-key' not in self.http_headers: - return self.failHandshake("HTTP Sec-WebSocket-Key header missing") - if http_headers_cnt["sec-websocket-key"] > 1: - return self.failHandshake("HTTP Sec-WebSocket-Key header appears more than once in opening handshake request") - key = self.http_headers["sec-websocket-key"].strip() - if len(key) != 24: # 16 bytes => (ceil(128/24)*24)/6 == 24 - return self.failHandshake("bad Sec-WebSocket-Key (length must be 24 ASCII chars) '%s'" % key) - if key[-2:] != "==": # 24 - ceil(128/6) == 2 - return self.failHandshake("bad Sec-WebSocket-Key (invalid base64 encoding) '%s'" % key) - for c in key[:-2]: - if c not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/": - return self.failHandshake("bad character '%s' in Sec-WebSocket-Key (invalid base64 encoding) '%s'" % (c, key)) + if 'sec-websocket-key' not in self.http_headers: + return self.failHandshake("HTTP Sec-WebSocket-Key header missing") + if http_headers_cnt["sec-websocket-key"] > 1: + return self.failHandshake("HTTP Sec-WebSocket-Key header appears more than once in opening handshake request") + key = self.http_headers["sec-websocket-key"].strip() + if len(key) != 24: # 16 bytes => (ceil(128/24)*24)/6 == 24 + return self.failHandshake("bad Sec-WebSocket-Key (length must be 24 ASCII chars) '%s'" % key) + if key[-2:] != "==": # 24 - ceil(128/6) == 2 + return self.failHandshake("bad Sec-WebSocket-Key (invalid base64 encoding) '%s'" % key) + for c in key[:-2]: + if c not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/": + return self.failHandshake("bad character '%s' in Sec-WebSocket-Key (invalid base64 encoding) '%s'" % (c, key)) # Sec-WebSocket-Extensions # self.websocket_extensions = [] if 'sec-websocket-extensions' in self.http_headers: - - if self.websocket_version == 0: - return self.failHandshake("HTTP Sec-WebSocket-Extensions header encountered for Hixie-76") + if http_headers_cnt["sec-websocket-extensions"] > 1: + return self.failHandshake("HTTP Sec-WebSocket-Extensions header appears more than once in opening handshake request") else: - if http_headers_cnt["sec-websocket-extensions"] > 1: - return self.failHandshake("HTTP Sec-WebSocket-Extensions header appears more than once in opening handshake request") - else: - # extensions requested/offered by client - # - self.websocket_extensions = self._parseExtensionsHeader(self.http_headers["sec-websocket-extensions"]) - - # For Hixie-76, we need 8 octets of HTTP request body to complete HS! - # - if self.websocket_version == 0: - if len(self.data) < end_of_header + 4 + 8: - return - else: - key3 = self.data[end_of_header + 4:end_of_header + 4 + 8] - if self.debug: - self.factory.log.debug("received HTTP request body containing key3 for Hixie-76: %s" % key3) + # extensions requested/offered by client + # + self.websocket_extensions = self._parseExtensionsHeader(self.http_headers["sec-websocket-extensions"]) # Ok, got complete HS input, remember rest (if any) # - if self.websocket_version == 0: - self.data = self.data[end_of_header + 4 + 8:] - else: - self.data = self.data[end_of_header + 4:] + self.data = self.data[end_of_header + 4:] # store WS key # - if self.websocket_version == 0: - # noinspection PyUnboundLocalVariable - self._wskey = (key1, key2, key3) - else: - # noinspection PyUnboundLocalVariable - self._wskey = key + # noinspection PyUnboundLocalVariable + self._wskey = key # DoS protection # @@ -3131,11 +2889,7 @@ class WebSocketServerProtocol(WebSocketProtocol): raise Exception("protocol accepted must be from the list client sent or None") self.websocket_protocol_in_use = protocol - - if self.websocket_version == 0: - key1, key2, key3 = self._wskey - else: - key = self._wskey + key = self._wskey # extensions effectively in use for this connection # @@ -3214,64 +2968,24 @@ class WebSocketServerProtocol(WebSocketProtocol): if self.websocket_protocol_in_use is not None: response += "Sec-WebSocket-Protocol: %s\x0d\x0a" % str(self.websocket_protocol_in_use) - if self.websocket_version == 0: - if self.websocket_origin: - # browser client provide the header, and expect it to be echo'ed - response += "Sec-WebSocket-Origin: %s\x0d\x0a" % str(self.websocket_origin) + # compute Sec-WebSocket-Accept + # + sha1 = hashlib.sha1() + # noinspection PyUnboundLocalVariable + sha1.update(key.encode('utf8') + WebSocketProtocol._WS_MAGIC) + sec_websocket_accept = base64.b64encode(sha1.digest()) - if self.debugCodePaths: - self.factory.log.debug('factory isSecure = %s port = %s' % (self.factory.isSecure, self.factory.externalPort)) + response += "Sec-WebSocket-Accept: %s\x0d\x0a" % sec_websocket_accept.decode() - if self.factory.externalPort and ((self.factory.isSecure and self.factory.externalPort != 443) or ((not self.factory.isSecure) and self.factory.externalPort != 80)): - if self.debugCodePaths: - self.factory.log.debug('factory running on non-default port') - response_port = ':' + str(self.factory.externalPort) - else: - if self.debugCodePaths: - self.factory.log.debug('factory running on default port') - response_port = '' + # agreed extensions + # + if len(extensionResponse) > 0: + response += "Sec-WebSocket-Extensions: %s\x0d\x0a" % ', '.join(extensionResponse) - # FIXME: check this! But see below .. - if False: - response_host = str(self.factory.host) - response_path = str(self.factory.path) - else: - response_host = str(self.http_request_host) - response_path = str(self.http_request_uri) - - location = "%s://%s%s%s" % ('wss' if self.factory.isSecure else 'ws', response_host, response_port, response_path) - - # Safari is very picky about this one - response += "Sec-WebSocket-Location: %s\x0d\x0a" % location - - # end of HTTP response headers - response += "\x0d\x0a" - - # compute accept body - # - # noinspection PyUnboundLocalVariable - accept_val = struct.pack(">II", key1, key2) + key3 - response_body = hashlib.md5(accept_val).digest() - - else: - # compute Sec-WebSocket-Accept - # - sha1 = hashlib.sha1() - # noinspection PyUnboundLocalVariable - sha1.update(key.encode('utf8') + WebSocketProtocol._WS_MAGIC) - sec_websocket_accept = base64.b64encode(sha1.digest()) - - response += "Sec-WebSocket-Accept: %s\x0d\x0a" % sec_websocket_accept.decode() - - # agreed extensions - # - if len(extensionResponse) > 0: - response += "Sec-WebSocket-Extensions: %s\x0d\x0a" % ', '.join(extensionResponse) - - # end of HTTP response headers - response += "\x0d\x0a" - response_body = None + # end of HTTP response headers + response += "\x0d\x0a" + response_body = None # send out opening handshake response # @@ -3303,8 +3017,7 @@ class WebSocketServerProtocol(WebSocketProtocol): # init state # self.inside_message = False - if self.websocket_version != 0: - self.current_frame = None + self.current_frame = None # automatic ping/pong # @@ -3422,13 +3135,13 @@ class WebSocketServerFactory(WebSocketFactory): protocol = WebSocketServerProtocol """ - The protocol to be spoken. Must be derived from :class:`autobahn.websocket.protocol.WebSocketServerProtocol`. - """ + The protocol to be spoken. Must be derived from :class:`autobahn.websocket.protocol.WebSocketServerProtocol`. + """ isServer = True """ - Flag indicating if this factory is client- or server-side. - """ + Flag indicating if this factory is client- or server-side. + """ def __init__(self, url=None, @@ -3531,7 +3244,6 @@ class WebSocketServerFactory(WebSocketFactory): Reset all WebSocket protocol options to defaults. """ self.versions = WebSocketProtocol.SUPPORTED_PROTOCOL_VERSIONS - self.allowHixie76 = WebSocketProtocol.DEFAULT_ALLOW_HIXIE76 self.webStatus = True self.utf8validateIncoming = True self.requireMaskedClientFrames = True @@ -3569,7 +3281,6 @@ class WebSocketServerFactory(WebSocketFactory): def setProtocolOptions(self, versions=None, - allowHixie76=None, webStatus=None, utf8validateIncoming=None, maskServerFrames=None, @@ -3596,8 +3307,6 @@ class WebSocketServerFactory(WebSocketFactory): :param versions: The WebSocket protocol versions accepted by the server (default: :func:`autobahn.websocket.protocol.WebSocketProtocol.SUPPORTED_PROTOCOL_VERSIONS`). :type versions: list of ints or None - :param allowHixie76: Allow to speak Hixie76 protocol version. - :type allowHixie76: bool or None :param webStatus: Return server status/version on HTTP/GET without WebSocket upgrade header (default: `True`). :type webStatus: bool or None :param utf8validateIncoming: Validate incoming UTF-8 in text message payloads (default: `True`). @@ -3644,15 +3353,10 @@ class WebSocketServerFactory(WebSocketFactory): :param maxConnections: Maximum number of concurrent connections. Set to `0` to disable (default: `0`). :type maxConnections: int or None """ - if allowHixie76 is not None and allowHixie76 != self.allowHixie76: - self.allowHixie76 = allowHixie76 - if versions is not None: for v in versions: if v not in WebSocketProtocol.SUPPORTED_PROTOCOL_VERSIONS: raise Exception("invalid WebSocket protocol version %s (allowed values: %s)" % (v, str(WebSocketProtocol.SUPPORTED_PROTOCOL_VERSIONS))) - if v == 0 and not self.allowHixie76: - raise Exception("use of Hixie-76 requires allowHixie76 == True") if set(versions) != set(self.versions): self.versions = versions @@ -3872,27 +3576,6 @@ class WebSocketClientProtocol(WebSocketProtocol): self.factory.log.debug("failing proxy connect ('%s')" % reason) self.dropConnection(abort=True) - def createHixieKey(self): - """ - Implements this algorithm: - - http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76#page-21 - Items 16 - 22 - """ - spaces1 = random.randint(1, 12) - max1 = int(4294967295 / spaces1) - number1 = random.randint(0, max1) - product1 = number1 * spaces1 - key1 = str(product1) - rchars = filter(lambda x: (0x21 <= x <= 0x2f) or (0x3a <= x <= 0x7e), range(0, 127)) - for i in xrange(random.randint(1, 12)): - p = random.randint(0, len(key1) - 1) - key1 = key1[:p] + chr(random.choice(rchars)) + key1[p:] - for i in xrange(spaces1): - p = random.randint(1, len(key1) - 2) - key1 = key1[:p] + ' ' + key1[p:] - return key1, number1 - def startHandshake(self): """ Start WebSocket opening handshake. @@ -3925,31 +3608,13 @@ class WebSocketClientProtocol(WebSocketProtocol): # handshake random key # - if self.version == 0: - (self.websocket_key1, number1) = self.createHixieKey() - (self.websocket_key2, number2) = self.createHixieKey() - self.websocket_key3 = os.urandom(8) - accept_val = struct.pack(">II", number1, number2) + self.websocket_key3 - self.websocket_expected_challenge_response = hashlib.md5(accept_val).digest() - - # Safari does NOT set Content-Length, even though the body is - # non-empty, and the request unchunked. We do it. - # See also: http://www.ietf.org/mail-archive/web/hybi/current/msg02149.html - request += "Content-Length: %s\x0d\x0a" % len(self.websocket_key3) - - # First two keys. - request += "Sec-WebSocket-Key1: %s\x0d\x0a" % self.websocket_key1 - request += "Sec-WebSocket-Key2: %s\x0d\x0a" % self.websocket_key2 - request_body = self.websocket_key3 - else: - self.websocket_key = base64.b64encode(os.urandom(16)) - request += "Sec-WebSocket-Key: %s\x0d\x0a" % self.websocket_key.decode() - request_body = None + self.websocket_key = base64.b64encode(os.urandom(16)) + request += "Sec-WebSocket-Key: %s\x0d\x0a" % self.websocket_key.decode() # optional origin announced # if self.factory.origin: - if self.version > 10 or self.version == 0: + if self.version > 10: request += "Origin: %s\x0d\x0a" % self.factory.origin else: request += "Sec-WebSocket-Origin: %s\x0d\x0a" % self.factory.origin @@ -3961,31 +3626,25 @@ class WebSocketClientProtocol(WebSocketProtocol): # extensions # - if self.version != 0: - extensions = [] + extensions = [] - # permessage-compress offers - # - for offer in self.perMessageCompressionOffers: - extensions.append(offer.getExtensionString()) - - if len(extensions) > 0: - request += "Sec-WebSocket-Extensions: %s\x0d\x0a" % ', '.join(extensions) - - # set WS protocol version depending on WS spec version + # permessage-compress offers # - if self.version != 0: - request += "Sec-WebSocket-Version: %d\x0d\x0a" % WebSocketProtocol.SPEC_TO_PROTOCOL_VERSION[self.version] + for offer in self.perMessageCompressionOffers: + extensions.append(offer.getExtensionString()) + + if len(extensions) > 0: + request += "Sec-WebSocket-Extensions: %s\x0d\x0a" % ', '.join(extensions) + + # set WS protocol version + # + request += "Sec-WebSocket-Version: %d\x0d\x0a" % WebSocketProtocol.SPEC_TO_PROTOCOL_VERSION[self.version] request += "\x0d\x0a" self.http_request_data = request.encode('utf8') self.sendData(self.http_request_data) - if request_body: - # Write HTTP request body for Hixie-76 - self.sendData(request_body) - if self.debug: self.factory.log.debug(request) @@ -4062,20 +3721,19 @@ class WebSocketClientProtocol(WebSocketProtocol): # compute Sec-WebSocket-Accept # - if self.version != 0: - if 'sec-websocket-accept' not in self.http_headers: - return self.failHandshake("HTTP Sec-WebSocket-Accept header missing in opening handshake reply") - else: - if http_headers_cnt["sec-websocket-accept"] > 1: - return self.failHandshake("HTTP Sec-WebSocket-Accept header appears more than once in opening handshake reply") - sec_websocket_accept_got = self.http_headers["sec-websocket-accept"].strip() + if 'sec-websocket-accept' not in self.http_headers: + return self.failHandshake("HTTP Sec-WebSocket-Accept header missing in opening handshake reply") + else: + if http_headers_cnt["sec-websocket-accept"] > 1: + return self.failHandshake("HTTP Sec-WebSocket-Accept header appears more than once in opening handshake reply") + sec_websocket_accept_got = self.http_headers["sec-websocket-accept"].strip() - sha1 = hashlib.sha1() - sha1.update(self.websocket_key + WebSocketProtocol._WS_MAGIC) - sec_websocket_accept = base64.b64encode(sha1.digest()).decode() + sha1 = hashlib.sha1() + sha1.update(self.websocket_key + WebSocketProtocol._WS_MAGIC) + sec_websocket_accept = base64.b64encode(sha1.digest()).decode() - if sec_websocket_accept_got != sec_websocket_accept: - return self.failHandshake("HTTP Sec-WebSocket-Accept bogus value : expected %s / got %s" % (sec_websocket_accept, sec_websocket_accept_got)) + if sec_websocket_accept_got != sec_websocket_accept: + return self.failHandshake("HTTP Sec-WebSocket-Accept bogus value : expected %s / got %s" % (sec_websocket_accept, sec_websocket_accept_got)) # Sec-WebSocket-Extensions # @@ -4086,15 +3744,12 @@ class WebSocketClientProtocol(WebSocketProtocol): if 'sec-websocket-extensions' in self.http_headers: - if self.version == 0: - return self.failHandshake("HTTP Sec-WebSocket-Extensions header encountered for Hixie-76") + if http_headers_cnt["sec-websocket-extensions"] > 1: + return self.failHandshake("HTTP Sec-WebSocket-Extensions header appears more than once in opening handshake reply") else: - if http_headers_cnt["sec-websocket-extensions"] > 1: - return self.failHandshake("HTTP Sec-WebSocket-Extensions header appears more than once in opening handshake reply") - else: - # extensions select by server - # - websocket_extensions = self._parseExtensionsHeader(self.http_headers["sec-websocket-extensions"]) + # extensions select by server + # + websocket_extensions = self._parseExtensionsHeader(self.http_headers["sec-websocket-extensions"]) # process extensions selected by server # @@ -4146,22 +3801,9 @@ class WebSocketClientProtocol(WebSocketProtocol): # self.websocket_protocol_in_use = sp - # For Hixie-76, we need 16 octets of HTTP request body to complete HS! - # - if self.version == 0: - if len(self.data) < end_of_header + 4 + 16: - return - else: - challenge_response = self.data[end_of_header + 4:end_of_header + 4 + 16] - if challenge_response != self.websocket_expected_challenge_response: - return self.failHandshake("invalid challenge response received from server (Hixie-76)") - # Ok, got complete HS input, remember rest (if any) # - if self.version == 0: - self.data = self.data[end_of_header + 4 + 16:] - else: - self.data = self.data[end_of_header + 4:] + self.data = self.data[end_of_header + 4:] # opening handshake completed, move WebSocket connection into OPEN state # @@ -4178,8 +3820,7 @@ class WebSocketClientProtocol(WebSocketProtocol): # init state # self.inside_message = False - if self.version != 0: - self.current_frame = None + self.current_frame = None self.websocket_version = self.version # we handle this symmetrical to server-side .. that is, give the @@ -4227,13 +3868,13 @@ class WebSocketClientFactory(WebSocketFactory): protocol = WebSocketClientProtocol """ - The protocol to be spoken. Must be derived from :class:`autobahn.websocket.protocol.WebSocketClientProtocol`. - """ + The protocol to be spoken. Must be derived from :class:`autobahn.websocket.protocol.WebSocketClientProtocol`. + """ isServer = False """ - Flag indicating if this factory is client- or server-side. - """ + Flag indicating if this factory is client- or server-side. + """ def __init__(self, url=None, @@ -4334,7 +3975,6 @@ class WebSocketClientFactory(WebSocketFactory): Reset all WebSocket protocol options to defaults. """ self.version = WebSocketProtocol.DEFAULT_SPEC_VERSION - self.allowHixie76 = WebSocketProtocol.DEFAULT_ALLOW_HIXIE76 self.utf8validateIncoming = True self.acceptMaskedServerFrames = False self.maskClientFrames = True @@ -4362,7 +4002,6 @@ class WebSocketClientFactory(WebSocketFactory): def setProtocolOptions(self, version=None, - allowHixie76=None, utf8validateIncoming=None, acceptMaskedServerFrames=None, maskClientFrames=None, @@ -4424,14 +4063,9 @@ class WebSocketClientFactory(WebSocketFactory): :param autoPingSize: Payload size for automatic pings/pongs. Must be an integer from `[4, 125]`. (default: `4`). :type autoPingSize: int """ - if allowHixie76 is not None and allowHixie76 != self.allowHixie76: - self.allowHixie76 = allowHixie76 - if version is not None: if version not in WebSocketProtocol.SUPPORTED_SPEC_VERSIONS: raise Exception("invalid WebSocket draft version %s (allowed values: %s)" % (version, str(WebSocketProtocol.SUPPORTED_SPEC_VERSIONS))) - if version == 0 and not self.allowHixie76: - raise Exception("use of Hixie-76 requires allowHixie76 == True") if version != self.version: self.version = version From 45755347aad2013cbf1d2cfb9b971c0d39c99e90 Mon Sep 17 00:00:00 2001 From: HawkOwl Date: Tue, 15 Sep 2015 14:22:22 +0800 Subject: [PATCH 03/59] no need to specify the modes now --- autobahn/websocket/protocol.py | 137 +++++++++++---------------------- 1 file changed, 44 insertions(+), 93 deletions(-) diff --git a/autobahn/websocket/protocol.py b/autobahn/websocket/protocol.py index 10470b90..bd6ec492 100755 --- a/autobahn/websocket/protocol.py +++ b/autobahn/websocket/protocol.py @@ -773,8 +773,6 @@ class WebSocketProtocol(object): the TCP connection either immediately (when we are a server) or after a timeout (when we are a client and expect the server to drop the TCP). - Modes: Hybi - :param code: Close status code, if there was one (:class:`WebSocketProtocol`.CLOSE_STATUS_CODE_*). :type code: int or None :param reasonRaw: Close reason (when present, a status code MUST have been also be present). @@ -871,8 +869,6 @@ class WebSocketProtocol(object): We (a client) expected the peer (a server) to drop the connection, but it didn't (in time self.serverConnectionDropTimeout). So we drop the connection, but set self.wasClean = False. - - Modes: Hybi """ self.serverConnectionDropTimeoutCall = None if self.state != WebSocketProtocol.STATE_CLOSED: @@ -891,8 +887,6 @@ class WebSocketProtocol(object): We expected the peer to complete the opening handshake with to us. It didn't do so (in time self.openHandshakeTimeout). So we drop the connection, but set self.wasClean = False. - - Modes: Hybi """ self.openHandshakeTimeoutCall = None if self.state in [WebSocketProtocol.STATE_CONNECTING, WebSocketProtocol.STATE_PROXY_CONNECTING]: @@ -920,8 +914,6 @@ class WebSocketProtocol(object): We expected the peer to respond to us initiating a close handshake. It didn't respond (in time self.closeHandshakeTimeout) with a close response frame though. So we drop the connection, but set self.wasClean = False. - - Modes: Hybi """ self.closeHandshakeTimeoutCall = None if self.state != WebSocketProtocol.STATE_CLOSED: @@ -949,8 +941,6 @@ class WebSocketProtocol(object): def dropConnection(self, abort=False): """ Drop the underlying TCP connection. - - Modes: Hybi """ if self.state != WebSocketProtocol.STATE_CLOSED: if self.debugCodePaths: @@ -971,8 +961,6 @@ class WebSocketProtocol(object): def failConnection(self, code=CLOSE_STATUS_CODE_GOING_AWAY, reason="Going Away"): """ Fails the WebSocket connection. - - Modes: Hybi """ if self.state != WebSocketProtocol.STATE_CLOSED: if self.debugCodePaths: @@ -1003,8 +991,6 @@ class WebSocketProtocol(object): """ Fired when a WebSocket protocol violation/error occurs. - Modes: Hybi - :param reason: Protocol violation that was encountered (human readable). :type reason: str @@ -1026,8 +1012,6 @@ class WebSocketProtocol(object): for text message when payload is invalid UTF-8 or close frames with close reason that is invalid UTF-8. - Modes: Hybi - :param reason: What was invalid for the payload (human readable). :type reason: str @@ -1061,10 +1045,7 @@ class WebSocketProtocol(object): """ This is called by network framework when a new TCP connection has been established and handed over to a Protocol instance (an instance of this class). - - Modes: Hybi """ - # copy default options from factory (so we are not affected by changed on # those), but only copy if not already set on protocol instance (allow # to set configuration individually) @@ -1177,9 +1158,8 @@ class WebSocketProtocol(object): def _connectionLost(self, reason): """ - This is called by network framework when a transport connection was lost. - - Modes: Hybi + This is called by network framework when a transport connection was + lost. """ # cancel any server connection drop timer if present # @@ -1222,25 +1202,22 @@ class WebSocketProtocol(object): def logRxOctets(self, data): """ - Hook fired right after raw octets have been received, but only when self.logOctets == True. - - Modes: Hybi + Hook fired right after raw octets have been received, but only when + self.logOctets == True. """ self.factory.log.debug("RX Octets from %s : octets = %s" % (self.peer, binascii.b2a_hex(data))) def logTxOctets(self, data, sync): """ - Hook fired right after raw octets have been sent, but only when self.logOctets == True. - - Modes: Hybi + Hook fired right after raw octets have been sent, but only when + self.logOctets == True. """ self.factory.log.debug("TX Octets to %s : sync = %s, octets = %s" % (self.peer, sync, binascii.b2a_hex(data))) def logRxFrame(self, frameHeader, payload): """ - Hook fired right after WebSocket frame has been received and decoded, but only when self.logFrames == True. - - Modes: Hybi + Hook fired right after WebSocket frame has been received and decoded, + but only when self.logFrames == True. """ data = b''.join(payload) info = (self.peer, @@ -1255,9 +1232,8 @@ class WebSocketProtocol(object): def logTxFrame(self, frameHeader, payload, repeatLength, chopsize, sync): """ - Hook fired right after WebSocket frame has been encoded and sent, but only when self.logFrames == True. - - Modes: Hybi + Hook fired right after WebSocket frame has been encoded and sent, but + only when self.logFrames == True. """ info = (self.peer, frameHeader.fin, @@ -1274,9 +1250,8 @@ class WebSocketProtocol(object): def _dataReceived(self, data): """ - This is called by network framework upon receiving data on transport connection. - - Modes: Hybi + This is called by network framework upon receiving data on transport + connection. """ if self.state == WebSocketProtocol.STATE_OPEN: self.trafficStats.incomingOctetsWireLevel += len(data) @@ -1291,10 +1266,7 @@ class WebSocketProtocol(object): def consumeData(self): """ Consume buffered (incoming) data. - - Modes: Hybi """ - # WebSocket is open (handshake was completed) or close was sent # if self.state == WebSocketProtocol.STATE_OPEN or self.state == WebSocketProtocol.STATE_CLOSING: @@ -1337,24 +1309,19 @@ class WebSocketProtocol(object): def processProxyConnect(self): """ Process proxy connect. - - Modes: Hybi """ raise Exception("must implement proxy connect (client or server) in derived class") def processHandshake(self): """ Process WebSocket handshake. - - Modes: Hybi """ raise Exception("must implement handshake (client or server) in derived class") def _trigger(self): """ - Trigger sending stuff from send queue (which is only used for chopped/synched writes). - - Modes: Hybi + Trigger sending stuff from send queue (which is only used for + chopped/synched writes). """ if not self.triggered: self.triggered = True @@ -1362,10 +1329,8 @@ class WebSocketProtocol(object): def _send(self): """ - Send out stuff from send queue. For details how this works, see test/trickling - in the repo. - - Modes: Hybi + Send out stuff from send queue. For details how this works, see + test/trickling in the repo. """ if len(self.send_queue) > 0: e = self.send_queue.popleft() @@ -1396,13 +1361,12 @@ class WebSocketProtocol(object): def sendData(self, data, sync=False, chopsize=None): """ Wrapper for self.transport.write which allows to give a chopsize. - When asked to chop up writing to TCP stream, we write only chopsize octets - and then give up control to select() in underlying reactor so that bytes - get onto wire immediately. Note that this is different from and unrelated - to WebSocket data message fragmentation. Note that this is also different - from the TcpNoDelay option which can be set on the socket. - - Modes: Hybi + When asked to chop up writing to TCP stream, we write only chopsize + octets and then give up control to select() in underlying reactor so + that bytes get onto wire immediately. Note that this is different from + and unrelated to WebSocket data message fragmentation. Note that this + is also different from the TcpNoDelay option which can be set on the + socket. """ if chopsize and chopsize > 0: i = 0 @@ -1444,8 +1408,6 @@ class WebSocketProtocol(object): """ After WebSocket handshake has been completed, this procedure will do all subsequent processing of incoming bytes. - - Modes: Hybi """ buffered_len = len(self.data) @@ -1690,8 +1652,6 @@ class WebSocketProtocol(object): def onFrameBegin(self): """ Begin of receive new frame. - - Modes: Hybi """ if self.current_frame.opcode > 7: self.control_frame_data = [] @@ -1733,8 +1693,6 @@ class WebSocketProtocol(object): def onFrameData(self, payload): """ New data received within frame. - - Modes: Hybi """ if self.current_frame.opcode > 7: self.control_frame_data.append(payload) @@ -1770,8 +1728,6 @@ class WebSocketProtocol(object): def onFrameEnd(self): """ End of frame received. - - Modes: Hybi """ if self.current_frame.opcode > 7: if self.logFrames: @@ -1813,10 +1769,7 @@ class WebSocketProtocol(object): def processControlFrame(self): """ Process a completely received control frame. - - Modes: Hybi """ - payload = b''.join(self.control_frame_data) self.control_frame_data = None @@ -1888,20 +1841,20 @@ class WebSocketProtocol(object): chopsize=None, sync=False): """ - Send out frame. Normally only used internally via sendMessage(), sendPing(), sendPong() and sendClose(). + Send out frame. Normally only used internally via sendMessage(), + sendPing(), sendPong() and sendClose(). - This method deliberately allows to send invalid frames (that is frames invalid - per-se, or frames invalid because of protocol state). Other than in fuzzing servers, - calling methods will ensure that no invalid frames are sent. + This method deliberately allows to send invalid frames (that is frames + invalid per-se, or frames invalid because of protocol state). Other + than in fuzzing servers, calling methods will ensure that no invalid + frames are sent. - In addition, this method supports explicit specification of payload length. - When payload_len is given, it will always write that many octets to the stream. - It'll wrap within payload, resending parts of that when more octets were requested - The use case is again for fuzzing server which want to sent increasing amounts - of payload data to peers without having to construct potentially large messages - themselves. - - Modes: Hybi + In addition, this method supports explicit specification of payload + length. When payload_len is given, it will always write that many + octets to the stream. It'll wrap within payload, resending parts of + that when more octets were requested The use case is again for fuzzing + server which want to sent increasing amounts of payload data to peers + without having to construct potentially large messages themselves. """ if payload_len is not None: if len(payload) < 1: @@ -2020,8 +1973,6 @@ class WebSocketProtocol(object): Send a close frame and update protocol state. Note, that this is an internal method which deliberately allows not send close frame with invalid payload. - - Modes: Hybi """ if self.state == WebSocketProtocol.STATE_CLOSING: if self.debugCodePaths: @@ -2423,10 +2374,10 @@ class PreparedMessage(object): :type isBinary: bool :param applyMask: Provide `True` if WebSocket message is to be masked (required for client to server WebSocket messages). :type applyMask: bool - :param doNotCompress: Iff `True`, never compress this message. This only applies to - Hybi-Mode and only when WebSocket compression has been negotiated on - the WebSocket connection. Use when you know the payload - incompressible (e.g. encrypted or already compressed). + :param doNotCompress: Iff `True`, never compress this message. This + only applies when WebSocket compression has been negotiated on the + WebSocket connection. Use when you know the payload incompressible + (e.g. encrypted or already compressed). :type doNotCompress: bool """ if not doNotCompress: @@ -2498,12 +2449,13 @@ class WebSocketFactory(object): :param payload: The message payload. :type payload: bytes - :param isBinary: `True` iff payload is binary, else the payload must be UTF-8 encoded text. + :param isBinary: `True` iff payload is binary, else the payload must be + UTF-8 encoded text. :type isBinary: bool - :param doNotCompress: Iff `True`, never compress this message. This only applies to - Hybi-Mode and only when WebSocket compression has been negotiated on - the WebSocket connection. Use when you know the payload - incompressible (e.g. encrypted or already compressed). + :param doNotCompress: Iff `True`, never compress this message. This + only applies when WebSocket compression has been negotiated on the + WebSocket connection. Use when you know the payload incompressible + (e.g. encrypted or already compressed). :type doNotCompress: bool :returns: obj -- An instance of :class:`autobahn.websocket.protocol.PreparedMessage`. @@ -3485,7 +3437,6 @@ class WebSocketClientProtocol(WebSocketProtocol): """ Connect to explicit proxy. """ - # construct proxy connect HTTP request # request = "CONNECT %s:%d HTTP/1.1\x0d\x0a" % (self.factory.host.encode("utf-8"), self.factory.port) From ff3ca06480d174f0096bf2bdce2c230baa5cb82c Mon Sep 17 00:00:00 2001 From: HawkOwl Date: Tue, 15 Sep 2015 14:39:36 +0800 Subject: [PATCH 04/59] pyflakes --- autobahn/websocket/protocol.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/autobahn/websocket/protocol.py b/autobahn/websocket/protocol.py index bd6ec492..f3d2b55d 100755 --- a/autobahn/websocket/protocol.py +++ b/autobahn/websocket/protocol.py @@ -2392,11 +2392,11 @@ class PreparedMessage(object): # first byte # - b0 = ((1 << 7) | 2) if binary else ((1 << 7) | 1) + b0 = ((1 << 7) | 2) if isBinary else ((1 << 7) | 1) # second byte, payload len bytes and mask # - if masked: + if applyMask: b1 = 1 << 7 mask = struct.pack("!I", random.getrandbits(32)) if l == 0: @@ -2920,7 +2920,6 @@ class WebSocketServerProtocol(WebSocketProtocol): if self.websocket_protocol_in_use is not None: response += "Sec-WebSocket-Protocol: %s\x0d\x0a" % str(self.websocket_protocol_in_use) - # compute Sec-WebSocket-Accept # sha1 = hashlib.sha1() From f91bbabe51408c307695d18e6cccc83aa4b94764 Mon Sep 17 00:00:00 2001 From: HawkOwl Date: Tue, 15 Sep 2015 15:29:55 +0800 Subject: [PATCH 05/59] cleanup things in the twisted package --- autobahn/twisted/resource.py | 27 --------------------------- autobahn/twisted/websocket.py | 2 -- 2 files changed, 29 deletions(-) diff --git a/autobahn/twisted/resource.py b/autobahn/twisted/resource.py index a52d3a7a..6437a461 100644 --- a/autobahn/twisted/resource.py +++ b/autobahn/twisted/resource.py @@ -37,40 +37,15 @@ from twisted.web.resource import IResource, Resource from six import PY3 # The following imports reactor at module level -# See: https://twistedmatrix.com/trac/ticket/6849 -from twisted.web.http import HTTPChannel - -# .. and this also, since it imports t.w.http # from twisted.web.server import NOT_DONE_YET __all__ = ( 'WebSocketResource', - 'HTTPChannelHixie76Aware', 'WSGIRootResource', ) -class HTTPChannelHixie76Aware(HTTPChannel): - """ - Hixie-76 is deadly broken. It includes 8 bytes of body, but then does not - set content-length header. This hacked HTTPChannel injects the missing - HTTP header upon detecting Hixie-76. We need this since otherwise - Twisted Web will silently ignore the body. - - To use this, set ``protocol = HTTPChannelHixie76Aware`` on your - `twisted.web.server.Site `_ instance. - - .. seealso: `Autobahn Twisted Web site example `_ - """ - - def headerReceived(self, line): - header = line.split(':')[0].lower() - if header == "sec-websocket-key1" and not self._transferDecoder: - HTTPChannel.headerReceived(self, "Content-Length: 8") - HTTPChannel.headerReceived(self, line) - - class WSGIRootResource(Resource): """ Root resource when you want a WSGI resource be the default serving @@ -110,7 +85,6 @@ class WebSocketResource(object): """ A Twisted Web resource for WebSocket. """ - isLeaf = True def __init__(self, factory): @@ -180,7 +154,6 @@ class WebSocketResource(object): for h in request.requestHeaders.getAllRawHeaders(): data += "%s: %s\x0d\x0a" % (h[0], ",".join(h[1])) data += "\x0d\x0a" - data += request.content.read() # we need this for Hixie-76 protocol.dataReceived(data) return NOT_DONE_YET diff --git a/autobahn/twisted/websocket.py b/autobahn/twisted/websocket.py index f69bcab3..6c91b030 100644 --- a/autobahn/twisted/websocket.py +++ b/autobahn/twisted/websocket.py @@ -178,8 +178,6 @@ class WebSocketAdapterProtocol(twisted.internet.protocol.Protocol): """ Register a Twisted producer with this protocol. - Modes: Hybi, Hixie - :param producer: A Twisted push or pull producer. :type producer: object :param streaming: Producer type. From db10039da745385117a851fbdaa87a1f472d1c3e Mon Sep 17 00:00:00 2001 From: HawkOwl Date: Tue, 15 Sep 2015 15:41:28 +0800 Subject: [PATCH 06/59] fix examples --- examples/twisted/websocket/broadcast/server.py | 1 - .../twisted/websocket/echo_service/echows/echoservice.py | 6 +----- examples/twisted/websocket/echo_site/server.py | 6 +----- examples/twisted/websocket/echo_site_tls/server.py | 6 +----- examples/twisted/websocket/echo_tls/server.py | 1 - examples/twisted/websocket/echo_variants/client.py | 3 --- .../twisted/websocket/echo_variants/client_reconnecting.py | 3 --- .../twisted/websocket/echo_variants/client_with_proxy.py | 3 --- examples/twisted/websocket/echo_variants/server.py | 1 - examples/twisted/websocket/echo_wsfallbacks/README.md | 6 +++--- examples/twisted/websocket/echo_wsfallbacks/server.py | 1 - examples/twisted/websocket/echo_wsgi/server.py | 7 +------ 12 files changed, 7 insertions(+), 37 deletions(-) diff --git a/examples/twisted/websocket/broadcast/server.py b/examples/twisted/websocket/broadcast/server.py index a7d4eba3..d0de935b 100644 --- a/examples/twisted/websocket/broadcast/server.py +++ b/examples/twisted/websocket/broadcast/server.py @@ -117,7 +117,6 @@ if __name__ == '__main__': debugCodePaths=debug) factory.protocol = BroadcastServerProtocol - factory.setProtocolOptions(allowHixie76=True) listenWS(factory) webdir = File(".") diff --git a/examples/twisted/websocket/echo_service/echows/echoservice.py b/examples/twisted/websocket/echo_service/echows/echoservice.py index 2e82c09a..460cfbd0 100644 --- a/examples/twisted/websocket/echo_service/echows/echoservice.py +++ b/examples/twisted/websocket/echo_service/echows/echoservice.py @@ -37,8 +37,7 @@ from twisted.web.static import File from autobahn.websocket import WebSocketServerFactory, \ WebSocketServerProtocol -from autobahn.resource import WebSocketResource, \ - HTTPChannelHixie76Aware +from autobahn.resource import WebSocketResource class EchoServerProtocol(WebSocketServerProtocol): @@ -61,9 +60,7 @@ class EchoService(service.Service): def startService(self): factory = WebSocketServerFactory(u"ws://127.0.0.1:%d" % self.port, debug=self.debug) - factory.protocol = EchoServerProtocol - factory.setProtocolOptions(allowHixie76=True) # needed if Hixie76 is to be supported # FIXME: Site.start/stopFactory should start/stop factories wrapped as Resources factory.startFactory() @@ -79,7 +76,6 @@ class EchoService(service.Service): # both under one Twisted Web Site site = Site(root) - site.protocol = HTTPChannelHixie76Aware # needed if Hixie76 is to be supported self.site = site self.factory = factory diff --git a/examples/twisted/websocket/echo_site/server.py b/examples/twisted/websocket/echo_site/server.py index c945be93..68274c77 100644 --- a/examples/twisted/websocket/echo_site/server.py +++ b/examples/twisted/websocket/echo_site/server.py @@ -34,8 +34,7 @@ from twisted.web.static import File from autobahn.twisted.websocket import WebSocketServerFactory, \ WebSocketServerProtocol -from autobahn.twisted.resource import WebSocketResource, \ - HTTPChannelHixie76Aware +from autobahn.twisted.resource import WebSocketResource class EchoServerProtocol(WebSocketServerProtocol): @@ -58,9 +57,7 @@ if __name__ == '__main__': factory = WebSocketServerFactory(u"ws://127.0.0.1:8080", debug=debug, debugCodePaths=debug) - factory.protocol = EchoServerProtocol - factory.setProtocolOptions(allowHixie76=True) # needed if Hixie76 is to be supported resource = WebSocketResource(factory) @@ -72,7 +69,6 @@ if __name__ == '__main__': # both under one Twisted Web Site site = Site(root) - site.protocol = HTTPChannelHixie76Aware # needed if Hixie76 is to be supported reactor.listenTCP(8080, site) reactor.run() diff --git a/examples/twisted/websocket/echo_site_tls/server.py b/examples/twisted/websocket/echo_site_tls/server.py index 428030af..a4f59021 100644 --- a/examples/twisted/websocket/echo_site_tls/server.py +++ b/examples/twisted/websocket/echo_site_tls/server.py @@ -34,8 +34,7 @@ from twisted.web.static import File from autobahn.twisted.websocket import WebSocketServerFactory, \ WebSocketServerProtocol -from autobahn.twisted.resource import WebSocketResource, \ - HTTPChannelHixie76Aware +from autobahn.twisted.resource import WebSocketResource class EchoServerProtocol(WebSocketServerProtocol): @@ -58,9 +57,7 @@ if __name__ == '__main__': factory = WebSocketServerFactory(u"wss://127.0.0.1:8080", debug=debug, debugCodePaths=debug) - factory.protocol = EchoServerProtocol - factory.setProtocolOptions(allowHixie76=True) # needed if Hixie76 is to be supported resource = WebSocketResource(factory) @@ -72,7 +69,6 @@ if __name__ == '__main__': # both under one Twisted Web Site site = Site(root) - site.protocol = HTTPChannelHixie76Aware # needed if Hixie76 is to be supported reactor.listenSSL(8080, site, contextFactory) diff --git a/examples/twisted/websocket/echo_tls/server.py b/examples/twisted/websocket/echo_tls/server.py index 645e0861..c720437f 100644 --- a/examples/twisted/websocket/echo_tls/server.py +++ b/examples/twisted/websocket/echo_tls/server.py @@ -61,7 +61,6 @@ if __name__ == '__main__': debugCodePaths=debug) factory.protocol = EchoServerProtocol - factory.setProtocolOptions(allowHixie76=True) listenWS(factory, contextFactory) webdir = File(".") diff --git a/examples/twisted/websocket/echo_variants/client.py b/examples/twisted/websocket/echo_variants/client.py index 8bb6a212..da0e512d 100644 --- a/examples/twisted/websocket/echo_variants/client.py +++ b/examples/twisted/websocket/echo_variants/client.py @@ -63,9 +63,6 @@ if __name__ == '__main__': factory = WebSocketClientFactory(sys.argv[1], debug=debug, debugCodePaths=debug) - - # uncomment to use Hixie-76 protocol - # factory.setProtocolOptions(allowHixie76 = True, version = 0) factory.protocol = EchoClientProtocol connectWS(factory) diff --git a/examples/twisted/websocket/echo_variants/client_reconnecting.py b/examples/twisted/websocket/echo_variants/client_reconnecting.py index 5b6ac12a..bfcfcfa8 100644 --- a/examples/twisted/websocket/echo_variants/client_reconnecting.py +++ b/examples/twisted/websocket/echo_variants/client_reconnecting.py @@ -85,9 +85,6 @@ if __name__ == '__main__': factory = EchoClientFactory(sys.argv[1], debug=debug, debugCodePaths=debug) - - # uncomment to use Hixie-76 protocol - # factory.setProtocolOptions(allowHixie76 = True, version = 0) connectWS(factory) reactor.run() diff --git a/examples/twisted/websocket/echo_variants/client_with_proxy.py b/examples/twisted/websocket/echo_variants/client_with_proxy.py index 46e8d247..1ee513f7 100644 --- a/examples/twisted/websocket/echo_variants/client_with_proxy.py +++ b/examples/twisted/websocket/echo_variants/client_with_proxy.py @@ -71,9 +71,6 @@ if __name__ == '__main__': proxy=proxy, debug=debug, debugCodePaths=debug) - - # uncomment to use Hixie-76 protocol - # factory.setProtocolOptions(allowHixie76 = True, version = 0) factory.protocol = EchoClientProtocol connectWS(factory) diff --git a/examples/twisted/websocket/echo_variants/server.py b/examples/twisted/websocket/echo_variants/server.py index f2b862a4..83b64557 100644 --- a/examples/twisted/websocket/echo_variants/server.py +++ b/examples/twisted/websocket/echo_variants/server.py @@ -55,7 +55,6 @@ if __name__ == '__main__': debugCodePaths=debug) factory.protocol = EchoServerProtocol - factory.setProtocolOptions(allowHixie76=True) listenWS(factory) webdir = File(".") diff --git a/examples/twisted/websocket/echo_wsfallbacks/README.md b/examples/twisted/websocket/echo_wsfallbacks/README.md index 5b335252..4ad91374 100644 --- a/examples/twisted/websocket/echo_wsfallbacks/README.md +++ b/examples/twisted/websocket/echo_wsfallbacks/README.md @@ -3,14 +3,14 @@ WebSocket Echo Server with Fallbacks This example has the [broadest browser](http://www.tavendo.de/webmq/browsers) support currently possible with Autobahn. -It supports native WebSocket protocol variants Hixie-76, Hybi-10+ and RFC6455. +It supports native WebSocket protocol variants Hybi-10+ and RFC6455. On IE6-9 it uses [Google Chrome Frame](http://www.google.com/chromeframe) when available. On IE8,9 it can use a [Flash-based WebSocket implementation](https://github.com/gimite/web-socket-js). This requires Adobe Flash 10+. > The Flash implementation can also be used on older Android devices without Chrome Mobile, but with Flash. You need to remove the conditional comments around the Flash file includes though in this case from the `index.html`. -> +> Running ------- @@ -51,5 +51,5 @@ Here is a typical browser log when the Flash implementation kicks in: Upgrade: WebSocket Connection: Upgrade Sec-WebSocket-Accept: 4wHBJpfr8P419FMUv8sJ/rT0x/4= - + Connected! diff --git a/examples/twisted/websocket/echo_wsfallbacks/server.py b/examples/twisted/websocket/echo_wsfallbacks/server.py index c5c69ac5..b9fc693a 100644 --- a/examples/twisted/websocket/echo_wsfallbacks/server.py +++ b/examples/twisted/websocket/echo_wsfallbacks/server.py @@ -61,7 +61,6 @@ if __name__ == '__main__': debugCodePaths=debug) factory.protocol = EchoServerProtocol - factory.setProtocolOptions(allowHixie76=True) listenWS(factory) # We need to start a "Flash Policy Server" on TCP/843 diff --git a/examples/twisted/websocket/echo_wsgi/server.py b/examples/twisted/websocket/echo_wsgi/server.py index 55d5c779..ccfdfa8f 100644 --- a/examples/twisted/websocket/echo_wsgi/server.py +++ b/examples/twisted/websocket/echo_wsgi/server.py @@ -37,9 +37,7 @@ from flask import Flask, render_template from autobahn.twisted.websocket import WebSocketServerFactory, \ WebSocketServerProtocol -from autobahn.twisted.resource import WebSocketResource, \ - WSGIRootResource, \ - HTTPChannelHixie76Aware +from autobahn.twisted.resource import WebSocketResource, WSGIRootResource ## @@ -83,8 +81,6 @@ if __name__ == "__main__": debugCodePaths=debug) wsFactory.protocol = EchoServerProtocol - wsFactory.setProtocolOptions(allowHixie76=True) # needed if Hixie76 is to be supported - wsResource = WebSocketResource(wsFactory) ## @@ -102,7 +98,6 @@ if __name__ == "__main__": # create a Twisted Web Site and run everything ## site = Site(rootResource) - site.protocol = HTTPChannelHixie76Aware # needed if Hixie76 is to be supported reactor.listenTCP(8080, site) reactor.run() From 9babf36cce95490f6888d66a7381b0a3b528a645 Mon Sep 17 00:00:00 2001 From: HawkOwl Date: Tue, 15 Sep 2015 15:42:34 +0800 Subject: [PATCH 07/59] fixed readme --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 25b204ac..f093ef5b 100644 --- a/README.rst +++ b/README.rst @@ -34,8 +34,8 @@ Features - framework for `WebSocket `__ and `WAMP `__ clients and servers - compatible with Python 2.6, 2.7, 3.3 and 3.4 - runs on `CPython `__, `PyPy `__ and `Jython `__ -- runs under `Twisted `__ and `asyncio `__ - implements WebSocket - `RFC6455 `__, Draft Hybi-10+, Hixie-76 +- runs under `Twisted `__ and `asyncio `__ - implements WebSocket + `RFC6455 `__ and Draft Hybi-10+ - implements `WebSocket compression `__ - implements `WAMP `__, the Web Application Messaging Protocol - high-performance, fully asynchronous implementation From 4307b1d53787f7fb7221e4a545bdcbf6251dc8e6 Mon Sep 17 00:00:00 2001 From: HawkOwl Date: Tue, 15 Sep 2015 16:16:02 +0800 Subject: [PATCH 08/59] hixie rejection test --- autobahn/websocket/test/test_protocol.py | 39 ++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/autobahn/websocket/test/test_protocol.py b/autobahn/websocket/test/test_protocol.py index 53a1df6e..8c20115f 100644 --- a/autobahn/websocket/test/test_protocol.py +++ b/autobahn/websocket/test/test_protocol.py @@ -26,6 +26,8 @@ from __future__ import absolute_import, print_function +import os + import unittest2 as unittest from autobahn.websocket.protocol import WebSocketServerProtocol @@ -34,10 +36,43 @@ from autobahn.websocket.protocol import WebSocketServerFactory class FakeTransport(object): _written = b"" + _open = True def write(self, msg): + if not self._open: + raise Exception("Can't write to a closed connection") self._written = self._written + msg + def loseConnection(self): + self._open = False + + +class Hixie76RejectionTests(unittest.TestCase): + """ + Hixie-76 should not be accepted by an Autobahn server. + """ + def test_handshake_fails(self): + """ + A handshake from a client only supporting Hixie-76 will fail. + """ + from autobahn.twisted.websocket import WebSocketServerFactory + from autobahn.twisted.websocket import WebSocketServerProtocol + + t = FakeTransport() + f = WebSocketServerFactory() + p = WebSocketServerProtocol() + p.factory = f + p.transport = t + + # from http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 + http_request = b"GET /demo HTTP/1.1\r\nHost: example.com\r\nConnection: Upgrade\r\nSec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\nSec-WebSocket-Protocol: sample\r\nUpgrade: WebSocket\r\nSec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\nOrigin: http://example.com\r\n\r\n^n:ds[4U" + + p._connectionMade() + p.data = http_request + p.processHandshake() + self.assertIn("HTTP/1.1 400", t._written) + self.assertIn("Hixie76 protocol not supported", t._written) + class WebSocketProtocolTests(unittest.TestCase): """ @@ -147,3 +182,7 @@ class WebSocketProtocolTests(unittest.TestCase): # We shouldn't have closed self.assertEqual(self.transport._written, b"") self.assertEqual(self.protocol.state, self.protocol.STATE_OPEN) + + +if not os.environ.get('USE_TWISTED', False): + del Hixie76RejectionTests From 88957b29fa7eaaab1fef24747fbcf9cfbf0ceea0 Mon Sep 17 00:00:00 2001 From: HawkOwl Date: Tue, 15 Sep 2015 16:26:29 +0800 Subject: [PATCH 09/59] remove from docs --- doc/index.rst | 2 +- doc/spelling_wordlist.txt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/index.rst b/doc/index.rst index a4fb0086..dcdd0bf3 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -58,7 +58,7 @@ WebSocket allows `bidirectional real-time messaging `_ * implements `WAMP`_, the Web Application Messaging Protocol * supports TLS (secure WebSocket) and proxies diff --git a/doc/spelling_wordlist.txt b/doc/spelling_wordlist.txt index 02281098..f25f8c03 100644 --- a/doc/spelling_wordlist.txt +++ b/doc/spelling_wordlist.txt @@ -16,7 +16,6 @@ serializer subprotocol subprotocols Hybi -Hixie args kwargs unserialized From 07ed9973bb893fcc12b344c706386664cfb47e8e Mon Sep 17 00:00:00 2001 From: HawkOwl Date: Tue, 15 Sep 2015 16:42:52 +0800 Subject: [PATCH 10/59] fix test on py3 --- autobahn/websocket/test/test_protocol.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autobahn/websocket/test/test_protocol.py b/autobahn/websocket/test/test_protocol.py index 8c20115f..f4111a2d 100644 --- a/autobahn/websocket/test/test_protocol.py +++ b/autobahn/websocket/test/test_protocol.py @@ -70,8 +70,8 @@ class Hixie76RejectionTests(unittest.TestCase): p._connectionMade() p.data = http_request p.processHandshake() - self.assertIn("HTTP/1.1 400", t._written) - self.assertIn("Hixie76 protocol not supported", t._written) + self.assertIn(b"HTTP/1.1 400", t._written) + self.assertIn(b"Hixie76 protocol not supported", t._written) class WebSocketProtocolTests(unittest.TestCase): From 11512ce4e9e50894ef00537fc116678b45ff9d97 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Tue, 15 Sep 2015 14:46:02 +0200 Subject: [PATCH 11/59] big cleanup of WebSocket API --- autobahn/asyncio/websocket.py | 10 +- autobahn/twisted/websocket.py | 13 +- autobahn/wamp/websocket.py | 4 +- autobahn/websocket/__init__.py | 20 ++ autobahn/websocket/compress.py | 6 +- autobahn/websocket/http.py | 240 ---------------------- autobahn/websocket/interfaces.py | 82 +++----- autobahn/websocket/protocol.py | 115 ++--------- autobahn/websocket/types.py | 331 +++++++++++++++++++++++++++++++ autobahn/websocket/useragent.py | 310 ----------------------------- 10 files changed, 407 insertions(+), 724 deletions(-) delete mode 100644 autobahn/websocket/http.py create mode 100644 autobahn/websocket/types.py delete mode 100644 autobahn/websocket/useragent.py diff --git a/autobahn/asyncio/websocket.py b/autobahn/asyncio/websocket.py index 76e5f4bc..c7863299 100644 --- a/autobahn/asyncio/websocket.py +++ b/autobahn/asyncio/websocket.py @@ -28,7 +28,6 @@ from collections import deque from autobahn.wamp import websocket from autobahn.websocket import protocol -from autobahn.websocket import http try: import asyncio @@ -42,6 +41,7 @@ except ImportError: from trollius import Future from autobahn._logging import make_logger +from autobahn.websocket.types import ConnectionDeny __all__ = ( @@ -192,10 +192,10 @@ class WebSocketServerProtocol(WebSocketAdapterProtocol, protocol.WebSocketServer res = self.onConnect(request) # if yields(res): # res = yield from res - except http.HttpException as exc: - self.failHandshake(exc.reason, exc.code) - except Exception: - self.failHandshake(http.INTERNAL_SERVER_ERROR[1], http.INTERNAL_SERVER_ERROR[0]) + except ConnectionDeny as e: + self.failHandshake(e.reason, e.code) + except Exception as e: + self.failHandshake("Internal server error: {}".format(e), ConnectionDeny.http.INTERNAL_SERVER_ERROR) else: self.succeedHandshake(res) diff --git a/autobahn/twisted/websocket.py b/autobahn/twisted/websocket.py index 000ffc52..82ba66da 100644 --- a/autobahn/twisted/websocket.py +++ b/autobahn/twisted/websocket.py @@ -37,8 +37,9 @@ from twisted.internet.error import ConnectionDone, ConnectionAborted, \ ConnectionLost from autobahn.wamp import websocket +from autobahn.websocket.types import ConnectionRequest, ConnectionResponse, \ + ConnectionDeny from autobahn.websocket import protocol -from autobahn.websocket import http from autobahn.twisted.util import peer2str from autobahn._logging import make_logger @@ -199,12 +200,12 @@ class WebSocketServerProtocol(WebSocketAdapterProtocol, protocol.WebSocketServer res.addCallback(self.succeedHandshake) def forwardError(failure): - if failure.check(http.HttpException): + if failure.check(ConnectionDeny): return self.failHandshake(failure.value.reason, failure.value.code) else: if self.debug: self.factory._log("Unexpected exception in onConnect ['%s']" % failure.value) - return self.failHandshake(http.INTERNAL_SERVER_ERROR[1], http.INTERNAL_SERVER_ERROR[0]) + return self.failHandshake("Internal server error: {}".format(failure.value), ConnectionDeny.INTERNAL_SERVER_ERROR) res.addErrback(forwardError) @@ -291,14 +292,14 @@ class WrappingWebSocketAdapter(object): def onConnect(self, requestOrResponse): # Negotiate either the 'binary' or the 'base64' WebSocket subprotocol - if isinstance(requestOrResponse, protocol.ConnectionRequest): + if isinstance(requestOrResponse, ConnectionRequest): request = requestOrResponse for p in request.protocols: if p in self.factory._subprotocols: self._binaryMode = (p != 'base64') return p - raise http.HttpException(http.NOT_ACCEPTABLE[0], "this server only speaks %s WebSocket subprotocols" % self.factory._subprotocols) - elif isinstance(requestOrResponse, protocol.ConnectionResponse): + raise ConnectionDeny(ConnectionDeny.NOT_ACCEPTABLE, "this server only speaks %s WebSocket subprotocols" % self.factory._subprotocols) + elif isinstance(requestOrResponse, ConnectionResponse): response = requestOrResponse if response.protocol not in self.factory._subprotocols: self.failConnection(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_PROTOCOL_ERROR, "this client only speaks %s WebSocket subprotocols" % self.factory._subprotocols) diff --git a/autobahn/wamp/websocket.py b/autobahn/wamp/websocket.py index 42edce86..24ca03f2 100644 --- a/autobahn/wamp/websocket.py +++ b/autobahn/wamp/websocket.py @@ -29,7 +29,7 @@ from __future__ import absolute_import, print_function import traceback from autobahn.websocket import protocol -from autobahn.websocket import http +from autobahn.websocket.types import ConnectionDeny from autobahn.wamp.interfaces import ITransport from autobahn.wamp.exception import ProtocolError, SerializationError, TransportLost @@ -184,7 +184,7 @@ class WampWebSocketServerProtocol(WampWebSocketProtocol): return subprotocol, headers if self.STRICT_PROTOCOL_NEGOTIATION: - raise http.HttpException(http.BAD_REQUEST[0], "This server only speaks WebSocket subprotocols %s" % ', '.join(self.factory.protocols)) + raise ConnectionDeny(ConnectionDeny.BAD_REQUEST, "This server only speaks WebSocket subprotocols %s" % ', '.join(self.factory.protocols)) else: # assume wamp.2.json self._serializer = self.factory._serializers['json'] diff --git a/autobahn/websocket/__init__.py b/autobahn/websocket/__init__.py index 8a4c67bf..d9aec846 100644 --- a/autobahn/websocket/__init__.py +++ b/autobahn/websocket/__init__.py @@ -23,3 +23,23 @@ # THE SOFTWARE. # ############################################################################### + + +from __future__ import absolute_import + +from autobahn.websocket.types import ConnectionRequest, ConnectionResponse, \ + ConnectionAccept, ConnectionDeny, Message, IncomingMessage, OutgoingMessage +from autobahn.websocket.interfaces import IWebSocketChannel + +__all__ = ( + 'IWebSocketChannel', + + 'Message', + 'IncomingMessage', + 'OutgoingMessage', + + 'ConnectionRequest', + 'ConnectionResponse', + 'ConnectionAccept', + 'ConnectionDeny', +) diff --git a/autobahn/websocket/compress.py b/autobahn/websocket/compress.py index 8d7b58cc..9cf79fb4 100644 --- a/autobahn/websocket/compress.py +++ b/autobahn/websocket/compress.py @@ -58,7 +58,7 @@ __all__ = [ ] # class for "permessage-deflate" is always available -## +# PERMESSAGE_COMPRESSION_EXTENSION = { PerMessageDeflateMixin.EXTENSION_NAME: { 'Offer': PerMessageDeflateOffer, @@ -71,7 +71,7 @@ PERMESSAGE_COMPRESSION_EXTENSION = { # include "permessage-bzip2" classes if bzip2 is available -## +# try: import bz2 except ImportError: @@ -102,7 +102,7 @@ else: # include "permessage-snappy" classes if Snappy is available -## +# try: # noinspection PyPackageRequirements import snappy diff --git a/autobahn/websocket/http.py b/autobahn/websocket/http.py deleted file mode 100644 index 8f4e0a80..00000000 --- a/autobahn/websocket/http.py +++ /dev/null @@ -1,240 +0,0 @@ -############################################################################### -# -# The MIT License (MIT) -# -# Copyright (c) Tavendo GmbH -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -############################################################################### - -# -# HTTP Status Codes -# -# Source: http://en.wikipedia.org/wiki/List_of_HTTP_status_codes -# Adapted on 2011/10/11 -# - -# -# 1xx Informational -# -# Request received, continuing process. -# -# This class of status code indicates a provisional response, consisting only of -# the Status-Line and optional headers, and is terminated by an empty line. -# Since HTTP/1.0 did not define any 1xx status codes, servers must not send -# a 1xx response to an HTTP/1.0 client except under experimental conditions. -# - -CONTINUE = (100, "Continue", - "This means that the server has received the request headers, and that the client should proceed to send the request body (in the case of a request for which a body needs to be sent; for example, a POST request). If the request body is large, sending it to a server when a request has already been rejected based upon inappropriate headers is inefficient. To have a server check if the request could be accepted based on the request's headers alone, a client must send Expect: 100-continue as a header in its initial request[2] and check if a 100 Continue status code is received in response before continuing (or receive 417 Expectation Failed and not continue).") -SWITCHING_PROTOCOLS = (101, "Switching Protocols", - "This means the requester has asked the server to switch protocols and the server is acknowledging that it will do so.") -PROCESSING = (102, "Processing (WebDAV) (RFC 2518)", - "As a WebDAV request may contain many sub-requests involving file operations, it may take a long time to complete the request. This code indicates that the server has received and is processing the request, but no response is available yet.[3] This prevents the client from timing out and assuming the request was lost.") -CHECKPOINT = (103, "Checkpoint", - "This code is used in the Resumable HTTP Requests Proposal to resume aborted PUT or POST requests.") -REQUEST_URI_TOO_LONG = (122, "Request-URI too long", - "This is a non-standard IE7-only code which means the URI is longer than a maximum of 2083 characters.[5][6] (See code 414.)") - -# -# 2xx Success -# -# This class of status codes indicates the action requested by the client was -# received, understood, accepted and processed successfully. -# - -OK = (200, "OK", - "Standard response for successful HTTP requests. The actual response will depend on the request method used. In a GET request, the response will contain an entity corresponding to the requested resource. In a POST request the response will contain an entity describing or containing the result of the action.") -CREATED = (201, "Created", - "The request has been fulfilled and resulted in a new resource being created.") -ACCEPTED = (202, "Accepted", - "The request has been accepted for processing, but the processing has not been completed. The request might or might not eventually be acted upon, as it might be disallowed when processing actually takes place.") -NON_AUTHORATATIVE = (203, "Non-Authoritative Information (since HTTP/1.1)", - "The server successfully processed the request, but is returning information that may be from another source.") -NO_CONTENT = (204, "No Content", - "The server successfully processed the request, but is not returning any content.") -RESET_CONTENT = (205, "Reset Content", - "The server successfully processed the request, but is not returning any content. Unlike a 204 response, this response requires that the requester reset the document view.") -PARTIAL_CONTENT = (206, "Partial Content", - "The server is delivering only part of the resource due to a range header sent by the client. The range header is used by tools like wget to enable resuming of interrupted downloads, or split a download into multiple simultaneous streams.") -MULTI_STATUS = (207, "Multi-Status (WebDAV) (RFC 4918)", - "The message body that follows is an XML message and can contain a number of separate response codes, depending on how many sub-requests were made.") -IM_USED = (226, "IM Used (RFC 3229)", - "The server has fulfilled a GET request for the resource, and the response is a representation of the result of one or more instance-manipulations applied to the current instance.") - -# -# 3xx Redirection -# -# The client must take additional action to complete the request. -# -# This class of status code indicates that further action needs to be taken -# by the user agent in order to fulfill the request. The action required may -# be carried out by the user agent without interaction with the user if and -# only if the method used in the second request is GET or HEAD. A user agent -# should not automatically redirect a request more than five times, since such -# redirections usually indicate an infinite loop. -# - -MULTIPLE_CHOICES = (300, "Multiple Choices", - "Indicates multiple options for the resource that the client may follow. It, for instance, could be used to present different format options for video, list files with different extensions, or word sense disambiguation.") -MOVED_PERMANENTLY = (301, "Moved Permanently", - "This and all future requests should be directed to the given URI.") -FOUND = (302, "Found", - "This is an example of industrial practice contradicting the standard. HTTP/1.0 specification (RFC 1945) required the client to perform a temporary redirect (the original describing phrase was 'Moved Temporarily', but popular browsers implemented 302 with the functionality of a 303 See Other. Therefore, HTTP/1.1 added status codes 303 and 307 to distinguish between the two behaviours. However, some Web applications and frameworks use the 302 status code as if it were the 303.") -SEE_OTHER = (303, "See Other (since HTTP/1.1)", - "The response to the request can be found under another URI using a GET method. When received in response to a POST (or PUT/DELETE), it should be assumed that the server has received the data and the redirect should be issued with a separate GET message.") -NOT_MODIFIED = (304, "Not Modified", - "Indicates the resource has not been modified since last requested.[2] Typically, the HTTP client provides a header like the If-Modified-Since header to provide a time against which to compare. Using this saves bandwidth and reprocessing on both the server and client, as only the header data must be sent and received in comparison to the entirety of the page being re-processed by the server, then sent again using more bandwidth of the server and client.") -USE_PROXY = (305, "Use Proxy (since HTTP/1.1)", - "Many HTTP clients (such as Mozilla[11] and Internet Explorer) do not correctly handle responses with this status code, primarily for security reasons.") -SWITCH_PROXY = (306, "Switch Proxy", - "No longer used. Originally meant 'Subsequent requests should use the specified proxy'.") -TEMPORARY_REDIRECT = (307, "Temporary Redirect (since HTTP/1.1)", - "In this occasion, the request should be repeated with another URI, but future requests can still use the original URI.[2] In contrast to 303, the request method should not be changed when reissuing the original request. For instance, a POST request must be repeated using another POST request.") -RESUME_INCOMPLETE = (308, "Resume Incomplete", - "This code is used in the Resumable HTTP Requests Proposal to resume aborted PUT or POST requests.") - -# -# 4xx Client Error -# -# The 4xx class of status code is intended for cases in which the client -# seems to have erred. Except when responding to a HEAD request, the server -# should include an entity containing an explanation of the error situation, -# and whether it is a temporary or permanent condition. These status codes are -# applicable to any request method. User agents should display any included -# entity to the user. These are typically the most common error codes -# encountered while online. -# - -BAD_REQUEST = (400, "Bad Request", - "The request cannot be fulfilled due to bad syntax.") -UNAUTHORIZED = (401, "Unauthorized", - "Similar to 403 Forbidden, but specifically for use when authentication is possible but has failed or not yet been provided.[2] The response must include a WWW-Authenticate header field containing a challenge applicable to the requested resource. See Basic access authentication and Digest access authentication.") -PAYMENT_REQUIRED = (402, "Payment Required", - "Reserved for future use.[2] The original intention was that this code might be used as part of some form of digital cash or micropayment scheme, but that has not happened, and this code is not usually used. As an example of its use, however, Apple's MobileMe service generates a 402 error if the MobileMe account is delinquent.") -FORBIDDEN = (403, "Forbidden", - "The request was a legal request, but the server is refusing to respond to it.[2] Unlike a 401 Unauthorized response, authenticating will make no difference.[2]") -NOT_FOUND = (404, "Not Found", - "The requested resource could not be found but may be available again in the future.[2] Subsequent requests by the client are permissible.") -METHOD_NOT_ALLOWED = (405, "Method Not Allowed", - "A request was made of a resource using a request method not supported by that resource;[2] for example, using GET on a form which requires data to be presented via POST, or using PUT on a read-only resource.") -NOT_ACCEPTABLE = (406, "Not Acceptable", - "The requested resource is only capable of generating content not acceptable according to the Accept headers sent in the request.") -PROXY_AUTH_REQUIRED = (407, "Proxy Authentication Required", - "The client must first authenticate itself with the proxy.") -REQUEST_TIMEOUT = (408, "Request Timeout", - "The server timed out waiting for the request. According to W3 HTTP specifications: 'The client did not produce a request within the time that the server was prepared to wait. The client MAY repeat the request without modifications at any later time.'") -CONFLICT = (409, "Conflict", - "Indicates that the request could not be processed because of conflict in the request, such as an edit conflict.") -GONE = (410, "Gone", - "Indicates that the resource requested is no longer available and will not be available again.[2] This should be used when a resource has been intentionally removed and the resource should be purged. Upon receiving a 410 status code, the client should not request the resource again in the future. Clients such as search engines should remove the resource from their indices. Most use cases do not require clients and search engines to purge the resource, and a '404 Not Found' may be used instead.") -LENGTH_REQUIRED = (411, "Length Required", - "The request did not specify the length of its content, which is required by the requested resource.") -PRECONDITION_FAILED = (412, "Precondition Failed", - "The server does not meet one of the preconditions that the requester put on the request.") -REQUEST_ENTITY_TOO_LARGE = (413, "Request Entity Too Large", - "The request is larger than the server is willing or able to process.") -REQUEST_URI_TOO_LARGE = (414, "Request-URI Too Long", - "The URI provided was too long for the server to process.") -UNSUPPORTED_MEDIA_TYPE = (415, "Unsupported Media Type", - "The request entity has a media type which the server or resource does not support. For example, the client uploads an image as image/svg+xml, but the server requires that images use a different format.") -INVALID_REQUEST_RANGE = (416, "Requested Range Not Satisfiable", - "The client has asked for a portion of the file, but the server cannot supply that portion.[2] For example, if the client asked for a part of the file that lies beyond the end of the file.") -EXPECTATION_FAILED = (417, "Expectation Failed", - "The server cannot meet the requirements of the Expect request-header field.") -TEAPOT = (418, "I'm a teapot (RFC 2324)", - "This code was defined in 1998 as one of the traditional IETF April Fools' jokes, in RFC 2324, Hyper Text Coffee Pot Control Protocol, and is not expected to be implemented by actual HTTP servers.") -UNPROCESSABLE_ENTITY = (422, "Unprocessable Entity (WebDAV) (RFC 4918)", - "The request was well-formed but was unable to be followed due to semantic errors.") -LOCKED = (423, "Locked (WebDAV) (RFC 4918)", - "The resource that is being accessed is locked.") -FAILED_DEPENDENCY = (424, "Failed Dependency (WebDAV) (RFC 4918)", - "The request failed due to failure of a previous request (e.g. a PROPPATCH).") -UNORDERED_COLLECTION = (425, "Unordered Collection (RFC 3648)", - "Defined in drafts of 'WebDAV Advanced Collections Protocol', but not present in 'Web Distributed Authoring and Versioning (WebDAV) Ordered Collections Protocol'.") -UPGRADE_REQUIRED = (426, "Upgrade Required (RFC 2817)", - "The client should switch to a different protocol such as TLS/1.0.") -NO_RESPONSE = (444, "No Response", - "A Nginx HTTP server extension. The server returns no information to the client and closes the connection (useful as a deterrent for malware).") -RETRY_WITH = (449, "Retry With", - "A Microsoft extension. The request should be retried after performing the appropriate action.") -PARANTAL_BLOCKED = (450, "Blocked by Windows Parental Controls", - "A Microsoft extension. This error is given when Windows Parental Controls are turned on and are blocking access to the given webpage.") -CLIENT_CLOSED_REQUEST = (499, "Client Closed Request", - "An Nginx HTTP server extension. This code is introduced to log the case when the connection is closed by client while HTTP server is processing its request, making server unable to send the HTTP header back.") - - -# -# 5xx Server Error -# -# The server failed to fulfill an apparently valid request. -# -# Response status codes beginning with the digit "5" indicate cases in which -# the server is aware that it has encountered an error or is otherwise incapable -# of performing the request. Except when responding to a HEAD request, the server -# should include an entity containing an explanation of the error situation, and -# indicate whether it is a temporary or permanent condition. Likewise, user agents -# should display any included entity to the user. These response codes are -# applicable to any request method. -# - -INTERNAL_SERVER_ERROR = (500, "Internal Server Error", - "A generic error message, given when no more specific message is suitable.") -NOT_IMPLEMENTED = (501, "Not Implemented", - "The server either does not recognize the request method, or it lacks the ability to fulfill the request.") -BAD_GATEWAY = (502, "Bad Gateway", - "The server was acting as a gateway or proxy and received an invalid response from the upstream server.") -SERVICE_UNAVAILABLE = (503, "Service Unavailable", - "The server is currently unavailable (because it is overloaded or down for maintenance). Generally, this is a temporary state.") -GATEWAY_TIMEOUT = (504, "Gateway Timeout", - "The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.") -UNSUPPORTED_HTTP_VERSION = (505, "HTTP Version Not Supported", - "The server does not support the HTTP protocol version used in the request.") -VARIANT_ALSO_NEGOTIATES = (506, "Variant Also Negotiates (RFC 2295)", - "Transparent content negotiation for the request results in a circular reference.") -INSUFFICIENT_STORAGE = (507, "Insufficient Storage (WebDAV)(RFC 4918)", - "The server is unable to store the representation needed to complete the request.") -BANDWIDTH_LIMIT_EXCEEDED = (509, "Bandwidth Limit Exceeded (Apache bw/limited extension)", - "This status code, while used by many servers, is not specified in any RFCs.") -NOT_EXTENDED = (510, "Not Extended (RFC 2774)", - "Further extensions to the request are required for the server to fulfill it.") -NETWORK_READ_TIMEOUT = (598, "Network read timeout error (Informal convention)", - "This status code is not specified in any RFCs, but is used by some HTTP proxies to signal a network read timeout behind the proxy to a client in front of the proxy.") -NETWORK_CONNECT_TIMEOUT = (599, "Network connect timeout error (Informal convention)", - "This status code is not specified in any RFCs, but is used by some HTTP proxies to signal a network connect timeout behind the proxy to a client in front of the proxy.") - - -class HttpException(Exception): - """ - Throw an instance of this class to deny a WebSocket connection - during handshake in :meth:`autobahn.websocket.protocol.WebSocketServerProtocol.onConnect`. - """ - - def __init__(self, code, reason): - """ - Constructor. - - :param code: HTTP error code. - :type code: int - :param reason: HTTP error reason. - :type reason: str - """ - self.code = code - self.reason = reason diff --git a/autobahn/websocket/interfaces.py b/autobahn/websocket/interfaces.py index b7319301..7bcdada4 100644 --- a/autobahn/websocket/interfaces.py +++ b/autobahn/websocket/interfaces.py @@ -43,80 +43,52 @@ class IWebSocketChannel(object): """ @abc.abstractmethod - def onConnect(self, requestOrResponse): + def on_connect(self, request_or_response): """ Callback fired during WebSocket opening handshake when a client connects (to a server with request from client) or when server connection established (by a client with response from - server). + server). This method may run asynchronous code. - :param requestOrResponse: Connection request (for servers) or response (for clients). - :type requestOrResponse: Instance of :class:`autobahn.websocket.protocol.ConnectionRequest` - or :class:`autobahn.websocket.protocol.ConnectionResponse`. + :param request_or_response: Connection request (for servers) or response (for clients). + :type request_or_response: Instance of :class:`autobahn.websocket.types.ConnectionRequest` + or :class:`autobahn.websocket.types.ConnectionResponse`. :returns: - When this callback is fired on a WebSocket server, you may return one of the - following: - - 1. ``None``: Connection accepted (no subprotocol) - 2. ``str``: Connection accepted with given subprotocol - 3. ``(subprotocol, headers)``: Connection accepted with given ``subprotocol`` (which - also may be ``None``) and set the given HTTP ``headers`` (e.g. cookies). ``headers`` - must be a ``dict`` with ``str`` keys and values for the HTTP header values to set. - - If a given header value is a non-string iterable (e.g. list or tuple), a separate - header line will be sent for each item in the iterable. - - If the client announced one or multiple subprotocols, the server MUST select - one of the given list. + When this callback is fired on a WebSocket server, you may return either ``None`` (in + which case the connection is accepted with no specific WebSocket subprotocol) or + an instance of :class:`autobahn.websocket.types.ConnectionAccept`. + When the callback is fired on a WebSocket client, this method must return ``None``. + Do deny a connection, raise an Exception. + You can also return a Deferred/Future that resolves/rejects to the above. """ @abc.abstractmethod - def onOpen(self): + def on_open(self): """ Callback fired when the initial WebSocket opening handshake was completed. You now can send and receive WebSocket messages. """ @abc.abstractmethod - def sendMessage(self, payload, isBinary=False, fragmentSize=None, sync=False, doNotCompress=False): + def send_message(self, message): """ Send a WebSocket message over the connection to the peer. - :param payload: The message payload. - :type payload: bytes - :param isBinary: ``True`` when payload is binary, else the payload must be UTF-8 encoded text. - :type isBinary: bool - :param fragmentSize: Fragment message into WebSocket fragments of this size (the last frame - potentially being shorter). - :type fragmentSize: int - :param sync: If ``True``, try to force data onto the wire immediately. - - .. warning:: - Do NOT use this feature for normal applications. - Performance likely will suffer significantly. - This feature is mainly here for use by Autobahn|Testsuite. - :type sync: bool - :param doNotCompress: Iff ``True``, never compress this message. This only applies to - Hybi-Mode and only when WebSocket compression has been negotiated on - the WebSocket connection. Use when you know the payload - incompressible (e.g. encrypted or already compressed). - :type doNotCompress: bool + :param message: The WebSocket message to be sent. + :type message: Instance of :class:`autobahn.websocket.types.OutgoingMessage` """ @abc.abstractmethod - def onMessage(self, payload, isBinary): + def on_message(self, message): """ Callback fired when a complete WebSocket message was received. - :param payload: Message payload (UTF-8 encoded text or binary). Can also be empty when - the WebSocket message contained no payload. - :type payload: bytes - :param isBinary: ``True`` iff payload is binary, else the payload is UTF-8 encoded text. - :type isBinary: bool + :param message: The WebSocket message received. + :type message: :class:`autobahn.websocket.types.IncomingMessage` """ @abc.abstractmethod - def sendClose(self, code=None, reason=None): + def send_close(self, code=None, reason=None): """ Starts a WebSocket closing handshake tearing down the WebSocket connection. @@ -125,20 +97,20 @@ class IWebSocketChannel(object): :type code: int :param reason: An optional close reason (a string that when present, a status code MUST also be present). - :type reason: str + :type reason: unicode """ @abc.abstractmethod - def onClose(self, wasClean, code, reason): + def on_close(self, was_clean, code, reason): """ Callback fired when the WebSocket connection has been closed (WebSocket closing handshake has been finished or the connection was closed uncleanly). :param wasClean: ``True`` iff the WebSocket connection was closed cleanly. :type wasClean: bool - :param code: Close status code (as sent by the WebSocket peer). + :param code: Close status code as sent by the WebSocket peer. :type code: int or None - :param reason: Close reason (as sent by the WebSocket peer). + :param reason: Close reason as sent by the WebSocket peer. :type reason: unicode or None """ @@ -152,7 +124,7 @@ class IWebSocketChannel(object): """ @abc.abstractmethod - def sendPing(self, payload=None): + def send_ping(self, payload=None): """ Send a WebSocket ping to the peer. @@ -164,7 +136,7 @@ class IWebSocketChannel(object): """ @abc.abstractmethod - def onPing(self, payload): + def on_ping(self, payload): """ Callback fired when a WebSocket ping was received. A default implementation responds by sending a WebSocket pong. @@ -174,7 +146,7 @@ class IWebSocketChannel(object): """ @abc.abstractmethod - def sendPong(self, payload=None): + def send_pong(self, payload=None): """ Send a WebSocket pong to the peer. @@ -186,7 +158,7 @@ class IWebSocketChannel(object): """ @abc.abstractmethod - def onPong(self, payload): + def on_pong(self, payload): """ Callback fired when a WebSocket pong was received. A default implementation does nothing. diff --git a/autobahn/websocket/protocol.py b/autobahn/websocket/protocol.py index f3d2b55d..5298507b 100755 --- a/autobahn/websocket/protocol.py +++ b/autobahn/websocket/protocol.py @@ -46,11 +46,12 @@ from autobahn.websocket.interfaces import IWebSocketChannel, \ IWebSocketChannelFrameApi, \ IWebSocketChannelStreamingApi +from autobahn.websocket.types import ConnectionRequest, ConnectionResponse + from autobahn.util import Stopwatch, newid, wildcards2patterns from autobahn.websocket.utf8validator import Utf8Validator from autobahn.websocket.xormasker import XorMaskerNull, createXorMasker from autobahn.websocket.compress import PERMESSAGE_COMPRESSION_EXTENSION -from autobahn.websocket import http from six.moves import urllib import txaio @@ -280,98 +281,6 @@ class FrameHeader(object): self.mask = mask -class ConnectionRequest(object): - """ - Thin-wrapper for WebSocket connection request information provided in - :meth:`autobahn.websocket.protocol.WebSocketServerProtocol.onConnect` when - a WebSocket client want to establish a connection to a WebSocket server. - """ - def __init__(self, peer, headers, host, path, params, version, origin, protocols, extensions): - """ - Constructor. - - :param peer: Descriptor of the connecting client (e.g. IP address/port in case of TCP transports). - :type peer: str - :param headers: HTTP headers from opening handshake request. - :type headers: dict - :param host: Host from opening handshake HTTP header. - :type host: str - :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`. - :type path: str - :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']}`. - :type params: dict of arrays of strings - :param version: The WebSocket protocol version the client announced (and will be spoken, when connection is accepted). - :type version: int - :param origin: The WebSocket origin header or None. Note that this only a reliable source of information for browser clients! - :type origin: str - :param protocols: The WebSocket (sub)protocols the client announced. You must select and return one of those (or None) in :meth:`autobahn.websocket.WebSocketServerProtocol.onConnect`. - :type protocols: list of str - :param extensions: The WebSocket extensions the client requested and the server accepted (and thus will be spoken, when WS connection is established). - :type extensions: list of str - """ - self.peer = peer - self.headers = headers - self.host = host - self.path = path - self.params = params - self.version = version - self.origin = origin - self.protocols = protocols - self.extensions = extensions - - def __json__(self): - return {'peer': self.peer, - 'headers': self.headers, - 'host': self.host, - 'path': self.path, - 'params': self.params, - 'version': self.version, - 'origin': self.origin, - 'protocols': self.protocols, - 'extensions': self.extensions} - - def __str__(self): - return json.dumps(self.__json__()) - - -class ConnectionResponse(object): - """ - Thin-wrapper for WebSocket connection response information provided in - :meth:`autobahn.websocket.protocol.WebSocketClientProtocol.onConnect` when - a WebSocket server has accepted a connection request by a client. - """ - def __init__(self, peer, headers, version, protocol, extensions): - """ - Constructor. - - :param peer: Descriptor of the connected server (e.g. IP address/port in case of TCP transport). - :type peer: str - :param headers: HTTP headers from opening handshake response. - :type headers: dict - :param version: The WebSocket protocol version that is spoken. - :type version: int - :param protocol: The WebSocket (sub)protocol in use. - :type protocol: str - :param extensions: The WebSocket extensions in use. - :type extensions: list of str - """ - self.peer = peer - self.headers = headers - self.version = version - self.protocol = protocol - self.extensions = extensions - - def __json__(self): - return {'peer': self.peer, - 'headers': self.headers, - 'version': self.version, - 'protocol': self.protocol, - 'extensions': self.extensions} - - def __str__(self): - return json.dumps(self.__json__()) - - def parseHttpHeader(data): """ Parses the beginning of a HTTP request header (the data up to the \n\n line) into a pair @@ -2545,10 +2454,10 @@ class WebSocketServerProtocol(WebSocketProtocol): if len(rl) != 3: return self.failHandshake("Bad HTTP request status line '%s'" % self.http_status_line) if rl[0].strip() != "GET": - return self.failHandshake("HTTP method '%s' not allowed" % rl[0], http.METHOD_NOT_ALLOWED[0]) + return self.failHandshake("HTTP method '%s' not allowed" % rl[0], 405) vs = rl[2].strip().split("/") if len(vs) != 2 or vs[0] != "HTTP" or vs[1] not in ["1.1"]: - return self.failHandshake("Unsupported HTTP version '%s'" % rl[2], http.UNSUPPORTED_HTTP_VERSION[0]) + return self.failHandshake("Unsupported HTTP version '%s'" % rl[2], 505) # HTTP Request line : REQUEST-URI # @@ -2648,7 +2557,7 @@ class WebSocketServerProtocol(WebSocketProtocol): self.dropConnection(abort=False) return else: - return self.failHandshake("HTTP Upgrade header missing", http.UPGRADE_REQUIRED[0]) + return self.failHandshake("HTTP Upgrade header missing", 426) # Upgrade Required upgradeWebSocket = False for u in self.http_headers["upgrade"].split(","): if u.strip().lower() == "websocket": @@ -2693,7 +2602,7 @@ class WebSocketServerProtocol(WebSocketProtocol): sv.reverse() svs = ','.join([str(x) for x in sv]) return self.failHandshake("WebSocket version %d not supported (supported versions: %s)" % (version, svs), - http.BAD_REQUEST[0], + 400, # Bad Request [("Sec-WebSocket-Version", svs)]) else: # store the protocol version we are supposed to talk @@ -2785,7 +2694,7 @@ class WebSocketServerProtocol(WebSocketProtocol): # maximum number of concurrent connections reached # - self.failHandshake("maximum number of connections reached", code=http.SERVICE_UNAVAILABLE[0]) + self.failHandshake("maximum number of connections reached", code=503) # Service Unavailable else: # WebSocket handshake validated => produce opening handshake response @@ -2891,7 +2800,7 @@ class WebSocketServerProtocol(WebSocketProtocol): # build response to complete WebSocket handshake # - response = "HTTP/1.1 %d Switching Protocols\x0d\x0a" % http.SWITCHING_PROTOCOLS[0] + response = "HTTP/1.1 101 Switching Protocols\x0d\x0a" if self.factory.server is not None and self.factory.server != "": response += "Server: %s\x0d\x0a" % self.factory.server @@ -2986,7 +2895,7 @@ class WebSocketServerProtocol(WebSocketProtocol): if len(self.data) > 0: self.consumeData() - def failHandshake(self, reason, code=http.BAD_REQUEST[0], responseHeaders=None): + def failHandshake(self, reason, code=400, responseHeaders=None): """ During opening handshake the client request was invalid, we send a HTTP error response and then drop the connection. @@ -3012,7 +2921,7 @@ class WebSocketServerProtocol(WebSocketProtocol): Send HTML page HTTP response. """ responseBody = html.encode('utf8') - response = "HTTP/1.1 %d %s\x0d\x0a" % (http.OK[0], http.OK[1]) + response = "HTTP/1.1 200 OK\x0d\x0a" if self.factory.server is not None and self.factory.server != "": response += "Server: %s\x0d\x0a" % self.factory.server response += "Content-Type: text/html; charset=UTF-8\x0d\x0a" @@ -3025,7 +2934,7 @@ class WebSocketServerProtocol(WebSocketProtocol): """ Send HTTP Redirect (303) response. """ - response = "HTTP/1.1 %d\x0d\x0a" % http.SEE_OTHER[0] + response = "HTTP/1.1 303\x0d\x0a" if self.factory.server is not None and self.factory.server != "": response += "Server: %s\x0d\x0a" % self.factory.server response += "Location: %s\x0d\x0a" % url @@ -3639,7 +3548,7 @@ class WebSocketClientProtocol(WebSocketProtocol): status_code = int(sl[1].strip()) except ValueError: return self.failHandshake("Bad HTTP status code ('%s')" % sl[1].strip()) - if status_code != http.SWITCHING_PROTOCOLS[0]: + if status_code != 101: # Switching Protocols # FIXME: handle redirects # FIXME: handle authentication required diff --git a/autobahn/websocket/types.py b/autobahn/websocket/types.py new file mode 100644 index 00000000..af1b034c --- /dev/null +++ b/autobahn/websocket/types.py @@ -0,0 +1,331 @@ +############################################################################### +# +# The MIT License (MIT) +# +# Copyright (c) Tavendo GmbH +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +############################################################################### + +from __future__ import absolute_import + +import json +import six + +__all__ = ( + 'ConnectionRequest', + 'ConnectionResponse', + 'ConnectionAccept', + 'ConnectionDeny', + 'Message', + 'IncomingMessage', + 'OutgoingMessage', +) + + +class ConnectionRequest(object): + """ + Thin-wrapper for WebSocket connection request information provided in + :meth:`autobahn.websocket.protocol.WebSocketServerProtocol.onConnect` when + a WebSocket client want to establish a connection to a WebSocket server. + """ + + __slots__ = ( + 'peer', + 'headers', + 'host', + 'path', + 'params', + 'version', + 'origin', + 'protocols', + 'extensions' + ) + + def __init__(self, peer, headers, host, path, params, version, origin, protocols, extensions): + """ + + :param peer: Descriptor of the connecting client (e.g. IP address/port in case of TCP transports). + :type peer: str + :param headers: HTTP headers from opening handshake request. + :type headers: dict + :param host: Host from opening handshake HTTP header. + :type host: str + :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`. + :type path: str + :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']}`. + :type params: dict of arrays of strings + :param version: The WebSocket protocol version the client announced (and will be spoken, when connection is accepted). + :type version: int + :param origin: The WebSocket origin header or None. Note that this only a reliable source of information for browser clients! + :type origin: str + :param protocols: The WebSocket (sub)protocols the client announced. You must select and return one of those (or None) in :meth:`autobahn.websocket.WebSocketServerProtocol.onConnect`. + :type protocols: list of str + :param extensions: The WebSocket extensions the client requested and the server accepted (and thus will be spoken, when WS connection is established). + :type extensions: list of str + """ + self.peer = peer + self.headers = headers + self.host = host + self.path = path + self.params = params + self.version = version + self.origin = origin + self.protocols = protocols + self.extensions = extensions + + def __json__(self): + return {'peer': self.peer, + 'headers': self.headers, + 'host': self.host, + 'path': self.path, + 'params': self.params, + 'version': self.version, + 'origin': self.origin, + 'protocols': self.protocols, + 'extensions': self.extensions} + + def __str__(self): + return json.dumps(self.__json__()) + + +class ConnectionResponse(object): + """ + Thin-wrapper for WebSocket connection response information provided in + :meth:`autobahn.websocket.protocol.WebSocketClientProtocol.onConnect` when + a WebSocket server has accepted a connection request by a client. + """ + + __slots__ = ( + 'peer', + 'headers', + 'version', + 'protocol', + 'extensions' + ) + + def __init__(self, peer, headers, version, protocol, extensions): + """ + Constructor. + + :param peer: Descriptor of the connected server (e.g. IP address/port in case of TCP transport). + :type peer: str + :param headers: HTTP headers from opening handshake response. + :type headers: dict + :param version: The WebSocket protocol version that is spoken. + :type version: int + :param protocol: The WebSocket (sub)protocol in use. + :type protocol: str + :param extensions: The WebSocket extensions in use. + :type extensions: list of str + """ + self.peer = peer + self.headers = headers + self.version = version + self.protocol = protocol + self.extensions = extensions + + def __json__(self): + return {'peer': self.peer, + 'headers': self.headers, + 'version': self.version, + 'protocol': self.protocol, + 'extensions': self.extensions} + + def __str__(self): + return json.dumps(self.__json__()) + + +class ConnectionAccept(object): + """ + Used by WebSocket servers to accept an incoming WebSocket connection. + If the client announced one or multiple subprotocols, the server MUST + select one of the subprotocols announced by the client. + """ + + __slots__ = ('subprotocol', 'headers') + + def __init__(self, subprotocol=None, headers=None): + """ + + :param subprotocol: The WebSocket connection is accepted with the + this WebSocket subprotocol chosen. The value must be a token + as defined by RFC 2616. + :type subprotocol: unicode or None + :param headers: Additional HTTP headers to send on the WebSocket + opening handshake reply, e.g. cookies. The keys must be unicode, + and the values either unicode or tuple/list. In the latter case + a separate HTTP header line will be sent for each item in + tuple/list. + :type headers: dict or None + """ + assert(subprotocol is None or type(subprotocol) == six.text_type) + assert(headers is None or type(headers) == dict) + if headers is not None: + for k, v in headers.items(): + assert(type(k) == unicode) + assert(type(v) == unicode or type(v) == list or type(v) == tuple) + self.subprotocol = subprotocol + self.headers = headers + + +class ConnectionDeny(Exception): + """ + Throw an instance of this class to deny a WebSocket connection + during handshake in :meth:`autobahn.websocket.protocol.WebSocketServerProtocol.onConnect`. + """ + + BAD_REQUEST = 400 + """ + Bad Request. The request cannot be fulfilled due to bad syntax. + """ + + FORBIDDEN = 403 + """ + Forbidden. The request was a legal request, but the server is refusing to respond to it.[2] Unlike a 401 Unauthorized response, authenticating will make no difference. + """ + + NOT_FOUND = 404 + """ + Not Found. The requested resource could not be found but may be available again in the future.[2] Subsequent requests by the client are permissible. + """ + + NOT_ACCEPTABLE = 406 + """ + Not Acceptable. The requested resource is only capable of generating content not acceptable according to the Accept headers sent in the request. + """ + + REQUEST_TIMEOUT = 408 + """ + Request Timeout. The server timed out waiting for the request. According to W3 HTTP specifications: 'The client did not produce a request within the time that the server was prepared to wait. The client MAY repeat the request without modifications at any later time. + """ + + INTERNAL_SERVER_ERROR = 500 + """ + Internal Server Error. A generic error message, given when no more specific message is suitable. + """ + + NOT_IMPLEMENTED = 501 + """ + Not Implemented. The server either does not recognize the request method, or it lacks the ability to fulfill the request. + """ + + SERVICE_UNAVAILABLE = 503 + """ + Service Unavailable. The server is currently unavailable (because it is overloaded or down for maintenance). Generally, this is a temporary state. + """ + + def __init__(self, code, reason=None): + """ + Constructor. + + :param code: HTTP error code. + :type code: int + :param reason: HTTP error reason. + :type reason: unicode + """ + assert(type(code) == int) + assert(reason is None or type(reason) == unicode) + self.code = code + self.reason = reason + + +class Message(object): + """ + Abstract base class for WebSocket messages. + """ + + __slots__ = () + + +class IncomingMessage(Message): + """ + An incoming WebSocket message. + """ + + __slots__ = ('payload', 'is_binary') + + def __init__(self, payload, is_binary=False): + """ + + :param payload: The WebSocket message payload, which can be UTF-8 + encoded text or a binary string. + :type payload: bytes + :param is_binary: ``True`` iff payload is binary, else the payload + contains UTF-8 encoded text. + :type is_binary: bool + """ + assert(type(payload) == bytes) + assert(type(is_binary) == bool) + + self.payload = payload + self.is_binary = is_binary + + +class OutgoingMessage(Message): + """ + An outgoing WebSocket message. + """ + + __slots__ = ('payload', 'is_binary', 'dont_compress') + + def __init__(self, payload, is_binary=False, dont_compress=False): + """ + + :param payload: The WebSocket message payload, which can be UTF-8 + encoded text or a binary string. + :type payload: bytes + :param is_binary: ``True`` iff payload is binary, else the payload + contains UTF-8 encoded text. + :type is_binary: bool + :param dont_compress: Iff ``True``, never compress this message. + This only has an effect when WebSocket compression has been negotiated + on the WebSocket connection. Use when you know the payload is + incompressible (e.g. encrypted or already compressed). + :type dont_compress: bool + """ + assert(type(payload) == bytes) + assert(type(is_binary) == bool) + assert(type(dont_compress) == bool) + + self.payload = payload + self.is_binary = is_binary + self.dont_compress = dont_compress + + +class Ping(object): + """ + A WebSocket ping message. + """ + + __slots__ = ('payload') + + def __init__(self, payload=None): + """ + + :param payload: The WebSocket ping message payload. + :type payload: bytes or None + """ + assert(payload is None or type(payload) == bytes), \ + ("invalid type {} for WebSocket ping payload - must be None or bytes".format(type(payload))) + if payload is not None: + assert(len(payload) < 126), \ + ("WebSocket ping payload too long ({} bytes) - must be <= 125 bytes".format(len(payload))) + self.payload = payload diff --git a/autobahn/websocket/useragent.py b/autobahn/websocket/useragent.py deleted file mode 100644 index e3681f83..00000000 --- a/autobahn/websocket/useragent.py +++ /dev/null @@ -1,310 +0,0 @@ -############################################################################### -# -# The MIT License (MIT) -# -# Copyright (c) Tavendo GmbH -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -############################################################################### - -import re - -__all__ = ("lookupWsSupport",) - -UA_FIREFOX = re.compile(".*Firefox/(\d*).*") -UA_CHROME = re.compile(".*Chrome/(\d*).*") -UA_CHROMEFRAME = re.compile(".*chromeframe/(\d*).*") -UA_WEBKIT = re.compile(".*AppleWebKit/([0-9+\.]*)\w*.*") -UA_WEBOS = re.compile(".*webos/([0-9+\.]*)\w*.*") -UA_HPWEBOS = re.compile(".*hpwOS/([0-9+\.]*)\w*.*") - -UA_DETECT_WS_SUPPORT_DB = {} - - -# Chrome ============================================================= - -# Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11 - - -# Chrome Frame ======================================================= - -# IE6 on Windows with Chrome Frame -# Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; chromeframe/11.0.660.0) - - -# Firefox ============================================================ - -# Windows 7 64 Bit -# Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0a2) Gecko/20120227 Firefox/12.0a2 - - -# Android ============================================================ - -# Firefox Mobile -# Mozilla/5.0 (Android; Linux armv7l; rv:10.0.2) Gecko/20120215 Firefox/10.0.2 Fennec/10.0.2 - -# Chrome for Android (on ICS) -# Mozilla/5.0 (Linux; U; Android-4.0.3; en-us; Galaxy Nexus Build/IML74K) AppleWebKit/535.7 (KHTML, like Gecko) CrMo/16.0.912.75 Mobile Safari/535.7 - -# Android builtin browser - -# Samsung Galaxy Tab 1 -# Mozilla/5.0 (Linux; U; Android 2.2; de-de; GT-P1000 Build/FROYO) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1 - -# Samsung Galaxy S -# Mozilla/5.0 (Linux; U; Android 2.3.3; de-de; GT-I9000 Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1 - -# Samsung Galaxy Note -# Mozilla/5.0 (Linux; U; Android 2.3.6; de-de; GT-N7000 Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1 - -# Samsung Galaxy ACE (no Flash since ARM) -# Mozilla/5.0 (Linux; U; Android 2.2.1; de-de; GT-S5830 Build/FROYO) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1 - - -# WebOS ============================================================== - -# HP Touchpad -# Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.5; U; en-US) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/234.83 Safari/534.6 TouchPad/1.0 -# => Qt-WebKit, Hixie-76, Flash - - -# Safari ============================================================= - -# iPod Touch, iOS 4.2.1 -# Mozilla/5.0 (iPod; U; CPU iPhone OS 4_2_1 like Mac OS X; de-de) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5 -# => Hixie-76 - -# MacBook Pro, OSX 10.5.8, Safari 5.0.6 -# Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/534.50.2 (KHTML, like Gecko) Version/5.0.6 Safari/533.22.3 -# => Hixie-76 - -# RFC6455 -# Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534+ (KHTML, like Gecko) Version/5.1.2 Safari/534.52.7 -# Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.24+ (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10 - -# Hixie-76 -# Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.53.11 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10 - -# Hixie-76 -# Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/534.50.2 (KHTML, like Gecko) Version/5.0.6 Safari/533.22.3 - - -# Opera ============================================================== - -# Windows 7 32-Bit -# Opera/9.80 (Windows NT 6.1; U; de) Presto/2.10.229 Version/11.61 - -# Windows 7 64-Bit -# Opera/9.80 (Windows NT 6.1; WOW64; U; de) Presto/2.10.229 Version/11.62 - -# Samsung Galaxy S -# Opera/9.80 (Android 2.3.3; Linux; Opera Mobi/ADR-1202231246; U; de) Presto/2.10.254 Version/12.00 - -# Samsung Galaxy Tab 1 -# Opera/9.80 (Android 2.2; Linux; Opera Tablet/ADR-1203051631; U; de) Presto/2.10.254 Version/12.00 - -# Samsung Galaxy ACE: -# Opera/9.80 (Android 2.2.1; Linux; Opera Mobi/ADR-1203051631; U; de) Presto/2.10.254 Version/12.00 - -# Nokia N8, Symbian S60 5th Ed., S60 Bell -# Opera/9.80 (S60; SymbOS; Opera Mobi/SYB-1111151949; U; de) Presto/2.9.201 Version/11.50 - - -def _lookupWsSupport(ua): - # Internet Explorer - ## - # FIXME: handle Windows Phone - ## - if ua.find("MSIE") >= 0: - # IE10 has native support - if ua.find("MSIE 10") >= 0: - # native Hybi-10+ - return True, False, True - - # first, check for Google Chrome Frame - # http://www.chromium.org/developers/how-tos/chrome-frame-getting-started/understanding-chrome-frame-user-agent - if ua.find("chromeframe") >= 0: - - r = UA_CHROMEFRAME.match(ua) - try: - v = int(r.groups()[0]) - if v >= 14: - # native Hybi-10+ - return True, False, True - except: - # detection problem - return False, False, False - - # Flash fallback - if ua.find("MSIE 8") >= 0 or ua.find("MSIE 9") >= 0: - return True, True, True - - # unsupported - return False, False, True - - # iOS - ## - if ua.find("iPhone") >= 0 or ua.find("iPad") >= 0 or ua.find("iPod") >= 0: - # native Hixie76 (as of March 2012), no Flash, no alternative browsers - return True, False, True - - # Android - ## - if ua.find("Android") >= 0: - - # Firefox Mobile - ## - if ua.find("Firefox") >= 0: - # Hybi-10+ for FF Mobile 8+ - return True, False, True - - # Opera Mobile - ## - if ua.find("Opera") >= 0: - # Hixie76 for Opera 11+ - return True, False, True - - # Chrome for Android - ## - if ua.find("CrMo") >= 0: - # http://code.google.com/chrome/mobile/docs/faq.html - return True, False, True - - # Android builtin Browser (ooold WebKit) - ## - if ua.find("AppleWebKit") >= 0: - - # Though we return WS = True, and Flash = True here, when the device has no actual Flash support, that - # will get later detected in JS. This applies to i.e. ARMv6 devices like Samsung Galaxy ACE - - # builtin browser, only works via Flash - return True, True, True - - # detection problem - return False, False, False - - # webOS - ## - if ua.find("hpwOS") >= 0 or ua.find("webos") >= 0: - try: - if ua.find("hpwOS") >= 0: - vv = [int(x) for x in UA_HPWEBOS.match(ua).groups()[0].split('.')] - if vv[0] >= 3: - return True, False, True - elif ua.find("webos") >= 0: - vv = [int(x) for x in UA_WEBOS.match(ua).groups()[0].split('.')] - if vv[0] >= 2: - return True, False, True - except: - # detection problem - return False, False, False - else: - # unsupported - return False, False, True - - # Opera - ## - if ua.find("Opera") >= 0: - # Opera 11+ has Hixie76 (needs to be manually activated though) - return True, False, True - - # Firefox - ## - if ua.find("Firefox") >= 0: - r = UA_FIREFOX.match(ua) - try: - v = int(r.groups()[0]) - if v >= 7: - # native Hybi-10+ - return True, False, True - elif v >= 3: - # works with Flash bridge - return True, True, True - else: - # unsupported - return False, False, True - except: - # detection problem - return False, False, False - - # Safari - ## - if ua.find("Safari") >= 0 and not ua.find("Chrome") >= 0: - - # rely on at least Hixie76 - return True, False, True - - # Chrome - ## - if ua.find("Chrome") >= 0: - r = UA_CHROME.match(ua) - try: - v = int(r.groups()[0]) - if v >= 14: - # native Hybi-10+ - return True, False, True - elif v >= 4: - # works with Flash bridge - return True, True, True - else: - # unsupported - return False, False, True - except: - # detection problem - return False, False, False - - # detection problem - return False, False, False - - -def lookupWsSupport(ua, debug=True): - """ - Lookup if browser supports WebSocket (Hixie76, Hybi10+, RFC6455) natively, - and if not, whether the `web-socket-js `__ - Flash bridge works to polyfill that. - - Returns a tuple of booleans ``(ws_supported, needs_flash, detected)`` where - - * ``ws_supported``: WebSocket is supported - * ``needs_flash``: Flash Bridge is needed for support - * ``detected`` the code has explicitly mapped support - - :param ua: The browser user agent string as sent in the HTTP header, e.g. provided as `flask.request.user_agent.string` in Flask. - :type ua: str - - :returns: tuple -- A tuple ``(ws_supported, needs_flash, detected)``. - """ - ws = _lookupWsSupport(ua) - if debug: - if ua not in UA_DETECT_WS_SUPPORT_DB: - UA_DETECT_WS_SUPPORT_DB[ua] = ws - - if not ws[2]: - msg = "UNDETECTED" - elif ws[0]: - msg = "SUPPORTED" - elif not ws[0]: - msg = "UNSUPPORTED" - else: - msg = "ERROR" - - print("DETECT_WS_SUPPORT: %s %s %s %s %s" % (ua, ws[0], ws[1], ws[2], msg)) - - return ws From 984fc47f11ad4cb10acbdd1de22faa44f19f64ca Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Tue, 15 Sep 2015 15:24:28 +0200 Subject: [PATCH 12/59] fixes #507 --- autobahn/twisted/flashpolicy.py | 127 -------------------------------- 1 file changed, 127 deletions(-) delete mode 100644 autobahn/twisted/flashpolicy.py diff --git a/autobahn/twisted/flashpolicy.py b/autobahn/twisted/flashpolicy.py deleted file mode 100644 index d370afac..00000000 --- a/autobahn/twisted/flashpolicy.py +++ /dev/null @@ -1,127 +0,0 @@ -############################################################################### -# -# The MIT License (MIT) -# -# Copyright (c) Tavendo GmbH -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -############################################################################### - -import re - -from twisted.internet.protocol import Protocol, Factory - -__all__ = ( - 'FlashPolicyProtocol', - 'FlashPolicyFactory' -) - - -class FlashPolicyProtocol(Protocol): - """ - Flash Player 9 (version 9.0.124.0 and above) implements a strict new access - policy for Flash applications that make Socket or XMLSocket connections to - a remote host. It now requires the presence of a socket policy file - on the server. - - We want this to support the Flash WebSockets bridge which is needed for - older browser, in particular MSIE9/8. - - .. seealso:: - * `Autobahn WebSocket fallbacks example `_ - * `Flash policy files background `_ - """ - - REQUESTPAT = re.compile("^\s*") - REQUESTMAXLEN = 200 - REQUESTTIMEOUT = 5 - POLICYFILE = """""" - - def __init__(self, allowedDomain, allowedPorts): - """ - - :param allowedPort: The port to which Flash player should be allowed to connect. - :type allowedPort: int - """ - self._allowedDomain = allowedDomain - self._allowedPorts = allowedPorts - self.received = "" - self.dropConnection = None - - def connectionMade(self): - # DoS protection - ## - def dropConnection(): - self.transport.abortConnection() - self.dropConnection = None - self.dropConnection = self.factory.reactor.callLater(FlashPolicyProtocol.REQUESTTIMEOUT, dropConnection) - - def connectionLost(self, reason): - if self.dropConnection: - self.dropConnection.cancel() - self.dropConnection = None - - def dataReceived(self, data): - self.received += data - if FlashPolicyProtocol.REQUESTPAT.match(self.received): - # got valid request: send policy file - ## - self.transport.write(FlashPolicyProtocol.POLICYFILE % (self._allowedDomain, self._allowedPorts)) - self.transport.loseConnection() - elif len(self.received) > FlashPolicyProtocol.REQUESTMAXLEN: - # possible DoS attack - ## - self.transport.abortConnection() - else: - # need more data - ## - pass - - -class FlashPolicyFactory(Factory): - - def __init__(self, allowedDomain=None, allowedPorts=None, reactor=None): - """ - - :param allowedDomain: The domain from which to allow Flash to connect from. - If ``None``, allow from anywhere. - :type allowedDomain: str or None - :param allowedPorts: The ports to which Flash player should be allowed to connect. - If ``None``, allow any ports. - :type allowedPorts: list of int or None - :param reactor: Twisted reactor to use. If not given, autoimport. - :type reactor: obj - """ - # lazy import to avoid reactor install upon module import - if reactor is None: - from twisted.internet import reactor - self.reactor = reactor - - self._allowedDomain = str(allowedDomain) or "*" - - if allowedPorts: - self._allowedPorts = ",".join([str(port) for port in allowedPorts]) - else: - self._allowedPorts = "*" - - def buildProtocol(self, addr): - proto = FlashPolicyProtocol(self._allowedDomain, self._allowedPorts) - proto.factory = self - return proto From 33b8a3d27f723a290a062bdb982c5bbdd0362e14 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Tue, 15 Sep 2015 15:41:54 +0200 Subject: [PATCH 13/59] fixes #508 --- autobahn/twisted/longpoll.py | 674 ----------------------------------- 1 file changed, 674 deletions(-) delete mode 100644 autobahn/twisted/longpoll.py diff --git a/autobahn/twisted/longpoll.py b/autobahn/twisted/longpoll.py deleted file mode 100644 index 33ac314f..00000000 --- a/autobahn/twisted/longpoll.py +++ /dev/null @@ -1,674 +0,0 @@ -######################################## -# -# The MIT License (MIT) -# -# Copyright (c) Tavendo GmbH -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -######################################## - -from __future__ import absolute_import - -import json -import traceback -import binascii - -from collections import deque - -from twisted.python import log -from twisted.web.resource import Resource, NoResource - -# Each of the following 2 trigger a reactor import at module level -from twisted.web import http -from twisted.web.server import NOT_DONE_YET - -from autobahn.util import newid - -from autobahn.wamp.websocket import parseSubprotocolIdentifier - -from autobahn.wamp.exception import SerializationError, \ - TransportLost - -__all__ = ( - 'WampLongPollResource', -) - - -class WampLongPollResourceSessionSend(Resource): - """ - A Web resource for sending via XHR that is part of :class:`autobahn.twisted.longpoll.WampLongPollResourceSession`. - """ - - def __init__(self, parent): - """ - - :param parent: The Web parent resource for the WAMP session. - :type parent: Instance of :class:`autobahn.twisted.longpoll.WampLongPollResourceSession`. - """ - Resource.__init__(self) - self._parent = parent - self._debug = self._parent._parent._debug - - def render_POST(self, request): - """ - A client sends a message via WAMP-over-Longpoll by HTTP/POSTing - to this Web resource. The body of the POST should contain a batch - of WAMP messages which are serialized according to the selected - serializer, and delimited by a single ``\0`` byte in between two WAMP - messages in the batch. - """ - payload = request.content.read() - if self._debug: - log.msg("WampLongPoll: receiving data for transport '{0}'\n{1}".format(self._parent._transport_id, binascii.hexlify(payload))) - - try: - # process (batch of) WAMP message(s) - self._parent.onMessage(payload, None) - - except Exception as e: - return self._parent._parent._failRequest(request, "could not unserialize WAMP message: {0}".format(e)) - - else: - request.setResponseCode(http.NO_CONTENT) - self._parent._parent._setStandardHeaders(request) - self._parent._isalive = True - return "" - - -class WampLongPollResourceSessionReceive(Resource): - """ - A Web resource for receiving via XHR that is part of :class:`autobahn.twisted.longpoll.WampLongPollResourceSession`. - """ - - def __init__(self, parent): - """ - - :param parent: The Web parent resource for the WAMP session. - :type parent: Instance of :class:`autobahn.twisted.longpoll.WampLongPollResourceSession`. - """ - Resource.__init__(self) - self._parent = parent - self._debug = self._parent._parent._debug - self.reactor = self._parent._parent.reactor - - self._queue = deque() - self._request = None - self._killed = False - - if self._debug: - def logqueue(): - if not self._killed: - log.msg("WampLongPoll: transport '{0}' - currently polled {1}, pending messages {2}".format(self._parent._transport_id, self._request is not None, len(self._queue))) - self.reactor.callLater(1, logqueue) - logqueue() - - def queue(self, data): - """ - Enqueue data to be received by client. - - :param data: The data to be received by the client. - :type data: bytes - """ - self._queue.append(data) - self._trigger() - - def _kill(self): - """ - Kill any outstanding request. - """ - if self._request: - self._request.finish() - self._request = None - self._killed = True - - def _trigger(self): - """ - Trigger batched sending of queued messages. - """ - if self._request and len(self._queue): - - if self._parent._serializer._serializer._batched: - # in batched mode, write all pending messages - while len(self._queue) > 0: - msg = self._queue.popleft() - self._request.write(msg) - else: - # in unbatched mode, only write 1 pending message - msg = self._queue.popleft() - self._request.write(msg) - - self._request.finish() - self._request = None - - def render_POST(self, request): - """ - A client receives WAMP messages by issuing a HTTP/POST to this - Web resource. The request will immediately return when there are - messages pending to be received. When there are no such messages - pending, the request will "just hang", until either a message - arrives to be received or a timeout occurs. - """ - # remember request, which marks the session as being polled - self._request = request - - self._parent._parent._setStandardHeaders(request) - request.setHeader('content-type', self._parent._serializer.MIME_TYPE) - - def cancel(_): - if self._debug: - log.msg("WampLongPoll: poll request for transport '{0}' has gone away".format(self._parent._transport_id)) - self._request = None - - request.notifyFinish().addErrback(cancel) - - self._parent._isalive = True - self._trigger() - - return NOT_DONE_YET - - -class WampLongPollResourceSessionClose(Resource): - """ - A Web resource for closing the Long-poll session WampLongPollResourceSession. - """ - - def __init__(self, parent): - """ - - :param parent: The Web parent resource for the WAMP session. - :type parent: Instance of :class:`autobahn.twisted.longpoll.WampLongPollResourceSession`. - """ - Resource.__init__(self) - self._parent = parent - self._debug = self._parent._parent._debug - - def render_POST(self, request): - """ - A client may actively close a session (and the underlying long-poll transport) - by issuing a HTTP/POST with empty body to this resource. - """ - if self._debug: - log.msg("WampLongPoll: closing transport '{0}'".format(self._parent._transport_id)) - - # now actually close the session - self._parent.close() - - if self._debug: - log.msg("WampLongPoll: session ended and transport {0} closed".format(self._parent._transport_id)) - - request.setResponseCode(http.NO_CONTENT) - self._parent._parent._setStandardHeaders(request) - return "" - - -class WampLongPollResourceSession(Resource): - """ - A Web resource representing an open WAMP session. - """ - - def __init__(self, parent, transport_details): - """ - Create a new Web resource representing a WAMP session. - - :param parent: The parent Web resource. - :type parent: Instance of :class:`autobahn.twisted.longpoll.WampLongPollResource`. - :param transport_details: Details on the WAMP-over-Longpoll transport session. - :type transport_details: dict - """ - Resource.__init__(self) - - self._parent = parent - self._debug = self._parent._debug - self._debug_wamp = True - self.reactor = self._parent.reactor - - self._transport_id = transport_details['transport'] - self._serializer = transport_details['serializer'] - self._session = None - - # session authentication information - # - self._authid = None - self._authrole = None - self._authmethod = None - self._authprovider = None - - self._send = WampLongPollResourceSessionSend(self) - self._receive = WampLongPollResourceSessionReceive(self) - self._close = WampLongPollResourceSessionClose(self) - - self.putChild("send", self._send) - self.putChild("receive", self._receive) - self.putChild("close", self._close) - - self._isalive = False - - # kill inactive sessions after this timeout - # - killAfter = self._parent._killAfter - if killAfter > 0: - def killIfDead(): - if not self._isalive: - if self._debug: - log.msg("WampLongPoll: killing inactive WAMP session with transport '{0}'".format(self._transport_id)) - - self.onClose(False, 5000, "session inactive") - self._receive._kill() - if self._transport_id in self._parent._transports: - del self._parent._transports[self._transport_id] - else: - if self._debug: - log.msg("WampLongPoll: transport '{0}' is still alive".format(self._transport_id)) - - self._isalive = False - self.reactor.callLater(killAfter, killIfDead) - - self.reactor.callLater(killAfter, killIfDead) - else: - if self._debug: - log.msg("WampLongPoll: transport '{0}' automatic killing of inactive session disabled".format(self._transport_id)) - - if self._debug: - log.msg("WampLongPoll: session resource for transport '{0}' initialized)".format(self._transport_id)) - - self.onOpen() - - def close(self): - """ - Implements :func:`autobahn.wamp.interfaces.ITransport.close` - """ - if self.isOpen(): - self.onClose(True, 1000, u"session closed") - self._receive._kill() - del self._parent._transports[self._transport_id] - else: - raise TransportLost() - - def abort(self): - """ - Implements :func:`autobahn.wamp.interfaces.ITransport.abort` - """ - if self.isOpen(): - self.onClose(True, 1000, u"session aborted") - self._receive._kill() - del self._parent._transports[self._transport_id] - else: - raise TransportLost() - - # noinspection PyUnusedLocal - def onClose(self, wasClean, code, reason): - """ - Callback from :func:`autobahn.websocket.interfaces.IWebSocketChannel.onClose` - """ - if self._session: - try: - self._session.onClose(wasClean) - except Exception: - # silently ignore exceptions raised here .. - if self._debug: - traceback.print_exc() - self._session = None - - def onOpen(self): - """ - Callback from :func:`autobahn.websocket.interfaces.IWebSocketChannel.onOpen` - """ - self._session = self._parent._factory() - # noinspection PyBroadException - try: - self._session.onOpen(self) - except Exception: - if self._debug: - traceback.print_exc() - - def onMessage(self, payload, isBinary): - """ - Callback from :func:`autobahn.websocket.interfaces.IWebSocketChannel.onMessage` - """ - for msg in self._serializer.unserialize(payload, isBinary): - if self._debug: - print("WampLongPoll: RX {0}".format(msg)) - self._session.onMessage(msg) - - def send(self, msg): - """ - Implements :func:`autobahn.wamp.interfaces.ITransport.send` - """ - if self.isOpen(): - try: - if self._debug: - print("WampLongPoll: TX {0}".format(msg)) - payload, isBinary = self._serializer.serialize(msg) - except Exception as e: - # all exceptions raised from above should be serialization errors .. - raise SerializationError("unable to serialize WAMP application payload ({0})".format(e)) - else: - self._receive.queue(payload) - else: - raise TransportLost() - - def isOpen(self): - """ - Implements :func:`autobahn.wamp.interfaces.ITransport.isOpen` - """ - return self._session is not None - - -class WampLongPollResourceOpen(Resource): - """ - A Web resource for creating new WAMP sessions. - """ - - def __init__(self, parent): - """ - - :param parent: The parent Web resource. - :type parent: Instance of :class:`autobahn.twisted.longpoll.WampLongPollResource`. - """ - Resource.__init__(self) - self._parent = parent - self._debug = self._parent._debug - - def render_POST(self, request): - """ - Request to create a new WAMP session. - """ - if self._debug: - log.msg("WampLongPoll: creating new session ..") - - payload = request.content.read() - try: - options = json.loads(payload) - except Exception as e: - return self._parent._failRequest(request, "could not parse WAMP session open request body: {0}".format(e)) - - if type(options) != dict: - return self._parent._failRequest(request, "invalid type for WAMP session open request [was {0}, expected dictionary]".format(type(options))) - - if 'protocols' not in options: - return self._parent._failRequest(request, "missing attribute 'protocols' in WAMP session open request") - - # determine the protocol to speak - # - protocol = None - serializer = None - for p in options['protocols']: - version, serializerId = parseSubprotocolIdentifier(p) - if version == 2 and serializerId in self._parent._serializers.keys(): - serializer = self._parent._serializers[serializerId] - protocol = p - break - - if protocol is None: - return self.__failRequest(request, "no common protocol to speak (I speak: {0})".format(["wamp.2.{0}".format(s) for s in self._parent._serializers.keys()])) - - # make up new transport ID - # - if self._parent._debug_transport_id: - # use fixed transport ID for debugging purposes - transport = self._parent._debug_transport_id - else: - transport = newid() - - # this doesn't contain all the info (when a header key appears multiple times) - # http_headers_received = request.getAllHeaders() - http_headers_received = {} - for key, values in request.requestHeaders.getAllRawHeaders(): - if key not in http_headers_received: - http_headers_received[key] = [] - http_headers_received[key].extend(values) - - transport_details = { - 'transport': transport, - 'serializer': serializer, - 'protocol': protocol, - 'peer': request.getClientIP(), - 'http_headers_received': http_headers_received, - 'http_headers_sent': None - } - - # create instance of WampLongPollResourceSession or subclass thereof .. - # - self._parent._transports[transport] = self._parent.protocol(self._parent, transport_details) - - # create response - # - self._parent._setStandardHeaders(request) - request.setHeader('content-type', 'application/json; charset=utf-8') - - result = { - 'transport': transport, - 'protocol': protocol - } - - payload = json.dumps(result) - - if self._debug: - log.msg("WampLongPoll: new session created on transport '{0}'".format(transport)) - - return payload - - -class WampLongPollResource(Resource): - """ - A WAMP-over-Longpoll resource for use with Twisted Web Resource trees. - - This class provides an implementation of the - `WAMP-over-Longpoll Transport `_ - for WAMP. - - The Resource exposes the following paths (child resources). - - Opening a new WAMP session: - - * ``/open`` - - Once a transport is created and the session is opened: - - * ``//send`` - * ``//receive`` - * ``//close`` - """ - - protocol = WampLongPollResourceSession - - def __init__(self, - factory, - serializers=None, - timeout=10, - killAfter=30, - queueLimitBytes=128 * 1024, - queueLimitMessages=100, - debug=False, - debug_transport_id=None, - reactor=None): - """ - Create new HTTP WAMP Web resource. - - :param factory: A (router) session factory. - :type factory: Instance of :class:`autobahn.twisted.wamp.RouterSessionFactory`. - :param serializers: List of WAMP serializers. - :type serializers: list of obj (which implement :class:`autobahn.wamp.interfaces.ISerializer`) - :param timeout: XHR polling timeout in seconds. - :type timeout: int - :param killAfter: Kill WAMP session after inactivity in seconds. - :type killAfter: int - :param queueLimitBytes: Kill WAMP session after accumulation of this many bytes in send queue (XHR poll). - :type queueLimitBytes: int - :param queueLimitMessages: Kill WAMP session after accumulation of this many message in send queue (XHR poll). - :type queueLimitMessages: int - :param debug: Enable debug logging. - :type debug: bool - :param debug_transport_id: If given, use this fixed transport ID. - :type debug_transport_id: str - :param reactor: The Twisted reactor to run under. - :type reactor: obj - """ - Resource.__init__(self) - - # RouterSessionFactory - self._factory = factory - - # lazy import to avoid reactor install upon module import - if reactor is None: - from twisted.internet import reactor - self.reactor = reactor - - self._debug = debug - self._debug_transport_id = debug_transport_id - self._timeout = timeout - self._killAfter = killAfter - self._queueLimitBytes = queueLimitBytes - self._queueLimitMessages = queueLimitMessages - - if serializers is None: - serializers = [] - - # try MsgPack WAMP serializer - try: - from autobahn.wamp.serializer import MsgPackSerializer - serializers.append(MsgPackSerializer(batched=True)) - serializers.append(MsgPackSerializer()) - except ImportError: - pass - - # try JSON WAMP serializer - try: - from autobahn.wamp.serializer import JsonSerializer - serializers.append(JsonSerializer(batched=True)) - serializers.append(JsonSerializer()) - except ImportError: - pass - - if not serializers: - raise Exception("could not import any WAMP serializers") - - self._serializers = {} - for ser in serializers: - self._serializers[ser.SERIALIZER_ID] = ser - - self._transports = {} - - # /open - # - self.putChild("open", WampLongPollResourceOpen(self)) - - if self._debug: - log.msg("WampLongPollResource initialized") - - def render_GET(self, request): - request.setHeader('content-type', 'text/html; charset=UTF-8') - peer = "{0}:{1}".format(request.client.host, request.client.port) - return self.getNotice(peer=peer) - - def getChild(self, name, request): - """ - Returns send/receive/close resource for transport. - - .. seealso:: - - * :class:`twisted.web.resource.Resource` - * :class:`zipfile.ZipFile` - """ - if name not in self._transports: - return NoResource("no WAMP transport '{0}'".format(name)) - - if len(request.postpath) != 1 or request.postpath[0] not in ['send', 'receive', 'close']: - return NoResource("invalid WAMP transport operation '{0}'".format(request.postpath)) - - return self._transports[name] - - def _setStandardHeaders(self, request): - """ - Set standard HTTP response headers. - """ - origin = request.getHeader("origin") - if origin is None or origin == "null": - origin = "*" - request.setHeader('access-control-allow-origin', origin) - request.setHeader('access-control-allow-credentials', 'true') - request.setHeader('cache-control', 'no-store, no-cache, must-revalidate, max-age=0') - - headers = request.getHeader('access-control-request-headers') - if headers is not None: - request.setHeader('access-control-allow-headers', headers) - - def _failRequest(self, request, msg): - """ - Fails a request to the long-poll service. - """ - self._setStandardHeaders(request) - request.setHeader('content-type', 'text/plain; charset=UTF-8') - request.setResponseCode(http.BAD_REQUEST) - return msg - - def getNotice(self, peer, redirectUrl=None, redirectAfter=0): - """ - Render a user notice (HTML page) when the Long-Poll root resource - is accessed via HTTP/GET (by a user). - - :param redirectUrl: Optional URL to redirect the user to. - :type redirectUrl: str - :param redirectAfter: When ``redirectUrl`` is provided, redirect after this time (seconds). - :type redirectAfter: int - """ - from autobahn import __version__ - - if redirectUrl: - redirect = """""" % (redirectAfter, redirectUrl) - else: - redirect = "" - html = """ - - - - %s - - - -

AutobahnPython %s

-

- I am not Web server, but a WAMP-over-LongPoll transport endpoint. -

-

- You can talk to me using the WAMP-over-LongPoll protocol. -

-

- For more information, please see: -

-

- - -""" % (redirect, __version__) - return html From 2139643ba64cee4b35351d7da3198140167622d7 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Tue, 15 Sep 2015 16:24:52 +0200 Subject: [PATCH 14/59] more cleanups and public API --- autobahn/asyncio/__init__.py | 17 ++++++++++++++++ autobahn/asyncio/websocket.py | 3 ++- autobahn/twisted/__init__.py | 38 +++++++++++++++++++++++++++++++++++ autobahn/twisted/resource.py | 5 ++++- autobahn/twisted/util.py | 17 +++++++++------- 5 files changed, 71 insertions(+), 9 deletions(-) diff --git a/autobahn/asyncio/__init__.py b/autobahn/asyncio/__init__.py index 8a4c67bf..7bc17e8b 100644 --- a/autobahn/asyncio/__init__.py +++ b/autobahn/asyncio/__init__.py @@ -23,3 +23,20 @@ # THE SOFTWARE. # ############################################################################### + + +from __future__ import absolute_import + +# WebSocket protocol support +from autobahn.asyncio.websocket import \ + WebSocketServerProtocol, \ + WebSocketClientProtocol, \ + WebSocketServerFactory, \ + WebSocketClientFactory + +__all__ = ( + 'WebSocketServerProtocol', + 'WebSocketClientProtocol', + 'WebSocketServerFactory', + 'WebSocketClientFactory', +) diff --git a/autobahn/asyncio/websocket.py b/autobahn/asyncio/websocket.py index c7863299..82bcb6d3 100644 --- a/autobahn/asyncio/websocket.py +++ b/autobahn/asyncio/websocket.py @@ -24,6 +24,8 @@ # ############################################################################### +from __future__ import absolute_import + from collections import deque from autobahn.wamp import websocket @@ -51,7 +53,6 @@ __all__ = ( 'WebSocketAdapterFactory', 'WebSocketServerFactory', 'WebSocketClientFactory', - 'WampWebSocketServerProtocol', 'WampWebSocketClientProtocol', 'WampWebSocketServerFactory', diff --git a/autobahn/twisted/__init__.py b/autobahn/twisted/__init__.py index 8a4c67bf..8b439b57 100644 --- a/autobahn/twisted/__init__.py +++ b/autobahn/twisted/__init__.py @@ -23,3 +23,41 @@ # THE SOFTWARE. # ############################################################################### + + +from __future__ import absolute_import + +# WebSocket protocol support +from autobahn.twisted.websocket import \ + WebSocketServerProtocol, \ + WebSocketClientProtocol, \ + WebSocketServerFactory, \ + WebSocketClientFactory + +# Twisted Web support +from autobahn.twisted.resource import WebSocketResource, WSGIRootResource + +# Twisted specific utilities (these should really be in Twisted, but +# they aren't, and we use these in example code, so it must be part of +# the public API) +from autobahn.twisted.util import sleep +from autobahn.twisted.choosereactor import install_reactor + + +__all__ = ( + # WebSocket + 'WebSocketServerProtocol', + 'WebSocketClientProtocol', + 'WebSocketServerFactory', + 'WebSocketClientFactory', + + # Twisted Web + 'WebSocketResource', + + # this should really be in Twisted + 'WSGIRootResource', + + # this should really be in Twisted + 'sleep', + 'install_reactor' +) diff --git a/autobahn/twisted/resource.py b/autobahn/twisted/resource.py index 6437a461..f903f77c 100644 --- a/autobahn/twisted/resource.py +++ b/autobahn/twisted/resource.py @@ -24,6 +24,9 @@ # ############################################################################### + +from __future__ import absolute_import + from zope.interface import implementer from twisted.protocols.policies import ProtocolWrapper @@ -36,7 +39,7 @@ except ImportError: from twisted.web.resource import IResource, Resource from six import PY3 -# The following imports reactor at module level +# The following triggers an import of reactor at module level! # from twisted.web.server import NOT_DONE_YET diff --git a/autobahn/twisted/util.py b/autobahn/twisted/util.py index a86b4e52..87332ca9 100644 --- a/autobahn/twisted/util.py +++ b/autobahn/twisted/util.py @@ -48,7 +48,7 @@ __all = ( def sleep(delay, reactor=None): """ - Inline sleep for use in coroutines (Twisted ``inlineCallback`` decorated functions). + Inline sleep for use in co-routines (Twisted ``inlineCallback`` decorated functions). .. seealso:: * `twisted.internet.defer.inlineCallbacks `__ @@ -68,18 +68,21 @@ def sleep(delay, reactor=None): def peer2str(addr): """ - Convert a Twisted address, as returned from ``self.transport.getPeer()`` to a string + Convert a Twisted address as returned from ``self.transport.getPeer()`` to a string. + + :returns: Returns a string representation of the peer on a Twisted transport. + :rtype: unicode """ if isinstance(addr, IPv4Address): - res = "tcp4:{0}:{1}".format(addr.host, addr.port) + res = u"tcp4:{0}:{1}".format(addr.host, addr.port) elif _HAS_IPV6 and isinstance(addr, IPv6Address): - res = "tcp6:{0}:{1}".format(addr.host, addr.port) + res = u"tcp6:{0}:{1}".format(addr.host, addr.port) elif isinstance(addr, UNIXAddress): - res = "unix:{0}".format(addr.name) + res = u"unix:{0}".format(addr.name) elif isinstance(addr, PipeAddress): - res = "" + res = u"" else: # gracefully fallback if we can't map the peer's address - res = "?:{0}".format(addr) + res = u"?:{0}".format(addr) return res From 8b316adaf9dbb3d6aca5e5cf99172b78b52827f6 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Tue, 15 Sep 2015 16:43:51 +0200 Subject: [PATCH 15/59] more public API --- autobahn/asyncio/__init__.py | 5 +++++ autobahn/twisted/__init__.py | 30 ++++++++++++++++++++++-------- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/autobahn/asyncio/__init__.py b/autobahn/asyncio/__init__.py index 7bc17e8b..a7ca34a9 100644 --- a/autobahn/asyncio/__init__.py +++ b/autobahn/asyncio/__init__.py @@ -34,9 +34,14 @@ from autobahn.asyncio.websocket import \ WebSocketServerFactory, \ WebSocketClientFactory +# WAMP support +from autobahn.asyncio.wamp import ApplicationSession + + __all__ = ( 'WebSocketServerProtocol', 'WebSocketClientProtocol', 'WebSocketServerFactory', 'WebSocketClientFactory', + 'ApplicationSession', ) diff --git a/autobahn/twisted/__init__.py b/autobahn/twisted/__init__.py index 8b439b57..32460b02 100644 --- a/autobahn/twisted/__init__.py +++ b/autobahn/twisted/__init__.py @@ -27,6 +27,12 @@ from __future__ import absolute_import +# Twisted specific utilities (these should really be in Twisted, but +# they aren't, and we use these in example code, so it must be part of +# the public API) +from autobahn.twisted.util import sleep +from autobahn.twisted.choosereactor import install_reactor + # WebSocket protocol support from autobahn.twisted.websocket import \ WebSocketServerProtocol, \ @@ -34,30 +40,38 @@ from autobahn.twisted.websocket import \ WebSocketServerFactory, \ WebSocketClientFactory +# support for running Twisted stream protocols over WebSocket +from autobahn.twisted.websocket import WrappingWebSocketServerFactory, \ + WrappingWebSocketClientFactory + # Twisted Web support from autobahn.twisted.resource import WebSocketResource, WSGIRootResource -# Twisted specific utilities (these should really be in Twisted, but -# they aren't, and we use these in example code, so it must be part of -# the public API) -from autobahn.twisted.util import sleep -from autobahn.twisted.choosereactor import install_reactor +# WAMP support +from autobahn.twisted.wamp import ApplicationSession __all__ = ( + # this should really be in Twisted + 'sleep', + 'install_reactor', + # WebSocket 'WebSocketServerProtocol', 'WebSocketClientProtocol', 'WebSocketServerFactory', 'WebSocketClientFactory', + # wrapping stream protocols in WebSocket + 'WrappingWebSocketServerFactory', + 'WrappingWebSocketClientFactory', + # Twisted Web 'WebSocketResource', # this should really be in Twisted 'WSGIRootResource', - # this should really be in Twisted - 'sleep', - 'install_reactor' + # WAMP support + 'ApplicationSession', ) From 79209f61c5d1d1a5f72f09a8e8e1e1e75d4e772e Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Tue, 15 Sep 2015 17:00:58 +0200 Subject: [PATCH 16/59] more public API --- DEVELOPERS.md | 10 ++++++++ autobahn/__init__.py | 18 ++++++++++++-- autobahn/util.py | 59 +++++++++++++++----------------------------- 3 files changed, 46 insertions(+), 41 deletions(-) diff --git a/DEVELOPERS.md b/DEVELOPERS.md index 228441b5..6325c08a 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -70,6 +70,16 @@ The **Private Library API** is for library internal use, crossing files, classes The **Private non-API** isn't an API at all: like class members which may only be used within that class, or functions which may only be used in the same module where the function is defined. +### Public API + +The new rule for the public API is simple: if something is exported from the modules below, then it is public. Otherwise not. + +* [Top](https://github.com/tavendo/AutobahnPython/blob/master/autobahn/__init__.py) +* [WebSocket](https://github.com/tavendo/AutobahnPython/blob/master/autobahn/websocket/__init__.py) +* [WAMP](https://github.com/tavendo/AutobahnPython/blob/master/autobahn/wamp/__init__.py) +* [Asyncio](https://github.com/tavendo/AutobahnPython/blob/master/autobahn/asyncio/__init__.py) +* [Twisted](https://github.com/tavendo/AutobahnPython/blob/master/autobahn/twisted/__init__.py) + ## Release Process diff --git a/autobahn/__init__.py b/autobahn/__init__.py index 912a2e36..a0f5db1c 100644 --- a/autobahn/__init__.py +++ b/autobahn/__init__.py @@ -24,5 +24,19 @@ # ############################################################################### -__version__ = "0.10.9" -version = __version__ # backward compat. + +from __future__ import absolute_import + +# we use the following in code examples, so it must be part of +# out public API +from autobahn.util import utcnow, utcstr + +__version__ = u"0.11.0" +""" +AutobahnPython library version. +""" + +__all__ = ( + 'utcnow', + 'utcstr', +) diff --git a/autobahn/util.py b/autobahn/util.py index da6a82df..dcdff0e2 100644 --- a/autobahn/util.py +++ b/autobahn/util.py @@ -38,7 +38,6 @@ from datetime import datetime, timedelta from pprint import pformat __all__ = ("utcnow", - "parseutc", "utcstr", "id", "rid", @@ -50,6 +49,25 @@ __all__ = ("utcnow", "IdGenerator") +def utcstr(ts=None): + """ + Format UTC timestamp in ISO 8601 format. + + Note: to parse an ISO 8601 formatted string, use the **iso8601** + module instead (e.g. ``iso8601.parse_date("2014-05-23T13:03:44.123Z")``). + + :param ts: The timestamp to format. + :type ts: instance of :py:class:`datetime.datetime` or None + + :returns: Timestamp formatted in ISO 8601 format. + :rtype: unicode + """ + assert(ts is None or isinstance(ts, datetime.datetime)) + if ts is None: + ts = datetime.utcnow() + return u"{0}Z".format(ts.strftime(u"%Y-%m-%dT%H:%M:%S.%f")[:-3]) + + def utcnow(): """ Get current time in UTC as ISO 8601 string. @@ -57,44 +75,7 @@ def utcnow(): :returns: Current time as string in ISO 8601 format. :rtype: unicode """ - now = datetime.utcnow() - return u"{0}Z".format(now.strftime(u"%Y-%m-%dT%H:%M:%S.%f")[:-3]) - - -def utcstr(ts): - """ - Format UTC timestamp in ISO 8601 format. - - :param ts: The timestamp to format. - :type ts: instance of :py:class:`datetime.datetime` - - :returns: Timestamp formatted in ISO 8601 format. - :rtype: unicode - """ - if ts: - return u"{0}Z".format(ts.strftime(u"%Y-%m-%dT%H:%M:%S.%f")[:-3]) - else: - return ts - - -def parseutc(datestr): - """ - Parse an ISO 8601 combined date and time string, like i.e. ``"2011-11-23T12:23:00Z"`` - into a UTC datetime instance. - - .. deprecated:: 0.8.12 - Use the **iso8601** module instead (e.g. ``iso8601.parse_date("2014-05-23T13:03:44.123Z")``) - - :param datestr: The datetime string to parse. - :type datestr: unicode - - :returns: The converted datetime object. - :rtype: instance of :py:class:`datetime.datetime` - """ - try: - return datetime.strptime(datestr, u"%Y-%m-%dT%H:%M:%SZ") - except ValueError: - return None + return utcstr() class IdGenerator(object): From 3afbe5cdbe7ae0a739b48c72a9c0dc36afbc0b03 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Tue, 15 Sep 2015 17:04:51 +0200 Subject: [PATCH 17/59] fix --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 211c22e2..b19c89d6 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,7 @@ LONGSDESC = open('README.rst').read() # VERSIONFILE = "autobahn/__init__.py" verstrline = open(VERSIONFILE, "rt").read() -VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]" +VSRE = r"^__version__ = u['\"]([^'\"]*)['\"]" mo = re.search(VSRE, verstrline, re.M) if mo: verstr = mo.group(1) From 18eb52914a134bca1d06353b3a825c3bcc90539d Mon Sep 17 00:00:00 2001 From: HawkOwl Date: Wed, 16 Sep 2015 11:48:54 +0800 Subject: [PATCH 18/59] fix datetime --- autobahn/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autobahn/util.py b/autobahn/util.py index dcdff0e2..d0410dc4 100644 --- a/autobahn/util.py +++ b/autobahn/util.py @@ -62,7 +62,7 @@ def utcstr(ts=None): :returns: Timestamp formatted in ISO 8601 format. :rtype: unicode """ - assert(ts is None or isinstance(ts, datetime.datetime)) + assert(ts is None or isinstance(ts, datetime)) if ts is None: ts = datetime.utcnow() return u"{0}Z".format(ts.strftime(u"%Y-%m-%dT%H:%M:%S.%f")[:-3]) From 6b83faf5cec0f1f16bc22604eecd69b632e5b381 Mon Sep 17 00:00:00 2001 From: HawkOwl Date: Wed, 16 Sep 2015 12:25:52 +0800 Subject: [PATCH 19/59] fix tests --- autobahn/test/__init__.py | 15 ++ .../twisted/test/test_application_runner.py | 101 +++++++------- autobahn/twisted/test/test_choosereactor.py | 131 +++++++++--------- autobahn/twisted/test/test_protocol.py | 58 ++++++++ autobahn/wamp/test/test_protocol.py | 3 + autobahn/websocket/test/test_protocol.py | 47 +------ setup.cfg | 2 + tox.ini | 2 +- 8 files changed, 195 insertions(+), 164 deletions(-) create mode 100644 autobahn/twisted/test/test_protocol.py create mode 100644 setup.cfg diff --git a/autobahn/test/__init__.py b/autobahn/test/__init__.py index 8a4c67bf..82b625a7 100644 --- a/autobahn/test/__init__.py +++ b/autobahn/test/__init__.py @@ -23,3 +23,18 @@ # THE SOFTWARE. # ############################################################################### + +from __future__ import absolute_import, print_function + + +class FakeTransport(object): + _written = b"" + _open = True + + def write(self, msg): + if not self._open: + raise Exception("Can't write to a closed connection") + self._written = self._written + msg + + def loseConnection(self): + self._open = False diff --git a/autobahn/twisted/test/test_application_runner.py b/autobahn/twisted/test/test_application_runner.py index 0019eacf..80827130 100644 --- a/autobahn/twisted/test/test_application_runner.py +++ b/autobahn/twisted/test/test_application_runner.py @@ -26,69 +26,68 @@ from __future__ import absolute_import -import os +# t.i.reactor doesn't exist until we've imported it once, but we +# need it to exist so we can @patch it out in the tests ... +from twisted.internet import reactor # noqa +from twisted.internet.defer import inlineCallbacks, succeed +from twisted.trial import unittest -if os.environ.get('USE_TWISTED', False): - # t.i.reactor doesn't exist until we've imported it once, but we - # need it to exist so we can @patch it out in the tests ... - from twisted.internet import reactor # noqa - from twisted.internet.defer import inlineCallbacks, succeed - from twisted.trial import unittest +from mock import patch, Mock - from mock import patch, Mock +from autobahn.twisted.wamp import ApplicationRunner - from autobahn.twisted.wamp import ApplicationRunner - def raise_error(*args, **kw): - raise RuntimeError("we always fail") +def raise_error(*args, **kw): + raise RuntimeError("we always fail") - class TestApplicationRunner(unittest.TestCase): - @patch('twisted.internet.reactor') - def test_runner_default(self, fakereactor): - fakereactor.connectTCP = Mock(side_effect=raise_error) - runner = ApplicationRunner(u'ws://fake:1234/ws', u'dummy realm') - # we should get "our" RuntimeError when we call run - self.assertRaises(RuntimeError, runner.run, raise_error) +class TestApplicationRunner(unittest.TestCase): + @patch('twisted.internet.reactor') + def test_runner_default(self, fakereactor): + fakereactor.connectTCP = Mock(side_effect=raise_error) + runner = ApplicationRunner(u'ws://fake:1234/ws', u'dummy realm') - # both reactor.run and reactor.stop should have been called - self.assertEqual(fakereactor.run.call_count, 1) - self.assertEqual(fakereactor.stop.call_count, 1) + # we should get "our" RuntimeError when we call run + self.assertRaises(RuntimeError, runner.run, raise_error) - @patch('twisted.internet.reactor') - @inlineCallbacks - def test_runner_no_run(self, fakereactor): - fakereactor.connectTCP = Mock(side_effect=raise_error) - runner = ApplicationRunner(u'ws://fake:1234/ws', u'dummy realm') + # both reactor.run and reactor.stop should have been called + self.assertEqual(fakereactor.run.call_count, 1) + self.assertEqual(fakereactor.stop.call_count, 1) - try: - yield runner.run(raise_error, start_reactor=False) - self.fail() # should have raise an exception, via Deferred + @patch('twisted.internet.reactor') + @inlineCallbacks + def test_runner_no_run(self, fakereactor): + fakereactor.connectTCP = Mock(side_effect=raise_error) + runner = ApplicationRunner(u'ws://fake:1234/ws', u'dummy realm') - except RuntimeError as e: - # make sure it's "our" exception - self.assertEqual(e.args[0], "we always fail") + try: + yield runner.run(raise_error, start_reactor=False) + self.fail() # should have raise an exception, via Deferred - # neither reactor.run() NOR reactor.stop() should have been called - # (just connectTCP() will have been called) - self.assertEqual(fakereactor.run.call_count, 0) - self.assertEqual(fakereactor.stop.call_count, 0) + except RuntimeError as e: + # make sure it's "our" exception + self.assertEqual(e.args[0], "we always fail") - @patch('twisted.internet.reactor') - def test_runner_no_run_happypath(self, fakereactor): - proto = Mock() - fakereactor.connectTCP = Mock(return_value=succeed(proto)) - runner = ApplicationRunner(u'ws://fake:1234/ws', u'dummy realm') + # neither reactor.run() NOR reactor.stop() should have been called + # (just connectTCP() will have been called) + self.assertEqual(fakereactor.run.call_count, 0) + self.assertEqual(fakereactor.stop.call_count, 0) - d = runner.run(Mock(), start_reactor=False) + @patch('twisted.internet.reactor') + def test_runner_no_run_happypath(self, fakereactor): + proto = Mock() + fakereactor.connectTCP = Mock(return_value=succeed(proto)) + runner = ApplicationRunner(u'ws://fake:1234/ws', u'dummy realm') - # shouldn't have actually connected to anything - # successfully, and the run() call shouldn't have inserted - # any of its own call/errbacks. (except the cleanup handler) - self.assertFalse(d.called) - self.assertEqual(1, len(d.callbacks)) + d = runner.run(Mock(), start_reactor=False) - # neither reactor.run() NOR reactor.stop() should have been called - # (just connectTCP() will have been called) - self.assertEqual(fakereactor.run.call_count, 0) - self.assertEqual(fakereactor.stop.call_count, 0) + # shouldn't have actually connected to anything + # successfully, and the run() call shouldn't have inserted + # any of its own call/errbacks. (except the cleanup handler) + self.assertFalse(d.called) + self.assertEqual(1, len(d.callbacks)) + + # neither reactor.run() NOR reactor.stop() should have been called + # (just connectTCP() will have been called) + self.assertEqual(fakereactor.run.call_count, 0) + self.assertEqual(fakereactor.stop.call_count, 0) diff --git a/autobahn/twisted/test/test_choosereactor.py b/autobahn/twisted/test/test_choosereactor.py index 1e904948..5e1282c3 100644 --- a/autobahn/twisted/test/test_choosereactor.py +++ b/autobahn/twisted/test/test_choosereactor.py @@ -26,87 +26,86 @@ from __future__ import absolute_import -import os import sys -if os.environ.get('USE_TWISTED', False): - import twisted.internet - from twisted.trial.unittest import TestCase +import twisted.internet +from twisted.trial.unittest import TestCase - from mock import Mock +from mock import Mock - from autobahn.twisted import choosereactor +from autobahn.twisted import choosereactor - class ChooseReactorTests(TestCase): - def patch_reactor(self, name, new_reactor): - """ - Patch ``name`` so that Twisted will grab a fake reactor instead of - a real one. - """ - if hasattr(twisted.internet, name): - self.patch(twisted.internet, name, new_reactor) - else: - def _cleanup(): - delattr(twisted.internet, name) - setattr(twisted.internet, name, new_reactor) - - def patch_modules(self): - """ - Patch ``sys.modules`` so that Twisted believes there is no - installed reactor. - """ - old_modules = dict(sys.modules) - - new_modules = dict(sys.modules) - del new_modules["twisted.internet.reactor"] +class ChooseReactorTests(TestCase): + def patch_reactor(self, name, new_reactor): + """ + Patch ``name`` so that Twisted will grab a fake reactor instead of + a real one. + """ + if hasattr(twisted.internet, name): + self.patch(twisted.internet, name, new_reactor) + else: def _cleanup(): - sys.modules = old_modules + delattr(twisted.internet, name) + setattr(twisted.internet, name, new_reactor) - self.addCleanup(_cleanup) - sys.modules = new_modules + def patch_modules(self): + """ + Patch ``sys.modules`` so that Twisted believes there is no + installed reactor. + """ + old_modules = dict(sys.modules) - def test_unknown(self): - """ - ``install_optimal_reactor`` will use the default reactor if it is - unable to detect the platform it is running on. - """ - reactor_mock = Mock() - self.patch_reactor("default", reactor_mock) - self.patch(sys, "platform", "unknown") + new_modules = dict(sys.modules) + del new_modules["twisted.internet.reactor"] - # Emulate that a reactor has not been installed - self.patch_modules() + def _cleanup(): + sys.modules = old_modules - choosereactor.install_optimal_reactor() - reactor_mock.install.assert_called_once_with() + self.addCleanup(_cleanup) + sys.modules = new_modules - def test_mac(self): - """ - ``install_optimal_reactor`` will install KQueueReactor on - Darwin (OS X). - """ - reactor_mock = Mock() - self.patch_reactor("kqreactor", reactor_mock) - self.patch(sys, "platform", "darwin") + def test_unknown(self): + """ + ``install_optimal_reactor`` will use the default reactor if it is + unable to detect the platform it is running on. + """ + reactor_mock = Mock() + self.patch_reactor("default", reactor_mock) + self.patch(sys, "platform", "unknown") - # Emulate that a reactor has not been installed - self.patch_modules() + # Emulate that a reactor has not been installed + self.patch_modules() - choosereactor.install_optimal_reactor() - reactor_mock.install.assert_called_once_with() + choosereactor.install_optimal_reactor() + reactor_mock.install.assert_called_once_with() - def test_linux(self): - """ - ``install_optimal_reactor`` will install EPollReactor on Linux. - """ - reactor_mock = Mock() - self.patch_reactor("epollreactor", reactor_mock) - self.patch(sys, "platform", "linux") + def test_mac(self): + """ + ``install_optimal_reactor`` will install KQueueReactor on + Darwin (OS X). + """ + reactor_mock = Mock() + self.patch_reactor("kqreactor", reactor_mock) + self.patch(sys, "platform", "darwin") - # Emulate that a reactor has not been installed - self.patch_modules() + # Emulate that a reactor has not been installed + self.patch_modules() - choosereactor.install_optimal_reactor() - reactor_mock.install.assert_called_once_with() + choosereactor.install_optimal_reactor() + reactor_mock.install.assert_called_once_with() + + def test_linux(self): + """ + ``install_optimal_reactor`` will install EPollReactor on Linux. + """ + reactor_mock = Mock() + self.patch_reactor("epollreactor", reactor_mock) + self.patch(sys, "platform", "linux") + + # Emulate that a reactor has not been installed + self.patch_modules() + + choosereactor.install_optimal_reactor() + reactor_mock.install.assert_called_once_with() diff --git a/autobahn/twisted/test/test_protocol.py b/autobahn/twisted/test/test_protocol.py new file mode 100644 index 00000000..2893b2c9 --- /dev/null +++ b/autobahn/twisted/test/test_protocol.py @@ -0,0 +1,58 @@ +############################################################################### +# +# The MIT License (MIT) +# +# Copyright (c) Tavendo GmbH +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +############################################################################### + +from __future__ import absolute_import, print_function + +import unittest2 as unittest + +from autobahn.twisted.websocket import WebSocketServerFactory +from autobahn.twisted.websocket import WebSocketServerProtocol +from autobahn.test import FakeTransport + + +class Hixie76RejectionTests(unittest.TestCase): + """ + Hixie-76 should not be accepted by an Autobahn server. + """ + def test_handshake_fails(self): + """ + A handshake from a client only supporting Hixie-76 will fail. + """ + t = FakeTransport() + f = WebSocketServerFactory() + p = WebSocketServerProtocol() + p.factory = f + p.transport = t + + # from http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 + http_request = b"GET /demo HTTP/1.1\r\nHost: example.com\r\nConnection: Upgrade\r\nSec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\nSec-WebSocket-Protocol: sample\r\nUpgrade: WebSocket\r\nSec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\nOrigin: http://example.com\r\n\r\n^n:ds[4U" + + p.openHandshakeTimeout = 0 + p._connectionMade() + p.data = http_request + p.processHandshake() + self.assertIn(b"HTTP/1.1 400", t._written) + self.assertIn(b"Hixie76 protocol not supported", t._written) diff --git a/autobahn/wamp/test/test_protocol.py b/autobahn/wamp/test/test_protocol.py index 9cd081af..efed56e0 100644 --- a/autobahn/wamp/test/test_protocol.py +++ b/autobahn/wamp/test/test_protocol.py @@ -36,6 +36,9 @@ if os.environ.get('USE_TWISTED', False): from twisted.trial import unittest from six import PY3 + from twisted.internet.base import DelayedCall + DelayedCall.debug = True + from autobahn import util from autobahn.twisted.wamp import ApplicationSession from autobahn.wamp import message, role, serializer, types diff --git a/autobahn/websocket/test/test_protocol.py b/autobahn/websocket/test/test_protocol.py index f4111a2d..86c25e38 100644 --- a/autobahn/websocket/test/test_protocol.py +++ b/autobahn/websocket/test/test_protocol.py @@ -26,52 +26,11 @@ from __future__ import absolute_import, print_function -import os - import unittest2 as unittest from autobahn.websocket.protocol import WebSocketServerProtocol from autobahn.websocket.protocol import WebSocketServerFactory - - -class FakeTransport(object): - _written = b"" - _open = True - - def write(self, msg): - if not self._open: - raise Exception("Can't write to a closed connection") - self._written = self._written + msg - - def loseConnection(self): - self._open = False - - -class Hixie76RejectionTests(unittest.TestCase): - """ - Hixie-76 should not be accepted by an Autobahn server. - """ - def test_handshake_fails(self): - """ - A handshake from a client only supporting Hixie-76 will fail. - """ - from autobahn.twisted.websocket import WebSocketServerFactory - from autobahn.twisted.websocket import WebSocketServerProtocol - - t = FakeTransport() - f = WebSocketServerFactory() - p = WebSocketServerProtocol() - p.factory = f - p.transport = t - - # from http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 - http_request = b"GET /demo HTTP/1.1\r\nHost: example.com\r\nConnection: Upgrade\r\nSec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\nSec-WebSocket-Protocol: sample\r\nUpgrade: WebSocket\r\nSec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\nOrigin: http://example.com\r\n\r\n^n:ds[4U" - - p._connectionMade() - p.data = http_request - p.processHandshake() - self.assertIn(b"HTTP/1.1 400", t._written) - self.assertIn(b"Hixie76 protocol not supported", t._written) +from autobahn.test import FakeTransport class WebSocketProtocolTests(unittest.TestCase): @@ -182,7 +141,3 @@ class WebSocketProtocolTests(unittest.TestCase): # We shouldn't have closed self.assertEqual(self.transport._written, b"") self.assertEqual(self.protocol.state, self.protocol.STATE_OPEN) - - -if not os.environ.get('USE_TWISTED', False): - del Hixie76RejectionTests diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..61bd1695 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[pytest] +norecursedirs = autobahn/twisted/* diff --git a/tox.ini b/tox.ini index bf9e8f13..a9c12e1b 100644 --- a/tox.ini +++ b/tox.ini @@ -32,7 +32,7 @@ commands = sh -c "which python" python -V coverage --version - asyncio,trollius: coverage run {envbindir}/py.test autobahn + asyncio,trollius: coverage run {envbindir}/py.test autobahn/ twtrunk,twcurrent,tw121,tw132,twcurrent: coverage run {envbindir}/trial autobahn coverage report whitelist_externals = sh From 5512b6fdea7fa24ec080cf520fa9bbde021a299c Mon Sep 17 00:00:00 2001 From: HawkOwl Date: Wed, 16 Sep 2015 12:28:23 +0800 Subject: [PATCH 20/59] don't use this --- autobahn/wamp/test/test_protocol.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/autobahn/wamp/test/test_protocol.py b/autobahn/wamp/test/test_protocol.py index efed56e0..9cd081af 100644 --- a/autobahn/wamp/test/test_protocol.py +++ b/autobahn/wamp/test/test_protocol.py @@ -36,9 +36,6 @@ if os.environ.get('USE_TWISTED', False): from twisted.trial import unittest from six import PY3 - from twisted.internet.base import DelayedCall - DelayedCall.debug = True - from autobahn import util from autobahn.twisted.wamp import ApplicationSession from autobahn.wamp import message, role, serializer, types From a53f0f12172a0bc315552891e3337c3e6a2e80fb Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Wed, 16 Sep 2015 16:45:52 +0200 Subject: [PATCH 21/59] more public API --- autobahn/wamp/__init__.py | 88 +++++++++++++++++++++++--------------- autobahn/wamp/exception.py | 2 +- autobahn/wamp/uri.py | 49 ++++++++++++++++++++- 3 files changed, 102 insertions(+), 37 deletions(-) diff --git a/autobahn/wamp/__init__.py b/autobahn/wamp/__init__.py index e36c3afa..be1db351 100644 --- a/autobahn/wamp/__init__.py +++ b/autobahn/wamp/__init__.py @@ -26,43 +26,61 @@ from __future__ import absolute_import -from autobahn.wamp.uri import Pattern +from autobahn.wamp.types import \ + SessionDetails, \ + CloseDetails, \ + RegisterOptions, \ + CallOptions, \ + CallDetails, \ + CallResult, \ + SubscribeOptions, \ + PublishOptions, \ + EventDetails + +from autobahn.wamp.exception import \ + Error, \ + SessionNotReady, \ + SerializationError, \ + ProtocolError, \ + TransportLost, \ + ApplicationError + +from autobahn.wamp.interfaces import \ + ICaller, \ + ICallee, \ + IPublisher, \ + ISubscriber + +from autobahn.wamp.uri import \ + error, \ + register, \ + subscribe -def register(uri): - """ - Decorator for WAMP procedure endpoints. - """ - def decorate(f): - assert(callable(f)) - if not hasattr(f, '_wampuris'): - f._wampuris = [] - f._wampuris.append(Pattern(uri, Pattern.URI_TARGET_ENDPOINT)) - return f - return decorate +__all__ = ( + 'SessionDetails', + 'CloseDetails', + 'RegisterOptions', + 'CallOptions', + 'CallDetails', + 'CallResult', + 'SubscribeOptions', + 'PublishOptions', + 'EventDetails', + 'Error', + 'SessionNotReady', + 'SerializationError', + 'ProtocolError', + 'TransportLost', + 'ApplicationError', -def subscribe(uri): - """ - Decorator for WAMP event handlers. - """ - def decorate(f): - assert(callable(f)) - if not hasattr(f, '_wampuris'): - f._wampuris = [] - f._wampuris.append(Pattern(uri, Pattern.URI_TARGET_HANDLER)) - return f - return decorate + 'ICaller', + 'ICallee', + 'IPublisher', + 'ISubscriber', - -def error(uri): - """ - Decorator for WAMP error classes. - """ - def decorate(cls): - assert(issubclass(cls, Exception)) - if not hasattr(cls, '_wampuris'): - cls._wampuris = [] - cls._wampuris.append(Pattern(uri, Pattern.URI_TARGET_EXCEPTION)) - return cls - return decorate + 'error', + 'register', + 'subscribe', +) diff --git a/autobahn/wamp/exception.py b/autobahn/wamp/exception.py index 02c161ae..9474ed5b 100644 --- a/autobahn/wamp/exception.py +++ b/autobahn/wamp/exception.py @@ -26,7 +26,7 @@ from __future__ import absolute_import -from autobahn.wamp import error +from autobahn.wamp.uri import error __all__ = ( 'Error', diff --git a/autobahn/wamp/uri.py b/autobahn/wamp/uri.py index 40b2a3e4..f05bb7ad 100644 --- a/autobahn/wamp/uri.py +++ b/autobahn/wamp/uri.py @@ -24,11 +24,19 @@ # ############################################################################### + +from __future__ import absolute_import + import re import six from autobahn.wamp.types import SubscribeOptions -__all__ = ('Pattern',) +__all__ = ( + 'Pattern', + 'register', + 'subscribe', + 'error', +) class Pattern(object): @@ -215,3 +223,42 @@ class Pattern(object): :rtype: bool """ return self._target == Pattern.URI_TARGET_EXCEPTION + + +def register(uri): + """ + Decorator for WAMP procedure endpoints. + """ + def decorate(f): + assert(callable(f)) + if not hasattr(f, '_wampuris'): + f._wampuris = [] + f._wampuris.append(Pattern(uri, Pattern.URI_TARGET_ENDPOINT)) + return f + return decorate + + +def subscribe(uri): + """ + Decorator for WAMP event handlers. + """ + def decorate(f): + assert(callable(f)) + if not hasattr(f, '_wampuris'): + f._wampuris = [] + f._wampuris.append(Pattern(uri, Pattern.URI_TARGET_HANDLER)) + return f + return decorate + + +def error(uri): + """ + Decorator for WAMP error classes. + """ + def decorate(cls): + assert(issubclass(cls, Exception)) + if not hasattr(cls, '_wampuris'): + cls._wampuris = [] + cls._wampuris.append(Pattern(uri, Pattern.URI_TARGET_EXCEPTION)) + return cls + return decorate From 6662d0e0d5d34d14b88f4eb0baff2c4914cf2ed5 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Wed, 16 Sep 2015 18:28:37 +0200 Subject: [PATCH 22/59] expand code style guide --- DEVELOPERS.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/DEVELOPERS.md b/DEVELOPERS.md index 6325c08a..c8ffd1da 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -80,6 +80,33 @@ The new rule for the public API is simple: if something is exported from the mod * [Asyncio](https://github.com/tavendo/AutobahnPython/blob/master/autobahn/asyncio/__init__.py) * [Twisted](https://github.com/tavendo/AutobahnPython/blob/master/autobahn/twisted/__init__.py) +### Use of assert vs Exceptions + +`assert` is for telling fellow programmers: "when I wrote this, I thought X could/would never really happen, and if it does this code will very Likely do the wrong thing". + +That is, use an assert if the following holds true: if the assert fails, it means we have a bug within the library itself. + +To check for user errors, such as application code using the wrong type when calling into the library, use Exceptions: + +```python +def publish(topic, *args, **kwargs): + if type(topic) != unicode: + raise RuntimeError(u"URIs must be unicode - got {} instead".format(type(topic))) +``` + +In this specific example, we also have a WAMP defined error: + +```python +from autobahn.wamp import ApplicationError + +def publish(topic, *args, **kwargs): + if type(topic) != unicode: + raise ApplicationError(ApplicationError.INVALID_URI, + "URIs must be unicode - got {} instead".format(type(topic))) +``` + + +See the discussion [here](https://github.com/tavendo/AutobahnPython/issues/99). ## Release Process From d6dc1ecb574c439902d66ac78dfdfed6fc63254b Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Wed, 16 Sep 2015 18:31:38 +0200 Subject: [PATCH 23/59] expand code style guide --- DEVELOPERS.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/DEVELOPERS.md b/DEVELOPERS.md index c8ffd1da..4f988dfd 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -89,20 +89,23 @@ That is, use an assert if the following holds true: if the assert fails, it mean To check for user errors, such as application code using the wrong type when calling into the library, use Exceptions: ```python -def publish(topic, *args, **kwargs): - if type(topic) != unicode: - raise RuntimeError(u"URIs must be unicode - got {} instead".format(type(topic))) +import six + +def foo(uri): + if type(topic) != six.text_type: + raise RuntimeError(u"URIs for foo() must be unicode - got {} instead".format(type(uri))) ``` In this specific example, we also have a WAMP defined error: ```python +import six from autobahn.wamp import ApplicationError -def publish(topic, *args, **kwargs): - if type(topic) != unicode: +def foo(uri): + if type(topic) != six.text_type: raise ApplicationError(ApplicationError.INVALID_URI, - "URIs must be unicode - got {} instead".format(type(topic))) + u"URIs for foo() must be unicode - got {} instead".format(type(uri))) ``` From de60f0415c7dd01652e4dbb19d459defe1fd1312 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Wed, 16 Sep 2015 18:34:04 +0200 Subject: [PATCH 24/59] polish style guide --- DEVELOPERS.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/DEVELOPERS.md b/DEVELOPERS.md index 4f988dfd..24244a72 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -82,35 +82,34 @@ The new rule for the public API is simple: if something is exported from the mod ### Use of assert vs Exceptions -`assert` is for telling fellow programmers: "when I wrote this, I thought X could/would never really happen, and if it does this code will very Likely do the wrong thing". +> See the discussion [here](https://github.com/tavendo/AutobahnPython/issues/99). -That is, use an assert if the following holds true: if the assert fails, it means we have a bug within the library itself. +`assert` is for telling fellow programmers: "When I wrote this, I thought X could/would never really happen, and if it does, this code will very likely do the wrong thing". -To check for user errors, such as application code using the wrong type when calling into the library, use Exceptions: +That is, **use an assert if the following holds true: if the assert fails, it means we have a bug within the library itself**. + +In contrast, to check e.g. for user errors, such as application code using the wrong type when calling into the library, use Exceptions: ```python import six def foo(uri): - if type(topic) != six.text_type: + if type(uri) != six.text_type: raise RuntimeError(u"URIs for foo() must be unicode - got {} instead".format(type(uri))) ``` -In this specific example, we also have a WAMP defined error: +In this specific example, we also have a WAMP defined error (which would be preferred compared to the generic exception used above): ```python import six from autobahn.wamp import ApplicationError def foo(uri): - if type(topic) != six.text_type: + if type(uri) != six.text_type: raise ApplicationError(ApplicationError.INVALID_URI, u"URIs for foo() must be unicode - got {} instead".format(type(uri))) ``` - -See the discussion [here](https://github.com/tavendo/AutobahnPython/issues/99). - ## Release Process 1. Travis is fully green From 6e834c5dbd838fb6d7e9ca27afd0f730334f53b3 Mon Sep 17 00:00:00 2001 From: meejah Date: Mon, 14 Sep 2015 21:59:57 -0600 Subject: [PATCH 25/59] playing with txaio logging API --- autobahn/_logging.py | 20 ++++++++++---------- autobahn/asyncio/websocket.py | 2 +- autobahn/twisted/wamp.py | 5 +---- autobahn/twisted/websocket.py | 4 ++-- setup.py | 2 +- tox.ini | 2 ++ 6 files changed, 17 insertions(+), 18 deletions(-) diff --git a/autobahn/_logging.py b/autobahn/_logging.py index a6076bcd..85e15cd9 100644 --- a/autobahn/_logging.py +++ b/autobahn/_logging.py @@ -39,14 +39,14 @@ h = PrintHandler() logging.getLogger("trollius").addHandler(h) -def make_logger(logger_type=None): - if logger_type == "twisted": - # If we've been asked for the Twisted logger, try and get the new one - try: - from twisted.logger import Logger - return Logger() - except ImportError: - pass +# def make_logger(logger_type=None): +# if logger_type == "twisted": +# # If we've been asked for the Twisted logger, try and get the new one +# try: +# from twisted.logger import Logger +# return Logger() +# except ImportError: +# pass - from logging import getLogger - return getLogger() +# from logging import getLogger +# return getLogger() diff --git a/autobahn/asyncio/websocket.py b/autobahn/asyncio/websocket.py index 82bcb6d3..55c6c0cc 100644 --- a/autobahn/asyncio/websocket.py +++ b/autobahn/asyncio/websocket.py @@ -42,8 +42,8 @@ except ImportError: from trollius import iscoroutine from trollius import Future -from autobahn._logging import make_logger from autobahn.websocket.types import ConnectionDeny +from txaio import make_logger __all__ = ( diff --git a/autobahn/twisted/wamp.py b/autobahn/twisted/wamp.py index 4f778108..52df2b1a 100644 --- a/autobahn/twisted/wamp.py +++ b/autobahn/twisted/wamp.py @@ -26,7 +26,6 @@ from __future__ import absolute_import -import sys import inspect import six @@ -169,9 +168,7 @@ class ApplicationRunner(object): isSecure, host, port, resource, path, params = parseWsUrl(self.url) - # start logging to console - if self.debug or self.debug_wamp or self.debug_app: - log.startLogging(sys.stdout) + txaio.start_logging() # factory for use ApplicationSession def create(): diff --git a/autobahn/twisted/websocket.py b/autobahn/twisted/websocket.py index 82ba66da..131ad755 100644 --- a/autobahn/twisted/websocket.py +++ b/autobahn/twisted/websocket.py @@ -42,7 +42,7 @@ from autobahn.websocket.types import ConnectionRequest, ConnectionResponse, \ from autobahn.websocket import protocol from autobahn.twisted.util import peer2str -from autobahn._logging import make_logger +from txaio import make_logger from autobahn.websocket.compress import PerMessageDeflateOffer, \ PerMessageDeflateOfferAccept, \ @@ -223,7 +223,7 @@ class WebSocketAdapterFactory(object): """ Adapter class for Twisted-based WebSocket client and server factories. """ - log = make_logger("twisted") + log = make_logger() class WebSocketServerFactory(WebSocketAdapterFactory, protocol.WebSocketServerFactory, twisted.internet.protocol.ServerFactory): diff --git a/setup.py b/setup.py index b19c89d6..32af0710 100644 --- a/setup.py +++ b/setup.py @@ -182,7 +182,7 @@ setup( platforms='Any', install_requires=[ 'six>=1.9.0', # MIT license - 'txaio>=1.1.0' # MIT license + 'txaio>=2.0.0', # MIT license ], extras_require={ 'all': extras_require_all, diff --git a/tox.ini b/tox.ini index a9c12e1b..d4697b53 100644 --- a/tox.ini +++ b/tox.ini @@ -12,11 +12,13 @@ max-line-length = 119 [testenv] +usedevelop = True deps = mock unittest2 coverage msgpack-python + ../txaio ; twisted dependencies twtrunk: https://github.com/twisted/twisted/archive/trunk.zip From 3a0bdae23929ccfade6cea3f7702a64f8204b5de Mon Sep 17 00:00:00 2001 From: meejah Date: Tue, 15 Sep 2015 13:17:39 -0600 Subject: [PATCH 26/59] use txaio.make_logger() --- autobahn/_logging.py | 52 ---------- autobahn/asyncio/websocket.py | 4 +- autobahn/twisted/choosereactor.py | 6 +- autobahn/twisted/wamp.py | 2 +- autobahn/twisted/websocket.py | 28 +++--- autobahn/wamp/protocol.py | 5 + autobahn/websocket/protocol.py | 160 +++++++++++++++--------------- 7 files changed, 106 insertions(+), 151 deletions(-) delete mode 100644 autobahn/_logging.py diff --git a/autobahn/_logging.py b/autobahn/_logging.py deleted file mode 100644 index 85e15cd9..00000000 --- a/autobahn/_logging.py +++ /dev/null @@ -1,52 +0,0 @@ -############################################################################### -# -# The MIT License (MIT) -# -# Copyright (c) Tavendo GmbH -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -############################################################################## - -from __future__ import absolute_import - -import logging - - -# A logging handler for Trollius that prints everything out -class PrintHandler(logging.Handler): - def emit(self, record): - print(record) - - -h = PrintHandler() -logging.getLogger("trollius").addHandler(h) - - -# def make_logger(logger_type=None): -# if logger_type == "twisted": -# # If we've been asked for the Twisted logger, try and get the new one -# try: -# from twisted.logger import Logger -# return Logger() -# except ImportError: -# pass - -# from logging import getLogger -# return getLogger() diff --git a/autobahn/asyncio/websocket.py b/autobahn/asyncio/websocket.py index 55c6c0cc..8cb516ca 100644 --- a/autobahn/asyncio/websocket.py +++ b/autobahn/asyncio/websocket.py @@ -43,7 +43,7 @@ except ImportError: from trollius import Future from autobahn.websocket.types import ConnectionDeny -from txaio import make_logger +import txaio __all__ = ( @@ -216,7 +216,7 @@ class WebSocketAdapterFactory(object): """ Adapter class for asyncio-based WebSocket client and server factories. """ - log = make_logger() + log = txaio.make_logger() def __call__(self): proto = self.protocol() diff --git a/autobahn/twisted/choosereactor.py b/autobahn/twisted/choosereactor.py index b6690405..7f97f023 100644 --- a/autobahn/twisted/choosereactor.py +++ b/autobahn/twisted/choosereactor.py @@ -26,7 +26,7 @@ from __future__ import absolute_import -from autobahn._logging import make_logger +from txaio import make_logger __all__ = ( 'install_optimal_reactor', @@ -41,7 +41,7 @@ def install_optimal_reactor(verbose=False): :param verbose: If ``True``, print what happens. :type verbose: bool """ - log = make_logger("twisted") + log = make_logger() import sys from twisted.python import reflect @@ -133,7 +133,7 @@ def install_reactor(explicit_reactor=None, verbose=False): import txaio txaio.use_twisted() # just to be sure... - log = make_logger("twisted") + log = make_logger() if explicit_reactor: # install explicitly given reactor diff --git a/autobahn/twisted/wamp.py b/autobahn/twisted/wamp.py index 52df2b1a..bcf4126f 100644 --- a/autobahn/twisted/wamp.py +++ b/autobahn/twisted/wamp.py @@ -168,7 +168,7 @@ class ApplicationRunner(object): isSecure, host, port, resource, path, params = parseWsUrl(self.url) - txaio.start_logging() + txaio.start_logging(level='debug') # factory for use ApplicationSession def create(): diff --git a/autobahn/twisted/websocket.py b/autobahn/twisted/websocket.py index 131ad755..20e05320 100644 --- a/autobahn/twisted/websocket.py +++ b/autobahn/twisted/websocket.py @@ -42,7 +42,7 @@ from autobahn.websocket.types import ConnectionRequest, ConnectionResponse, \ from autobahn.websocket import protocol from autobahn.twisted.util import peer2str -from txaio import make_logger +import txaio from autobahn.websocket.compress import PerMessageDeflateOffer, \ PerMessageDeflateOfferAccept, \ @@ -79,6 +79,7 @@ class WebSocketAdapterProtocol(twisted.internet.protocol.Protocol): Adapter class for Twisted WebSocket client and server protocols. """ peer = '' + log = txaio.make_logger() def connectionMade(self): # the peer we are connected to @@ -91,6 +92,7 @@ class WebSocketAdapterProtocol(twisted.internet.protocol.Protocol): self.peer = peer2str(peer) self._connectionMade() + self.log.info('Connection made to {peer}', peer=self.peer) # Set "Nagle" try: @@ -101,12 +103,12 @@ class WebSocketAdapterProtocol(twisted.internet.protocol.Protocol): def connectionLost(self, reason): if isinstance(reason.value, ConnectionDone): - self.factory.log.debug("Connection to/from {peer} was closed cleanly", - peer=self.peer) + self.log.debug("Connection to/from {peer} was closed cleanly", + peer=self.peer) elif isinstance(reason.value, ConnectionAborted): - self.factory.log.debug("Connection to/from {peer} was aborted locally", - peer=self.peer) + self.log.debug("Connection to/from {peer} was aborted locally", + peer=self.peer) elif isinstance(reason.value, ConnectionLost): # The following is ridiculous, but the treatment of reason.value.args @@ -119,16 +121,16 @@ class WebSocketAdapterProtocol(twisted.internet.protocol.Protocol): message = None if message: - self.factory.log.debug("Connection to/from {peer} was lost in a non-clean fashion: {message}", - peer=self.peer, message=message) + self.log.debug("Connection to/from {peer} was lost in a non-clean fashion: {message}", + peer=self.peer, message=message) else: - self.factory.log.debug("Connection to/from {peer} was lost in a non-clean fashion", - peer=self.peer) + self.log.debug("Connection to/from {peer} was lost in a non-clean fashion", + peer=self.peer) # at least: FileDescriptorOverrun, ConnectionFdescWentAway - but maybe others as well? else: - self.factory.log.info("Connection to/from {peer} lost ({error_type}): {error})", - peer=self.peer, error_type=type(reason.value), error=reason.value) + self.log.info("Connection to/from {peer} lost ({error_type}): {error})", + peer=self.peer, error_type=type(reason.value), error=reason.value) self._connectionLost(reason) @@ -203,8 +205,7 @@ class WebSocketServerProtocol(WebSocketAdapterProtocol, protocol.WebSocketServer if failure.check(ConnectionDeny): return self.failHandshake(failure.value.reason, failure.value.code) else: - if self.debug: - self.factory._log("Unexpected exception in onConnect ['%s']" % failure.value) + self.log.debug("Unexpected exception in onConnect ['{failure.value}']", failure=failure) return self.failHandshake("Internal server error: {}".format(failure.value), ConnectionDeny.INTERNAL_SERVER_ERROR) res.addErrback(forwardError) @@ -223,7 +224,6 @@ class WebSocketAdapterFactory(object): """ Adapter class for Twisted-based WebSocket client and server factories. """ - log = make_logger() class WebSocketServerFactory(WebSocketAdapterFactory, protocol.WebSocketServerFactory, twisted.internet.protocol.ServerFactory): diff --git a/autobahn/wamp/protocol.py b/autobahn/wamp/protocol.py index 2842afa8..a97f1769 100644 --- a/autobahn/wamp/protocol.py +++ b/autobahn/wamp/protocol.py @@ -423,6 +423,8 @@ class ApplicationSession(BaseSession): * :class:`autobahn.wamp.interfaces.ITransportHandler` """ + log = txaio.make_logger() + def __init__(self, config=None): """ Constructor. @@ -544,6 +546,9 @@ class ApplicationSession(BaseSession): """ Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onMessage` """ + print("BLAM", id(self.log), type(self.log)) + self.log.debug("onMessage: {message}", session_id=self._session_id, message=msg) + self.log.trace("onMessage: {message}", session_id=self._session_id, message=msg) if self._session_id is None: # the first message must be WELCOME, ABORT or CHALLENGE .. diff --git a/autobahn/websocket/protocol.py b/autobahn/websocket/protocol.py index 5298507b..20d3c260 100755 --- a/autobahn/websocket/protocol.py +++ b/autobahn/websocket/protocol.py @@ -556,6 +556,8 @@ class WebSocketProtocol(object): Configuration attributes specific to clients. """ + log = txaio.make_logger() + def __init__(self): #: a Future/Deferred that fires when we hit STATE_CLOSED self.is_closed = txaio.create_future() @@ -564,8 +566,7 @@ class WebSocketProtocol(object): """ Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.onOpen` """ - if self.debugCodePaths: - self.factory.log.debug("WebSocketProtocol.onOpen") + self.log.debug("WebSocketProtocol.onOpen") def onMessageBegin(self, isBinary): """ @@ -637,14 +638,14 @@ class WebSocketProtocol(object): Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.onMessage` """ if self.debug: - self.factory.log.debug("WebSocketProtocol.onMessage") + self.log.debug("WebSocketProtocol.onMessage") def onPing(self, payload): """ Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.onPing` """ if self.debug: - self.factory.log.debug("WebSocketProtocol.onPing") + self.log.debug("WebSocketProtocol.onPing") if self.state == WebSocketProtocol.STATE_OPEN: self.sendPong(payload) @@ -653,7 +654,7 @@ class WebSocketProtocol(object): Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.onPong` """ if self.debug: - self.factory.log.debug("WebSocketProtocol.onPong") + self.log.debug("WebSocketProtocol.onPong") def onClose(self, wasClean, code, reason): """ @@ -673,7 +674,7 @@ class WebSocketProtocol(object): s += "self.localCloseReason=%s\n" % self.localCloseReason s += "self.remoteCloseCode=%s\n" % self.remoteCloseCode s += "self.remoteCloseReason=%s\n" % self.remoteCloseReason - self.factory.log.debug(s) + self.log.debug(s) def onCloseFrame(self, code, reasonRaw): """ @@ -688,7 +689,7 @@ class WebSocketProtocol(object): :type reason: str or None """ if self.debugCodePaths: - self.factory.log.debug("WebSocketProtocol.onCloseFrame") + self.log.debug("WebSocketProtocol.onCloseFrame") self.remoteCloseCode = code @@ -722,7 +723,7 @@ class WebSocketProtocol(object): # if self.closeHandshakeTimeoutCall is not None: if self.debugCodePaths: - self.factory.log.debug("closeHandshakeTimeoutCall.cancel") + self.log.debug("closeHandshakeTimeoutCall.cancel") self.closeHandshakeTimeoutCall.cancel() self.closeHandshakeTimeoutCall = None @@ -782,14 +783,14 @@ class WebSocketProtocol(object): self.serverConnectionDropTimeoutCall = None if self.state != WebSocketProtocol.STATE_CLOSED: if self.debugCodePaths: - self.factory.log.debug("onServerConnectionDropTimeout") + self.log.debug("onServerConnectionDropTimeout") self.wasClean = False self.wasNotCleanReason = "server did not drop TCP connection (in time)" self.wasServerConnectionDropTimeout = True self.dropConnection(abort=True) else: if self.debugCodePaths: - self.factory.log.debug("skipping onServerConnectionDropTimeout since connection is already closed") + self.log.debug("skipping onServerConnectionDropTimeout since connection is already closed") def onOpenHandshakeTimeout(self): """ @@ -800,20 +801,20 @@ class WebSocketProtocol(object): self.openHandshakeTimeoutCall = None if self.state in [WebSocketProtocol.STATE_CONNECTING, WebSocketProtocol.STATE_PROXY_CONNECTING]: if self.debugCodePaths: - self.factory.log.debug("onOpenHandshakeTimeout fired") + self.log.debug("onOpenHandshakeTimeout fired") self.wasClean = False self.wasNotCleanReason = "peer did not finish (in time) the opening handshake" self.wasOpenHandshakeTimeout = True self.dropConnection(abort=True) elif self.state == WebSocketProtocol.STATE_OPEN: if self.debugCodePaths: - self.factory.log.debug("skipping onOpenHandshakeTimeout since WebSocket connection is open (opening handshake already finished)") + self.log.debug("skipping onOpenHandshakeTimeout since WebSocket connection is open (opening handshake already finished)") elif self.state == WebSocketProtocol.STATE_CLOSING: if self.debugCodePaths: - self.factory.log.debug("skipping onOpenHandshakeTimeout since WebSocket connection is closing") + self.log.debug("skipping onOpenHandshakeTimeout since WebSocket connection is closing") elif self.state == WebSocketProtocol.STATE_CLOSED: if self.debugCodePaths: - self.factory.log.debug("skipping onOpenHandshakeTimeout since WebSocket connection already closed") + self.log.debug("skipping onOpenHandshakeTimeout since WebSocket connection already closed") else: # should not arrive here raise Exception("logic error") @@ -827,14 +828,14 @@ class WebSocketProtocol(object): self.closeHandshakeTimeoutCall = None if self.state != WebSocketProtocol.STATE_CLOSED: if self.debugCodePaths: - self.factory.log.debug("onCloseHandshakeTimeout fired") + self.log.debug("onCloseHandshakeTimeout fired") self.wasClean = False self.wasNotCleanReason = "peer did not respond (in time) in closing handshake" self.wasCloseHandshakeTimeout = True self.dropConnection(abort=True) else: if self.debugCodePaths: - self.factory.log.debug("skipping onCloseHandshakeTimeout since connection is already closed") + self.log.debug("skipping onCloseHandshakeTimeout since connection is already closed") def onAutoPingTimeout(self): """ @@ -842,7 +843,7 @@ class WebSocketProtocol(object): did not reply in time to our ping. We drop the connection. """ if self.debugCodePaths: - self.factory.log.debug("Auto ping/pong: onAutoPingTimeout fired") + self.log.debug("Auto ping/pong: onAutoPingTimeout fired") self.autoPingTimeoutCall = None self.dropConnection(abort=True) @@ -853,7 +854,7 @@ class WebSocketProtocol(object): """ if self.state != WebSocketProtocol.STATE_CLOSED: if self.debugCodePaths: - self.factory.log.debug("dropping connection") + self.log.debug("dropping connection") self.droppedByMe = True # this code-path will be hit (*without* hitting @@ -865,7 +866,7 @@ class WebSocketProtocol(object): self._closeConnection(abort) else: if self.debugCodePaths: - self.factory.log.debug("skipping dropConnection since connection is already closed") + self.log.debug("skipping dropConnection since connection is already closed") def failConnection(self, code=CLOSE_STATUS_CODE_GOING_AWAY, reason="Going Away"): """ @@ -873,7 +874,7 @@ class WebSocketProtocol(object): """ if self.state != WebSocketProtocol.STATE_CLOSED: if self.debugCodePaths: - self.factory.log.debug("Failing connection : %s - %s" % (code, reason)) + self.log.debug("Failing connection : %s - %s" % (code, reason)) self.failedByMe = True @@ -894,7 +895,7 @@ class WebSocketProtocol(object): else: if self.debugCodePaths: - self.factory.log.debug("skipping failConnection since connection is already closed") + self.log.debug("skipping failConnection since connection is already closed") def protocolViolation(self, reason): """ @@ -906,7 +907,7 @@ class WebSocketProtocol(object): :returns: bool -- True, when any further processing should be discontinued. """ if self.debugCodePaths: - self.factory.log.debug("Protocol violation : %s" % reason) + self.log.debug("Protocol violation : %s" % reason) self.failConnection(WebSocketProtocol.CLOSE_STATUS_CODE_PROTOCOL_ERROR, reason) if self.failByDrop: return True @@ -927,7 +928,7 @@ class WebSocketProtocol(object): :returns: bool -- True, when any further processing should be discontinued. """ if self.debugCodePaths: - self.factory.log.debug("Invalid payload : %s" % reason) + self.log.debug("Invalid payload : %s" % reason) self.failConnection(WebSocketProtocol.CLOSE_STATUS_CODE_INVALID_PAYLOAD, reason) if self.failByDrop: return True @@ -969,8 +970,8 @@ class WebSocketProtocol(object): configAttrLog.append((configAttr, getattr(self, configAttr), configAttrSource)) if self.debug: - # self.factory.log.debug(configAttrLog) - self.factory.log.debug("\n" + pformat(configAttrLog)) + # self.log.debug(configAttrLog) + self.log.debug("\n" + pformat(configAttrLog)) # permessage-compress extension self._perMessageCompress = None @@ -1072,9 +1073,10 @@ class WebSocketProtocol(object): """ # cancel any server connection drop timer if present # + self.log.debug('_connectionLost: {reason}', reason=reason) if not self.factory.isServer and self.serverConnectionDropTimeoutCall is not None: if self.debugCodePaths: - self.factory.log.debug("serverConnectionDropTimeoutCall.cancel") + self.log.debug("serverConnectionDropTimeoutCall.cancel") self.serverConnectionDropTimeoutCall.cancel() self.serverConnectionDropTimeoutCall = None @@ -1082,13 +1084,13 @@ class WebSocketProtocol(object): # if self.autoPingPendingCall: if self.debugCodePaths: - self.factory.log.debug("Auto ping/pong: canceling autoPingPendingCall upon lost connection") + self.log.debug("Auto ping/pong: canceling autoPingPendingCall upon lost connection") self.autoPingPendingCall.cancel() self.autoPingPendingCall = None if self.autoPingTimeoutCall: if self.debugCodePaths: - self.factory.log.debug("Auto ping/pong: canceling autoPingTimeoutCall upon lost connection") + self.log.debug("Auto ping/pong: canceling autoPingTimeoutCall upon lost connection") self.autoPingTimeoutCall.cancel() self.autoPingTimeoutCall = None @@ -1100,7 +1102,7 @@ class WebSocketProtocol(object): if self.wasServingFlashSocketPolicyFile: if self.debug: - self.factory.log.debug("connection dropped after serving Flash Socket Policy File") + self.log.debug("connection dropped after serving Flash Socket Policy File") else: if not self.wasClean: if not self.droppedByMe and self.wasNotCleanReason is None: @@ -1114,14 +1116,14 @@ class WebSocketProtocol(object): Hook fired right after raw octets have been received, but only when self.logOctets == True. """ - self.factory.log.debug("RX Octets from %s : octets = %s" % (self.peer, binascii.b2a_hex(data))) + self.log.debug("RX Octets from %s : octets = %s" % (self.peer, binascii.b2a_hex(data))) def logTxOctets(self, data, sync): """ Hook fired right after raw octets have been sent, but only when self.logOctets == True. """ - self.factory.log.debug("TX Octets to %s : sync = %s, octets = %s" % (self.peer, sync, binascii.b2a_hex(data))) + self.log.debug("TX Octets to %s : sync = %s, octets = %s" % (self.peer, sync, binascii.b2a_hex(data))) def logRxFrame(self, frameHeader, payload): """ @@ -1137,7 +1139,7 @@ class WebSocketProtocol(object): frameHeader.length, data if frameHeader.opcode == 1 else binascii.b2a_hex(data)) - self.factory.log.debug("RX Frame from %s : fin = %s, rsv = %s, opcode = %s, mask = %s, length = %s, payload = %s" % info) + self.log.debug("RX Frame from %s : fin = %s, rsv = %s, opcode = %s, mask = %s, length = %s, payload = %s" % info) def logTxFrame(self, frameHeader, payload, repeatLength, chopsize, sync): """ @@ -1155,7 +1157,7 @@ class WebSocketProtocol(object): sync, payload if frameHeader.opcode == 1 else binascii.b2a_hex(payload)) - self.factory.log.debug("TX Frame to %s : fin = %s, rsv = %s, opcode = %s, mask = %s, length = %s, repeat_length = %s, chopsize = %s, sync = %s, payload = %s" % info) + self.log.debug("TX Frame to %s : fin = %s, rsv = %s, opcode = %s, mask = %s, length = %s, repeat_length = %s, chopsize = %s, sync = %s, payload = %s" % info) def _dataReceived(self, data): """ @@ -1208,7 +1210,7 @@ class WebSocketProtocol(object): # ignore any data received after WS was closed # if self.debugCodePaths: - self.factory.log.debug("received data in STATE_CLOSED") + self.log.debug("received data in STATE_CLOSED") # should not arrive here (invalid state) # @@ -1257,7 +1259,7 @@ class WebSocketProtocol(object): self.logTxOctets(e[0], e[1]) else: if self.debugCodePaths: - self.factory.log.debug("skipped delayed write, since connection is closed") + self.log.debug("skipped delayed write, since connection is closed") # we need to reenter the reactor to make the latter # reenter the OS network stack, so that octets # can get on the wire. Note: this is a "heuristic", @@ -1611,7 +1613,7 @@ class WebSocketProtocol(object): if self._isMessageCompressed: compressedLen = len(payload) if self.debug: - self.factory.log.debug("RX compressed [%d]: %s" % (compressedLen, binascii.b2a_hex(payload))) + self.log.debug("RX compressed [%d]: %s" % (compressedLen, binascii.b2a_hex(payload))) payload = self._perMessageCompress.decompressMessageData(payload) uncompressedLen = len(payload) @@ -1665,7 +1667,7 @@ class WebSocketProtocol(object): return False # if self.debug: - # self.factory.log.debug("Traffic statistics:\n" + str(self.trafficStats)) + # self.log.debug("Traffic statistics:\n" + str(self.trafficStats)) if self.state == WebSocketProtocol.STATE_OPEN: self.trafficStats.incomingWebSocketMessages += 1 @@ -1712,7 +1714,7 @@ class WebSocketProtocol(object): try: if payload == self.autoPingPending: if self.debugCodePaths: - self.factory.log.debug("Auto ping/pong: received pending pong for auto-ping/pong") + self.log.debug("Auto ping/pong: received pending pong for auto-ping/pong") if self.autoPingTimeoutCall: self.autoPingTimeoutCall.cancel() @@ -1724,10 +1726,10 @@ class WebSocketProtocol(object): self.autoPingPendingCall = txaio.call_later(self.autoPingInterval, self._sendAutoPing) else: if self.debugCodePaths: - self.factory.log.debug("Auto ping/pong: received non-pending pong") + self.log.debug("Auto ping/pong: received non-pending pong") except: if self.debugCodePaths: - self.factory.log.debug("Auto ping/pong: received non-pending pong") + self.log.debug("Auto ping/pong: received non-pending pong") # fire app-level callback # @@ -1850,7 +1852,7 @@ class WebSocketProtocol(object): def _sendAutoPing(self): # Sends an automatic ping and sets up a timeout. if self.debugCodePaths: - self.factory.log.debug("Auto ping/pong: sending ping auto-ping/pong") + self.log.debug("Auto ping/pong: sending ping auto-ping/pong") self.autoPingPendingCall = None @@ -1860,7 +1862,7 @@ class WebSocketProtocol(object): if self.autoPingTimeout: if self.debugCodePaths: - self.factory.log.debug("Auto ping/pong: expecting ping in {0} seconds for auto-ping/pong".format(self.autoPingTimeout)) + self.log.debug("Auto ping/pong: expecting ping in {0} seconds for auto-ping/pong".format(self.autoPingTimeout)) self.autoPingTimeoutCall = txaio.call_later(self.autoPingTimeout, self.onAutoPingTimeout) def sendPong(self, payload=None): @@ -1885,11 +1887,11 @@ class WebSocketProtocol(object): """ if self.state == WebSocketProtocol.STATE_CLOSING: if self.debugCodePaths: - self.factory.log.debug("ignoring sendCloseFrame since connection is closing") + self.log.debug("ignoring sendCloseFrame since connection is closing") elif self.state == WebSocketProtocol.STATE_CLOSED: if self.debugCodePaths: - self.factory.log.debug("ignoring sendCloseFrame since connection already closed") + self.log.debug("ignoring sendCloseFrame since connection already closed") elif self.state in [WebSocketProtocol.STATE_PROXY_CONNECTING, WebSocketProtocol.STATE_CONNECTING]: raise Exception("cannot close a connection not yet connected") @@ -2226,7 +2228,7 @@ class WebSocketProtocol(object): i += pfs # if self.debug: - # self.factory.log.debug("Traffic statistics:\n" + str(self.trafficStats)) + # self.log.debug("Traffic statistics:\n" + str(self.trafficStats)) def _parseExtensionsHeader(self, header, removeQuotes=True): """ @@ -2410,7 +2412,7 @@ class WebSocketServerProtocol(WebSocketProtocol): WebSocketProtocol._connectionMade(self) self.factory.countConnections += 1 if self.debug: - self.factory.log.debug("connection accepted from peer %s" % self.peer) + self.log.debug("connection accepted from peer %s" % self.peer) def _connectionLost(self, reason): """ @@ -2436,7 +2438,7 @@ class WebSocketServerProtocol(WebSocketProtocol): self.http_request_data = self.data[:end_of_header + 4] if self.debug: - self.factory.log.debug("received HTTP request:\n\n%s\n\n" % self.http_request_data) + self.log.debug("received HTTP request:\n\n%s\n\n" % self.http_request_data) # extract HTTP status line and headers # @@ -2445,8 +2447,8 @@ class WebSocketServerProtocol(WebSocketProtocol): # validate WebSocket opening handshake client request # if self.debug: - self.factory.log.debug("received HTTP status line in opening handshake : %s" % str(self.http_status_line)) - self.factory.log.debug("received HTTP headers in opening handshake : %s" % str(self.http_headers)) + self.log.debug("received HTTP status line in opening handshake : %s" % str(self.http_status_line)) + self.log.debug("received HTTP headers in opening handshake : %s" % str(self.http_headers)) # HTTP Request line : METHOD, VERSION # @@ -2505,7 +2507,7 @@ class WebSocketServerProtocol(WebSocketProtocol): 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.externalPort)) else: if self.debugCodePaths: - self.factory.log.debug("skipping opening handshake port checking - neither WS URL nor external port set") + self.log.debug("skipping opening handshake port checking - neither WS URL nor external port set") self.http_request_host = h @@ -2516,7 +2518,7 @@ class WebSocketServerProtocol(WebSocketProtocol): 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.externalPort, self.factory.isSecure)) else: if self.debugCodePaths: - self.factory.log.debug("skipping opening handshake port checking - neither WS URL nor external port set") + self.log.debug("skipping opening handshake port checking - neither WS URL nor external port set") # Upgrade # @@ -2544,15 +2546,15 @@ class WebSocketServerProtocol(WebSocketProtocol): if 'after' in self.http_request_params and len(self.http_request_params['after']) > 0: after = int(self.http_request_params['after'][0]) if self.debugCodePaths: - self.factory.log.debug("HTTP Upgrade header missing : render server status page and meta-refresh-redirecting to %s after %d seconds" % (url, after)) + self.log.debug("HTTP Upgrade header missing : render server status page and meta-refresh-redirecting to %s after %d seconds" % (url, after)) self.sendServerStatus(url, after) else: if self.debugCodePaths: - self.factory.log.debug("HTTP Upgrade header missing : 303-redirecting to %s" % url) + self.log.debug("HTTP Upgrade header missing : 303-redirecting to %s" % url) self.sendRedirect(url) else: if self.debugCodePaths: - self.factory.log.debug("HTTP Upgrade header missing : render server status page") + self.log.debug("HTTP Upgrade header missing : render server status page") self.sendServerStatus() self.dropConnection(abort=False) return @@ -2582,11 +2584,11 @@ class WebSocketServerProtocol(WebSocketProtocol): # if 'sec-websocket-version' not in self.http_headers: if self.debugCodePaths: - self.factory.log.debug("Hixie76 protocol detected") + self.log.debug("Hixie76 protocol detected") return self.failHandshake("WebSocket connection denied - Hixie76 protocol not supported.") else: if self.debugCodePaths: - self.factory.log.debug("Hybi protocol detected") + self.log.debug("Hybi protocol detected") if http_headers_cnt["sec-websocket-version"] > 1: return self.failHandshake("HTTP Sec-WebSocket-Version header appears more than once in opening handshake request") try: @@ -2717,11 +2719,11 @@ class WebSocketServerProtocol(WebSocketProtocol): flash_policy_file_request = self.data.find(b"\x00") if flash_policy_file_request >= 0: if self.debug: - self.factory.log.debug("received Flash Socket Policy File request") + self.log.debug("received Flash Socket Policy File request") if self.serveFlashSocketPolicy: if self.debug: - self.factory.log.debug("sending Flash Socket Policy File :\n%s" % self.flashSocketPolicy) + self.log.debug("sending Flash Socket Policy File :\n%s" % self.flashSocketPolicy) self.sendData(self.flashSocketPolicy.encode('utf8')) @@ -2730,7 +2732,7 @@ class WebSocketServerProtocol(WebSocketProtocol): self.dropConnection() else: if self.debug: - self.factory.log.debug("No Flash Policy File served. You might want to serve a Flask Socket Policy file on the destination port since you received a request for it. See WebSocketServerFactory.serveFlashSocketPolicy and WebSocketServerFactory.flashSocketPolicy") + self.log.debug("No Flash Policy File served. You might want to serve a Flask Socket Policy file on the destination port since you received a request for it. See WebSocketServerFactory.serveFlashSocketPolicy and WebSocketServerFactory.flashSocketPolicy") def succeedHandshake(self, res): """ @@ -2767,7 +2769,7 @@ class WebSocketServerProtocol(WebSocketProtocol): for (extension, params) in self.websocket_extensions: if self.debug: - self.factory.log.debug("parsed WebSocket extension '%s' with params '%s'" % (extension, params)) + self.log.debug("parsed WebSocket extension '%s' with params '%s'" % (extension, params)) # process permessage-compress extension # @@ -2783,7 +2785,7 @@ class WebSocketServerProtocol(WebSocketProtocol): else: if self.debug: - self.factory.log.debug("client requested '%s' extension we don't support or which is not activated" % extension) + self.log.debug("client requested '%s' extension we don't support or which is not activated" % extension) # handle permessage-compress offers by the client # @@ -2796,7 +2798,7 @@ class WebSocketServerProtocol(WebSocketProtocol): extensionResponse.append(accept.getExtensionString()) else: if self.debug: - self.factory.log.debug("client request permessage-compress extension, but we did not accept any offer [%s]" % pmceOffers) + self.log.debug("client request permessage-compress extension, but we did not accept any offer [%s]" % pmceOffers) # build response to complete WebSocket handshake # @@ -2850,12 +2852,12 @@ class WebSocketServerProtocol(WebSocketProtocol): # send out opening handshake response # if self.debug: - self.factory.log.debug("sending HTTP response:\n\n%s" % response) + self.log.debug("sending HTTP response:\n\n%s" % response) self.sendData(response.encode('utf8')) if response_body: if self.debug: - self.factory.log.debug("sending HTTP response body:\n\n%s" % binascii.b2a_hex(response_body)) + self.log.debug("sending HTTP response body:\n\n%s" % binascii.b2a_hex(response_body)) self.sendData(response_body) # save response for testsuite @@ -2870,7 +2872,7 @@ class WebSocketServerProtocol(WebSocketProtocol): # if self.openHandshakeTimeoutCall is not None: if self.debugCodePaths: - self.factory.log.debug("openHandshakeTimeoutCall.cancel") + self.log.debug("openHandshakeTimeoutCall.cancel") self.openHandshakeTimeoutCall.cancel() self.openHandshakeTimeoutCall = None @@ -2901,7 +2903,7 @@ class WebSocketServerProtocol(WebSocketProtocol): error response and then drop the connection. """ self.wasNotCleanReason = reason - self.factory.log.info("failing WebSocket opening handshake ('{reason}')", reason=reason) + self.log.info("failing WebSocket opening handshake ('{reason}')", reason=reason) self.sendHttpErrorResponse(code, reason, responseHeaders) self.dropConnection(abort=False) @@ -3323,7 +3325,7 @@ class WebSocketClientProtocol(WebSocketProtocol): """ WebSocketProtocol._connectionMade(self) if self.debug: - self.factory.log.debug("connection to %s established" % self.peer) + self.log.debug("connection to %s established" % self.peer) if not self.factory.isServer and self.factory.proxy is not None: # start by doing a HTTP/CONNECT for explicit proxies @@ -3352,7 +3354,7 @@ class WebSocketClientProtocol(WebSocketProtocol): request += "\x0d\x0a" if self.debug: - self.factory.log.debug(request) + self.log.debug(request) self.sendData(request) @@ -3367,7 +3369,7 @@ class WebSocketClientProtocol(WebSocketProtocol): http_response_data = self.data[:end_of_header + 4] if self.debug: - self.factory.log.debug("received HTTP response:\n\n%s\n\n" % http_response_data) + self.log.debug("received HTTP response:\n\n%s\n\n" % http_response_data) # extract HTTP status line and headers # @@ -3376,8 +3378,8 @@ class WebSocketClientProtocol(WebSocketProtocol): # validate proxy connect response # if self.debug: - self.factory.log.debug("received HTTP status line for proxy connect request : %s" % str(http_status_line)) - self.factory.log.debug("received HTTP headers for proxy connect request : %s" % str(http_headers)) + self.log.debug("received HTTP status line for proxy connect request : %s" % str(http_status_line)) + self.log.debug("received HTTP headers for proxy connect request : %s" % str(http_headers)) # Response Line # @@ -3432,7 +3434,7 @@ class WebSocketClientProtocol(WebSocketProtocol): connection. """ if self.debug: - self.factory.log.debug("failing proxy connect ('%s')" % reason) + self.log.debug("failing proxy connect ('%s')" % reason) self.dropConnection(abort=True) def startHandshake(self): @@ -3505,7 +3507,7 @@ class WebSocketClientProtocol(WebSocketProtocol): self.sendData(self.http_request_data) if self.debug: - self.factory.log.debug(request) + self.log.debug(request) def processHandshake(self): """ @@ -3518,7 +3520,7 @@ class WebSocketClientProtocol(WebSocketProtocol): self.http_response_data = self.data[:end_of_header + 4] if self.debug: - self.factory.log.debug("received HTTP response:\n\n%s\n\n" % self.http_response_data) + self.log.debug("received HTTP response:\n\n%s\n\n" % self.http_response_data) # extract HTTP status line and headers # @@ -3527,8 +3529,8 @@ class WebSocketClientProtocol(WebSocketProtocol): # validate WebSocket opening handshake server response # if self.debug: - self.factory.log.debug("received HTTP status line in opening handshake : %s" % str(self.http_status_line)) - self.factory.log.debug("received HTTP headers in opening handshake : %s" % str(self.http_headers)) + self.log.debug("received HTTP status line in opening handshake : %s" % str(self.http_status_line)) + self.log.debug("received HTTP headers in opening handshake : %s" % str(self.http_headers)) # Response Line # @@ -3615,7 +3617,7 @@ class WebSocketClientProtocol(WebSocketProtocol): for (extension, params) in websocket_extensions: if self.debug: - self.factory.log.debug("parsed WebSocket extension '%s' with params '%s'" % (extension, params)) + self.log.debug("parsed WebSocket extension '%s' with params '%s'" % (extension, params)) # process permessage-compress extension # @@ -3672,7 +3674,7 @@ class WebSocketClientProtocol(WebSocketProtocol): # if self.openHandshakeTimeoutCall is not None: if self.debugCodePaths: - self.factory.log.debug("openHandshakeTimeoutCall.cancel") + self.log.debug("openHandshakeTimeoutCall.cancel") self.openHandshakeTimeoutCall.cancel() self.openHandshakeTimeoutCall = None @@ -3716,7 +3718,7 @@ class WebSocketClientProtocol(WebSocketProtocol): connection. """ self.wasNotCleanReason = reason - self.factory.log.info("failing WebSocket opening handshake ('{reason}')", reason=reason) + self.log.info("failing WebSocket opening handshake ('{reason}')", reason=reason) self.dropConnection(abort=True) From e09df6d80abe2b5097b7825f8c3315d03f3037a9 Mon Sep 17 00:00:00 2001 From: meejah Date: Tue, 15 Sep 2015 14:57:41 -0600 Subject: [PATCH 27/59] more fixes and moving to txaio logging --- autobahn/twisted/rawsocket.py | 50 +++++++++++------------ autobahn/twisted/wamp.py | 16 ++++---- autobahn/wamp/protocol.py | 14 ++----- autobahn/wamp/test/test_protocol.py | 61 ++++++++++++++--------------- autobahn/wamp/test/test_runner.py | 1 + tox.ini | 2 +- 6 files changed, 69 insertions(+), 75 deletions(-) diff --git a/autobahn/twisted/rawsocket.py b/autobahn/twisted/rawsocket.py index 56458d08..96218113 100644 --- a/autobahn/twisted/rawsocket.py +++ b/autobahn/twisted/rawsocket.py @@ -28,7 +28,6 @@ from __future__ import absolute_import import binascii -from twisted.python import log from twisted.internet.protocol import Factory from twisted.protocols.basic import Int32StringReceiver from twisted.internet.error import ConnectionDone @@ -36,6 +35,8 @@ from twisted.internet.error import ConnectionDone from autobahn.twisted.util import peer2str from autobahn.wamp.exception import ProtocolError, SerializationError, TransportLost +import txaio + __all__ = ( 'WampRawSocketServerProtocol', 'WampRawSocketClientProtocol', @@ -48,10 +49,11 @@ class WampRawSocketProtocol(Int32StringReceiver): """ Base class for Twisted-based WAMP-over-RawSocket protocols. """ + log = txaio.make_logger() def connectionMade(self): if self.factory.debug: - log.msg("WampRawSocketProtocol: connection made") + self.log.debug("WampRawSocketProtocol: connection made") # the peer we are connected to # @@ -92,42 +94,42 @@ class WampRawSocketProtocol(Int32StringReceiver): except Exception as e: # Exceptions raised in onOpen are fatal .. if self.factory.debug: - log.msg("WampRawSocketProtocol: ApplicationSession constructor / onOpen raised ({0})".format(e)) + self.log.info("WampRawSocketProtocol: ApplicationSession constructor / onOpen raised ({0})".format(e)) self.abort() else: if self.factory.debug: - log.msg("ApplicationSession started.") + self.log.info("ApplicationSession started.") def connectionLost(self, reason): if self.factory.debug: - log.msg("WampRawSocketProtocol: connection lost: reason = '{0}'".format(reason)) + self.log.info("WampRawSocketProtocol: connection lost: reason = '{0}'".format(reason)) try: wasClean = isinstance(reason.value, ConnectionDone) self._session.onClose(wasClean) except Exception as e: # silently ignore exceptions raised here .. if self.factory.debug: - log.msg("WampRawSocketProtocol: ApplicationSession.onClose raised ({0})".format(e)) + self.log.info("WampRawSocketProtocol: ApplicationSession.onClose raised ({0})".format(e)) self._session = None def stringReceived(self, payload): if self.factory.debug: - log.msg("WampRawSocketProtocol: RX octets: {0}".format(binascii.hexlify(payload))) + self.log.info("WampRawSocketProtocol: RX octets: {0}".format(binascii.hexlify(payload))) try: for msg in self._serializer.unserialize(payload): if self.factory.debug: - log.msg("WampRawSocketProtocol: RX WAMP message: {0}".format(msg)) + self.log.info("WampRawSocketProtocol: RX WAMP message: {0}".format(msg)) self._session.onMessage(msg) except ProtocolError as e: - log.msg(str(e)) + self.log.info(str(e)) if self.factory.debug: - log.msg("WampRawSocketProtocol: WAMP Protocol Error ({0}) - aborting connection".format(e)) + self.log.info("WampRawSocketProtocol: WAMP Protocol Error ({0}) - aborting connection".format(e)) self.abort() except Exception as e: if self.factory.debug: - log.msg("WampRawSocketProtocol: WAMP Internal Error ({0}) - aborting connection".format(e)) + self.log.info("WampRawSocketProtocol: WAMP Internal Error ({0}) - aborting connection".format(e)) self.abort() def send(self, msg): @@ -136,7 +138,7 @@ class WampRawSocketProtocol(Int32StringReceiver): """ if self.isOpen(): if self.factory.debug: - log.msg("WampRawSocketProtocol: TX WAMP message: {0}".format(msg)) + self.log.info("WampRawSocketProtocol: TX WAMP message: {0}".format(msg)) try: payload, _ = self._serializer.serialize(msg) except Exception as e: @@ -145,7 +147,7 @@ class WampRawSocketProtocol(Int32StringReceiver): else: self.sendString(payload) if self.factory.debug: - log.msg("WampRawSocketProtocol: TX octets: {0}".format(binascii.hexlify(payload))) + self.log.info("WampRawSocketProtocol: TX octets: {0}".format(binascii.hexlify(payload))) else: raise TransportLost() @@ -194,18 +196,18 @@ class WampRawSocketServerProtocol(WampRawSocketProtocol): if len(self._handshake_bytes) == 4: if self.factory.debug: - log.msg("WampRawSocketProtocol: opening handshake received - {0}".format(binascii.b2a_hex(self._handshake_bytes))) + self.log.info("WampRawSocketProtocol: opening handshake received - {0}".format(binascii.b2a_hex(self._handshake_bytes))) if ord(self._handshake_bytes[0]) != 0x7f: if self.factory.debug: - log.msg("WampRawSocketProtocol: invalid magic byte (octet 1) in opening handshake: was 0x{0}, but expected 0x7f".format(binascii.b2a_hex(self._handshake_bytes[0]))) + self.log.info("WampRawSocketProtocol: invalid magic byte (octet 1) in opening handshake: was 0x{0}, but expected 0x7f".format(binascii.b2a_hex(self._handshake_bytes[0]))) self.abort() # peer requests us to send messages of maximum length 2**max_len_exp # self._max_len_send = 2 ** (9 + (ord(self._handshake_bytes[1]) >> 4)) if self.factory.debug: - log.msg("WampRawSocketProtocol: client requests us to send out most {} bytes per message".format(self._max_len_send)) + self.log.info("WampRawSocketProtocol: client requests us to send out most {} bytes per message".format(self._max_len_send)) # client wants to speak this serialization format # @@ -213,10 +215,10 @@ class WampRawSocketServerProtocol(WampRawSocketProtocol): if ser_id in self.factory._serializers: self._serializer = self.factory._serializers[ser_id] if self.factory.debug: - log.msg("WampRawSocketProtocol: client wants to use serializer {}".format(ser_id)) + self.log.info("WampRawSocketProtocol: client wants to use serializer {}".format(ser_id)) else: if self.factory.debug: - log.msg("WampRawSocketProtocol: opening handshake - no suitable serializer found (client requested {0}, and we have {1})".format(ser_id, self.factory._serializers.keys())) + self.log.info("WampRawSocketProtocol: opening handshake - no suitable serializer found (client requested {0}, and we have {1})".format(ser_id, self.factory._serializers.keys())) self.abort() # we request the peer to send message of maximum length 2**reply_max_len_exp @@ -235,7 +237,7 @@ class WampRawSocketServerProtocol(WampRawSocketProtocol): self._on_handshake_complete() if self.factory.debug: - log.msg("WampRawSocketProtocol: opening handshake completed", self._serializer) + self.log.info("WampRawSocketProtocol: opening handshake completed", self._serializer) # consume any remaining data received already .. # @@ -275,25 +277,25 @@ class WampRawSocketClientProtocol(WampRawSocketProtocol): if len(self._handshake_bytes) == 4: if self.factory.debug: - log.msg("WampRawSocketProtocol: opening handshake received - {0}".format(binascii.b2a_hex(self._handshake_bytes))) + self.log.info("WampRawSocketProtocol: opening handshake received - {0}".format(binascii.b2a_hex(self._handshake_bytes))) if ord(self._handshake_bytes[0]) != 0x7f: if self.factory.debug: - log.msg("WampRawSocketProtocol: invalid magic byte (octet 1) in opening handshake: was 0x{0}, but expected 0x7f".format(binascii.b2a_hex(self._handshake_bytes[0]))) + self.log.info("WampRawSocketProtocol: invalid magic byte (octet 1) in opening handshake: was 0x{0}, but expected 0x7f".format(binascii.b2a_hex(self._handshake_bytes[0]))) self.abort() # peer requests us to send messages of maximum length 2**max_len_exp # self._max_len_send = 2 ** (9 + (ord(self._handshake_bytes[1]) >> 4)) if self.factory.debug: - log.msg("WampRawSocketProtocol: server requests us to send out most {} bytes per message".format(self._max_len_send)) + self.log.info("WampRawSocketProtocol: server requests us to send out most {} bytes per message".format(self._max_len_send)) # client wants to speak this serialization format # ser_id = ord(self._handshake_bytes[1]) & 0x0F if ser_id != self._serializer.RAWSOCKET_SERIALIZER_ID: if self.factory.debug: - log.msg("WampRawSocketProtocol: opening handshake - no suitable serializer found (server replied {0}, and we requested {1})".format(ser_id, self._serializer.RAWSOCKET_SERIALIZER_ID)) + self.log.info("WampRawSocketProtocol: opening handshake - no suitable serializer found (server replied {0}, and we requested {1})".format(ser_id, self._serializer.RAWSOCKET_SERIALIZER_ID)) self.abort() self._handshake_complete = True @@ -301,7 +303,7 @@ class WampRawSocketClientProtocol(WampRawSocketProtocol): self._on_handshake_complete() if self.factory.debug: - log.msg("WampRawSocketProtocol: opening handshake completed", self._serializer) + self.log.info("WampRawSocketProtocol: opening handshake completed", self._serializer) # consume any remaining data received already .. # diff --git a/autobahn/twisted/wamp.py b/autobahn/twisted/wamp.py index bcf4126f..e47cc270 100644 --- a/autobahn/twisted/wamp.py +++ b/autobahn/twisted/wamp.py @@ -30,7 +30,6 @@ import inspect import six -from twisted.python import log from twisted.internet.defer import inlineCallbacks from autobahn.wamp import protocol @@ -67,11 +66,9 @@ class ApplicationSession(protocol.ApplicationSession): """ Override of wamp.ApplicationSession """ - # see docs; will print currently-active exception to the logs, - # which is just what we want. - log.err(e) + self.log.error(txaio.failure_format_traceback(txaio.create_future_error(e))) # also log the framework-provided error-message - log.err(msg) + self.log.error(msg) class ApplicationSessionFactory(protocol.ApplicationSessionFactory): @@ -168,7 +165,10 @@ class ApplicationRunner(object): isSecure, host, port, resource, path, params = parseWsUrl(self.url) - txaio.start_logging(level='debug') + if self.debug or self.debug_wamp or self.debug_app: + txaio.start_logging(level='debug') + else: + txaio.start_logging(level='info') # factory for use ApplicationSession def create(): @@ -178,7 +178,7 @@ class ApplicationRunner(object): except Exception as e: if start_reactor: # the app component could not be created .. fatal - log.err(str(e)) + self.log.error(str(e)) reactor.stop() else: # if we didn't start the reactor, it's up to the @@ -526,7 +526,7 @@ class Application(object): yield handler(*args, **kwargs) except Exception as e: # FIXME - log.msg("Warning: exception in signal handler swallowed", e) + self.log.info("Warning: exception in signal handler swallowed", e) if service: diff --git a/autobahn/wamp/protocol.py b/autobahn/wamp/protocol.py index a97f1769..186784fd 100644 --- a/autobahn/wamp/protocol.py +++ b/autobahn/wamp/protocol.py @@ -520,8 +520,8 @@ class ApplicationSession(BaseSession): :param msg: an informative message from the library. It is suggested you log this immediately after the exception. """ - traceback.print_exc() - print(msg) + self.log.error(txaio.failure_format_traceback(txaio.create_future_error(e))) + self.log.error(msg) def _swallow_error(self, fail, msg): ''' @@ -535,7 +535,6 @@ class ApplicationSession(BaseSession): chain for a Deferred/coroutine that will make it out to user code. ''' - # print("_swallow_error", typ, exc, tb) try: self.onUserError(fail.value, msg) except: @@ -546,7 +545,6 @@ class ApplicationSession(BaseSession): """ Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onMessage` """ - print("BLAM", id(self.log), type(self.log)) self.log.debug("onMessage: {message}", session_id=self._session_id, message=msg) self.log.trace("onMessage: {message}", session_id=self._session_id, message=msg) if self._session_id is None: @@ -814,11 +812,7 @@ class ApplicationSession(BaseSession): pass formatted_tb = None if self.traceback_app: - # if asked to marshal the traceback within the WAMP error message, extract it - # noinspection PyCallingNonCallable - tb = StringIO() - err.printTraceback(file=tb) - formatted_tb = tb.getvalue().splitlines() + formatted_tb = txaio.failure_format_traceback(err) del self._invocations[msg.request] @@ -972,7 +966,7 @@ class ApplicationSession(BaseSession): Implements :func:`autobahn.wamp.interfaces.ISession.onLeave` """ if details.reason.startswith('wamp.error.'): - print('{error}: {message}'.format(error=details.reason, message=details.message)) + self.log.error('{reason}: {message}', reason=details.reason, message=details.message) if self._transport: self.disconnect() # do we ever call onLeave with a valid transport? diff --git a/autobahn/wamp/test/test_protocol.py b/autobahn/wamp/test/test_protocol.py index 9cd081af..8d4dd682 100644 --- a/autobahn/wamp/test/test_protocol.py +++ b/autobahn/wamp/test/test_protocol.py @@ -32,7 +32,6 @@ if os.environ.get('USE_TWISTED', False): from twisted.internet.defer import inlineCallbacks, Deferred, returnValue from twisted.internet.defer import succeed, DeferredList - from twisted.python import log from twisted.trial import unittest from six import PY3 @@ -463,37 +462,28 @@ if os.environ.get('USE_TWISTED', False): error_instance = RuntimeError("we have a problem") got_err_d = Deferred() - def observer(kw): - if kw['isError'] and 'failure' in kw: - fail = kw['failure'] - fail.trap(RuntimeError) - if error_instance == fail.value: - got_err_d.callback(True) - log.addObserver(observer) + def observer(e, msg): + if error_instance == e: + got_err_d.callback(True) + handler.onUserError = observer def boom(): raise error_instance - try: - sub = yield handler.subscribe(boom, u'com.myapp.topic1') + sub = yield handler.subscribe(boom, u'com.myapp.topic1') - # MockTransport gives us the ack reply and then we do our - # own event message - publish = yield handler.publish( - u'com.myapp.topic1', - options=types.PublishOptions(acknowledge=True, exclude_me=False), - ) - msg = message.Event(sub.id, publish.id) - handler.onMessage(msg) + # MockTransport gives us the ack reply and then we do our + # own event message + publish = yield handler.publish( + u'com.myapp.topic1', + options=types.PublishOptions(acknowledge=True, exclude_me=False), + ) + msg = message.Event(sub.id, publish.id) + handler.onMessage(msg) - # we know it worked if our observer worked and did - # .callback on our Deferred above. - self.assertTrue(got_err_d.called) - # ...otherwise trial will fail the test anyway - self.flushLoggedErrors() - - finally: - log.removeObserver(observer) + # we know it worked if our observer worked and did + # .callback on our Deferred above. + self.assertTrue(got_err_d.called) @inlineCallbacks def test_unsubscribe(self): @@ -598,6 +588,10 @@ if os.environ.get('USE_TWISTED', False): handler = ApplicationSession() handler.traceback_app = True MockTransport(handler) + errors = [] + def log_error(e, msg): + errors.append((e, msg)) + handler.onUserError = log_error name_error = NameError('foo') @@ -618,9 +612,8 @@ if os.environ.get('USE_TWISTED', False): # also, we should have logged the real NameError to # Twisted. - errs = self.flushLoggedErrors() - self.assertEqual(1, len(errs)) - self.assertEqual(name_error, errs[0].value) + self.assertEqual(1, len(errors)) + self.assertEqual(name_error, errors[0][0].value) @inlineCallbacks def test_invoke_progressive_result(self): @@ -674,6 +667,10 @@ if os.environ.get('USE_TWISTED', False): got_progress = Deferred() progress_error = NameError('foo') + logged_errors = [] + def got_error(e, msg): + logged_errors.append((e, msg)) + handler.onUserError = got_error def progress(arg, something=None): self.assertEqual('nothing', something) @@ -693,15 +690,15 @@ if os.environ.get('USE_TWISTED', False): options=types.CallOptions(on_progress=progress), key='word', ) + self.assertEqual(42, res) # our progress handler raised an error, but not before # recording success. self.assertTrue(got_progress.called) self.assertEqual('life', got_progress.result) # make sure our progress-handler error was logged - errs = self.flushLoggedErrors() - self.assertEqual(1, len(errs)) - self.assertEqual(progress_error, errs[0].value) + self.assertEqual(1, len(logged_errors)) + self.assertEqual(progress_error, logged_errors[0][0]) @inlineCallbacks def test_invoke_progressive_result_no_args(self): diff --git a/autobahn/wamp/test/test_runner.py b/autobahn/wamp/test/test_runner.py index 31b2e3e0..c51bbebe 100644 --- a/autobahn/wamp/test/test_runner.py +++ b/autobahn/wamp/test/test_runner.py @@ -28,6 +28,7 @@ from __future__ import absolute_import, print_function import os import unittest2 as unittest +import txaio if os.environ.get('USE_TWISTED', False): from mock import patch diff --git a/tox.ini b/tox.ini index d4697b53..2137f0c5 100644 --- a/tox.ini +++ b/tox.ini @@ -18,7 +18,7 @@ deps = unittest2 coverage msgpack-python - ../txaio + git+https://github.com/meejah/txaio@issue8-squash ; twisted dependencies twtrunk: https://github.com/twisted/twisted/archive/trunk.zip From 7cbcbcd3d36e0bde4a18e2073eaa0853a37fa1a4 Mon Sep 17 00:00:00 2001 From: meejah Date: Tue, 15 Sep 2015 15:02:11 -0600 Subject: [PATCH 28/59] run tests with -p to get complete coverage --- Makefile | 7 ++++--- tox.ini | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 76491398..c57459a7 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ test_styleguide: # direct test via pytest (only here because of setuptools test integration) test_pytest: - python -m pytest -rsx . + python -m pytest -rsx autobahn/ # test via setuptools command test_setuptools: @@ -62,13 +62,14 @@ test_twisted_coverage: test_coverage: -rm .coverage - tox -e py27twisted,py27asyncio,py34asyncio + tox -e py27-twcurrent,py27-trollius,py34-asyncio + coverage combine coverage html coverage report --show-missing # test under asyncio test_asyncio: - USE_ASYNCIO=1 python -m pytest -rsx + USE_ASYNCIO=1 python -m pytest -rsx autobahn #WAMP_ROUTER_URL="ws://127.0.0.1:8080/ws" USE_ASYNCIO=1 python -m pytest -rsx test1: diff --git a/tox.ini b/tox.ini index 2137f0c5..696f1a5c 100644 --- a/tox.ini +++ b/tox.ini @@ -34,8 +34,8 @@ commands = sh -c "which python" python -V coverage --version - asyncio,trollius: coverage run {envbindir}/py.test autobahn/ - twtrunk,twcurrent,tw121,tw132,twcurrent: coverage run {envbindir}/trial autobahn + asyncio,trollius: coverage run --parallel-mode {envbindir}/py.test autobahn/ + twtrunk,twcurrent,tw121,tw132,twcurrent: coverage run --parallel-mode {envbindir}/trial autobahn coverage report whitelist_externals = sh setenv = From df9971e92eb5d6d72961c48da308114dd9aba1a4 Mon Sep 17 00:00:00 2001 From: meejah Date: Wed, 16 Sep 2015 10:46:03 -0600 Subject: [PATCH 29/59] change onUserError API to take an IFailedFuture It used to receive a bare Exception object, which means we couldn't format tracebacks properly if desired. --- autobahn/twisted/wamp.py | 8 -------- autobahn/wamp/exception.py | 3 +++ autobahn/wamp/protocol.py | 35 +++++++++++++++++++++++++---------- autobahn/wamp/websocket.py | 2 ++ 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/autobahn/twisted/wamp.py b/autobahn/twisted/wamp.py index e47cc270..a8f3b855 100644 --- a/autobahn/twisted/wamp.py +++ b/autobahn/twisted/wamp.py @@ -62,14 +62,6 @@ class ApplicationSession(protocol.ApplicationSession): WAMP application session for Twisted-based applications. """ - def onUserError(self, e, msg): - """ - Override of wamp.ApplicationSession - """ - self.log.error(txaio.failure_format_traceback(txaio.create_future_error(e))) - # also log the framework-provided error-message - self.log.error(msg) - class ApplicationSessionFactory(protocol.ApplicationSessionFactory): """ diff --git a/autobahn/wamp/exception.py b/autobahn/wamp/exception.py index 9474ed5b..05729cf3 100644 --- a/autobahn/wamp/exception.py +++ b/autobahn/wamp/exception.py @@ -207,6 +207,9 @@ class ApplicationError(Error): self.kwargs = kwargs self.error = error + def error_message(self): + return '{}: {}'.format(self.error, ' '.join(self.args)) + def __str__(self): if self.kwargs and 'traceback' in self.kwargs: tb = ':\n' + '\n'.join(self.kwargs.pop('traceback')) + '\n' diff --git a/autobahn/wamp/protocol.py b/autobahn/wamp/protocol.py index 186784fd..512d0f95 100644 --- a/autobahn/wamp/protocol.py +++ b/autobahn/wamp/protocol.py @@ -389,7 +389,10 @@ class BaseSession(object): exc = ecls() except Exception as e: try: - self.onUserError(e, "While re-constructing exception") + self.onUserError( + txaio.create_future_error(), + "While re-constructing exception", + ) except: pass @@ -504,7 +507,7 @@ class ApplicationSession(BaseSession): """ return self._transport is not None - def onUserError(self, e, msg): + def onUserError(self, fail, msg): """ This is called when we try to fire a callback, but get an exception from user code -- for example, a registered publish @@ -515,13 +518,19 @@ class ApplicationSession(BaseSession): provide logging if they prefer. The Twisted implemention does this. (See :class:`autobahn.twisted.wamp.ApplicationSession`) - :param e: the Exception we caught. + :param fail: instance implementing txaio.IFailedFuture :param msg: an informative message from the library. It is suggested you log this immediately after the exception. """ - self.log.error(txaio.failure_format_traceback(txaio.create_future_error(e))) - self.log.error(msg) + if isinstance(fail.value, exception.ApplicationError): + self.log.error(fail.value.error_message()) + else: + self.log.error( + '{msg}: {traceback}', + msg=msg, + traceback=txaio.failure_format_traceback(fail), + ) def _swallow_error(self, fail, msg): ''' @@ -536,7 +545,7 @@ class ApplicationSession(BaseSession): code. ''' try: - self.onUserError(fail.value, msg) + self.onUserError(fail, msg) except: pass return None @@ -713,9 +722,12 @@ class ApplicationSession(BaseSession): try: # XXX what if on_progress returns a Deferred/Future? call_request.options.on_progress(*args, **kw) - except Exception as e: + except Exception: try: - self.onUserError(e, "While firing on_progress") + self.onUserError( + txaio.create_future_error(), + "While firing on_progress", + ) except: pass @@ -845,10 +857,13 @@ class ApplicationSession(BaseSession): # noinspection PyBroadException try: self._invocations[msg.request].cancel() - except Exception as e: + except Exception: # XXX can .cancel() return a Deferred/Future? try: - self.onUserError(e, "While cancelling call.") + self.onUserError( + txaio.create_future_error(), + "While cancelling call.", + ) except: pass finally: diff --git a/autobahn/wamp/websocket.py b/autobahn/wamp/websocket.py index 24ca03f2..4c8824ef 100644 --- a/autobahn/wamp/websocket.py +++ b/autobahn/wamp/websocket.py @@ -33,6 +33,8 @@ from autobahn.websocket.types import ConnectionDeny from autobahn.wamp.interfaces import ITransport from autobahn.wamp.exception import ProtocolError, SerializationError, TransportLost +import txaio + __all__ = ('WampWebSocketServerProtocol', 'WampWebSocketClientProtocol', 'WampWebSocketServerFactory', From abe42b05b4505a80fd95cbe8b284011a2aa00620 Mon Sep 17 00:00:00 2001 From: meejah Date: Wed, 16 Sep 2015 10:52:44 -0600 Subject: [PATCH 30/59] improve error logging --- autobahn/wamp/websocket.py | 6 ++---- autobahn/websocket/protocol.py | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/autobahn/wamp/websocket.py b/autobahn/wamp/websocket.py index 4c8824ef..f66b868e 100644 --- a/autobahn/wamp/websocket.py +++ b/autobahn/wamp/websocket.py @@ -63,8 +63,7 @@ class WampWebSocketProtocol(object): self._session = self.factory._factory() self._session.onOpen(self) except Exception as e: - if self.factory.debug_wamp: - traceback.print_exc() + self.log.critical(traceback.format_exc()) # Exceptions raised in onOpen are fatal .. reason = "WAMP Internal Error ({0})".format(e) self._bailout(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_INTERNAL_ERROR, reason=reason) @@ -105,8 +104,7 @@ class WampWebSocketProtocol(object): self._bailout(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_PROTOCOL_ERROR, reason=reason) except Exception as e: - if self.factory.debug_wamp: - traceback.print_exc() + self.log.critical(traceback.format_exc()) reason = "WAMP Internal Error ({0})".format(e) self._bailout(protocol.WebSocketProtocol.CLOSE_STATUS_CODE_INTERNAL_ERROR, reason=reason) diff --git a/autobahn/websocket/protocol.py b/autobahn/websocket/protocol.py index 20d3c260..d2cb083e 100755 --- a/autobahn/websocket/protocol.py +++ b/autobahn/websocket/protocol.py @@ -853,8 +853,7 @@ class WebSocketProtocol(object): Drop the underlying TCP connection. """ if self.state != WebSocketProtocol.STATE_CLOSED: - if self.debugCodePaths: - self.log.debug("dropping connection") + self.log.debug("dropping connection: {reason}", reason=self.wasNotCleanReason) self.droppedByMe = True # this code-path will be hit (*without* hitting @@ -873,8 +872,7 @@ class WebSocketProtocol(object): Fails the WebSocket connection. """ if self.state != WebSocketProtocol.STATE_CLOSED: - if self.debugCodePaths: - self.log.debug("Failing connection : %s - %s" % (code, reason)) + self.log.debug("Failing connection: {code}: {reason}", code=code, reason=reason) self.failedByMe = True From 86212a6b33fd2f0b65ca23115b3c76d14b63d257 Mon Sep 17 00:00:00 2001 From: meejah Date: Wed, 16 Sep 2015 10:52:51 -0600 Subject: [PATCH 31/59] whitespace/flake8 --- autobahn/websocket/protocol.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/autobahn/websocket/protocol.py b/autobahn/websocket/protocol.py index d2cb083e..b127ea65 100755 --- a/autobahn/websocket/protocol.py +++ b/autobahn/websocket/protocol.py @@ -586,10 +586,16 @@ class WebSocketProtocol(object): if not self.failedByMe: if 0 < self.maxMessagePayloadSize < self.message_data_total_length: self.wasMaxMessagePayloadSizeExceeded = True - self.failConnection(WebSocketProtocol.CLOSE_STATUS_CODE_MESSAGE_TOO_BIG, "message exceeds payload limit of %d octets" % self.maxMessagePayloadSize) + self.failConnection( + WebSocketProtocol.CLOSE_STATUS_CODE_MESSAGE_TOO_BIG, + "message exceeds payload limit of %d octets" % self.maxMessagePayloadSize + ) elif 0 < self.maxFramePayloadSize < length: self.wasMaxFramePayloadSizeExceeded = True - self.failConnection(WebSocketProtocol.CLOSE_STATUS_CODE_POLICY_VIOLATION, "frame exceeds payload limit of %d octets" % self.maxFramePayloadSize) + self.failConnection( + WebSocketProtocol.CLOSE_STATUS_CODE_POLICY_VIOLATION, + "frame exceeds payload limit of %d octets" % self.maxFramePayloadSize + ) def onMessageFrameData(self, payload): """ From 14686130c89dda4440d4e36d8214cefab1d03111 Mon Sep 17 00:00:00 2001 From: meejah Date: Wed, 16 Sep 2015 11:45:46 -0600 Subject: [PATCH 32/59] Fix improper onUserError API use, and flake8 --- autobahn/wamp/protocol.py | 14 +++++--------- autobahn/wamp/test/test_protocol.py | 10 ++++++---- autobahn/wamp/test/test_runner.py | 1 - autobahn/wamp/test/test_user_handler_errors.py | 2 +- autobahn/wamp/websocket.py | 2 -- tox.ini | 2 +- 6 files changed, 13 insertions(+), 18 deletions(-) diff --git a/autobahn/wamp/protocol.py b/autobahn/wamp/protocol.py index 512d0f95..95cb4c6b 100644 --- a/autobahn/wamp/protocol.py +++ b/autobahn/wamp/protocol.py @@ -26,10 +26,8 @@ from __future__ import absolute_import -import traceback import inspect import six -from six import StringIO from autobahn.wamp.interfaces import ISession, \ IPublication, \ @@ -387,10 +385,10 @@ class BaseSession(object): exc = ecls(*msg.args) else: exc = ecls() - except Exception as e: + except Exception: try: self.onUserError( - txaio.create_future_error(), + txaio.create_failure(), "While re-constructing exception", ) except: @@ -554,8 +552,6 @@ class ApplicationSession(BaseSession): """ Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onMessage` """ - self.log.debug("onMessage: {message}", session_id=self._session_id, message=msg) - self.log.trace("onMessage: {message}", session_id=self._session_id, message=msg) if self._session_id is None: # the first message must be WELCOME, ABORT or CHALLENGE .. @@ -725,7 +721,7 @@ class ApplicationSession(BaseSession): except Exception: try: self.onUserError( - txaio.create_future_error(), + txaio.create_failure(), "While firing on_progress", ) except: @@ -861,7 +857,7 @@ class ApplicationSession(BaseSession): # XXX can .cancel() return a Deferred/Future? try: self.onUserError( - txaio.create_future_error(), + txaio.create_failure(), "While cancelling call.", ) except: @@ -981,7 +977,7 @@ class ApplicationSession(BaseSession): Implements :func:`autobahn.wamp.interfaces.ISession.onLeave` """ if details.reason.startswith('wamp.error.'): - self.log.error('{reason}: {message}', reason=details.reason, message=details.message) + self.log.error('{reason}: {wamp_message}', reason=details.reason, wamp_message=details.message) if self._transport: self.disconnect() # do we ever call onLeave with a valid transport? diff --git a/autobahn/wamp/test/test_protocol.py b/autobahn/wamp/test/test_protocol.py index 8d4dd682..f9904c20 100644 --- a/autobahn/wamp/test/test_protocol.py +++ b/autobahn/wamp/test/test_protocol.py @@ -463,7 +463,7 @@ if os.environ.get('USE_TWISTED', False): got_err_d = Deferred() def observer(e, msg): - if error_instance == e: + if error_instance == e.value: got_err_d.callback(True) handler.onUserError = observer @@ -589,8 +589,9 @@ if os.environ.get('USE_TWISTED', False): handler.traceback_app = True MockTransport(handler) errors = [] + def log_error(e, msg): - errors.append((e, msg)) + errors.append((e.value, msg)) handler.onUserError = log_error name_error = NameError('foo') @@ -613,7 +614,7 @@ if os.environ.get('USE_TWISTED', False): # also, we should have logged the real NameError to # Twisted. self.assertEqual(1, len(errors)) - self.assertEqual(name_error, errors[0][0].value) + self.assertEqual(name_error, errors[0][0]) @inlineCallbacks def test_invoke_progressive_result(self): @@ -668,8 +669,9 @@ if os.environ.get('USE_TWISTED', False): got_progress = Deferred() progress_error = NameError('foo') logged_errors = [] + def got_error(e, msg): - logged_errors.append((e, msg)) + logged_errors.append((e.value, msg)) handler.onUserError = got_error def progress(arg, something=None): diff --git a/autobahn/wamp/test/test_runner.py b/autobahn/wamp/test/test_runner.py index c51bbebe..31b2e3e0 100644 --- a/autobahn/wamp/test/test_runner.py +++ b/autobahn/wamp/test/test_runner.py @@ -28,7 +28,6 @@ from __future__ import absolute_import, print_function import os import unittest2 as unittest -import txaio if os.environ.get('USE_TWISTED', False): from mock import patch diff --git a/autobahn/wamp/test/test_user_handler_errors.py b/autobahn/wamp/test/test_user_handler_errors.py index 2a64157c..c8a8f624 100644 --- a/autobahn/wamp/test/test_user_handler_errors.py +++ b/autobahn/wamp/test/test_user_handler_errors.py @@ -58,7 +58,7 @@ if os.environ.get('USE_TWISTED', False): self._transport = MockTransport() def onUserError(self, e, msg): - self.errors.append((e, msg)) + self.errors.append((e.value, msg)) def exception_raiser(exc): ''' diff --git a/autobahn/wamp/websocket.py b/autobahn/wamp/websocket.py index f66b868e..e5ebfeb1 100644 --- a/autobahn/wamp/websocket.py +++ b/autobahn/wamp/websocket.py @@ -33,8 +33,6 @@ from autobahn.websocket.types import ConnectionDeny from autobahn.wamp.interfaces import ITransport from autobahn.wamp.exception import ProtocolError, SerializationError, TransportLost -import txaio - __all__ = ('WampWebSocketServerProtocol', 'WampWebSocketClientProtocol', 'WampWebSocketServerFactory', diff --git a/tox.ini b/tox.ini index 696f1a5c..7ead69aa 100644 --- a/tox.ini +++ b/tox.ini @@ -18,7 +18,7 @@ deps = unittest2 coverage msgpack-python - git+https://github.com/meejah/txaio@issue8-squash + git+https://github.com/tavendo/txaio ; twisted dependencies twtrunk: https://github.com/twisted/twisted/archive/trunk.zip From d8c5a0a22373d92eb5819ff9e543b6d877b8c207 Mon Sep 17 00:00:00 2001 From: meejah Date: Wed, 16 Sep 2015 11:51:37 -0600 Subject: [PATCH 33/59] remove local tox.ini changes --- tox.ini | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 7ead69aa..969dfda9 100644 --- a/tox.ini +++ b/tox.ini @@ -12,7 +12,6 @@ max-line-length = 119 [testenv] -usedevelop = True deps = mock unittest2 @@ -34,8 +33,8 @@ commands = sh -c "which python" python -V coverage --version - asyncio,trollius: coverage run --parallel-mode {envbindir}/py.test autobahn/ - twtrunk,twcurrent,tw121,tw132,twcurrent: coverage run --parallel-mode {envbindir}/trial autobahn + asyncio,trollius: coverage run {envbindir}/py.test autobahn/ + twtrunk,twcurrent,tw121,tw132,twcurrent: coverage run {envbindir}/trial autobahn coverage report whitelist_externals = sh setenv = From df4a18b52c2196257a5ba7f267fc0525097baa51 Mon Sep 17 00:00:00 2001 From: meejah Date: Wed, 16 Sep 2015 21:36:27 -0600 Subject: [PATCH 34/59] developer notes about txaio logging + future handling --- DEVELOPERS.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/DEVELOPERS.md b/DEVELOPERS.md index 24244a72..ddfba475 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -80,6 +80,45 @@ The new rule for the public API is simple: if something is exported from the mod * [Asyncio](https://github.com/tavendo/AutobahnPython/blob/master/autobahn/asyncio/__init__.py) * [Twisted](https://github.com/tavendo/AutobahnPython/blob/master/autobahn/twisted/__init__.py) +### Cross-platform Considerations + +Autobahn supports many different platforms and both major async frameworks. One thing that helps with this is the [txaio](https://github.com/tavendo/txaio) library. This is used for all Deferred/Future operations throughout the code and more recently for logging. + +Here is a recommended way to do **logging**: + +```python +class Foo(object): + log = txaio.make_logger() + + def connect(self): + try: + self.log.info("Connecting") + raise Exception("an error") + except: + fail = txaio.create_failure() + self.log.error("Connection failed: {msg}", msg=txaio.failure_message(fail)) + self.log.debug("{traceback}", traceback=txaio.failure_format_traceback(fail)) + # Exception instance in fail.value +``` + +Note that ``create_failure()`` can (and should) be called without arguments when inside an ``except`` block; this will give it a valid traceback instance. The only attribute you can depend on is ``fail.value`` which is the ``Exception`` instance. Otherwise use ``txaio.failre_*`` methods. + +How to **handler async methods** with txaio: + +```python +f = txaio.as_future(mightReturnDeferred, 'arg0') + +def success(result): + print("It worked! {}".format(result)) + +def error(fail): + print("It failed! {}".format(txaio.failure_message(fail))) +txaio.add_callbacks(f, success, error) +``` + +Either the success or error callback can be ``None`` (e.g. if you just need to add an error-handler). ``fail`` must implement ``txaio.IFailedFuture`` (but only that; don't depend on any other methods). You cannot use ``@asyncio.coroutine`` or ``@inlineCallbacks``. + + ### Use of assert vs Exceptions > See the discussion [here](https://github.com/tavendo/AutobahnPython/issues/99). From 91b7140046c6c3c14af5fec4908f91af375a3bae Mon Sep 17 00:00:00 2001 From: meejah Date: Wed, 16 Sep 2015 21:38:43 -0600 Subject: [PATCH 35/59] logger attributes for ApplicationRunner --- autobahn/twisted/wamp.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/autobahn/twisted/wamp.py b/autobahn/twisted/wamp.py index a8f3b855..c4ad3467 100644 --- a/autobahn/twisted/wamp.py +++ b/autobahn/twisted/wamp.py @@ -83,6 +83,8 @@ class ApplicationRunner(object): connecting to a WAMP router. """ + log = txaio.make_logger() + def __init__(self, url, realm, extra=None, serializers=None, debug=False, debug_wamp=False, debug_app=False, ssl=None): @@ -314,6 +316,8 @@ class Application(object): creating, debugging and running WAMP application components. """ + log = txaio.make_logger() + def __init__(self, prefix=None): """ From ab3f30fbc25db487427615206e73fcd1d56af589 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Thu, 17 Sep 2015 15:05:00 +0200 Subject: [PATCH 36/59] more public API --- autobahn/wamp/interfaces.py | 257 +++++++----------------------------- autobahn/wamp/message.py | 180 +++++++++++++++++-------- autobahn/wamp/protocol.py | 40 +----- autobahn/wamp/types.py | 87 ++++++++++++ 4 files changed, 257 insertions(+), 307 deletions(-) diff --git a/autobahn/wamp/interfaces.py b/autobahn/wamp/interfaces.py index c6ada894..e166cd79 100644 --- a/autobahn/wamp/interfaces.py +++ b/autobahn/wamp/interfaces.py @@ -66,78 +66,6 @@ class IObjectSerializer(object): """ -@six.add_metaclass(abc.ABCMeta) -class IMessage(object): - """ - A WAMP message. - """ - - @abc.abstractproperty - def MESSAGE_TYPE(self): - """ - WAMP message type code. - """ - - @abc.abstractmethod - def marshal(self): - """ - Marshal this object into a raw message for subsequent serialization to bytes. - - :returns: list -- The serialized raw message. - """ - - # @abc.abstractstaticmethod ## FIXME: this is Python 3 only - # noinspection PyMethodParameters - def parse(wmsg): - """ - Factory method that parses a unserialized raw message (as returned byte - :func:`autobahn.interfaces.ISerializer.unserialize`) into an instance - of this class. - - :returns: obj -- An instance of this class. - """ - - @abc.abstractmethod - def serialize(self, serializer): - """ - Serialize this object into a wire level bytes representation and cache - the resulting bytes. If the cache already contains an entry for the given - serializer, return the cached representation directly. - - :param serializer: The wire level serializer to use. - :type serializer: An instance that implements :class:`autobahn.interfaces.ISerializer` - - :returns: bytes -- The serialized bytes. - """ - - @abc.abstractmethod - def uncache(self): - """ - Resets the serialization cache. - """ - - @abc.abstractmethod - def __eq__(self, other): - """ - Message equality. This does an attribute-wise comparison (but skips attributes - that start with `_`). - """ - - @abc.abstractmethod - def __ne__(self, other): - """ - Message inequality (just the negate of message equality). - """ - - @abc.abstractmethod - def __str__(self): - """ - Returns text representation of this message. - - :returns: str -- Human readable representation (e.g. for logging or debugging purposes). - """ - - @six.add_metaclass(abc.ABCMeta) class ISerializer(object): """ @@ -159,23 +87,26 @@ class ISerializer(object): @abc.abstractmethod def serialize(self, message): """ - Serializes a WAMP message to bytes to be sent to a transport. + Serializes a WAMP message to bytes for sending over a transport. :param message: An instance that implements :class:`autobahn.wamp.interfaces.IMessage` :type message: obj - :returns: tuple -- A pair ``(bytes, isBinary)``. + :returns: tuple -- A pair ``(payload, is_binary)``. """ @abc.abstractmethod - def unserialize(self, payload, isBinary): + def unserialize(self, payload, is_binary): """ Deserialize bytes from a transport and parse into WAMP messages. :param payload: Byte string from wire. :type payload: bytes + :param is_binary: Type of payload. True if payload is a binary string, else + the payload is UTF-8 encoded Unicode text. + :type is_binary: bool - :returns: list -- List of objects that implement :class:`autobahn.wamp.interfaces.IMessage`. + :returns: list -- List of ``a.w.m.Message`` objects. """ @@ -191,13 +122,18 @@ class ITransport(object): """ Send a WAMP message over the transport to the peer. If the transport is not open, this raises :class:`autobahn.wamp.exception.TransportLost`. + Returns a deferred/future when the message has been processed and more + messages may be sent. When send() is called while a previous deferred/future + has not yet fired, the send will fail immediately. :param message: An instance that implements :class:`autobahn.wamp.interfaces.IMessage` :type message: obj + + :returns: obj -- A Deferred/Future """ @abc.abstractmethod - def isOpen(self): + def is_open(self): """ Check if the transport is open for messaging. @@ -225,31 +161,43 @@ class ITransport(object): @six.add_metaclass(abc.ABCMeta) class ITransportHandler(object): - @abc.abstractmethod - def onOpen(self, transport): + @abc.abstractproperty + def transport(self): """ - Callback fired when transport is open. + When the transport this handler is attached to is currently open, this property + can be read from. The property should be considered read-only. When the transport + is gone, this property is set to None. + """ + + @abc.abstractmethod + def on_open(self, transport): + """ + Callback fired when transport is open. May run asynchronously. The transport + is considered running and is_open() would return true, as soon as this callback + has completed successfully. :param transport: An instance that implements :class:`autobahn.wamp.interfaces.ITransport` :type transport: obj """ @abc.abstractmethod - def onMessage(self, message): + def on_message(self, message): """ - Callback fired when a WAMP message was received. + Callback fired when a WAMP message was received. May run asynchronously. The callback + should return or fire the returned deferred/future when it's done processing the message. + In particular, an implementation of this callback must not access the message afterwards. :param message: An instance that implements :class:`autobahn.wamp.interfaces.IMessage` :type message: obj """ @abc.abstractmethod - def onClose(self, wasClean): + def on_close(self, was_clean): """ Callback fired when the transport has been closed. - :param wasClean: Indicates if the transport has been closed regularly. - :type wasClean: bool + :param was_clean: Indicates if the transport has been closed regularly. + :type was_clean: bool """ @@ -260,7 +208,7 @@ class ISession(object): """ @abc.abstractmethod - def onConnect(self): + def on_connect(self): """ Callback fired when the transport this session will run over has been established. """ @@ -272,7 +220,7 @@ class ISession(object): """ @abc.abstractmethod - def onChallenge(self, challenge): + def on_challenge(self, challenge): """ Callback fired when the peer demands authentication. @@ -283,7 +231,7 @@ class ISession(object): """ @abc.abstractmethod - def onJoin(self, details): + def on_join(self, details): """ Callback fired when WAMP session has been established. @@ -308,7 +256,7 @@ class ISession(object): """ @abc.abstractmethod - def onLeave(self, details): + def on_leave(self, details): """ Callback fired when WAMP session has is closed @@ -329,11 +277,19 @@ class ISession(object): """ @abc.abstractmethod - def onDisconnect(self): + def on_disconnect(self): """ Callback fired when underlying transport has been closed. """ + +@six.add_metaclass(abc.ABCMeta) +class IApplicationSession(ISession): + """ + Interface for WAMP client peers implementing the four different + WAMP roles (caller, callee, publisher, subscriber). + """ + @abc.abstractmethod def define(self, exception, error=None): """ @@ -346,12 +302,6 @@ class ISession(object): :type error: str """ - -class ICaller(ISession): - """ - Interface for WAMP peers implementing role *Caller*. - """ - @abc.abstractmethod def call(self, procedure, *args, **kwargs): """ @@ -386,54 +336,6 @@ class ICaller(ISession): :rtype: instance of :tx:`twisted.internet.defer.Deferred` / :py:class:`asyncio.Future` """ - -@six.add_metaclass(abc.ABCMeta) -class IRegistration(object): - """ - Represents a registration of an endpoint. - """ - - @abc.abstractproperty - def id(self): - """ - The WAMP registration ID for this registration. - """ - - @abc.abstractproperty - def active(self): - """ - Flag indicating if registration is active. - """ - - @abc.abstractmethod - def unregister(self): - """ - Unregister this registration that was previously created from - :func:`autobahn.wamp.interfaces.ICallee.register`. - - After a registration has been unregistered successfully, no calls - will be routed to the endpoint anymore. - - Returns an instance of :tx:`twisted.internet.defer.Deferred` (when - running on **Twisted**) or an instance of :py:class:`asyncio.Future` - (when running on **asyncio**). - - - If the unregistration succeeds, the returned Deferred/Future will - *resolve* (with no return value). - - - If the unregistration fails, the returned Deferred/Future will be rejected - with an instance of :class:`autobahn.wamp.exception.ApplicationError`. - - :returns: A Deferred/Future for the unregistration - :rtype: instance(s) of :tx:`twisted.internet.defer.Deferred` / :py:class:`asyncio.Future` - """ - - -class ICallee(ISession): - """ - Interface for WAMP peers implementing role *Callee*. - """ - @abc.abstractmethod def register(self, endpoint, procedure=None, options=None): """ @@ -467,25 +369,6 @@ class ICallee(ISession): :rtype: instance(s) of :tx:`twisted.internet.defer.Deferred` / :py:class:`asyncio.Future` """ - -@six.add_metaclass(abc.ABCMeta) -class IPublication(object): - """ - Represents a publication of an event. This is used with acknowledged publications. - """ - - @abc.abstractproperty - def id(self): - """ - The WAMP publication ID for this publication. - """ - - -class IPublisher(ISession): - """ - Interface for WAMP peers implementing role *Publisher*. - """ - @abc.abstractmethod def publish(self, topic, *args, **kwargs): """ @@ -520,54 +403,6 @@ class IPublisher(ISession): :rtype: ``None`` or instance of :tx:`twisted.internet.defer.Deferred` / :py:class:`asyncio.Future` """ - -@six.add_metaclass(abc.ABCMeta) -class ISubscription(object): - """ - Represents a subscription to a topic. - """ - - @abc.abstractproperty - def id(self): - """ - The WAMP subscription ID for this subscription. - """ - - @abc.abstractproperty - def active(self): - """ - Flag indicating if subscription is active. - """ - - @abc.abstractmethod - def unsubscribe(self): - """ - Unsubscribe this subscription that was previously created from - :func:`autobahn.wamp.interfaces.ISubscriber.subscribe`. - - After a subscription has been unsubscribed successfully, no events - will be routed to the event handler anymore. - - Returns an instance of :tx:`twisted.internet.defer.Deferred` (when - running on **Twisted**) or an instance of :py:class:`asyncio.Future` - (when running on **asyncio**). - - - If the unsubscription succeeds, the returned Deferred/Future will - *resolve* (with no return value). - - - If the unsubscription fails, the returned Deferred/Future will *reject* - with an instance of :class:`autobahn.wamp.exception.ApplicationError`. - - :returns: A Deferred/Future for the unsubscription - :rtype: instance(s) of :tx:`twisted.internet.defer.Deferred` / :py:class:`asyncio.Future` - """ - - -class ISubscriber(ISession): - """ - Interface for WAMP peers implementing role *Subscriber*. - """ - @abc.abstractmethod def subscribe(self, handler, topic=None, options=None): """ diff --git a/autobahn/wamp/message.py b/autobahn/wamp/message.py index e8ac6e67..4b5df942 100644 --- a/autobahn/wamp/message.py +++ b/autobahn/wamp/message.py @@ -32,7 +32,6 @@ import six import autobahn from autobahn import util from autobahn.wamp.exception import ProtocolError -from autobahn.wamp.interfaces import IMessage from autobahn.wamp.role import ROLE_NAME_TO_CLASS __all__ = ('Message', @@ -173,24 +172,46 @@ def check_or_raise_extra(value, message=u"WAMP message invalid"): class Message(util.EqualityMixin): """ - WAMP message base class. Implements :class:`autobahn.wamp.interfaces.IMessage`. + WAMP message base class. .. note:: This is not supposed to be instantiated. """ + MESSAGE_TYPE = None + """ + WAMP message type code. + """ + def __init__(self): # serialization cache: mapping from ISerializer instances to serialized bytes self._serialized = {} + @staticmethod + def parse(wmsg): + """ + Factory method that parses a unserialized raw message (as returned byte + :func:`autobahn.interfaces.ISerializer.unserialize`) into an instance + of this class. + + :returns: obj -- An instance of this class. + """ + def uncache(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.uncache` + Resets the serialization cache. """ self._serialized = {} def serialize(self, serializer): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.serialize` + Serialize this object into a wire level bytes representation and cache + the resulting bytes. If the cache already contains an entry for the given + serializer, return the cached representation directly. + + :param serializer: The wire level serializer to use. + :type serializer: An instance that implements :class:`autobahn.interfaces.ISerializer` + + :returns: bytes -- The serialized bytes. """ # only serialize if not cached .. if serializer not in self._serialized: @@ -198,9 +219,6 @@ class Message(util.EqualityMixin): return self._serialized[serializer] -IMessage.register(Message) - - class Hello(Message): """ A WAMP ``HELLO`` message. @@ -210,8 +228,8 @@ class Hello(Message): MESSAGE_TYPE = 1 """ - The WAMP message code for this type of message. - """ + The WAMP message code for this type of message. + """ def __init__(self, realm, roles, authmethods=None, authid=None): """ @@ -317,7 +335,9 @@ class Hello(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ details = {u'roles': {}} for role in self.roles.values(): @@ -338,7 +358,7 @@ class Hello(Message): def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Return a string representation of this message. """ return "WAMP HELLO Message (realm = {0}, roles = {1}, authmethods = {2}, authid = {3})".format(self.realm, self.roles, self.authmethods, self.authid) @@ -449,7 +469,9 @@ class Welcome(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ details = { u'roles': {} @@ -479,7 +501,7 @@ class Welcome(Message): def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP WELCOME Message (session = {0}, roles = {1}, authid = {2}, authrole = {3}, authmethod = {4}, authprovider = {5})".format(self.session, self.roles, self.authid, self.authrole, self.authmethod, self.authprovider) @@ -547,7 +569,9 @@ class Abort(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ details = {} if self.message: @@ -557,7 +581,7 @@ class Abort(Message): def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP ABORT Message (message = {0}, reason = {1})".format(self.message, self.reason) @@ -618,13 +642,15 @@ class Challenge(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ return [Challenge.MESSAGE_TYPE, self.method, self.extra] def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP CHALLENGE Message (method = {0}, extra = {1})".format(self.method, self.extra) @@ -685,13 +711,15 @@ class Authenticate(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ return [Authenticate.MESSAGE_TYPE, self.signature, self.extra] def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP AUTHENTICATE Message (signature = {0}, extra = {1})".format(self.signature, self.extra) @@ -764,7 +792,9 @@ class Goodbye(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ details = {} if self.message: @@ -774,7 +804,7 @@ class Goodbye(Message): def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP GOODBYE Message (message = {0}, reason = {1})".format(self.message, self.reason) @@ -876,7 +906,9 @@ class Error(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ details = {} @@ -889,7 +921,7 @@ class Error(Message): def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP Error Message (request_type = {0}, request = {1}, error = {2}, args = {3}, kwargs = {4})".format(self.request_type, self.request, self.error, self.args, self.kwargs) @@ -1069,7 +1101,9 @@ class Publish(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ options = {} @@ -1093,7 +1127,7 @@ class Publish(Message): def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP PUBLISH Message (request = {0}, topic = {1}, args = {2}, kwargs = {3}, acknowledge = {4}, exclude_me = {5}, exclude = {6}, eligible = {7}, disclose_me = {8})".format(self.request, self.topic, self.args, self.kwargs, self.acknowledge, self.exclude_me, self.exclude, self.eligible, self.disclose_me) @@ -1151,13 +1185,15 @@ class Published(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ return [Published.MESSAGE_TYPE, self.request, self.publication] def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP PUBLISHED Message (request = {0}, publication = {1})".format(self.request, self.publication) @@ -1238,7 +1274,9 @@ class Subscribe(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ options = {} @@ -1249,7 +1287,7 @@ class Subscribe(Message): def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP SUBSCRIBE Message (request = {0}, topic = {1}, match = {2})".format(self.request, self.topic, self.match) @@ -1307,13 +1345,15 @@ class Subscribed(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ return [Subscribed.MESSAGE_TYPE, self.request, self.subscription] def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP SUBSCRIBED Message (request = {0}, subscription = {1})".format(self.request, self.subscription) @@ -1371,13 +1411,15 @@ class Unsubscribe(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ return [Unsubscribe.MESSAGE_TYPE, self.request, self.subscription] def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP UNSUBSCRIBE Message (request = {0}, subscription = {1})".format(self.request, self.subscription) @@ -1460,7 +1502,9 @@ class Unsubscribed(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ if self.reason is not None or self.subscription is not None: details = {} @@ -1474,7 +1518,7 @@ class Unsubscribed(Message): def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP UNSUBSCRIBED Message (request = {0}, reason = {1}, subscription = {2})".format(self.request, self.reason, self.subscription) @@ -1590,7 +1634,9 @@ class Event(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ details = {} @@ -1609,7 +1655,7 @@ class Event(Message): def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP EVENT Message (subscription = {0}, publication = {1}, args = {2}, kwargs = {3}, publisher = {4}, topic = {5})".format(self.subscription, self.publication, self.args, self.kwargs, self.publisher, self.topic) @@ -1751,7 +1797,9 @@ class Call(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ options = {} @@ -1773,7 +1821,7 @@ class Call(Message): def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP CALL Message (request = {0}, procedure = {1}, args = {2}, kwargs = {3}, timeout = {4}, receive_progress = {5}, disclose_me = {6})".format(self.request, self.procedure, self.args, self.kwargs, self.timeout, self.receive_progress, self.disclose_me) @@ -1851,7 +1899,9 @@ class Cancel(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ options = {} @@ -1862,7 +1912,7 @@ class Cancel(Message): def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP CANCEL Message (request = {0}, mode = '{1}'')".format(self.request, self.mode) @@ -1957,7 +2007,9 @@ class Result(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ details = {} @@ -1973,7 +2025,7 @@ class Result(Message): def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP RESULT Message (request = {0}, args = {1}, kwargs = {2}, progress = {3})".format(self.request, self.args, self.kwargs, self.progress) @@ -2077,7 +2129,9 @@ class Register(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ options = {} @@ -2091,7 +2145,7 @@ class Register(Message): def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP REGISTER Message (request = {0}, procedure = {1}, match = {2}, invoke = {3})".format(self.request, self.procedure, self.match, self.invoke) @@ -2149,13 +2203,15 @@ class Registered(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ return [Registered.MESSAGE_TYPE, self.request, self.registration] def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP REGISTERED Message (request = {0}, registration = {1})".format(self.request, self.registration) @@ -2213,13 +2269,15 @@ class Unregister(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ return [Unregister.MESSAGE_TYPE, self.request, self.registration] def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP UNREGISTER Message (request = {0}, registration = {1})".format(self.request, self.registration) @@ -2301,7 +2359,9 @@ class Unregistered(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ if self.reason is not None or self.registration is not None: details = {} @@ -2315,7 +2375,7 @@ class Unregistered(Message): def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP UNREGISTERED Message (request = {0}, reason = {1}, registration = {2})".format(self.request, self.reason, self.registration) @@ -2471,7 +2531,9 @@ class Invocation(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ options = {} @@ -2496,7 +2558,7 @@ class Invocation(Message): def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP INVOCATION Message (request = {0}, registration = {1}, args = {2}, kwargs = {3}, timeout = {4}, receive_progress = {5}, caller = {6}, procedure = {7})".format(self.request, self.registration, self.args, self.kwargs, self.timeout, self.receive_progress, self.caller, self.procedure) @@ -2573,7 +2635,9 @@ class Interrupt(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ options = {} @@ -2584,7 +2648,7 @@ class Interrupt(Message): def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP INTERRUPT Message (request = {0}, mode = '{1}')".format(self.request, self.mode) @@ -2679,7 +2743,9 @@ class Yield(Message): def marshal(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.marshal` + Marshal this object into a raw message for subsequent serialization to bytes. + + :returns: list -- The serialized raw message. """ options = {} @@ -2695,6 +2761,6 @@ class Yield(Message): def __str__(self): """ - Implements :func:`autobahn.wamp.interfaces.IMessage.__str__` + Returns string representation of this message. """ return "WAMP YIELD Message (request = {0}, args = {1}, kwargs = {2}, progress = {3})".format(self.request, self.args, self.kwargs, self.progress) diff --git a/autobahn/wamp/protocol.py b/autobahn/wamp/protocol.py index 95cb4c6b..ee7475f3 100644 --- a/autobahn/wamp/protocol.py +++ b/autobahn/wamp/protocol.py @@ -156,9 +156,6 @@ class Subscription(object): return "Subscription(id={0}, is_active={1})".format(self.id, self.active) -ISubscription.register(Subscription) - - class Handler(object): """ Object representing an event handler attached to a subscription. @@ -193,9 +190,6 @@ class Publication(object): return "Publication(id={0})".format(self.id) -IPublication.register(Publication) - - class Registration(object): """ Object representing a registration. @@ -219,9 +213,6 @@ class Registration(object): raise Exception("registration no longer active") -IRegistration.register(Registration) - - class Endpoint(object): """ Object representing an procedure endpoint attached to a registration. @@ -283,26 +274,6 @@ class BaseSession(object): # generator for WAMP request IDs self._request_id_gen = IdGenerator() - def onConnect(self): - """ - Implements :func:`autobahn.wamp.interfaces.ISession.onConnect` - """ - - def onJoin(self, details): - """ - Implements :func:`autobahn.wamp.interfaces.ISession.onJoin` - """ - - def onLeave(self, details): - """ - Implements :func:`autobahn.wamp.interfaces.ISession.onLeave` - """ - - def onDisconnect(self): - """ - Implements :func:`autobahn.wamp.interfaces.ISession.onDisconnect` - """ - def define(self, exception, error=None): """ Implements :func:`autobahn.wamp.interfaces.ISession.define` @@ -410,18 +381,9 @@ class BaseSession(object): return exc -ISession.register(BaseSession) - - class ApplicationSession(BaseSession): """ - WAMP endpoint session. This class implements - - * :class:`autobahn.wamp.interfaces.IPublisher` - * :class:`autobahn.wamp.interfaces.ISubscriber` - * :class:`autobahn.wamp.interfaces.ICaller` - * :class:`autobahn.wamp.interfaces.ICallee` - * :class:`autobahn.wamp.interfaces.ITransportHandler` + WAMP endpoint session. """ log = txaio.make_logger() diff --git a/autobahn/wamp/types.py b/autobahn/wamp/types.py index 91f59eca..15873427 100644 --- a/autobahn/wamp/types.py +++ b/autobahn/wamp/types.py @@ -482,3 +482,90 @@ class CallResult(object): def __str__(self): return "CallResult(results = {0}, kwresults = {1})".format(self.results, self.kwresults) + + +class IPublication(object): + """ + Represents a publication of an event. This is used with acknowledged publications. + """ + + def id(self): + """ + The WAMP publication ID for this publication. + """ + + +class ISubscription(object): + """ + Represents a subscription to a topic. + """ + + def id(self): + """ + The WAMP subscription ID for this subscription. + """ + + def active(self): + """ + Flag indicating if subscription is active. + """ + + def unsubscribe(self): + """ + Unsubscribe this subscription that was previously created from + :func:`autobahn.wamp.interfaces.ISubscriber.subscribe`. + + After a subscription has been unsubscribed successfully, no events + will be routed to the event handler anymore. + + Returns an instance of :tx:`twisted.internet.defer.Deferred` (when + running on **Twisted**) or an instance of :py:class:`asyncio.Future` + (when running on **asyncio**). + + - If the unsubscription succeeds, the returned Deferred/Future will + *resolve* (with no return value). + + - If the unsubscription fails, the returned Deferred/Future will *reject* + with an instance of :class:`autobahn.wamp.exception.ApplicationError`. + + :returns: A Deferred/Future for the unsubscription + :rtype: instance(s) of :tx:`twisted.internet.defer.Deferred` / :py:class:`asyncio.Future` + """ + + +class IRegistration(object): + """ + Represents a registration of an endpoint. + """ + + def id(self): + """ + The WAMP registration ID for this registration. + """ + + def active(self): + """ + Flag indicating if registration is active. + """ + + def unregister(self): + """ + Unregister this registration that was previously created from + :func:`autobahn.wamp.interfaces.ICallee.register`. + + After a registration has been unregistered successfully, no calls + will be routed to the endpoint anymore. + + Returns an instance of :tx:`twisted.internet.defer.Deferred` (when + running on **Twisted**) or an instance of :py:class:`asyncio.Future` + (when running on **asyncio**). + + - If the unregistration succeeds, the returned Deferred/Future will + *resolve* (with no return value). + + - If the unregistration fails, the returned Deferred/Future will be rejected + with an instance of :class:`autobahn.wamp.exception.ApplicationError`. + + :returns: A Deferred/Future for the unregistration + :rtype: instance(s) of :tx:`twisted.internet.defer.Deferred` / :py:class:`asyncio.Future` + """ From 11b76aadc4afd52b0cdc1f82e9e8d1e68e977f56 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Thu, 17 Sep 2015 15:20:02 +0200 Subject: [PATCH 37/59] some fixes --- autobahn/wamp/__init__.py | 16 +++++++--------- autobahn/wamp/interfaces.py | 9 +++++++++ autobahn/wamp/protocol.py | 27 ++++++++++----------------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/autobahn/wamp/__init__.py b/autobahn/wamp/__init__.py index be1db351..dd8748d2 100644 --- a/autobahn/wamp/__init__.py +++ b/autobahn/wamp/__init__.py @@ -43,13 +43,12 @@ from autobahn.wamp.exception import \ SerializationError, \ ProtocolError, \ TransportLost, \ - ApplicationError + ApplicationError, \ + InvalidUri from autobahn.wamp.interfaces import \ - ICaller, \ - ICallee, \ - IPublisher, \ - ISubscriber + ISession, \ + IApplicationSession from autobahn.wamp.uri import \ error, \ @@ -74,11 +73,10 @@ __all__ = ( 'ProtocolError', 'TransportLost', 'ApplicationError', + 'InvalidUri', - 'ICaller', - 'ICallee', - 'IPublisher', - 'ISubscriber', + 'ISession', + 'IApplicationSession', 'error', 'register', diff --git a/autobahn/wamp/interfaces.py b/autobahn/wamp/interfaces.py index e166cd79..5aaeb076 100644 --- a/autobahn/wamp/interfaces.py +++ b/autobahn/wamp/interfaces.py @@ -27,6 +27,15 @@ import abc import six +__all__ = ( + 'IObjectSerializer', + 'ISerializer', + 'ITransport', + 'ITransportHandler', + 'ISession', + 'IApplicationSession', +) + @six.add_metaclass(abc.ABCMeta) class IObjectSerializer(object): diff --git a/autobahn/wamp/protocol.py b/autobahn/wamp/protocol.py index ee7475f3..a4b8e0e0 100644 --- a/autobahn/wamp/protocol.py +++ b/autobahn/wamp/protocol.py @@ -26,17 +26,9 @@ from __future__ import absolute_import -import inspect import six - -from autobahn.wamp.interfaces import ISession, \ - IPublication, \ - IPublisher, \ - ISubscription, \ - ISubscriber, \ - ICaller, \ - IRegistration, \ - ITransportHandler +import txaio +import inspect from autobahn import wamp from autobahn.wamp import uri @@ -45,11 +37,10 @@ from autobahn.wamp import types from autobahn.wamp import role from autobahn.wamp import exception from autobahn.wamp.exception import ApplicationError, ProtocolError, SessionNotReady, SerializationError +from autobahn.wamp.interfaces import IApplicationSession # noqa from autobahn.wamp.types import SessionDetails from autobahn.util import IdGenerator -import txaio - def is_method_or_function(f): return inspect.ismethod(f) or inspect.isfunction(f) @@ -963,6 +954,11 @@ class ApplicationSession(BaseSession): else: raise SessionNotReady(u"Already requested to close the session") + def onDisconnect(self): + """ + Implements :func:`autobahn.wamp.interfaces.ISession.onDisconnect` + """ + def publish(self, topic, *args, **kwargs): """ Implements :func:`autobahn.wamp.interfaces.IPublisher.publish` @@ -1203,11 +1199,8 @@ class ApplicationSession(BaseSession): return on_reply -IPublisher.register(ApplicationSession) -ISubscriber.register(ApplicationSession) -ICaller.register(ApplicationSession) -# ICallee.register(ApplicationSession) # FIXME: ".register" collides with the ABC "register" method -ITransportHandler.register(ApplicationSession) +# IApplicationSession.register collides with the abc.ABCMeta.register method +# IApplicationSession.register(ApplicationSession) class ApplicationSessionFactory(object): From 2fcc70038640b73ecd228317c19191ebdb62fb82 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Thu, 17 Sep 2015 16:48:41 +0200 Subject: [PATCH 38/59] start prototyping Connection --- autobahn/twisted/wamp.py | 15 +- autobahn/wamp/protocol.py | 192 ++------------------ autobahn/wamp/request.py | 259 +++++++++++++++++++++++++++ examples/twisted/wamp/test_newapi.py | 22 +++ 4 files changed, 308 insertions(+), 180 deletions(-) create mode 100644 autobahn/wamp/request.py create mode 100644 examples/twisted/wamp/test_newapi.py diff --git a/autobahn/twisted/wamp.py b/autobahn/twisted/wamp.py index c4ad3467..6d1beddb 100644 --- a/autobahn/twisted/wamp.py +++ b/autobahn/twisted/wamp.py @@ -70,8 +70,8 @@ class ApplicationSessionFactory(protocol.ApplicationSessionFactory): session = ApplicationSession """ - The application session class this application session factory will use. Defaults to :class:`autobahn.twisted.wamp.ApplicationSession`. - """ + The application session class this application session factory will use. Defaults to :class:`autobahn.twisted.wamp.ApplicationSession`. + """ class ApplicationRunner(object): @@ -256,6 +256,17 @@ class ApplicationRunner(object): return d +class Connection(object): + + def __init__(self, transport=u'ws://127.0.0.1:8080/ws', realm=u'realm1', reactor=None): + pass + + def connect(self, main): + d = txaio.create_future() + txaio.resolve(d, u"hello") + return d + + class _ApplicationSession(ApplicationSession): """ WAMP application session class used internally with :class:`autobahn.twisted.app.Application`. diff --git a/autobahn/wamp/protocol.py b/autobahn/wamp/protocol.py index a4b8e0e0..acdfc14b 100644 --- a/autobahn/wamp/protocol.py +++ b/autobahn/wamp/protocol.py @@ -41,189 +41,25 @@ from autobahn.wamp.interfaces import IApplicationSession # noqa from autobahn.wamp.types import SessionDetails from autobahn.util import IdGenerator +from autobahn.wamp.request import \ + Publication, \ + Subscription, \ + Handler, \ + Registration, \ + Endpoint, \ + PublishRequest, \ + SubscribeRequest, \ + UnsubscribeRequest, \ + CallRequest, \ + InvocationRequest, \ + RegisterRequest, \ + UnregisterRequest + def is_method_or_function(f): return inspect.ismethod(f) or inspect.isfunction(f) -class Request(object): - """ - Object representing an outstanding request, such as for subscribe/unsubscribe, - register/unregister or call/publish. - """ - - def __init__(self, request_id, on_reply): - self.request_id = request_id - self.on_reply = on_reply - - -class InvocationRequest(Request): - """ - Object representing an outstanding request to invoke an endpoint. - """ - - -class CallRequest(Request): - """ - Object representing an outstanding request to call a procedure. - """ - - def __init__(self, request_id, on_reply, options): - Request.__init__(self, request_id, on_reply) - self.options = options - - -class PublishRequest(Request): - """ - Object representing an outstanding request to publish (acknowledged) an event. - """ - - -class SubscribeRequest(Request): - """ - Object representing an outstanding request to subscribe to a topic. - """ - - def __init__(self, request_id, on_reply, handler): - Request.__init__(self, request_id, on_reply) - self.handler = handler - - -class UnsubscribeRequest(Request): - """ - Object representing an outstanding request to unsubscribe a subscription. - """ - - def __init__(self, request_id, on_reply, subscription_id): - Request.__init__(self, request_id, on_reply) - self.subscription_id = subscription_id - - -class RegisterRequest(Request): - """ - Object representing an outstanding request to register a procedure. - """ - - def __init__(self, request_id, on_reply, procedure, endpoint): - Request.__init__(self, request_id, on_reply) - self.procedure = procedure - self.endpoint = endpoint - - -class UnregisterRequest(Request): - """ - Object representing an outstanding request to unregister a registration. - """ - - def __init__(self, request_id, on_reply, registration_id): - Request.__init__(self, request_id, on_reply) - self.registration_id = registration_id - - -class Subscription(object): - """ - Object representing a handler subscription. - - This class implements :class:`autobahn.wamp.interfaces.ISubscription`. - """ - def __init__(self, subscription_id, session, handler): - """ - """ - self.id = subscription_id - self.active = True - self.session = session - self.handler = handler - - def unsubscribe(self): - """ - Implements :func:`autobahn.wamp.interfaces.ISubscription.unsubscribe` - """ - if self.active: - return self.session._unsubscribe(self) - else: - raise Exception("subscription no longer active") - - def __str__(self): - return "Subscription(id={0}, is_active={1})".format(self.id, self.active) - - -class Handler(object): - """ - Object representing an event handler attached to a subscription. - """ - - def __init__(self, fn, obj=None, details_arg=None): - """ - - :param fn: The event handler function to be called. - :type fn: callable - :param obj: The (optional) object upon which to call the function. - :type obj: obj or None - :param details_arg: The keyword argument under which event details should be provided. - :type details_arg: str or None - """ - self.fn = fn - self.obj = obj - self.details_arg = details_arg - - -class Publication(object): - """ - Object representing a publication (feedback from publishing an event when doing - an acknowledged publish). - - This class implements :class:`autobahn.wamp.interfaces.IPublication`. - """ - def __init__(self, publication_id): - self.id = publication_id - - def __str__(self): - return "Publication(id={0})".format(self.id) - - -class Registration(object): - """ - Object representing a registration. - - This class implements :class:`autobahn.wamp.interfaces.IRegistration`. - """ - def __init__(self, session, registration_id, procedure, endpoint): - self.id = registration_id - self.active = True - self.session = session - self.procedure = procedure - self.endpoint = endpoint - - def unregister(self): - """ - Implements :func:`autobahn.wamp.interfaces.IRegistration.unregister` - """ - if self.active: - return self.session._unregister(self) - else: - raise Exception("registration no longer active") - - -class Endpoint(object): - """ - Object representing an procedure endpoint attached to a registration. - """ - - def __init__(self, fn, obj=None, details_arg=None): - """ - - :param fn: The endpoint procedure to be called. - :type fn: callable - :param obj: The (optional) object upon which to call the function. - :type obj: obj or None - :param details_arg: The keyword argument under which call details should be provided. - :type details_arg: str or None - """ - self.fn = fn - self.obj = obj - self.details_arg = details_arg - - class BaseSession(object): """ WAMP session base class. diff --git a/autobahn/wamp/request.py b/autobahn/wamp/request.py new file mode 100644 index 00000000..2edd9b75 --- /dev/null +++ b/autobahn/wamp/request.py @@ -0,0 +1,259 @@ +############################################################################### +# +# The MIT License (MIT) +# +# Copyright (c) Tavendo GmbH +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +############################################################################### + + +from __future__ import absolute_import + +__all__ = ( + 'Publication', + 'Subscription', + 'Handler', + 'Registration', + 'Endpoint', + + 'PublishRequest', + 'SubscribeRequest', + 'UnsubscribeRequest', + + 'CallRequest', + 'InvocationRequest', + 'RegisterRequest', + 'UnregisterRequest', +) + + +class Publication(object): + """ + Object representing a publication (feedback from publishing an event when doing + an acknowledged publish). + """ + + __slots__ = ('id') + + def __init__(self, publication_id): + self.id = publication_id + + def __str__(self): + return "Publication(id={0})".format(self.id) + + +class Subscription(object): + """ + Object representing a handler subscription. + """ + + __slots__ = ('id', 'active', 'session', 'handler') + + def __init__(self, subscription_id, session, handler): + """ + """ + self.id = subscription_id + self.active = True + self.session = session + self.handler = handler + + def unsubscribe(self): + """ + """ + if self.active: + return self.session._unsubscribe(self) + else: + raise Exception("subscription no longer active") + + def __str__(self): + return "Subscription(id={0}, is_active={1})".format(self.id, self.active) + + +class Handler(object): + """ + Object representing an event handler attached to a subscription. + """ + + __slots__ = ('fn', 'obj', 'details_arg') + + def __init__(self, fn, obj=None, details_arg=None): + """ + + :param fn: The event handler function to be called. + :type fn: callable + :param obj: The (optional) object upon which to call the function. + :type obj: obj or None + :param details_arg: The keyword argument under which event details should be provided. + :type details_arg: str or None + """ + self.fn = fn + self.obj = obj + self.details_arg = details_arg + + +class Registration(object): + """ + Object representing a registration. + """ + + __slots__ = ('id', 'active', 'session', 'procedure', 'endpoint') + + def __init__(self, session, registration_id, procedure, endpoint): + self.id = registration_id + self.active = True + self.session = session + self.procedure = procedure + self.endpoint = endpoint + + def unregister(self): + """ + """ + if self.active: + return self.session._unregister(self) + else: + raise Exception("registration no longer active") + + +class Endpoint(object): + """ + Object representing an procedure endpoint attached to a registration. + """ + + __slots__ = ('fn', 'obj', 'details_arg') + + def __init__(self, fn, obj=None, details_arg=None): + """ + + :param fn: The endpoint procedure to be called. + :type fn: callable + :param obj: The (optional) object upon which to call the function. + :type obj: obj or None + :param details_arg: The keyword argument under which call details should be provided. + :type details_arg: str or None + """ + self.fn = fn + self.obj = obj + self.details_arg = details_arg + + +class Request(object): + """ + Object representing an outstanding request, such as for subscribe/unsubscribe, + register/unregister or call/publish. + """ + + __slots__ = ('request_id', 'on_reply') + + def __init__(self, request_id, on_reply): + """ + + :param request_id: The WAMP request ID. + :type request_id: int + :param on_reply: The Deferred/Future to be fired when the request returns. + :type on_reply: Deferred/Future + """ + self.request_id = request_id + self.on_reply = on_reply + + +class PublishRequest(Request): + """ + Object representing an outstanding request to publish (acknowledged) an event. + """ + + +class SubscribeRequest(Request): + """ + Object representing an outstanding request to subscribe to a topic. + """ + + __slots__ = ('handler',) + + def __init__(self, request_id, on_reply, handler): + """ + + :param request_id: The WAMP request ID. + :type request_id: int + :param on_reply: The Deferred/Future to be fired when the request returns. + :type on_reply: Deferred/Future + :param handler: WAMP call options that are in use for this call. + :type handler: callable + """ + Request.__init__(self, request_id, on_reply) + self.handler = handler + + +class UnsubscribeRequest(Request): + """ + Object representing an outstanding request to unsubscribe a subscription. + """ + + def __init__(self, request_id, on_reply, subscription_id): + Request.__init__(self, request_id, on_reply) + self.subscription_id = subscription_id + + +class CallRequest(Request): + """ + Object representing an outstanding request to call a procedure. + """ + + __slots__ = ('options',) + + def __init__(self, request_id, on_reply, options): + """ + + :param request_id: The WAMP request ID. + :type request_id: int + :param on_reply: The Deferred/Future to be fired when the request returns. + :type on_reply: Deferred/Future + :param options: WAMP call options that are in use for this call. + :type options: dict + """ + Request.__init__(self, request_id, on_reply) + self.options = options + + +class InvocationRequest(Request): + """ + Object representing an outstanding request to invoke an endpoint. + """ + + +class RegisterRequest(Request): + """ + Object representing an outstanding request to register a procedure. + """ + + def __init__(self, request_id, on_reply, procedure, endpoint): + Request.__init__(self, request_id, on_reply) + self.procedure = procedure + self.endpoint = endpoint + + +class UnregisterRequest(Request): + """ + Object representing an outstanding request to unregister a registration. + """ + + def __init__(self, request_id, on_reply, registration_id): + Request.__init__(self, request_id, on_reply) + self.registration_id = registration_id diff --git a/examples/twisted/wamp/test_newapi.py b/examples/twisted/wamp/test_newapi.py new file mode 100644 index 00000000..854d5ae7 --- /dev/null +++ b/examples/twisted/wamp/test_newapi.py @@ -0,0 +1,22 @@ +from autobahn.twisted.wamp import Connection + +def on_join(session): + print('session connected: {}'.format(session)) + session.leave() + +def main(connection): + connection.on_join(on_join) + +if __name__ == '__main__': + from twisted.internet import reactor + + #connection = Connection() + #done = connection.connect(main) + + def finish(res): + print(res) + #reactor.stop() + + #done.addBoth(finish) + + reactor.run() From 13f549c718ec1241e030fb83ba2c7b681f2a94c8 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Thu, 17 Sep 2015 18:36:54 +0200 Subject: [PATCH 39/59] some changes to user code test --- examples/twisted/wamp/test_newapi.py | 65 +++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/examples/twisted/wamp/test_newapi.py b/examples/twisted/wamp/test_newapi.py index 854d5ae7..444ca8b8 100644 --- a/examples/twisted/wamp/test_newapi.py +++ b/examples/twisted/wamp/test_newapi.py @@ -1,22 +1,73 @@ from autobahn.twisted.wamp import Connection + def on_join(session): + """ + This is user code triggered when a session was created on top of + a connection, and the sessin has joined a realm. + """ print('session connected: {}'.format(session)) + + def on_leave(details): + print("on_leave", details) + + session.on_leave(on_leave) + + # explicitly leaving a realm will disconnect the connection + # cleanly and not try to reconnect, but exit cleanly. session.leave() -def main(connection): - connection.on_join(on_join) -if __name__ == '__main__': +def main(connection): + """ + This is the main entry into user code. It _gets_ a connection + instance, which it then can hook onto. + """ + def on_connect(session): + session.on_join(on_join) + session.join(u'public') + + # we attach our listener code on the connection. whenever there + # is a session created which has joined, our callback code is run + connection.on_connect(on_connect) + + +def run(main): + """ + This could be a high level "runner" tool we ship. + """ from twisted.internet import reactor - #connection = Connection() - #done = connection.connect(main) + # multiple, configurable transports, either via dict-like config, or + # from native Twisted endpoints + transports = [ + { + "type": "websocket", + "url": "ws://127.0.0.1:8080/ws" + } + ] + + # a connection connects and automatically reconnects WAMP client + # transports to a WAMP router. A connection has a listener system + # where user code can hook into different events : on_join + connection = Connection(main, transports=transports, + reactor=reactor) + + # the following returns a deferred that fires when the connection is + # finally done: either by explicit close by user code, or by error or + # when stop reconnecting + done = connection.connect() def finish(res): print(res) - #reactor.stop() + reactor.stop() - #done.addBoth(finish) + done.addBoth(finish) reactor.run() + + +if __name__ == '__main__': + # here, run() could be s.th. we ship, and a user would just + # provide a main() thing and run: + return run(main) From 579aa7ff8bf5af9c27e38bfe6cbeeac89f30808d Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Thu, 17 Sep 2015 19:05:50 +0200 Subject: [PATCH 40/59] expand example --- examples/twisted/wamp/test_newapi.py | 4 ++-- examples/twisted/wamp/test_newapi_short.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 examples/twisted/wamp/test_newapi_short.py diff --git a/examples/twisted/wamp/test_newapi.py b/examples/twisted/wamp/test_newapi.py index 444ca8b8..58c876ef 100644 --- a/examples/twisted/wamp/test_newapi.py +++ b/examples/twisted/wamp/test_newapi.py @@ -50,8 +50,8 @@ def run(main): # a connection connects and automatically reconnects WAMP client # transports to a WAMP router. A connection has a listener system # where user code can hook into different events : on_join - connection = Connection(main, transports=transports, - reactor=reactor) + connection = Connection(main, realm=u'public', + transports=transports, reactor=reactor) # the following returns a deferred that fires when the connection is # finally done: either by explicit close by user code, or by error or diff --git a/examples/twisted/wamp/test_newapi_short.py b/examples/twisted/wamp/test_newapi_short.py new file mode 100644 index 00000000..b2dd6738 --- /dev/null +++ b/examples/twisted/wamp/test_newapi_short.py @@ -0,0 +1,14 @@ +from autobahn.twisted import run + + +def on_join(session): + print('session connected: {}'.format(session)) + session.leave() + + +def main(connection): + connection.session.on_join(on_join) + + +if __name__ == '__main__': + return run(main) From 34a663810d36048c2d67c410b9ed3028f0581bac Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Thu, 17 Sep 2015 19:25:20 +0200 Subject: [PATCH 41/59] polish up --- examples/twisted/wamp/test_newapi_short.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/examples/twisted/wamp/test_newapi_short.py b/examples/twisted/wamp/test_newapi_short.py index b2dd6738..225824a5 100644 --- a/examples/twisted/wamp/test_newapi_short.py +++ b/examples/twisted/wamp/test_newapi_short.py @@ -1,14 +1,24 @@ +from twisted.internet.defer import inlineCallbacks as coroutine from autobahn.twisted import run - +@coroutine def on_join(session): - print('session connected: {}'.format(session)) - session.leave() - + # the session has joined the realm and the full range of + # WAMP interactions are now possible + try: + res = yield session.call(u'com.example.add2', 2, 3) + print("Result: {}".format(res)) + except Exception as e: + print("Error: {}".format(e)) + finally: + session.leave() def main(connection): - connection.session.on_join(on_join) - + # this is hooking into a session event, but that is + # replicated on as a connection event + connection.on_join(on_join) if __name__ == '__main__': + # this is using the defaults: WAMP-over-WebSocket + # to "ws://127.0.0.1:8080/ws" and realm "public" return run(main) From b5d86ee8a3e4407b01fa29c04b930f68b0759abe Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Thu, 17 Sep 2015 20:19:52 +0200 Subject: [PATCH 42/59] more new api example code --- .../wamp/test_newapi_multiple_connections.py | 55 +++++++++++++++++++ examples/twisted/wamp/test_newapi_short.py | 20 +++---- 2 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 examples/twisted/wamp/test_newapi_multiple_connections.py diff --git a/examples/twisted/wamp/test_newapi_multiple_connections.py b/examples/twisted/wamp/test_newapi_multiple_connections.py new file mode 100644 index 00000000..9e6521a6 --- /dev/null +++ b/examples/twisted/wamp/test_newapi_multiple_connections.py @@ -0,0 +1,55 @@ +from twisted.internet import reactor +import txaio +from autobahn.twisted.wamp import Connection + + +def main1(connection): + print('main1 created', connection) + + def on_join(session): + print('main1 joined', session) + session.leave() + + connection.on_join(on_join) + + +def main2(connection): + print('main2 created', connection) + + def on_join(session): + print('main2 joined', session) + session.leave() + + connection.on_join(on_join) + + +def run(entry_points): + + transports = [ + { + "type": "websocket", + "url": "ws://127.0.0.1:8080/ws" + } + ] + + done = [] + + for main in entry_points: + connection = Connection(main, realm=u'public', + transports=transports, reactor=reactor) + done.append(connection.connect()) + + # deferred that fires when all connections are done + done = txaio.gather(done) + + def finish(res): + print("all connections done", res) + reactor.stop() + + done.addBoth(finish) + + reactor.run() + + +if __name__ == '__main__': + return run([main1, main2]) diff --git a/examples/twisted/wamp/test_newapi_short.py b/examples/twisted/wamp/test_newapi_short.py index 225824a5..dd206bb6 100644 --- a/examples/twisted/wamp/test_newapi_short.py +++ b/examples/twisted/wamp/test_newapi_short.py @@ -1,10 +1,9 @@ from twisted.internet.defer import inlineCallbacks as coroutine -from autobahn.twisted import run +from autobahn.twisted import Runner + @coroutine def on_join(session): - # the session has joined the realm and the full range of - # WAMP interactions are now possible try: res = yield session.call(u'com.example.add2', 2, 3) print("Result: {}".format(res)) @@ -13,12 +12,13 @@ def on_join(session): finally: session.leave() -def main(connection): - # this is hooking into a session event, but that is - # replicated on as a connection event - connection.on_join(on_join) if __name__ == '__main__': - # this is using the defaults: WAMP-over-WebSocket - # to "ws://127.0.0.1:8080/ws" and realm "public" - return run(main) + # this is Runner, a high-level API above Connection and Session + runner = Runner() + + # "on_join" is a Session event that bubbled up via Connection + # to Runner here. this works since Connection/Session have default + # implementations that work with WAMP defaults + runner.on_join(on_join) + runner.run() From 499280417991ecbf170779740e2bba1aabd7c217 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Thu, 17 Sep 2015 20:28:37 +0200 Subject: [PATCH 43/59] more new api example code --- examples/twisted/wamp/test_newapi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/twisted/wamp/test_newapi.py b/examples/twisted/wamp/test_newapi.py index 58c876ef..c5acb148 100644 --- a/examples/twisted/wamp/test_newapi.py +++ b/examples/twisted/wamp/test_newapi.py @@ -18,7 +18,7 @@ def on_join(session): session.leave() -def main(connection): +def on_create(connection): """ This is the main entry into user code. It _gets_ a connection instance, which it then can hook onto. @@ -50,7 +50,7 @@ def run(main): # a connection connects and automatically reconnects WAMP client # transports to a WAMP router. A connection has a listener system # where user code can hook into different events : on_join - connection = Connection(main, realm=u'public', + connection = Connection(on_create, realm=u'public', transports=transports, reactor=reactor) # the following returns a deferred that fires when the connection is From 5224aab41e887ad69b5c4fe80df0684589ad957f Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Thu, 17 Sep 2015 20:31:37 +0200 Subject: [PATCH 44/59] more new api example code --- examples/twisted/wamp/test_newapi.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/twisted/wamp/test_newapi.py b/examples/twisted/wamp/test_newapi.py index c5acb148..c44189ff 100644 --- a/examples/twisted/wamp/test_newapi.py +++ b/examples/twisted/wamp/test_newapi.py @@ -32,7 +32,7 @@ def on_create(connection): connection.on_connect(on_connect) -def run(main): +def run(on_create): """ This could be a high level "runner" tool we ship. """ @@ -69,5 +69,5 @@ def run(main): if __name__ == '__main__': # here, run() could be s.th. we ship, and a user would just - # provide a main() thing and run: - return run(main) + # provide a on_create() thing and run: + return run(on_create) From ac445c39d1210d41baa0c6a9b6d747c73c1da8c0 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Thu, 17 Sep 2015 20:50:27 +0200 Subject: [PATCH 45/59] more new api example code --- examples/twisted/wamp/test_newapi_short.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/examples/twisted/wamp/test_newapi_short.py b/examples/twisted/wamp/test_newapi_short.py index dd206bb6..b130eb00 100644 --- a/examples/twisted/wamp/test_newapi_short.py +++ b/examples/twisted/wamp/test_newapi_short.py @@ -1,5 +1,5 @@ from twisted.internet.defer import inlineCallbacks as coroutine -from autobahn.twisted import Runner +from autobahn.twisted import Client @coroutine @@ -14,11 +14,15 @@ def on_join(session): if __name__ == '__main__': - # this is Runner, a high-level API above Connection and Session - runner = Runner() + # this is Client, a high-level API above Connection and Session + # it's a what is nowerdays ApplicationRunner, but with a better + # name and a listener based interface + client = Client() # "on_join" is a Session event that bubbled up via Connection - # to Runner here. this works since Connection/Session have default - # implementations that work with WAMP defaults - runner.on_join(on_join) - runner.run() + # to Client here. this works since Connection/Session have default + # implementations that by using WAMP defaults + client.on_join(on_join) + + # now make it run .. + client.run() From 0b620c8cb7cc93447d8deef364a82e70d14fcc60 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sat, 19 Sep 2015 13:01:50 +0200 Subject: [PATCH 46/59] listener --- autobahn/util.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/autobahn/util.py b/autobahn/util.py index d0410dc4..3b7db045 100644 --- a/autobahn/util.py +++ b/autobahn/util.py @@ -458,3 +458,30 @@ def wildcards2patterns(wildcards): :rtype: list of obj """ return [re.compile(wc.replace('.', '\.').replace('*', '.*')) for wc in wildcards] + + +class Observable(object): + + def __init__(self): + self._listeners = {} + + def on(self, event, handler): + if event not in self._listeners: + self._listeners[event] = set() + self._listeners[event].add(handler) + + def off(self, event=None, handler=None): + if event is None: + self._listeners = {} + else: + if event in self._listeners: + if handler is None: + del self._listeners[event] + else: + self._listeners[event].discard(handler) + + def fire(self, event, *args, **kwargs): + res = [] + for handler in selg._listeners.get(event, []): + res.append(handler(*args, **kwargs)) + return res From d6df84975604c0b71c2bb5d4f44c5ce354b702ac Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sat, 19 Sep 2015 20:10:40 +0200 Subject: [PATCH 47/59] fix observable mixin --- autobahn/util.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/autobahn/util.py b/autobahn/util.py index 3b7db045..ce456029 100644 --- a/autobahn/util.py +++ b/autobahn/util.py @@ -46,6 +46,7 @@ __all__ = ("utcnow", "Stopwatch", "Tracker", "EqualityMixin", + "ObservableMixin", "IdGenerator") @@ -460,7 +461,7 @@ def wildcards2patterns(wildcards): return [re.compile(wc.replace('.', '\.').replace('*', '.*')) for wc in wildcards] -class Observable(object): +class ObservableMixin(object): def __init__(self): self._listeners = {} @@ -482,6 +483,6 @@ class Observable(object): def fire(self, event, *args, **kwargs): res = [] - for handler in selg._listeners.get(event, []): + for handler in self._listeners.get(event, []): res.append(handler(*args, **kwargs)) return res From 5f798af53394616fabc09aeefe56ba119b7e454f Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sat, 19 Sep 2015 20:22:19 +0200 Subject: [PATCH 48/59] startup from Connection end --- autobahn/twisted/connection.py | 38 ++++++++++++++++++++ autobahn/wamp/connection.py | 52 +++++++++++++++++++++++++++ examples/twisted/wamp/Makefile | 2 ++ examples/twisted/wamp/test_newapi2.py | 20 +++++++++++ 4 files changed, 112 insertions(+) create mode 100644 autobahn/twisted/connection.py create mode 100644 autobahn/wamp/connection.py create mode 100644 examples/twisted/wamp/Makefile create mode 100644 examples/twisted/wamp/test_newapi2.py diff --git a/autobahn/twisted/connection.py b/autobahn/twisted/connection.py new file mode 100644 index 00000000..177d85a8 --- /dev/null +++ b/autobahn/twisted/connection.py @@ -0,0 +1,38 @@ +############################################################################### +# +# The MIT License (MIT) +# +# Copyright (c) Tavendo GmbH +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +############################################################################### + + +from __future__ import absolute_import + +from autobahn.wamp import connection + +__all__ = ('Connection') + + +class Connection(connection.Connection): + + def __init__(self, main, transports=u'ws://127.0.0.1:8080/ws', realm=u'default', extra=None): + connection.Connection.__init__(self, main, realm, extra) diff --git a/autobahn/wamp/connection.py b/autobahn/wamp/connection.py new file mode 100644 index 00000000..1d821731 --- /dev/null +++ b/autobahn/wamp/connection.py @@ -0,0 +1,52 @@ +############################################################################### +# +# The MIT License (MIT) +# +# Copyright (c) Tavendo GmbH +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +############################################################################### + + +from __future__ import absolute_import + +from autobahn.util import ObservableMixin + +__all__ = ('Connection') + + +def check_transport(transport): + """ + Check a WAMP transport configuration. + """ + pass + + +class Connection(ObservableMixin): + + def __init__(self, main, transports=u'ws://127.0.0.1:8080/ws', realm=u'default', extra=None): + ObservableMixin.__init__(self) + self._main = main + self._transports = transports + self._realm = realm + self._extra = extra + + def start(self, reactor): + raise RuntimeError('not implemented') diff --git a/examples/twisted/wamp/Makefile b/examples/twisted/wamp/Makefile new file mode 100644 index 00000000..effe8642 --- /dev/null +++ b/examples/twisted/wamp/Makefile @@ -0,0 +1,2 @@ +test: + PYTHONPATH=../../.. python test_newapi2.py diff --git a/examples/twisted/wamp/test_newapi2.py b/examples/twisted/wamp/test_newapi2.py new file mode 100644 index 00000000..c092b38d --- /dev/null +++ b/examples/twisted/wamp/test_newapi2.py @@ -0,0 +1,20 @@ +from twisted.internet.task import react +from autobahn.twisted.connection import Connection + + +def main(reactor, connection): + # called exactly once when this connection is started + print('connection setup time!') + + def on_join(session): + # called each time a transport was (re)connected and a session + # was established. until we leave explicitly or stop reconnecting. + print('session ready!') + #session.leave() + + connection.on('join', on_join) + + +if __name__ == '__main__': + connection = Connection(main) + react(connection.start) From f61675c25db4bed6b0ced91d431759eb87226cd2 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sat, 19 Sep 2015 22:44:21 +0200 Subject: [PATCH 49/59] this does even connect --- autobahn/twisted/connection.py | 106 +++++++++++++++++++++++++- examples/twisted/wamp/test_newapi2.py | 29 ++++++- 2 files changed, 131 insertions(+), 4 deletions(-) diff --git a/autobahn/twisted/connection.py b/autobahn/twisted/connection.py index 177d85a8..2cab1dfd 100644 --- a/autobahn/twisted/connection.py +++ b/autobahn/twisted/connection.py @@ -27,12 +27,114 @@ from __future__ import absolute_import +import txaio + from autobahn.wamp import connection +from autobahn.websocket.protocol import parseWsUrl +from autobahn.twisted.wamp import ApplicationSession +from autobahn.wamp.types import ComponentConfig +from autobahn.twisted.websocket import WampWebSocketClientFactory +from twisted.internet.defer import Deferred __all__ = ('Connection') class Connection(connection.Connection): - def __init__(self, main, transports=u'ws://127.0.0.1:8080/ws', realm=u'default', extra=None): - connection.Connection.__init__(self, main, realm, extra) + log = txaio.make_logger() + + def __init__(self, main, transports=u'ws://127.0.0.1:8080/ws', realm=u'default', extra=None, session_klass=None): + connection.Connection.__init__(self, main, transports, realm, extra) + self._session_klass = session_klass + + def start(self, reactor=None): + if reactor is None: + from twisted.internet import reactor + start_reactor = True + else: + start_reactor = False + + txaio.use_twisted() + txaio.config.loop = reactor + + url = u'ws://127.0.0.1:8080/ws' + + isSecure, host, port, resource, path, params = parseWsUrl(url) + + txaio.start_logging(level='debug') + + # factory for use ApplicationSession + def create(): + cfg = ComponentConfig(self._realm, self._extra) + try: + if self._session_klass: + session = self._session_klass(cfg) + else: + session = ApplicationSession(cfg) + except Exception as e: + if start_reactor: + # the app component could not be created .. fatal + self.log.error(str(e)) + reactor.stop() + else: + # if we didn't start the reactor, it's up to the + # caller to deal with errors + raise + else: + return session + + # create a WAMP-over-WebSocket transport client factory + transport_factory = WampWebSocketClientFactory(create, url=url, serializers=None) + from twisted.internet.endpoints import TCP4ClientEndpoint + client = TCP4ClientEndpoint(reactor, host, port) + + done = Deferred() + d = client.connect(transport_factory) + + # as the reactor shuts down, we wish to wait until we've sent + # out our "Goodbye" message; leave() returns a Deferred that + # fires when the transport gets to STATE_CLOSED + def cleanup(proto): + done.callback(None) + if hasattr(proto, '_session') and proto._session is not None: + return proto._session.leave() + + # when our proto was created and connected, make sure it's cleaned + # up properly later on when the reactor shuts down for whatever reason + def init_proto(proto): + reactor.addSystemEventTrigger('before', 'shutdown', cleanup, proto) + return proto + + # if we connect successfully, the arg is a WampWebSocketClientProtocol + d.addCallback(init_proto) + + return done + + # if the user didn't ask us to start the reactor, then they + # get to deal with any connect errors themselves. + if start_reactor: + # if an error happens in the connect(), we save the underlying + # exception so that after the event-loop exits we can re-raise + # it to the caller. + + class ErrorCollector(object): + exception = None + + def __call__(self, failure): + self.exception = failure.value + # print(failure.getErrorMessage()) + reactor.stop() + connect_error = ErrorCollector() + d.addErrback(connect_error) + + # now enter the Twisted reactor loop + reactor.run() + + # if we exited due to a connection error, raise that to the + # caller + if connect_error.exception: + raise connect_error.exception + + else: + # let the caller handle any errors + return d diff --git a/examples/twisted/wamp/test_newapi2.py b/examples/twisted/wamp/test_newapi2.py index c092b38d..39671ffc 100644 --- a/examples/twisted/wamp/test_newapi2.py +++ b/examples/twisted/wamp/test_newapi2.py @@ -1,6 +1,26 @@ from twisted.internet.task import react +from twisted.internet.defer import inlineCallbacks from autobahn.twisted.connection import Connection +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner + + +class MySession(ApplicationSession): + + @inlineCallbacks + def onJoin(self, details): + print("on_join", details) + try: + res = yield self.call(u'com.example.add2', [2, 3]) + print(res) + except Exception as e: + print(e) + self.leave() + + def onLeave(self, details): + print("on_leave: {}".format(details)) + self.disconnect() + def main(reactor, connection): # called exactly once when this connection is started @@ -16,5 +36,10 @@ def main(reactor, connection): if __name__ == '__main__': - connection = Connection(main) - react(connection.start) + + if True: + connection = Connection(main, realm=u'realm1', session_klass=MySession) + react(connection.start) + else: + runner = ApplicationRunner(u"ws://127.0.0.1:8080/ws", u"realm1") + runner.run(MySession) From e57ae403a3cf6281141d1c8d7e440aa4fb9de722 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sat, 19 Sep 2015 23:26:38 +0200 Subject: [PATCH 50/59] more new API --- autobahn/twisted/connection.py | 43 +++++++++++++++++----- autobahn/twisted/wamp.py | 23 ++++++------ examples/twisted/wamp/test_newapi2.py | 52 ++++++++++----------------- examples/twisted/wamp/test_newapi3.py | 45 +++++++++++++++++++++++ 4 files changed, 110 insertions(+), 53 deletions(-) create mode 100644 examples/twisted/wamp/test_newapi3.py diff --git a/autobahn/twisted/connection.py b/autobahn/twisted/connection.py index 2cab1dfd..dd7a4cb5 100644 --- a/autobahn/twisted/connection.py +++ b/autobahn/twisted/connection.py @@ -40,14 +40,44 @@ __all__ = ('Connection') class Connection(connection.Connection): + """ + A connection establishes a transport and attached a session + to a realm using the transport for communication. + + The transports a connection tries to use can be configured, + as well as the auto-reconnect strategy. + """ log = txaio.make_logger() - def __init__(self, main, transports=u'ws://127.0.0.1:8080/ws', realm=u'default', extra=None, session_klass=None): - connection.Connection.__init__(self, main, transports, realm, extra) - self._session_klass = session_klass + session = ApplicationSession + """ + The factory of the session we will instantiate. + """ - def start(self, reactor=None): + def __init__(self, transports=u'ws://127.0.0.1:8080/ws', realm=u'realm1', extra=None): + """ + + :param main: The + """ + connection.Connection.__init__(self, None, transports, realm, extra) + + def start(self, reactor=None, main=None): + """ + Starts the connection. The connection will establish a transport + and attach a session to a realm using the transport. + + When the transport is lost, a retry strategy is used to start + reconnect attempts. Retrying ends when maximum configurable limits have + been reached, or when the connection was ended explicitly. + + This procedure returns a Deferred/Future that will fire when the + connection is finally done, that is won't reconnect or has ended + explicitly. When the connection has ended, either successfully or + with failure, the returned Deferred/Future will fire. + + :returns: obj -- A Deferred or Future. + """ if reactor is None: from twisted.internet import reactor start_reactor = True @@ -67,10 +97,7 @@ class Connection(connection.Connection): def create(): cfg = ComponentConfig(self._realm, self._extra) try: - if self._session_klass: - session = self._session_klass(cfg) - else: - session = ApplicationSession(cfg) + session = self.session(cfg) except Exception as e: if start_reactor: # the app component could not be created .. fatal diff --git a/autobahn/twisted/wamp.py b/autobahn/twisted/wamp.py index 6d1beddb..b92943a8 100644 --- a/autobahn/twisted/wamp.py +++ b/autobahn/twisted/wamp.py @@ -37,6 +37,9 @@ from autobahn.wamp.types import ComponentConfig from autobahn.websocket.protocol import parseWsUrl from autobahn.twisted.websocket import WampWebSocketClientFactory +# new API +# from autobahn.twisted.connection import Connection + import txaio txaio.use_twisted() @@ -46,7 +49,10 @@ __all__ = [ 'ApplicationSessionFactory', 'ApplicationRunner', 'Application', - 'Service' + 'Service', + + # new API + 'Session' ] try: @@ -256,17 +262,6 @@ class ApplicationRunner(object): return d -class Connection(object): - - def __init__(self, transport=u'ws://127.0.0.1:8080/ws', realm=u'realm1', reactor=None): - pass - - def connect(self, main): - d = txaio.create_future() - txaio.resolve(d, u"hello") - return d - - class _ApplicationSession(ApplicationSession): """ WAMP application session class used internally with :class:`autobahn.twisted.app.Application`. @@ -614,3 +609,7 @@ if service: client = clientClass(host, port, transport_factory) client.setServiceParent(self) + + +# new API +Session = ApplicationSession diff --git a/examples/twisted/wamp/test_newapi2.py b/examples/twisted/wamp/test_newapi2.py index 39671ffc..fd211eb1 100644 --- a/examples/twisted/wamp/test_newapi2.py +++ b/examples/twisted/wamp/test_newapi2.py @@ -1,45 +1,31 @@ from twisted.internet.task import react -from twisted.internet.defer import inlineCallbacks +from twisted.internet.defer import inlineCallbacks as coroutine +from autobahn.twisted.wamp import Session from autobahn.twisted.connection import Connection -from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner +class MySession(Session): -class MySession(ApplicationSession): - - @inlineCallbacks + @coroutine def onJoin(self, details): - print("on_join", details) + print("on_join: {}".format(details)) + + def add2(a, b): + return a + b + + yield self.register(add2, u'com.example.add2') + try: - res = yield self.call(u'com.example.add2', [2, 3]) - print(res) + res = yield self.call(u'com.example.add2', 2, 3) + print("result: {}".format(res)) except Exception as e: - print(e) - self.leave() - - def onLeave(self, details): - print("on_leave: {}".format(details)) - self.disconnect() - - -def main(reactor, connection): - # called exactly once when this connection is started - print('connection setup time!') - - def on_join(session): - # called each time a transport was (re)connected and a session - # was established. until we leave explicitly or stop reconnecting. - print('session ready!') - #session.leave() - - connection.on('join', on_join) + print("error: {}".format(e)) + finally: + self.leave() if __name__ == '__main__': - if True: - connection = Connection(main, realm=u'realm1', session_klass=MySession) - react(connection.start) - else: - runner = ApplicationRunner(u"ws://127.0.0.1:8080/ws", u"realm1") - runner.run(MySession) + connection = Connection() + connection.session = MySession + react(connection.start) diff --git a/examples/twisted/wamp/test_newapi3.py b/examples/twisted/wamp/test_newapi3.py new file mode 100644 index 00000000..39671ffc --- /dev/null +++ b/examples/twisted/wamp/test_newapi3.py @@ -0,0 +1,45 @@ +from twisted.internet.task import react +from twisted.internet.defer import inlineCallbacks +from autobahn.twisted.connection import Connection + +from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner + + +class MySession(ApplicationSession): + + @inlineCallbacks + def onJoin(self, details): + print("on_join", details) + try: + res = yield self.call(u'com.example.add2', [2, 3]) + print(res) + except Exception as e: + print(e) + self.leave() + + def onLeave(self, details): + print("on_leave: {}".format(details)) + self.disconnect() + + +def main(reactor, connection): + # called exactly once when this connection is started + print('connection setup time!') + + def on_join(session): + # called each time a transport was (re)connected and a session + # was established. until we leave explicitly or stop reconnecting. + print('session ready!') + #session.leave() + + connection.on('join', on_join) + + +if __name__ == '__main__': + + if True: + connection = Connection(main, realm=u'realm1', session_klass=MySession) + react(connection.start) + else: + runner = ApplicationRunner(u"ws://127.0.0.1:8080/ws", u"realm1") + runner.run(MySession) From 709280f4534bf4dd1952511649dbbe045aea07b2 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sat, 19 Sep 2015 23:44:03 +0200 Subject: [PATCH 51/59] polish --- autobahn/twisted/wamp.py | 20 +++++++++++++++++- examples/twisted/wamp/test_newapi2.py | 2 +- examples/twisted/wamp/test_newapi3.py | 30 ++++----------------------- 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/autobahn/twisted/wamp.py b/autobahn/twisted/wamp.py index b92943a8..6d4b8bbf 100644 --- a/autobahn/twisted/wamp.py +++ b/autobahn/twisted/wamp.py @@ -612,4 +612,22 @@ if service: # new API -Session = ApplicationSession +class Session(ApplicationSession): + + def onJoin(self, details): + return self.on_join(details) + + def onLeave(self, details): + return self.on_leave(details) + + def onDisconnect(self): + return self.on_disconnect() + + def on_join(self): + pass + + def on_leave(self, details): + self.disconnect() + + def on_disconnect(self): + pass diff --git a/examples/twisted/wamp/test_newapi2.py b/examples/twisted/wamp/test_newapi2.py index fd211eb1..f38ba53a 100644 --- a/examples/twisted/wamp/test_newapi2.py +++ b/examples/twisted/wamp/test_newapi2.py @@ -7,7 +7,7 @@ from autobahn.twisted.connection import Connection class MySession(Session): @coroutine - def onJoin(self, details): + def on_join(self, details): print("on_join: {}".format(details)) def add2(a, b): diff --git a/examples/twisted/wamp/test_newapi3.py b/examples/twisted/wamp/test_newapi3.py index 39671ffc..4d73db37 100644 --- a/examples/twisted/wamp/test_newapi3.py +++ b/examples/twisted/wamp/test_newapi3.py @@ -2,13 +2,10 @@ from twisted.internet.task import react from twisted.internet.defer import inlineCallbacks from autobahn.twisted.connection import Connection -from autobahn.twisted.wamp import ApplicationSession, ApplicationRunner +def main(reactor, connection): -class MySession(ApplicationSession): - - @inlineCallbacks - def onJoin(self, details): + def on_join(session, details): print("on_join", details) try: res = yield self.call(u'com.example.add2', [2, 3]) @@ -17,29 +14,10 @@ class MySession(ApplicationSession): print(e) self.leave() - def onLeave(self, details): - print("on_leave: {}".format(details)) - self.disconnect() - - -def main(reactor, connection): - # called exactly once when this connection is started - print('connection setup time!') - - def on_join(session): - # called each time a transport was (re)connected and a session - # was established. until we leave explicitly or stop reconnecting. - print('session ready!') - #session.leave() - connection.on('join', on_join) if __name__ == '__main__': - if True: - connection = Connection(main, realm=u'realm1', session_klass=MySession) - react(connection.start) - else: - runner = ApplicationRunner(u"ws://127.0.0.1:8080/ws", u"realm1") - runner.run(MySession) + connection = Connection() + react(connection.start, [main]) From 9fb67e72718038017f37a09839171e35fca8c2ce Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sat, 19 Sep 2015 23:49:36 +0200 Subject: [PATCH 52/59] polish --- examples/twisted/wamp/test_newapi3.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/examples/twisted/wamp/test_newapi3.py b/examples/twisted/wamp/test_newapi3.py index 4d73db37..2e75ab55 100644 --- a/examples/twisted/wamp/test_newapi3.py +++ b/examples/twisted/wamp/test_newapi3.py @@ -1,18 +1,26 @@ from twisted.internet.task import react -from twisted.internet.defer import inlineCallbacks +from twisted.internet.defer import inlineCallbacks as coroutine from autobahn.twisted.connection import Connection def main(reactor, connection): + @coroutine def on_join(session, details): - print("on_join", details) + print("on_join: {}".format(details)) + + def add2(a, b): + return a + b + + yield self.register(add2, u'com.example.add2') + try: - res = yield self.call(u'com.example.add2', [2, 3]) - print(res) + res = yield self.call(u'com.example.add2', 2, 3) + print("result: {}".format(res)) except Exception as e: - print(e) - self.leave() + print("error: {}".format(e)) + finally: + self.leave() connection.on('join', on_join) From 1b09f68110db4e1c6291d98083d7e1b3f4e7b8ab Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sun, 20 Sep 2015 00:59:15 +0200 Subject: [PATCH 53/59] working 2nd flav --- autobahn/twisted/connection.py | 7 ++++++ autobahn/util.py | 16 +++++++++--- autobahn/wamp/protocol.py | 7 ++++-- examples/twisted/wamp/Makefile | 2 +- examples/twisted/wamp/test_newapi3.py | 6 ++--- examples/twisted/wamp/test_newapi4.py | 35 +++++++++++++++++++++++++++ 6 files changed, 63 insertions(+), 10 deletions(-) create mode 100644 examples/twisted/wamp/test_newapi4.py diff --git a/autobahn/twisted/connection.py b/autobahn/twisted/connection.py index dd7a4cb5..b7aef6e8 100644 --- a/autobahn/twisted/connection.py +++ b/autobahn/twisted/connection.py @@ -93,11 +93,18 @@ class Connection(connection.Connection): txaio.start_logging(level='debug') + if main: + main(reactor, self) + # factory for use ApplicationSession def create(): cfg = ComponentConfig(self._realm, self._extra) try: session = self.session(cfg) + + # let child listener bubble up event + session._parent = self + except Exception as e: if start_reactor: # the app component could not be created .. fatal diff --git a/autobahn/util.py b/autobahn/util.py index ce456029..f4c0ef73 100644 --- a/autobahn/util.py +++ b/autobahn/util.py @@ -461,9 +461,12 @@ def wildcards2patterns(wildcards): return [re.compile(wc.replace('.', '\.').replace('*', '.*')) for wc in wildcards] +import txaio + class ObservableMixin(object): - def __init__(self): + def __init__(self, parent=None): + self._parent = parent self._listeners = {} def on(self, event, handler): @@ -483,6 +486,11 @@ class ObservableMixin(object): def fire(self, event, *args, **kwargs): res = [] - for handler in self._listeners.get(event, []): - res.append(handler(*args, **kwargs)) - return res + print self, self._listeners + if event in self._listeners: + for handler in self._listeners[event]: + value = txaio.as_future(handler, *args, **kwargs) + res.append(value) + if self._parent is not None: + res.append(self._parent.fire(event, *args, **kwargs)) + return txaio.gather(res) diff --git a/autobahn/wamp/protocol.py b/autobahn/wamp/protocol.py index acdfc14b..f8846827 100644 --- a/autobahn/wamp/protocol.py +++ b/autobahn/wamp/protocol.py @@ -39,7 +39,7 @@ from autobahn.wamp import exception from autobahn.wamp.exception import ApplicationError, ProtocolError, SessionNotReady, SerializationError from autobahn.wamp.interfaces import IApplicationSession # noqa from autobahn.wamp.types import SessionDetails -from autobahn.util import IdGenerator +from autobahn.util import IdGenerator, ObservableMixin from autobahn.wamp.request import \ Publication, \ @@ -60,7 +60,7 @@ def is_method_or_function(f): return inspect.ismethod(f) or inspect.isfunction(f) -class BaseSession(object): +class BaseSession(ObservableMixin): """ WAMP session base class. @@ -71,6 +71,8 @@ class BaseSession(object): """ """ + ObservableMixin.__init__(self) + # this is for library level debugging self.debug = False @@ -760,6 +762,7 @@ class ApplicationSession(BaseSession): """ Implements :func:`autobahn.wamp.interfaces.ISession.onJoin` """ + return self.fire('join', self, details) def onLeave(self, details): """ diff --git a/examples/twisted/wamp/Makefile b/examples/twisted/wamp/Makefile index effe8642..c918221b 100644 --- a/examples/twisted/wamp/Makefile +++ b/examples/twisted/wamp/Makefile @@ -1,2 +1,2 @@ test: - PYTHONPATH=../../.. python test_newapi2.py + PYTHONPATH=../../.. python test_newapi3.py diff --git a/examples/twisted/wamp/test_newapi3.py b/examples/twisted/wamp/test_newapi3.py index 2e75ab55..ae357bb6 100644 --- a/examples/twisted/wamp/test_newapi3.py +++ b/examples/twisted/wamp/test_newapi3.py @@ -12,15 +12,15 @@ def main(reactor, connection): def add2(a, b): return a + b - yield self.register(add2, u'com.example.add2') + yield session.register(add2, u'com.example.add2') try: - res = yield self.call(u'com.example.add2', 2, 3) + res = yield session.call(u'com.example.add2', 2, 3) print("result: {}".format(res)) except Exception as e: print("error: {}".format(e)) finally: - self.leave() + session.leave() connection.on('join', on_join) diff --git a/examples/twisted/wamp/test_newapi4.py b/examples/twisted/wamp/test_newapi4.py new file mode 100644 index 00000000..834f8356 --- /dev/null +++ b/examples/twisted/wamp/test_newapi4.py @@ -0,0 +1,35 @@ +from twisted.internet.task import react +from twisted.internet.defer import inlineCallbacks as coroutine +from autobahn.twisted.wamp import Session +from autobahn.twisted.connection import Connection + + +def make_session(config): + + @coroutine + def on_join(session, details): + print("on_join: {}".format(details)) + + def add2(a, b): + return a + b + + yield session.register(add2, u'com.example.add2') + + try: + res = yield session.call(u'com.example.add2', 2, 3) + print("result: {}".format(res)) + except Exception as e: + print("error: {}".format(e)) + finally: + session.leave() + + session = Session(config=config) + session.on('join', on_join) + return session + + +if __name__ == '__main__': + + session = make_session() + connection = Connection() + react(connection.start, [session]) From 47f8350754dde198435416109c5f64191e943b97 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sun, 20 Sep 2015 01:03:11 +0200 Subject: [PATCH 54/59] fixes --- autobahn/util.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/autobahn/util.py b/autobahn/util.py index f4c0ef73..d0c703e8 100644 --- a/autobahn/util.py +++ b/autobahn/util.py @@ -37,6 +37,9 @@ import random from datetime import datetime, timedelta from pprint import pformat +import txaio + + __all__ = ("utcnow", "utcstr", "id", @@ -461,8 +464,6 @@ def wildcards2patterns(wildcards): return [re.compile(wc.replace('.', '\.').replace('*', '.*')) for wc in wildcards] -import txaio - class ObservableMixin(object): def __init__(self, parent=None): @@ -486,7 +487,6 @@ class ObservableMixin(object): def fire(self, event, *args, **kwargs): res = [] - print self, self._listeners if event in self._listeners: for handler in self._listeners[event]: value = txaio.as_future(handler, *args, **kwargs) From 836f2732287c0df16b37a148529d28dead6e42e6 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sun, 20 Sep 2015 15:54:00 +0200 Subject: [PATCH 55/59] work on new API --- autobahn/twisted/connection.py | 164 +++++++++++++++++++++++++- autobahn/wamp/connection.py | 79 ++++++++++++- examples/twisted/wamp/test_newapi2.py | 11 +- 3 files changed, 243 insertions(+), 11 deletions(-) diff --git a/autobahn/twisted/connection.py b/autobahn/twisted/connection.py index b7aef6e8..f6b5dbb8 100644 --- a/autobahn/twisted/connection.py +++ b/autobahn/twisted/connection.py @@ -25,20 +25,124 @@ ############################################################################### -from __future__ import absolute_import +from __future__ import absolute_import, print_function + +from twisted.internet.interfaces import IStreamClientEndpoint +from twisted.internet.endpoints import UNIXClientEndpoint +from twisted.internet.endpoints import TCP4ClientEndpoint + +try: + _TLS = True + from twisted.internet.endpoints import SSL4ClientEndpoint + from twisted.internet.ssl import optionsForClientTLS, CertificateOptions + from twisted.internet.interfaces import IOpenSSLClientConnectionCreator +except ImportError: + _TLS = False import txaio -from autobahn.wamp import connection from autobahn.websocket.protocol import parseWsUrl -from autobahn.twisted.wamp import ApplicationSession -from autobahn.wamp.types import ComponentConfig from autobahn.twisted.websocket import WampWebSocketClientFactory -from twisted.internet.defer import Deferred +from autobahn.twisted.rawsocket import WampRawSocketClientFactory + +from autobahn.wamp import connection +from autobahn.wamp.types import ComponentConfig + +from autobahn.twisted.wamp import ApplicationSession + __all__ = ('Connection') +def _create_transport_factory(reactor, transport_config, session_factory): + """ + Create a WAMP-over-XXX transport factory. + """ + if transport_config['type'] == 'websocket': + return WampWebSocketClientFactory(session_factory, url=transport_config['url']) + elif transport_config['type'] == 'rawsocket': + return WampRawSocketClientFactory(session_factory) + else: + assert(False), 'should not arrive here' + + +def _create_transport_endpoint(reactor, endpoint_config): + """ + Create a Twisted client endpoint for a WAMP-over-XXX transport. + """ + if IStreamClientEndpoint.providedBy(endpoint_config): + endpoint = IStreamClientEndpoint(endpoint_config) + else: + # create a connecting TCP socket + if endpoint_config['type'] == 'tcp': + + version = int(endpoint_config.get('version', 4)) + host = str(endpoint_config['host']) + port = int(endpoint_config['port']) + timeout = int(endpoint_config.get('timeout', 10)) # in seconds + tls = endpoint_config.get('tls', None) + + # create a TLS enabled connecting TCP socket + if tls: + if not _TLS: + raise RuntimeError('TLS configured in transport, but TLS support is not installed (eg OpenSSL?)') + + # FIXME: create TLS context from configuration + if IOpenSSLClientConnectionCreator.providedBy(tls): + # eg created from twisted.internet.ssl.optionsForClientTLS() + context = IOpenSSLClientConnectionCreator(tls) + + elif isinstance(tls, CertificateOptions): + context = tls + + elif tls is True: + context = optionsForClientTLS(host) + + else: + raise RuntimeError('unknown type {} for "tls" configuration in transport'.format(type(tls))) + + if version == 4: + endpoint = SSL4ClientEndpoint(reactor, host, port, context, timeout=timeout) + elif version == 6: + # there is no SSL6ClientEndpoint! + raise RuntimeError('TLS on IPv6 not implemented') + else: + assert(False), 'should not arrive here' + + # create a non-TLS connecting TCP socket + else: + if version == 4: + endpoint = TCP4ClientEndpoint(reactor, host, port, timeout=timeout) + elif version == 6: + try: + from twisted.internet.endpoints import TCP6ClientEndpoint + except ImportError: + raise RuntimeError('IPv6 is not supported (please upgrade Twisted)') + endpoint = TCP6ClientEndpoint(reactor, host, port, timeout=timeout) + else: + assert(False), 'should not arrive here' + + # create a connecting Unix domain socket + elif endpoint_config['type'] == 'unix': + path = endpoint_config['path'] + timeout = int(endpoint_config.get('timeout', 10)) # in seconds + endpoint = UNIXClientEndpoint(reactor, path, timeout=timeout) + + else: + assert(False), 'should not arrive here' + + return endpoint + + +def _connect_transport(reactor, transport_config, session_factory): + """ + Create and connect a WAMP-over-XXX transport. + """ + transport_factory = _create_transport_factory(reactor, transport_config, session_factory) + transport_endpoint = _create_transport_endpoint(reactor, transport_config['endpoint']) + return transport_endpoint.connect(transport_factory) + + class Connection(connection.Connection): """ A connection establishes a transport and attached a session @@ -63,6 +167,54 @@ class Connection(connection.Connection): connection.Connection.__init__(self, None, transports, realm, extra) def start(self, reactor=None, main=None): + print("FOOL!") + if reactor is None: + from twisted.internet import reactor + + txaio.use_twisted() + txaio.config.loop = reactor + + txaio.start_logging(level='debug') + + if main: + main(reactor, self) + + # factory for use ApplicationSession + def create(): + cfg = ComponentConfig(self._realm, self._extra) + try: + session = self.session(cfg) + + # let child listener bubble up event + session._parent = self + + except Exception as e: + if False: + # the app component could not be created .. fatal + self.log.error(str(e)) + reactor.stop() + else: + # if we didn't start the reactor, it's up to the + # caller to deal with errors + raise + else: + return session + + done = txaio.create_future() + + d = _connect_transport(reactor, self._transports[0], create) + + def on_connect_sucess(res): + print('on_connect_sucess', res) + + def on_connect_failure(err): + print('on_connect_failure', err) + + d.addCallbacks(on_connect_sucess, on_connect_failure) + + return done + + def start2(self, reactor=None, main=None): """ Starts the connection. The connection will establish a transport and attach a session to a realm using the transport. @@ -122,7 +274,7 @@ class Connection(connection.Connection): from twisted.internet.endpoints import TCP4ClientEndpoint client = TCP4ClientEndpoint(reactor, host, port) - done = Deferred() + done = txaio.create_future() d = client.connect(transport_factory) # as the reactor shuts down, we wish to wait until we've sent diff --git a/autobahn/wamp/connection.py b/autobahn/wamp/connection.py index 1d821731..924256e4 100644 --- a/autobahn/wamp/connection.py +++ b/autobahn/wamp/connection.py @@ -27,22 +27,93 @@ from __future__ import absolute_import +import six + from autobahn.util import ObservableMixin +from autobahn.websocket.protocol import parseWsUrl __all__ = ('Connection') -def check_transport(transport): +def check_endpoint(endpoint, check_native_endpoint=None): """ - Check a WAMP transport configuration. + Check a WAMP connecting endpoint configuration. """ - pass + if type(endpoint) != dict: + check_native_endpoint(endpoint) + else: + if 'type' not in endpoint: + raise RuntimeError('missing type in endpoint') + if endpoint['type'] not in ['tcp', 'unix']: + raise RuntimeError('invalid type "{}" in endpoint'.format(endpoint['type'])) + + if endpoint['type'] == 'tcp': + pass + elif endpoint['type'] == 'unix': + pass + else: + assert(False), 'should not arrive here' + + +def check_transport(transport, check_native_endpoint=None): + """ + Check a WAMP connecting transport configuration. + """ + if type(transport) != dict: + raise RuntimeError('invalid type {} for transport configuration - must be a dict'.format(type(transport))) + + if 'type' not in transport: + raise RuntimeError('missing type in transport') + + if transport['type'] not in ['websocket', 'rawsocket']: + raise RuntimeError('invalid transport type {}'.format(transport['type'])) + + if transport['type'] == 'websocket': + pass + elif transport['type'] == 'rawsocket': + pass + else: + assert(False), 'should not arrive here' class Connection(ObservableMixin): - def __init__(self, main, transports=u'ws://127.0.0.1:8080/ws', realm=u'default', extra=None): + session = None + """ + The factory of the session we will instantiate. + """ + + def __init__(self, main=None, transports=u'ws://127.0.0.1:8080/ws', realm=u'default', extra=None): ObservableMixin.__init__(self) + + if main is not None and not callable(main): + raise RuntimeError('"main" must be a callable if given') + + if type(realm) != six.text_type: + raise RuntimeError('invalid type {} for "realm" - must be Unicode'.format(type(realm))) + + # backward compatibility / convenience: allows to provide an URL instead of a + # list of transports + if type(transports) == six.text_type: + url = transports + is_secure, host, port, resource, path, params = parseWsUrl(url) + transport = { + 'type': 'websocket', + 'url': url, + 'endpoint': { + 'type': 'tcp', + 'host': host, + 'port': port + } + } + if is_secure: + # FIXME + transport['endpoint']['tls'] = {} + transports = [transport] + + for transport in transports: + check_transport(transport) + self._main = main self._transports = transports self._realm = realm diff --git a/examples/twisted/wamp/test_newapi2.py b/examples/twisted/wamp/test_newapi2.py index f38ba53a..5b9eba4f 100644 --- a/examples/twisted/wamp/test_newapi2.py +++ b/examples/twisted/wamp/test_newapi2.py @@ -23,9 +23,18 @@ class MySession(Session): finally: self.leave() + def on_leave(self, details): + print('on_leave: {}'.format(details)) + self.disconnect() + + def on_disconnect(self): + print('on_disconnect') + if __name__ == '__main__': - connection = Connection() + transports = u'ws://localhost:8080/ws' + + connection = Connection(transports=transports) connection.session = MySession react(connection.start) From 50a35e6508ab5bb1f5aa9690650285a7c3f9b8d3 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sun, 20 Sep 2015 16:55:08 +0200 Subject: [PATCH 56/59] work on new API --- autobahn/twisted/connection.py | 37 +++++++++++++++++++++++++-- autobahn/wamp/protocol.py | 8 ++++-- examples/twisted/wamp/test_newapi2.py | 6 +++-- examples/twisted/wamp/test_newapi3.py | 9 ++++++- 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/autobahn/twisted/connection.py b/autobahn/twisted/connection.py index f6b5dbb8..3cfdad74 100644 --- a/autobahn/twisted/connection.py +++ b/autobahn/twisted/connection.py @@ -27,6 +27,7 @@ from __future__ import absolute_import, print_function +from twisted.internet.defer import inlineCallbacks from twisted.internet.interfaces import IStreamClientEndpoint from twisted.internet.endpoints import UNIXClientEndpoint from twisted.internet.endpoints import TCP4ClientEndpoint @@ -48,6 +49,7 @@ from autobahn.twisted.rawsocket import WampRawSocketClientFactory from autobahn.wamp import connection from autobahn.wamp.types import ComponentConfig +from autobahn.twisted.util import sleep from autobahn.twisted.wamp import ApplicationSession @@ -166,6 +168,7 @@ class Connection(connection.Connection): """ connection.Connection.__init__(self, None, transports, realm, extra) + @inlineCallbacks def start(self, reactor=None, main=None): print("FOOL!") if reactor is None: @@ -179,6 +182,23 @@ class Connection(connection.Connection): if main: main(reactor, self) + reconnect = True + + while reconnect: + try: + print("connecting ..") + res = yield self._connect_once(reactor) + print("XXXXXX", res) + except Exception as e: + print(e) + yield sleep(2) + else: + reconnect = False + + def _connect_once(self, reactor): + + done = txaio.create_future() + # factory for use ApplicationSession def create(): cfg = ComponentConfig(self._realm, self._extra) @@ -188,6 +208,20 @@ class Connection(connection.Connection): # let child listener bubble up event session._parent = self + def on_leave(session, details): + print("on_leave: {}".format(details)) + + session.on('leave', on_leave) + + def on_disconnect(session, was_clean): + print("on_disconnect: {}".format(was_clean)) + if was_clean: + done.callback(None) + else: + done.errback(RuntimeError('transport closed uncleanly')) + + session.on('disconnect', on_disconnect) + except Exception as e: if False: # the app component could not be created .. fatal @@ -200,8 +234,6 @@ class Connection(connection.Connection): else: return session - done = txaio.create_future() - d = _connect_transport(reactor, self._transports[0], create) def on_connect_sucess(res): @@ -209,6 +241,7 @@ class Connection(connection.Connection): def on_connect_failure(err): print('on_connect_failure', err) + done.errback(err) d.addCallbacks(on_connect_sucess, on_connect_failure) diff --git a/autobahn/wamp/protocol.py b/autobahn/wamp/protocol.py index f8846827..44730677 100644 --- a/autobahn/wamp/protocol.py +++ b/autobahn/wamp/protocol.py @@ -746,7 +746,7 @@ class ApplicationSession(BaseSession): self._session_id = None - d = txaio.as_future(self.onDisconnect) + d = txaio.as_future(self.onDisconnect, wasClean) def _error(e): return self._swallow_error(e, "While firing onDisconnect") @@ -770,6 +770,9 @@ class ApplicationSession(BaseSession): """ if details.reason.startswith('wamp.error.'): self.log.error('{reason}: {wamp_message}', reason=details.reason, wamp_message=details.message) + + self.fire('leave', self, details) + if self._transport: self.disconnect() # do we ever call onLeave with a valid transport? @@ -793,10 +796,11 @@ class ApplicationSession(BaseSession): else: raise SessionNotReady(u"Already requested to close the session") - def onDisconnect(self): + def onDisconnect(self, wasClean): """ Implements :func:`autobahn.wamp.interfaces.ISession.onDisconnect` """ + return self.fire('disconnect', self, wasClean) def publish(self, topic, *args, **kwargs): """ diff --git a/examples/twisted/wamp/test_newapi2.py b/examples/twisted/wamp/test_newapi2.py index 5b9eba4f..107330f8 100644 --- a/examples/twisted/wamp/test_newapi2.py +++ b/examples/twisted/wamp/test_newapi2.py @@ -21,10 +21,12 @@ class MySession(Session): except Exception as e: print("error: {}".format(e)) finally: - self.leave() + print('leaving ..') + #self.leave() + def on_leave(self, details): - print('on_leave: {}'.format(details)) + print('on_leave xx: {}'.format(details)) self.disconnect() def on_disconnect(self): diff --git a/examples/twisted/wamp/test_newapi3.py b/examples/twisted/wamp/test_newapi3.py index ae357bb6..58face9c 100644 --- a/examples/twisted/wamp/test_newapi3.py +++ b/examples/twisted/wamp/test_newapi3.py @@ -20,10 +20,17 @@ def main(reactor, connection): except Exception as e: print("error: {}".format(e)) finally: - session.leave() + print("leaving ..") + #session.leave() connection.on('join', on_join) + def on_leave(session, details): + print("on_leave: {}".format(details)) + session.disconnect() + + #connection.on('leave', on_leave) + if __name__ == '__main__': From 68e6dbc541060e6c541c46ff5143f9d74f056964 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sun, 20 Sep 2015 17:16:20 +0200 Subject: [PATCH 57/59] polish --- autobahn/twisted/connection.py | 129 ++------------------------------- 1 file changed, 8 insertions(+), 121 deletions(-) diff --git a/autobahn/twisted/connection.py b/autobahn/twisted/connection.py index 3cfdad74..1025cbff 100644 --- a/autobahn/twisted/connection.py +++ b/autobahn/twisted/connection.py @@ -27,6 +27,8 @@ from __future__ import absolute_import, print_function +import itertools + from twisted.internet.defer import inlineCallbacks from twisted.internet.interfaces import IStreamClientEndpoint from twisted.internet.endpoints import UNIXClientEndpoint @@ -162,15 +164,10 @@ class Connection(connection.Connection): """ def __init__(self, transports=u'ws://127.0.0.1:8080/ws', realm=u'realm1', extra=None): - """ - - :param main: The - """ connection.Connection.__init__(self, None, transports, realm, extra) @inlineCallbacks def start(self, reactor=None, main=None): - print("FOOL!") if reactor is None: from twisted.internet import reactor @@ -182,20 +179,21 @@ class Connection(connection.Connection): if main: main(reactor, self) + transport_gen = itertools.cycle(self._transports) + reconnect = True while reconnect: + transport_config = next(transport_gen) try: - print("connecting ..") - res = yield self._connect_once(reactor) - print("XXXXXX", res) + res = yield self._connect_once(reactor, transport_config) except Exception as e: print(e) yield sleep(2) else: reconnect = False - def _connect_once(self, reactor): + def _connect_once(self, reactor, transport_config): done = txaio.create_future() @@ -234,7 +232,7 @@ class Connection(connection.Connection): else: return session - d = _connect_transport(reactor, self._transports[0], create) + d = _connect_transport(reactor, transport_config, create) def on_connect_sucess(res): print('on_connect_sucess', res) @@ -246,114 +244,3 @@ class Connection(connection.Connection): d.addCallbacks(on_connect_sucess, on_connect_failure) return done - - def start2(self, reactor=None, main=None): - """ - Starts the connection. The connection will establish a transport - and attach a session to a realm using the transport. - - When the transport is lost, a retry strategy is used to start - reconnect attempts. Retrying ends when maximum configurable limits have - been reached, or when the connection was ended explicitly. - - This procedure returns a Deferred/Future that will fire when the - connection is finally done, that is won't reconnect or has ended - explicitly. When the connection has ended, either successfully or - with failure, the returned Deferred/Future will fire. - - :returns: obj -- A Deferred or Future. - """ - if reactor is None: - from twisted.internet import reactor - start_reactor = True - else: - start_reactor = False - - txaio.use_twisted() - txaio.config.loop = reactor - - url = u'ws://127.0.0.1:8080/ws' - - isSecure, host, port, resource, path, params = parseWsUrl(url) - - txaio.start_logging(level='debug') - - if main: - main(reactor, self) - - # factory for use ApplicationSession - def create(): - cfg = ComponentConfig(self._realm, self._extra) - try: - session = self.session(cfg) - - # let child listener bubble up event - session._parent = self - - except Exception as e: - if start_reactor: - # the app component could not be created .. fatal - self.log.error(str(e)) - reactor.stop() - else: - # if we didn't start the reactor, it's up to the - # caller to deal with errors - raise - else: - return session - - # create a WAMP-over-WebSocket transport client factory - transport_factory = WampWebSocketClientFactory(create, url=url, serializers=None) - from twisted.internet.endpoints import TCP4ClientEndpoint - client = TCP4ClientEndpoint(reactor, host, port) - - done = txaio.create_future() - d = client.connect(transport_factory) - - # as the reactor shuts down, we wish to wait until we've sent - # out our "Goodbye" message; leave() returns a Deferred that - # fires when the transport gets to STATE_CLOSED - def cleanup(proto): - done.callback(None) - if hasattr(proto, '_session') and proto._session is not None: - return proto._session.leave() - - # when our proto was created and connected, make sure it's cleaned - # up properly later on when the reactor shuts down for whatever reason - def init_proto(proto): - reactor.addSystemEventTrigger('before', 'shutdown', cleanup, proto) - return proto - - # if we connect successfully, the arg is a WampWebSocketClientProtocol - d.addCallback(init_proto) - - return done - - # if the user didn't ask us to start the reactor, then they - # get to deal with any connect errors themselves. - if start_reactor: - # if an error happens in the connect(), we save the underlying - # exception so that after the event-loop exits we can re-raise - # it to the caller. - - class ErrorCollector(object): - exception = None - - def __call__(self, failure): - self.exception = failure.value - # print(failure.getErrorMessage()) - reactor.stop() - connect_error = ErrorCollector() - d.addErrback(connect_error) - - # now enter the Twisted reactor loop - reactor.run() - - # if we exited due to a connection error, raise that to the - # caller - if connect_error.exception: - raise connect_error.exception - - else: - # let the caller handle any errors - return d From 571e282287afe6d6a4d88cc65af2d0197a4209f8 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sun, 20 Sep 2015 20:28:36 +0200 Subject: [PATCH 58/59] refactor code --- autobahn/twisted/connection.py | 73 +++++----------------------------- autobahn/wamp/connection.py | 52 ++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 64 deletions(-) diff --git a/autobahn/twisted/connection.py b/autobahn/twisted/connection.py index 1025cbff..02cad77b 100644 --- a/autobahn/twisted/connection.py +++ b/autobahn/twisted/connection.py @@ -44,12 +44,10 @@ except ImportError: import txaio -from autobahn.websocket.protocol import parseWsUrl from autobahn.twisted.websocket import WampWebSocketClientFactory from autobahn.twisted.rawsocket import WampRawSocketClientFactory from autobahn.wamp import connection -from autobahn.wamp.types import ComponentConfig from autobahn.twisted.util import sleep from autobahn.twisted.wamp import ApplicationSession @@ -138,15 +136,6 @@ def _create_transport_endpoint(reactor, endpoint_config): return endpoint -def _connect_transport(reactor, transport_config, session_factory): - """ - Create and connect a WAMP-over-XXX transport. - """ - transport_factory = _create_transport_factory(reactor, transport_config, session_factory) - transport_endpoint = _create_transport_endpoint(reactor, transport_config['endpoint']) - return transport_endpoint.connect(transport_factory) - - class Connection(connection.Connection): """ A connection establishes a transport and attached a session @@ -166,6 +155,14 @@ class Connection(connection.Connection): def __init__(self, transports=u'ws://127.0.0.1:8080/ws', realm=u'realm1', extra=None): connection.Connection.__init__(self, None, transports, realm, extra) + def _connect_transport(self, reactor, transport_config, session_factory): + """ + Create and connect a WAMP-over-XXX transport. + """ + transport_factory = _create_transport_factory(reactor, transport_config, session_factory) + transport_endpoint = _create_transport_endpoint(reactor, transport_config['endpoint']) + return transport_endpoint.connect(transport_factory) + @inlineCallbacks def start(self, reactor=None, main=None): if reactor is None: @@ -186,61 +183,9 @@ class Connection(connection.Connection): while reconnect: transport_config = next(transport_gen) try: - res = yield self._connect_once(reactor, transport_config) + yield self._connect_once(reactor, transport_config) except Exception as e: print(e) yield sleep(2) else: reconnect = False - - def _connect_once(self, reactor, transport_config): - - done = txaio.create_future() - - # factory for use ApplicationSession - def create(): - cfg = ComponentConfig(self._realm, self._extra) - try: - session = self.session(cfg) - - # let child listener bubble up event - session._parent = self - - def on_leave(session, details): - print("on_leave: {}".format(details)) - - session.on('leave', on_leave) - - def on_disconnect(session, was_clean): - print("on_disconnect: {}".format(was_clean)) - if was_clean: - done.callback(None) - else: - done.errback(RuntimeError('transport closed uncleanly')) - - session.on('disconnect', on_disconnect) - - except Exception as e: - if False: - # the app component could not be created .. fatal - self.log.error(str(e)) - reactor.stop() - else: - # if we didn't start the reactor, it's up to the - # caller to deal with errors - raise - else: - return session - - d = _connect_transport(reactor, transport_config, create) - - def on_connect_sucess(res): - print('on_connect_sucess', res) - - def on_connect_failure(err): - print('on_connect_failure', err) - done.errback(err) - - d.addCallbacks(on_connect_sucess, on_connect_failure) - - return done diff --git a/autobahn/wamp/connection.py b/autobahn/wamp/connection.py index 924256e4..b2b49a45 100644 --- a/autobahn/wamp/connection.py +++ b/autobahn/wamp/connection.py @@ -29,8 +29,11 @@ from __future__ import absolute_import import six +import txaio + from autobahn.util import ObservableMixin from autobahn.websocket.protocol import parseWsUrl +from autobahn.wamp.types import ComponentConfig __all__ = ('Connection') @@ -121,3 +124,52 @@ class Connection(ObservableMixin): def start(self, reactor): raise RuntimeError('not implemented') + + def _connect_once(self, reactor, transport_config): + + done = txaio.create_future() + + # factory for ISession objects + def create_session(): + cfg = ComponentConfig(self._realm, self._extra) + try: + session = self.session(cfg) + except Exception: + # couldn't instantiate session calls, which is fatal. + # let the reconnection logic deal with that + raise + else: + # let child listener bubble up event + session._parent = self + + # listen on leave events + def on_leave(session, details): + print("on_leave: {}".format(details)) + + session.on('leave', on_leave) + + # listen on disconnect events + def on_disconnect(session, was_clean): + print("on_disconnect: {}".format(was_clean)) + if was_clean: + done.callback(None) + else: + done.errback(RuntimeError('transport closed uncleanly')) + + session.on('disconnect', on_disconnect) + + # return the fresh session object + return session + + d = self._connect_transport(reactor, transport_config, create_session) + + def on_connect_sucess(res): + print('on_connect_sucess', res) + + def on_connect_failure(err): + print('on_connect_failure', err) + done.errback(err) + + txaio.add_callbacks(d, on_connect_sucess, on_connect_failure) + + return done From ecfe1e8b9c116da8c4279274df050f337722d2e7 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Mon, 21 Sep 2015 00:17:47 +0200 Subject: [PATCH 59/59] new Api --- autobahn/twisted/connection.py | 5 ++--- examples/twisted/wamp/test_newapi3.py | 14 +++++--------- examples/twisted/wamp/test_newapi5.py | 21 +++++++++++++++++++++ 3 files changed, 28 insertions(+), 12 deletions(-) create mode 100644 examples/twisted/wamp/test_newapi5.py diff --git a/autobahn/twisted/connection.py b/autobahn/twisted/connection.py index 02cad77b..0c8acc86 100644 --- a/autobahn/twisted/connection.py +++ b/autobahn/twisted/connection.py @@ -164,7 +164,7 @@ class Connection(connection.Connection): return transport_endpoint.connect(transport_factory) @inlineCallbacks - def start(self, reactor=None, main=None): + def start(self, reactor=None): if reactor is None: from twisted.internet import reactor @@ -173,8 +173,7 @@ class Connection(connection.Connection): txaio.start_logging(level='debug') - if main: - main(reactor, self) + yield self.fire('start', reactor, self) transport_gen = itertools.cycle(self._transports) diff --git a/examples/twisted/wamp/test_newapi3.py b/examples/twisted/wamp/test_newapi3.py index 58face9c..469f51aa 100644 --- a/examples/twisted/wamp/test_newapi3.py +++ b/examples/twisted/wamp/test_newapi3.py @@ -10,6 +10,7 @@ def main(reactor, connection): print("on_join: {}".format(details)) def add2(a, b): + print("add2() called", a, b) return a + b yield session.register(add2, u'com.example.add2') @@ -21,18 +22,13 @@ def main(reactor, connection): print("error: {}".format(e)) finally: print("leaving ..") - #session.leave() + session.leave() connection.on('join', on_join) - def on_leave(session, details): - print("on_leave: {}".format(details)) - session.disconnect() - - #connection.on('leave', on_leave) - if __name__ == '__main__': - connection = Connection() - react(connection.start, [main]) + connection.on('start', main) + + react(connection.start) diff --git a/examples/twisted/wamp/test_newapi5.py b/examples/twisted/wamp/test_newapi5.py new file mode 100644 index 00000000..024bf7e0 --- /dev/null +++ b/examples/twisted/wamp/test_newapi5.py @@ -0,0 +1,21 @@ +from twisted.internet.task import react +from twisted.internet.defer import inlineCallbacks as coroutine +from autobahn.twisted.connection import Connection + + +@coroutine +def main(reactor, connection): + + transport = yield connection.connect() + session = yield transport.join(u'realm1') + result = yield session.call(u'com.example.add2', 2, 3) + yield session.leave() + yield transport.disconnect() + yield connection.close() + + +if __name__ == '__main__': + connection = Connection() + connection.on('start', main) + + react(connection.start)