def _parseHead(self): try: endOfHead = self._findHead(self._data, self._start).end() except AttributeError: return command, rawHeaders = None, [] for line in self._data[self._start:endOfHead].decode(self._codec).split(StompSpec.LINE_DELIMITER): if line[-1:] == self._stripLineDelimiter: line = line[:-1] if command is None: command = line if command not in self._commands: self._raise('Invalid command (version %s): %r' % (self.version, command)) _unescape = unescape(self.version, command) continue if not line: break try: name, value = line.split(StompSpec.HEADER_SEPARATOR, 1) except ValueError: self._raise('No separator in header line: %r' % line) rawHeaders.append((_unescape(name), _unescape(value))) self._frame = StompFrame(command=command, rawHeaders=rawHeaders, version=self.version) self._start = endOfHead try: self._eof = self._seek = self._start + int(self._frame.headers[StompSpec.CONTENT_LENGTH_HEADER]) except KeyError: pass return True
def disconnect(receipt=None, version=None): """Create a **DISCONNECT** frame. :param receipt: Add a **receipt** header with this id to request a **RECEIPT** frame from the broker. If :obj:`None`, no such header is added. """ headers = {} frame = StompFrame(StompSpec.DISCONNECT, headers, version=version) _addReceiptHeader(frame, receipt) return frame
def commit(transaction, receipt=None, version=None): """Create a **COMMIT** frame. :param transaction: The id of the transaction. :param receipt: See :meth:`disconnect`. """ frame = StompFrame(StompSpec.COMMIT, {StompSpec.TRANSACTION_HEADER: transaction}, version=version) _addReceiptHeader(frame, receipt) return frame
def ack(frame, transactions=None, receipt=None): """Create an **ACK** frame for a received **MESSAGE** frame. :param frame: The :class:`~.frame.StompFrame` object representing the **MESSAGE** frame we wish to ack. :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 ACK frame. :param receipt: See :func:`disconnect`. """ frame = StompFrame(StompSpec.ACK, _ackHeaders(frame, transactions), version=frame.version) _addReceiptHeader(frame, receipt) return frame
def send(destination, body=b'', headers=None, receipt=None, version=None): """Create a **SEND** frame. :param destination: Destination for the frame. :param body: Binary message body. If the body contains null-bytes, it must be accompanied by the STOMP header **content-length** which specifies the number of bytes in the message body. :param headers: Additional STOMP headers. :param receipt: See :func:`disconnect`. """ frame = StompFrame(StompSpec.SEND, dict(headers or []), body, version=version) frame.headers[StompSpec.DESTINATION_HEADER] = destination _addReceiptHeader(frame, receipt) return frame
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 unsubscribe(token, receipt=None, version=None): """Create an **UNSUBSCRIBE** frame. :param token: The result of the :func:`subscribe` command which you used to initiate the subscription in question. :param receipt: See :meth:`disconnect`. """ version = _version(version) frame = StompFrame(StompSpec.UNSUBSCRIBE, dict([token]), version=version) _addReceiptHeader(frame, receipt) try: _checkHeader(frame, StompSpec.ID_HEADER) except StompProtocolError: if version != StompSpec.VERSION_1_0: raise _checkHeader(frame, StompSpec.DESTINATION_HEADER) return frame
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 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 subscribe(destination, headers, receipt=None, version=None): """Create a pair (frame, token) of a **SUBSCRIBE** frame and a token which you have to keep if you wish to match incoming **MESSAGE** frames to this subscription with :func:`message` or to :func:`unsubscribe` later. :param destination: Destination for the subscription. :param headers: Additional STOMP headers. :param receipt: See :func:`disconnect`. """ version = _version(version) frame = StompFrame(StompSpec.SUBSCRIBE, dict(headers or []), version=version) frame.headers[StompSpec.DESTINATION_HEADER] = destination _addReceiptHeader(frame, receipt) subscription = None try: subscription = _checkHeader(frame, StompSpec.ID_HEADER) except StompProtocolError: if (version != StompSpec.VERSION_1_0): raise token = (StompSpec.DESTINATION_HEADER, destination) if (subscription is None) else (StompSpec.ID_HEADER, subscription) return frame, tuple(map(textType, token))
"""This module implements a low-level and stateless API for all commands of the STOMP protocol version supported by stompest. All STOMP command frames are represented as :class:`~.frame.StompFrame` objects. It forms the basis for :class:`~.session.StompSession` which represents the full state of an abstract STOMP protocol session and (via :class:`~.session.StompSession`) of both high-level STOMP clients. You can use the commands API independently of other stompest modules to roll your own STOMP related functionality. .. note :: Whenever you have to pass a **version** parameter to a command, this is because the behavior of that command depends on the STOMP protocol version of your current session. The default version is the value of :attr:`StompSpec.DEFAULT_VERSION`, which is currently :obj:`'1.0'` but may change in upcoming versions of stompest (or you might override it yourself). Any command which does not conform to the STOMP protocol version in question will result in a :class:`~.error.StompProtocolError`. The **version** parameter will always be the last argument in the signature; since command signatures may vary with a new STOMP protocol version, you are advised to always specify it as a keyword (as opposed to a positional) argument. Examples: >>> from stompest.protocol import commands, StompFrame, StompSpec >>> versions = list(commands.versions(StompSpec.VERSION_1_1)) >>> versions ['1.0', '1.1'] >>> commands.connect(versions=versions) StompFrame(command='CONNECT', headers={'host': '', 'accept-version': '1.0,1.1'}) >>> frame, token = commands.subscribe('/queue/test', {StompSpec.ACK_HEADER: 'client-individual', 'activemq.prefetchSize': '100'}) >>> frame = StompFrame(StompSpec.MESSAGE, {StompSpec.DESTINATION_HEADER: '/queue/test', StompSpec.MESSAGE_ID_HEADER: '007'}, b'hello') >>> frame StompFrame(command='MESSAGE', headers={'destination': '/queue/test', 'message-id': '007'}, body=b'hello') >>> commands.message(frame) == token # This message matches your subscription. True >>> commands.message(frame) ('destination', '/queue/test') >>> frame.version = StompSpec.VERSION_1_1 >>> commands.message(frame) Traceback (most recent call last): File "<stdin>", line 1, in <module> stompest.error.StompProtocolError: Invalid MESSAGE frame (subscription header mandatory in version 1.1) [headers={'destination': '/queue/test', 'message-id': '007'}] >>> commands.disconnect(receipt='message-12345') StompFrame(command='DISCONNECT', headers={'receipt': 'message-12345'}) .. seealso :: Specification of STOMP protocols `1.0 <http://stomp.github.com//stomp-specification-1.0.html>`_ and `1.1 <http://stomp.github.com//stomp-specification-1.1.html>`_, your favorite broker's documentation for additional STOMP headers. """