def test_extract_message_multi(self): """ Test the handling of multiple concatenated messages by the buffer. """ m1 = 'CONNECT\nsession:207567f3-cce7-4a0a-930b-46fc394dd53d\n\n0123456789\x00\n' m2 = 'SUBSCRIBE\nack:auto\ndestination:/queue/test\n\n\x00SEND\ndestination:/queue/test\n\n\x00' sb = StompFrameBuffer() sb.append(m1) f1 = sb.extract_message() assert f1.cmd == 'CONNECT' assert f1.body == '0123456789' assert sb.extract_message() is None sb.append(m2) f2 = sb.extract_message() f3 = sb.extract_message() assert f2.cmd == 'SUBSCRIBE' assert f2.body == '' assert f3.cmd == 'SEND' assert f3.destination == '/queue/test' assert f3.body == '' assert sb.extract_message() is None
def test_extract_message(self): """ Test extracting a single frame. """ sb = StompFrameBuffer() m1 = self.createMessage('connect', {'session': uuid.uuid4()}, 'This is the body') sb.append(m1) msg = sb.extract_message() assert isinstance(msg, stomper.Frame) assert m1 == msg.pack()
class StompProtocol(Protocol, StompConnection): """ Subclass of C{twisted.internet.protocol.Protocol} for handling STOMP communications. An instance of this class will be created for each connecting client. @ivar buffer: A StompBuffer instance which buffers received data (to ensure we deal with complete STOMP messages. @type buffer: C{stomper.stompbuffer.StompBuffer} @ivar engine: The STOMP protocol engine. @type engine: L{coilmq.engine.StompEngine} """ def __init__(self, queue_manager, topic_manager): self.log = logging.getLogger( '%s.%s' % (self.__class__.__module__, self.__class__.__name__)) self.log.debug("Initializing StompProtocol.") self.buffer = StompFrameBuffer() self.engine = StompEngine( connection=self, authenticator=None, # FIXME: Add the authenticator queue_manager=queue_manager, topic_manager=topic_manager) def connectionLost(self, reason): Protocol.connectionLost(self, reason) self.log.debug("Connection lost.") self.engine.unbind() def connectionMade(self): self.log.debug("Connection made.") def dataReceived(self, data): """ Twisted calls this method when data is received. Note: The data may not be not be a complete frame or may be more than one frame. """ self.log.debug("Data received: %s" % data) self.buffer.append(data) # print '%r' % self.buffer for frame in self.buffer: self.log.debug("Processing frame: %s" % frame) self.engine.process_frame(frame) def send_frame(self, frame): """ Sends a frame to connected socket client. (Sorry, Twisted, our code is PEP-8.) @param frame: The frame to send. @type frame: L{coilmq.frame.StompFrame} """ self.transport.write(frame.pack())
class StompProtocol(Protocol, StompConnection): """ Subclass of C{twisted.internet.protocol.Protocol} for handling STOMP communications. An instance of this class will be created for each connecting client. @ivar buffer: A StompBuffer instance which buffers received data (to ensure we deal with complete STOMP messages. @type buffer: C{stomper.stompbuffer.StompBuffer} @ivar engine: The STOMP protocol engine. @type engine: L{coilmq.engine.StompEngine} """ def __init__(self, queue_manager, topic_manager): self.log = logging.getLogger('%s.%s' % (self.__class__.__module__, self.__class__.__name__)) self.log.debug("Initializing StompProtocol.") self.buffer = StompFrameBuffer() self.engine = StompEngine(connection=self, authenticator=None, # FIXME: Add the authenticator queue_manager=queue_manager, topic_manager=topic_manager) def connectionLost(self, reason): Protocol.connectionLost(self, reason) self.log.debug("Connection lost.") self.engine.unbind() def connectionMade(self): self.log.debug("Connection made.") def dataReceived(self, data): """ Twisted calls this method when data is received. Note: The data may not be not be a complete frame or may be more than one frame. """ self.log.debug("Data received: %s" % data) self.buffer.append(data) # print '%r' % self.buffer for frame in self.buffer: self.log.debug("Processing frame: %s" % frame) self.engine.process_frame(frame) def send_frame(self, frame): """ Sends a frame to connected socket client. (Sorry, Twisted, our code is PEP-8.) @param frame: The frame to send. @type frame: L{coilmq.frame.StompFrame} """ self.transport.write(frame.pack())
class StompRequestHandler(BaseRequestHandler, StompConnection): """ Class that will be instantiated to handle STOMP connections. This class will be instantiated once per connection to the server. In a multi-threaded context, that means that instances of this class are scoped to a single thread. It should be noted that while the L{coilmq.engine.StompEngine} instance will be thread-local, the storage containers configured into the engine are not thread-local (and hence must be thread-safe). @ivar buffer: A StompBuffer instance which buffers received data (to ensure we deal with complete STOMP messages. @type buffer: C{stomper.stompbuffer.StompBuffer} @ivar engine: The STOMP protocol engine. @type engine: L{coilmq.engine.StompEngine} @ivar debug: Whether to enable extra-verbose debug logging. (Will be logged at debug level.) @type debug: C{bool} """ def setup(self): self.debug = False self.log = logging.getLogger('%s.%s' % (self.__module__, self.__class__.__name__)) self.buffer = StompFrameBuffer() self.engine = StompEngine(connection=self, authenticator=self.server.authenticator, queue_manager=self.server.queue_manager, topic_manager=self.server.topic_manager) def handle(self): """ Handle a new socket connection. """ # self.request is the TCP socket connected to the client try: while True: data = self.request.recv(8192) if not data: break if self.debug: self.log.debug("RECV: %r" % data) self.buffer.append(data) for frame in self.buffer: self.log.debug("Processing frame: %s" % frame) self.engine.process_frame(frame) except Exception, e: self.log.error("Error receiving data (unbinding): %s" % e) self.engine.unbind() raise
class StompRequestHandler(BaseRequestHandler, StompConnection): """ Class that will be instantiated to handle STOMP connections. This class will be instantiated once per connection to the server. In a multi-threaded context, that means that instances of this class are scoped to a single thread. It should be noted that while the L{coilmq.engine.StompEngine} instance will be thread-local, the storage containers configured into the engine are not thread-local (and hence must be thread-safe). @ivar buffer: A StompBuffer instance which buffers received data (to ensure we deal with complete STOMP messages. @type buffer: C{stomper.stompbuffer.StompBuffer} @ivar engine: The STOMP protocol engine. @type engine: L{coilmq.engine.StompEngine} @ivar debug: Whether to enable extra-verbose debug logging. (Will be logged at debug level.) @type debug: C{bool} """ def setup(self): self.debug = False self.log = logging.getLogger( '%s.%s' % (self.__module__, self.__class__.__name__)) self.buffer = StompFrameBuffer() self.engine = StompEngine(connection=self, authenticator=self.server.authenticator, queue_manager=self.server.queue_manager, topic_manager=self.server.topic_manager) def handle(self): """ Handle a new socket connection. """ # self.request is the TCP socket connected to the client try: while True: data = self.request.recv(8192) if not data: break if self.debug: self.log.debug("RECV: %r" % data) self.buffer.append(data) for frame in self.buffer: self.log.debug("Processing frame: %s" % frame) self.engine.process_frame(frame) except Exception, e: self.log.error("Error receiving data (unbinding): %s" % e) self.engine.unbind() raise
def test_extract_message_fragmented(self): """ Test the handling of fragmented frame data. """ m1_1 = 'CONNECT\nsession:207567f3-cce7-4a0a-930b-' m1_2 = '46fc394dd53d\n\n0123456789\x00\nSUBSCRIBE\nack:a' m1_3 = 'uto\ndestination:/queue/test\n\n\x00SE' m1_4 = 'ND\ndestination:/queue/test\n\n0123456789\x00' sb = StompFrameBuffer() sb.append(m1_1) assert sb.extract_message() is None sb.append(m1_2) f1 = sb.extract_message() assert f1.cmd == 'CONNECT' assert f1.body == '0123456789' assert sb.extract_message() is None sb.append(m1_3) f2 = sb.extract_message() assert f2.cmd == 'SUBSCRIBE' assert sb.extract_message() is None sb.append(m1_4) f3 = sb.extract_message() assert f3.cmd == 'SEND' assert f3.destination == '/queue/test' assert f3.body == '0123456789'
def test_extract_message_binary(self): """ Test extracting a binary frame. """ sb = StompFrameBuffer() binmsg = "\x00\x00HELLO\x00\x00DONKEY\x00\x00" m1 = self.createMessage('send', {'content-length': len(binmsg)}, binmsg) sb.append(m1) msg = sb.extract_message() assert isinstance(msg, stomper.Frame) assert msg.pack() == m1 m2 = self.createMessage('send', {'content-length': len(binmsg), 'x-other-header': 'value'}, binmsg) sb.append(m2) msg = sb.extract_message() assert isinstance(msg, stomper.Frame) assert msg.pack() == m2
def test_iteration(self): """ Test the iteration feature of our buffer.""" sb = StompFrameBuffer() m1 = self.createMessage('connect', {'session': uuid.uuid4()}, 'This is the body') m2 = self.createMessage('send', {'destination': '/queue/sample'}, 'This is the body-2') print '%r' % m1 print '%r' % m2 sb.append(m1) sb.append(m2) assert sb is iter(sb) idx = 0 expected = (m1, m2) for frame in sb: assert isinstance(frame, stomper.Frame) assert expected[idx] == frame.pack() idx += 1 assert idx == 2
class TestStompClient(object): """ A stomp client for use in testing. This client spawns a listener thread and pushes anything that comes in onto the read_frames queue. @ivar received_frames: A queue of StompFrame instances that have been received. @type received_frames: C{Queue.Queue} containing any received L{coilmq.frame.StompFrame} """ def __init__(self, addr, connect=True): """ @param addr: The (host,port) tuple for connection. @type addr: C{tuple} @param connect: Whether to connect socket to specified addr. @type connect: C{bool} """ self.log = logging.getLogger('%s.%s' % (self.__module__, self.__class__.__name__)) self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.addr = addr self.received_frames = Queue() self.read_stopped = threading.Event() self.buffer = StompFrameBuffer() if connect: self._connect() def connect(self, headers=None): if headers is None: headers = {} self.send_frame(StompFrame('CONNECT', headers=headers)) def send(self, destination, message, set_content_length=True): headers = {'destination': destination} if set_content_length: headers['content-length'] = len(message) self.send_frame(StompFrame('SEND', headers=headers, body=message)) def subscribe(self, destination): self.send_frame(StompFrame('SUBSCRIBE', headers={'destination': destination})) def send_frame(self, frame): """ Sends a stomp frame. @param frame: The stomp frame to send. @type frame: L{coilmq.frame.StompFrame} """ if not self.connected: raise RuntimeError("Not connected") self.sock.send(frame.pack()) def _connect(self): self.sock.connect(self.addr) self.connected = True self.read_stopped.clear() t = threading.Thread(target=self._read_loop, name="client-receiver-%s" % hex(id(self))) t.start() def _read_loop(self): while self.connected: r, w, e = select.select([self.sock], [], [], 0.1) if r: data = self.sock.recv(1024) self.log.debug("Data received: %r" % data) self.buffer.append(data) for frame in self.buffer: self.log.debug("Processing frame: %s" % frame) self.received_frames.put(frame) self.read_stopped.set() # print "Read loop has been quit! for %s" % id(self) def disconnect(self): self.send_frame(StompFrame('DISCONNECT')) def close(self): if not self.connected: raise RuntimeError("Not connected") self.connected = False self.read_stopped.wait() self.sock.close()