def connect(login=None, passcode=None, headers=None, versions=None, host=None, heartBeats=None): """Create a **CONNECT** frame. :param login: The **login** header. The default is :obj:`None`, which means that no such header will be added. :param passcode: The **passcode** header. The default is :obj:`None`, which means that no such header will be added. :param headers: Additional STOMP headers. :param versions: A list of the STOMP versions we wish to support. The default is :obj:`None`, which means that we will offer the broker to accept any version prior or equal to the default STOMP protocol version. :param host: The **host** header which gives this client a human readable name on the broker side. :param heartBeats: A pair (client heart-beat, server heart-beat) of integer heart-beat intervals in ms. Both intervals must be non-negative. A client heart-beat of 0 means that no heart-beats will be sent by the client. Similarly, a server heart-beat of 0 means that the client does not expect heart-beats from the server. """ headers = dict(headers or []) if login is not None: headers[StompSpec.LOGIN_HEADER] = login if passcode is not None: headers[StompSpec.PASSCODE_HEADER] = passcode versions = [StompSpec.VERSION_1_0] if (versions is None) else list(sorted(_version(v) for v in versions)) if versions != [StompSpec.VERSION_1_0]: headers[StompSpec.ACCEPT_VERSION_HEADER] = ','.join(_version(version) for version in versions) if host is None: host = '' headers[StompSpec.HOST_HEADER] = host if heartBeats: if versions == [StompSpec.VERSION_1_0]: raise StompProtocolError('Heart-beating not supported (version %s)' % StompSpec.VERSION_1_0) try: heartBeats = tuple(int(t) for t in heartBeats) if not all(t >= 0 for t in heartBeats): raise heartBeats = '%d,%d' % heartBeats except: raise StompProtocolError('Invalid heart-beats (two non-negative integers required): %s' % str(heartBeats)) headers[StompSpec.HEART_BEAT_HEADER] = heartBeats return StompFrame(StompSpec.CONNECT, headers)
def connected(frame, versions=None): """Handle a **CONNECTED** frame. :param versions: The same **versions** parameter you used to create the **CONNECT** frame. """ versions = [StompSpec.VERSION_1_0] if (versions is None) else list(sorted(_version(v) for v in versions)) version = versions[-1] _checkCommand(frame, [StompSpec.CONNECTED]) headers = frame.headers try: if version != StompSpec.VERSION_1_0: version = _version(headers.get(StompSpec.VERSION_HEADER, StompSpec.VERSION_1_0)) if version not in versions: raise StompProtocolError('') except StompProtocolError: raise StompProtocolError('Server version incompatible with accepted versions %s [headers=%s]' % (versions, headers)) session = headers.get(StompSpec.SESSION_HEADER) server = None if (version == StompSpec.VERSION_1_0) else headers.get(StompSpec.SERVER_HEADER) heartBeats = (0, 0) if (version != StompSpec.VERSION_1_0) and (StompSpec.HEART_BEAT_HEADER in headers): try: heartBeats = tuple(int(t) for t in headers[StompSpec.HEART_BEAT_HEADER].split(StompSpec.HEART_BEAT_SEPARATOR)) if (len(heartBeats) != 2) or any((t < 0) for t in heartBeats): raise ValueError('') except: raise StompProtocolError('Invalid %s header (two comma-separated and non-negative integers required): %s' % (StompSpec.HEART_BEAT_HEADER, heartBeats)) return version, server, session, heartBeats
def stomp(login=None, passcode=None, headers=None, versions=None, host=None, heartBeats=None): """Create a **STOMP** frame. Not supported in STOMP protocol 1.0, synonymous to :func:`connect` for STOMP protocol 1.1 and higher. """ if (versions is None) or (list(versions) == [StompSpec.VERSION_1_0]): raise StompProtocolError('Unsupported command (version %s): %s' % (StompSpec.VERSION_1_0, StompSpec.STOMP)) frame = connect(login=login, passcode=passcode, headers=headers, versions=versions, host=host, heartBeats=heartBeats) return StompFrame(StompSpec.STOMP, frame.headers, frame.body)
def beat(version=None): """Create a STOMP heart-beat. """ version = _version(version) if version == StompSpec.VERSION_1_0: raise StompProtocolError('Heart-beating not supported (version %s)' % version) return StompHeartBeat()
def receipt(self, frame): """Handle a **RECEIPT** frame. Returns the receipt id which you can use to match this receipt to the command that requested it.""" self.__check('receipt', [self.CONNECTED, self.DISCONNECTING]) receipt = commands.receipt(frame) try: self._receipts.remove(receipt) except KeyError: raise StompProtocolError('Unexpected receipt: %s' % receipt) return receipt
def unsubscribe(self, token, receipt=None): """Create an **UNSUBSCRIBE** frame and lose track of the subscription assiocated to it.""" self.__check('unsubscribe', [self.CONNECTED]) frame = commands.unsubscribe(token, receipt, version=self.version) try: self._subscriptions.pop(token) except KeyError: raise StompProtocolError('No such subscription [%s=%s]' % token) self._receipt(receipt) return frame
def message(self, frame): """Handle a **MESSAGE** frame. Returns a token which you can use to match this message to its subscription. .. seealso :: The :meth:`subscribe` method. """ self.__check('message', [self.CONNECTED]) token = commands.message(frame) if token not in self._subscriptions: raise StompProtocolError('No such subscription [%s=%s]' % token) return token
def version(cls, version=None): """Check whether **version** is a valid STOMP protocol version. :param version: A candidate version, or :obj:`None` (which is equivalent to the value of :attr:`StompSpec.DEFAULT_VERSION`). """ if version is None: version = cls.DEFAULT_VERSION if version not in cls.VERSIONS: raise StompProtocolError('Version is not supported [%s]' % version) return version
def subscribe(self, destination, headers=None, receipt=None, context=None): """Create a **SUBSCRIBE** frame and keep track of the subscription assiocated to it. This method returns a token which you have to keep if you wish to match incoming **MESSAGE** frames to this subscription with :meth:`message` or to :meth:`unsubscribe` later. :param context: An arbitrary context object which you can use to store any information related to the subscription at hand. """ self.__check('subscribe', [self.CONNECTED]) frame, token = commands.subscribe(destination, headers, receipt, version=self.version) if token in self._subscriptions: raise StompProtocolError('Already subscribed [%s=%s]' % token) self._receipt(receipt) self._subscriptions[token] = (self._nextSubscription(), destination, copy.deepcopy(headers), receipt, context) return frame, token
def nack(frame, transactions=None, receipt=None): """Create a **NACK** frame for a received **MESSAGE** frame. :param frame: The :class:`~.frame.StompFrame` object representing the **MESSAGE** frame we wish to nack. :param transactions: The ids of currently active transactions --- only if the **frame** is part of one of these transactions, the **transaction** header is included in the NACK frame. :param receipt: See :func:`disconnect`. """ version = frame.version if version == StompSpec.VERSION_1_0: raise StompProtocolError('%s not supported (version %s)' % (StompSpec.NACK, version)) frame = StompFrame(StompSpec.NACK, _ackHeaders(frame, transactions), version=frame.version) _addReceiptHeader(frame, receipt) return frame
def begin(self, transaction=None, receipt=None): """Create a **BEGIN** frame and begin an abstract STOMP transaction. :param transaction: See :meth:`transaction`. .. note :: If you try and begin a pending transaction twice, this will result in a :class:`~.stompest.error.StompProtocolError`. """ self.__check('begin', [self.CONNECTED]) frame = commands.begin(transaction, receipt, version=self.version) if transaction in self._transactions: raise StompProtocolError('Transaction already active: %s' % transaction) self._transactions.add(transaction) self._receipt(receipt) return frame
def commit(self, transaction, receipt=None): """Send a **COMMIT** command to commit a STOMP transaction. :param transaction: See :meth:`transaction`. .. note :: If you try and commit a transaction which is not pending, this will result in a :class:`~.stompest.error.StompProtocolError`. """ self.__check('commit', [self.CONNECTED]) frame = commands.commit(transaction, receipt, version=self.version) try: self._transactions.remove(transaction) except KeyError: raise StompProtocolError('Transaction unknown: %s' % transaction) self._receipt(receipt) return frame
def _connect(self, headers, versions, host, heartBeats, timeout): frame = self.session.connect(self._config.login, self._config.passcode, headers, versions, host, heartBeats) self.sendFrame(frame) if not self.canRead(timeout): self.session.disconnect() raise StompProtocolError( 'STOMP session connect failed [timeout=%s]' % timeout) frame = self.receiveFrame() self.session.connected(frame) self.log.info('Connected to stomp broker [session=%s, version=%s]' % (self.session.id, self.session.version)) self._transport.setVersion(self.session.version) for (destination, headers, receipt, _) in self.session.replay(): self.log.info('Replaying subscription %s' % headers) self.subscribe(destination, headers, receipt)
def _versions(self, versions): if versions and (set(versions) - set(commands.versions(self.version))): raise StompProtocolError('Invalid versions: %s [version=%s]' % (versions, self.version)) self.__versions = versions
def __check(self, command, states): if self._check and (self.state not in states): raise StompProtocolError('Cannot handle command %s in state %s (only in states %s)' % (repr(command), repr(self.state), ', '.join(map(repr, states))))
def _receipt(self, receipt): if not receipt: return if receipt in self._receipts: raise StompProtocolError('Duplicate receipt: %s' % receipt) self._receipts.add(receipt)
def _addReceiptHeader(frame, receipt): if not receipt: return if not isinstance(receipt, basestring): raise StompProtocolError('Invalid receipt (not a string): %s' % repr(receipt)) frame.headers[StompSpec.RECEIPT_HEADER] = str(receipt)
def _checkCommand(frame, commands=None): if frame.command not in (commands or StompSpec.COMMANDS): raise StompProtocolError('Cannot handle command: %s [expected=%s, headers=%s]' % (frame.command, ', '.join(commands), frame.headers))
def _checkHeader(frame, header): try: return frame.headers[header] except KeyError: raise StompProtocolError('Invalid %s frame (%s header mandatory in version %s) [headers=%s]' % (frame.command, header, frame.version, frame.headers))
def test_5_integration_stomp_1_1_heartbeat(self): version = StompSpec.VERSION_1_1 port = 61612 if ( BROKER == 'activemq' ) else PORT # stomp+nio on 61613 does not work properly, so use stomp on 61612 client = Stomp(self.getConfig(StompSpec.VERSION_1_1, port)) self.assertEquals(client.lastReceived, None) self.assertEquals(client.lastSent, None) heartBeatPeriod = 100 try: client.connect(host=VIRTUALHOST, heartBeats=(heartBeatPeriod, heartBeatPeriod), versions=[version]) except StompProtocolError as e: print 'Broker does not support STOMP protocol %s. Skipping this test case. [%s]' % ( e, version) return self.assertTrue((time.time() - client.lastReceived) < 0.1) if not (client.serverHeartBeat and client.clientHeartBeat): print 'broker does not support heart-beating. disconnecting ...' client.disconnect() client.close() return serverHeartBeatInSeconds = client.serverHeartBeat / 1000.0 clientHeartBeatInSeconds = client.clientHeartBeat / 1000.0 start = time.time() while (time.time() - start) < ( 2.5 * max(serverHeartBeatInSeconds, clientHeartBeatInSeconds)): time.sleep(0.5 * min(serverHeartBeatInSeconds, clientHeartBeatInSeconds)) client.canRead(0) self.assertTrue( (time.time() - client.lastReceived) < (2.0 * serverHeartBeatInSeconds)) if (time.time() - client.lastSent) > (0.5 * clientHeartBeatInSeconds): client.beat() self.assertTrue((time.time() - client.lastSent) < 0.1) start = time.time() try: while not client.canRead(0.5 * clientHeartBeatInSeconds): pass if client.receiveFrame().command == StompSpec.ERROR: raise StompProtocolError() except (StompConnectionError, StompProtocolError): self.assertTrue( (time.time() - start) < (3.0 * clientHeartBeatInSeconds)) self.assertTrue( (time.time() - client.lastReceived) < (2.0 * serverHeartBeatInSeconds)) self.assertTrue( (time.time() - client.lastSent) > clientHeartBeatInSeconds) else: raise client.close()