diff --git a/lib/stomper.egg-info/PKG-INFO b/lib/stomper.egg-info/PKG-INFO index 2049457..f73cfc2 100644 --- a/lib/stomper.egg-info/PKG-INFO +++ b/lib/stomper.egg-info/PKG-INFO @@ -1,6 +1,6 @@ -Metadata-Version: 1.0 +Metadata-Version: 1.1 Name: stomper -Version: 0.2.2 +Version: 0.2.5 Summary: This is a transport neutral client implementation of the STOMP protocol. Home-page: http://code.google.com/p/stomper Author: Oisin Mulvihill @@ -12,27 +12,28 @@ Description: ======= .. content: - :Author: - Oisin Mulvihill + :Author: + Oisin Mulvihill Contributors: - Micheal Twomey, Ricky Iacovou + Micheal Twomey, Ricky Iacovou , + Arfrever Frehtes Taifersar Arahesis Introduction ------------ - This is a python client implementation of the STOMP protocol. + This is a python client implementation of the STOMP protocol. - The client is attempting to be transport layer neutral. This module provides - functions to create and parse STOMP messages in a programatic fashion. The + The client is attempting to be transport layer neutral. This module provides + functions to create and parse STOMP messages in a programatic fashion. The messages can be easily generated and parsed, however its up to the user to do the sending and receiving. The STOMP protocol specification can be found here: - `Stomp Protocol `_ - I've looked at the stomp client by Jason R. Briggs. I've based some of the - 'function to message' generation on how his client does it. The client can + I've looked at the stomp client by Jason R. Briggs. I've based some of the + 'function to message' generation on how his client does it. The client can be found at the follow address however it isn't a dependancy. - `stompy `_ @@ -47,7 +48,7 @@ Description: ======= Source Code ----------- - The code can be accessed via subversion via google project hosting. Further + The code can be accessed via subversion via google project hosting. Further details can be found here: - `Stomper http://code.google.com/p/stomper/`_ @@ -60,41 +61,55 @@ Description: ======= ~~~~~~~~~~~ To see some basic code usage example see "*example/stomper_usage.py*". The unit test - "*tests/teststomper.py*" illustrates how to use all aspects of the code. + "*tests/teststomper.py*" illustrates how to use all aspects of the code. Receive/Sender ~~~~~~~~~~~~~~ The example "*receiver.py*" and "*sender.py*" show how messages and generated and then - transmitted using the twisted framework. Other frameworks could be used instead. The - examples also demonstrate the state machine I used to determine a response to received - messages. + transmitted using the twisted framework. Other frameworks could be used instead. The + examples also demonstrate the state machine I used to determine a response to received + messages. - I've also included "*stompbuffer-rx.py*" and "*stompbuffer-tx.py*" as examples of using + I've also included "*stompbuffer-rx.py*" and "*stompbuffer-tx.py*" as examples of using the new stompbuffer module contributed by Ricky Iacovou. Version History --------------- - 0.2.2 + 0.2.4 ~~~~~ - - Applied patch from esteve.fernandez to resolve "Issue 4: First Message not received" in the + OM: A minor relase fixing the problem whereby uuid would be installed on python2.5+. It + is not needed after python2.4 as it comes with python. Arfrever Frehtes Taifersar Arahesis + contributed the fix for this. + + + 0.2.3 + ~~~~~ + + OM: I've fixed issue #9 with the example code. All messages are sent and received correctly. + + + 0.2.2 + ~~~~~ + + - Applied patch from esteve.fernandez to resolve "Issue 4: First Message not received" in the example code (http://code.google.com/p/stomper/issues/detail?id=4&can=1). - - I've (Oisin) updated the examples to use twisted's line receiver and got it to "detect" + - I've (Oisin) updated the examples to use twisted's line receiver and got it to "detect" complete stomp messages. The old example would not work if a large amount of data was streamed. In this case dataReceived would be called with all the chunks of a message. This means that it - would not be correct for it to attempt to unpack and react until the whole message has been + would not be correct for it to attempt to unpack and react until the whole message has been received. Using twisted's line receiver looking for the \x00 works like a charm for this. This release integrates the bug fixes and the optional stompbuffer contributed by Ricky Iacovou: - - - Removed the trailing '\n\n' inserted by Frame.pack(). I believe that adding this is + + - Removed the trailing '\n\n' inserted by Frame.pack(). I believe that adding this is incorrect, for the following reasons: http://stomp.codehaus.org/Protocol gives the example: @@ -105,37 +120,37 @@ Description: ======= ^@ - and comments, "the body is empty in this case". This gives the impression that the body - is *exactly* defined as "the bytes, if any, between the '\n\n' at the end of the header + and comments, "the body is empty in this case". This gives the impression that the body + is *exactly* defined as "the bytes, if any, between the '\n\n' at the end of the header and the null byte". - This works for both binary and ASCII payloads: if I want to send a string without a + This works for both binary and ASCII payloads: if I want to send a string without a newline, I should be able to, in which case the body should look like: this is a string without a newline^@ ... and the receiver should deal with this. - This impression is reinforced by the fact that ActiveMQ will complain if you supply a + This impression is reinforced by the fact that ActiveMQ will complain if you supply a content-length header with any other byte count than that described above. - I am also unsure about the newline after the null byte as nothing in the protocol says - that there should be a newline after the null byte. Much of the code in StompBuffer - actively expects it to be there, but I suspect that *relying* on a frame ending '\x00\n' - may well limit compatibility. It's not an issue with Stomper-to-Stomper communication, - of course, as the sender puts it, the receiver accepts it, and ActiveMQ happily sends + I am also unsure about the newline after the null byte as nothing in the protocol says + that there should be a newline after the null byte. Much of the code in StompBuffer + actively expects it to be there, but I suspect that *relying* on a frame ending '\x00\n' + may well limit compatibility. It's not an issue with Stomper-to-Stomper communication, + of course, as the sender puts it, the receiver accepts it, and ActiveMQ happily sends it along. - - StompBuffer has had a few fixes; most notably, a fix that prevents a content-length "header" - in the *body* from being picked up and used (!). The biggest change is a new method, - syncBuffer(), which allows a corrupted buffer to recover from the corruption. Note that - I've never actually *seen* the buffer corruption when using Twisted, but the thought + - StompBuffer has had a few fixes; most notably, a fix that prevents a content-length "header" + in the *body* from being picked up and used (!). The biggest change is a new method, + syncBuffer(), which allows a corrupted buffer to recover from the corruption. Note that + I've never actually *seen* the buffer corruption when using Twisted, but the thought occurred to me that a single corrupt buffer could hang the entire message handling process. - - Fixed the typo "NO_REPONSE_NEEDED". I've changed it to NO_RESPONSE_NEEDED, but kept the + - Fixed the typo "NO_REPONSE_NEEDED". I've changed it to NO_RESPONSE_NEEDED, but kept the old variable for backwards compatibility; - - I've also modified the string format in send() to include the '\n\n' between the header + - I've also modified the string format in send() to include the '\n\n' between the header and the body, which I think is missing (it currently has only one '\n'). - Added CONNECTED to VALID_COMMANDS so syncBuffer() does not decide these messages are bogus. diff --git a/lib/stomper.egg-info/SOURCES.txt b/lib/stomper.egg-info/SOURCES.txt index 1434abb..2ccdbc1 100644 --- a/lib/stomper.egg-info/SOURCES.txt +++ b/lib/stomper.egg-info/SOURCES.txt @@ -7,7 +7,6 @@ lib/stomper/utils.py lib/stomper.egg-info/PKG-INFO lib/stomper.egg-info/SOURCES.txt lib/stomper.egg-info/dependency_links.txt -lib/stomper.egg-info/requires.txt lib/stomper.egg-info/top_level.txt lib/stomper/doc/__init__.py lib/stomper/doc/stomper.stx diff --git a/lib/stomper/__init__.py b/lib/stomper/__init__.py index 6749e91..ee98b1f 100644 --- a/lib/stomper/__init__.py +++ b/lib/stomper/__init__.py @@ -1,10 +1,10 @@ """ -This is a python client implementation of the STOMP protocol. +This is a python client implementation of the STOMP protocol. -It aims to be transport layer neutral. This module provides functions to +It aims to be transport layer neutral. This module provides functions to create and parse STOMP messages in a programatic fashion. -The examples package contains two examples using twisted as the transport +The examples package contains two examples using twisted as the transport framework. Other frameworks can be used and I may add other examples as time goes on. @@ -13,7 +13,7 @@ * http://stomp.codehaus.org/Protocol I've looked at the stomp client by Jason R. Briggs and have based the message -generation on how his client does it. The client can be found at the follow +generation on how his client does it. The client can be found at the follow address however it isn't a dependancy. * http://www.briggs.net.nz/log/projects/stomppy @@ -55,16 +55,16 @@ # STOMP Spec v1.0 valid commands: VALID_COMMANDS = [ - 'ABORT', 'ACK', 'BEGIN', 'COMMIT', + 'ABORT', 'ACK', 'BEGIN', 'COMMIT', 'CONNECT', 'CONNECTED', 'DISCONNECT', 'MESSAGE', 'SEND', 'SUBSCRIBE', 'UNSUBSCRIBE', - 'RECEIPT', 'ERROR', + 'RECEIPT', 'ERROR', ] def get_log(): return logging.getLogger("stomper") - + class FrameError(Exception): """Raise for problem with frame generation or parsing. @@ -72,44 +72,44 @@ class FrameError(Exception): class Frame(object): - """This class is used to create or read STOMP message frames. - + """This class is used to create or read STOMP message frames. + The method pack() is used to create a STOMP message ready for transmission. - - The method unpack() is used to read a STOMP message into + + The method unpack() is used to read a STOMP message into a frame instance. It uses the unpack_frame(...) function to do the intial parsing. The frame has three important member variables: - + * cmd * headers * body - - The 'cmd' is a property that represents the STOMP message + + The 'cmd' is a property that represents the STOMP message command. When you assign this a check is done to make sure its one of the VALID_COMMANDS. If not then FrameError will be raised. - - The 'headers' is a dictionary which the user can added to - if needed. There are no restrictions or checks imposed on + + The 'headers' is a dictionary which the user can added to + if needed. There are no restrictions or checks imposed on what values are inserted. - - The 'body' is just a member variable that the body text - is assigned to. - - """ + + The 'body' is just a member variable that the body text + is assigned to. + + """ def __init__(self): """Setup the internal state.""" self._cmd = '' self.body = '' self.headers = {} - + def getCmd(self): """Don't use _cmd directly!""" return self._cmd - + def setCmd(self, cmd): """Check the cmd is valid, FrameError will be raised if its not.""" cmd = cmd.upper() @@ -119,46 +119,45 @@ def setCmd(self, cmd): ) else: self._cmd = cmd - + cmd = property(getCmd, setCmd) - def pack(self): """Called to create a STOMP message from the internal values. """ - headers = ['%s:%s'%(f,v) for f,v in self.headers.items()] - headers = "\n".join(headers) - - stomp_mesage = "%s\n%s\n\n%s%s\n" % (self._cmd, headers, self.body, NULL) + headers = ''.join( + ['%s:%s\n' % (f, v) for f, v in self.headers.items()] + ) + stomp_mesage = "%s\n%s\n%s%s\n" % (self._cmd, headers, self.body, NULL) # import pprint # print "stomp_mesage: ", pprint.pprint(stomp_mesage) - + return stomp_mesage - + def unpack(self, message): """Called to extract a STOMP message into this instance. - + message: - This is a text string representing a valid + This is a text string representing a valid STOMP (v1.0) message. - - This method uses unpack_frame(...) to extract the + + This method uses unpack_frame(...) to extract the information, before it is assigned internally. retuned: The result of the unpack_frame(...) call. - + """ if not message: raise FrameError("Unpack error! The given message isn't valid '%s'!" % message) - + msg = unpack_frame(message) - + self.cmd = msg['cmd'] self.headers = msg['headers'] - + # Assign directly as the message will have the null # character in the message already. self.body = msg['body'] @@ -168,11 +167,11 @@ def unpack(self, message): def unpack_frame(message): """Called to unpack a STOMP message into a dictionary. - + returned = { # STOMP Command: 'cmd' : '...', - + # Headers e.g. 'headers' : { 'destination' : 'xyz', @@ -180,15 +179,15 @@ def unpack_frame(message): : etc, } - + # Body: 'body' : '...1234...\x00', } - + """ body = [] returned = dict(cmd='', headers={}, body='') - + breakdown = message.split('\n') # Get the message command: @@ -202,7 +201,7 @@ def headD(field): if index: header = field[:index].strip() data = field[index+1:].strip() -# print "header '%s' data '%s'" % (header, data) +# print "header '%s' data '%s'" % (header, data) returned['headers'][header.strip()] = data.strip() def bodyD(field): @@ -227,55 +226,55 @@ def bodyD(field): returned['body'] = body.replace('\x00', '') # print "2. body: <%s>" % returned['body'] - + return returned - + def abort(transactionid): """STOMP abort transaction command. Rollback whatever actions in this transaction. - + transactionid: This is the id that all actions in this transaction. - + """ return "ABORT\ntransaction: %s\n\n\x00\n" % transactionid def ack(messageid, transactionid=None): """STOMP acknowledge command. - + Acknowledge receipt of a specific message from the server. messageid: This is the id of the message we are acknowledging, what else could it be? ;) - + transactionid: - This is the id that all actions in this transaction + This is the id that all actions in this transaction will have. If this is not given then a random UUID will be generated for this. - + """ header = 'message-id: %s' % messageid if transactionid: header = 'message-id: %s\ntransaction: %s' % (messageid, transactionid) - + return "ACK\n%s\n\n\x00\n" % header - + def begin(transactionid=None): """STOMP begin command. Start a transaction... - + transactionid: - This is the id that all actions in this transaction + This is the id that all actions in this transaction will have. If this is not given then a random UUID will be generated for this. - + """ if not transactionid: # Generate a random UUID: @@ -283,109 +282,109 @@ def begin(transactionid=None): return "BEGIN\ntransaction: %s\n\n\x00\n" % transactionid - + def commit(transactionid): """STOMP commit command. Do whatever is required to make the series of actions permenant for this transactionid. - + transactionid: This is the id that all actions in this transaction. - + """ return "COMMIT\ntransaction: %s\n\n\x00\n" % transactionid def connect(username, password): """STOMP connect command. - + username, password: - These are the needed auth details to connect to the + These are the needed auth details to connect to the message server. - + After sending this we will receive a CONNECTED message which will contain our session id. - + """ return "CONNECT\nlogin:%s\npasscode:%s\n\n\x00\n" % (username, password) def disconnect(): """STOMP disconnect command. - + Tell the server we finished and we'll be closing the socket soon. - + """ return "DISCONNECT\n\n\x00\n" - + def send(dest, msg, transactionid=None): """STOMP send command. - + dest: This is the channel we wish to subscribe to - + msg: This is the message body to be sent. - + transactionid: This is an optional field and is not needed by default. - + """ transheader = '' - + if transactionid: transheader = 'transaction: %s' % transactionid - + return "SEND\ndestination: %s\n%s\n\n%s\x00\n" % (dest, transheader, msg) - - + + def subscribe(dest, ack='auto'): """STOMP subscribe command. - + dest: This is the channel we wish to subscribe to - + ack: 'auto' | 'client' If the ack is set to client, then messages received will have to have an acknowledge as a reply. Otherwise the server will assume delivery failure. - + """ return "SUBSCRIBE\ndestination: %s\nack: %s\n\n\x00\n" % (dest, ack) def unsubscribe(dest): """STOMP unsubscribe command. - + dest: This is the channel we wish to subscribe to - + Tell the server we no longer wish to receive any further messages for the given subscription. - + """ return "UNSUBSCRIBE\ndestination:%s\n\n\x00\n" % dest - + class Engine(object): - """This is a simple state machine to return a response to received + """This is a simple state machine to return a response to received message if needed. - + """ def __init__(self, testing=False): self.testing = testing - + self.log = logging.getLogger("stomper.Engine") - + self.sessionId = '' - + # Entry Format: # - # COMMAND : Handler_Function + # COMMAND : Handler_Function # self.states = { 'CONNECTED' : self.connected, @@ -393,11 +392,11 @@ def __init__(self, testing=False): 'ERROR' : self.error, 'RECEIPT' : self.receipt, } - - + + def react(self, msg): """Called to provide a response to a message if needed. - + msg: This is a dictionary as returned by unpack_frame(...) or it can be a straight STOMP message. This function @@ -405,106 +404,106 @@ def react(self, msg): returned: A message to return or an empty string. - + """ returned = "" - # If its not a string assume its a dict. + # If its not a string assume its a dict. mtype = type(msg) if mtype in types.StringTypes: - msg = unpack_frame(msg) + msg = unpack_frame(msg) elif mtype == types.DictType: pass else: raise FrameError("Unknown message type '%s', I don't know what to do with this!" % mtype) - + if self.states.has_key(msg['cmd']): # print("reacting to message - %s" % msg['cmd']) returned = self.states[msg['cmd']](msg) - + return returned - - + + def connected(self, msg): - """No reponse is needed to a connected frame. - - This method stores the session id as a the + """No reponse is needed to a connected frame. + + This method stores the session id as a the member sessionId for later use. - + returned: NO_RESPONSE_NEEDED - + """ self.sessionId = msg['headers']['session'] #print "connected: session id '%s'." % self.sessionId - + return NO_RESPONSE_NEEDED def ack(self, msg): """Called when a MESSAGE has been received. - + Override this method to handle received messages. - - This function will generate an acknowlege message + + This function will generate an acknowlege message for the given message and transaction (if present). - + """ message_id = msg['headers']['message-id'] transaction_id = None if msg['headers'].has_key('transaction-id'): transaction_id = msg['headers']['transaction-id'] - + # print "acknowledging message id <%s>." % message_id - + return ack(message_id, transaction_id) def error(self, msg): """Called to handle an error message received from the server. - + This method just logs the error message - + returned: NO_RESPONSE_NEEDED - + """ body = msg['body'].replace(NULL, '') - + brief_msg = "" if msg['headers'].has_key('message'): brief_msg = msg['headers']['message'] - + self.log.error("Received server error - message%s\n\n%s" % (brief_msg, body)) - + returned = NO_RESPONSE_NEEDED if self.testing: returned = 'error' - + return returned def receipt(self, msg): """Called to handle a receipt message received from the server. - + This method just logs the receipt message - + returned: NO_RESPONSE_NEEDED - + """ body = msg['body'].replace(NULL, '') - + brief_msg = "" if msg['headers'].has_key('receipt-id'): brief_msg = msg['headers']['receipt-id'] - + self.log.info("Received server receipt message - receipt-id:%s\n\n%s" % (brief_msg, body)) - + returned = NO_RESPONSE_NEEDED if self.testing: returned = 'receipt' - + return returned diff --git a/lib/stomper/doc/stomper.stx b/lib/stomper/doc/stomper.stx index 9284693..3a86140 100644 --- a/lib/stomper/doc/stomper.stx +++ b/lib/stomper/doc/stomper.stx @@ -4,28 +4,30 @@ Stomper .. content: -:Author: +:Author: Oisin Mulvihill Contributors: - Micheal Twomey, Ricky Iacovou , + Micheal Twomey, Ricky Iacovou , Arfrever Frehtes Taifersar Arahesis + Niki Pore + Introduction ------------ -This is a python client implementation of the STOMP protocol. +This is a python client implementation of the STOMP protocol. -The client is attempting to be transport layer neutral. This module provides -functions to create and parse STOMP messages in a programatic fashion. The +The client is attempting to be transport layer neutral. This module provides +functions to create and parse STOMP messages in a programatic fashion. The messages can be easily generated and parsed, however its up to the user to do the sending and receiving. The STOMP protocol specification can be found here: - `Stomp Protocol `_ -I've looked at the stomp client by Jason R. Briggs. I've based some of the -'function to message' generation on how his client does it. The client can +I've looked at the stomp client by Jason R. Briggs. I've based some of the +'function to message' generation on how his client does it. The client can be found at the follow address however it isn't a dependancy. - `stompy `_ @@ -40,7 +42,7 @@ page is here: Source Code ----------- -The code can be accessed via subversion via google project hosting. Further +The code can be accessed via subversion via google project hosting. Further details can be found here: - `Stomper http://code.google.com/p/stomper/`_ @@ -53,55 +55,62 @@ Basic Usage ~~~~~~~~~~~ To see some basic code usage example see "*example/stomper_usage.py*". The unit test -"*tests/teststomper.py*" illustrates how to use all aspects of the code. +"*tests/teststomper.py*" illustrates how to use all aspects of the code. Receive/Sender ~~~~~~~~~~~~~~ The example "*receiver.py*" and "*sender.py*" show how messages and generated and then -transmitted using the twisted framework. Other frameworks could be used instead. The -examples also demonstrate the state machine I used to determine a response to received -messages. +transmitted using the twisted framework. Other frameworks could be used instead. The +examples also demonstrate the state machine I used to determine a response to received +messages. -I've also included "*stompbuffer-rx.py*" and "*stompbuffer-tx.py*" as examples of using +I've also included "*stompbuffer-rx.py*" and "*stompbuffer-tx.py*" as examples of using the new stompbuffer module contributed by Ricky Iacovou. Version History --------------- +0.2.5 +~~~~~ + +Add the contributed fix for issue #14 by Niki Pore. The issue was reported by +Roger Hoover. This removes the extra line ending which can cause problems. + + 0.2.4 ~~~~~ -OM: A minor relase fixing the problem whereby uuid would be installed on python2.5+. It -is not needed after python2.4 as it comes with python. Arfrever Frehtes Taifersar Arahesis +OM: A minor relase fixing the problem whereby uuid would be installed on python2.5+. It +is not needed after python2.4 as it comes with python. Arfrever Frehtes Taifersar Arahesis contributed the fix for this. 0.2.3 ~~~~~ -OM: I've fixed issue #9 with the example code. All messages are sent and received correctly. +OM: I've fixed issue #9 with the example code. All messages are sent and received correctly. -0.2.2 +0.2.2 ~~~~~ -- Applied patch from esteve.fernandez to resolve "Issue 4: First Message not received" in the +- Applied patch from esteve.fernandez to resolve "Issue 4: First Message not received" in the example code (http://code.google.com/p/stomper/issues/detail?id=4&can=1). -- I've (Oisin) updated the examples to use twisted's line receiver and got it to "detect" +- I've (Oisin) updated the examples to use twisted's line receiver and got it to "detect" complete stomp messages. The old example would not work if a large amount of data was streamed. In this case dataReceived would be called with all the chunks of a message. This means that it -would not be correct for it to attempt to unpack and react until the whole message has been +would not be correct for it to attempt to unpack and react until the whole message has been received. Using twisted's line receiver looking for the \x00 works like a charm for this. This release integrates the bug fixes and the optional stompbuffer contributed by Ricky Iacovou: - -- Removed the trailing '\n\n' inserted by Frame.pack(). I believe that adding this is + +- Removed the trailing '\n\n' inserted by Frame.pack(). I believe that adding this is incorrect, for the following reasons: http://stomp.codehaus.org/Protocol gives the example: @@ -112,37 +121,37 @@ passcode: ^@ -and comments, "the body is empty in this case". This gives the impression that the body -is *exactly* defined as "the bytes, if any, between the '\n\n' at the end of the header +and comments, "the body is empty in this case". This gives the impression that the body +is *exactly* defined as "the bytes, if any, between the '\n\n' at the end of the header and the null byte". -This works for both binary and ASCII payloads: if I want to send a string without a +This works for both binary and ASCII payloads: if I want to send a string without a newline, I should be able to, in which case the body should look like: this is a string without a newline^@ ... and the receiver should deal with this. -This impression is reinforced by the fact that ActiveMQ will complain if you supply a +This impression is reinforced by the fact that ActiveMQ will complain if you supply a content-length header with any other byte count than that described above. -I am also unsure about the newline after the null byte as nothing in the protocol says -that there should be a newline after the null byte. Much of the code in StompBuffer -actively expects it to be there, but I suspect that *relying* on a frame ending '\x00\n' -may well limit compatibility. It's not an issue with Stomper-to-Stomper communication, -of course, as the sender puts it, the receiver accepts it, and ActiveMQ happily sends +I am also unsure about the newline after the null byte as nothing in the protocol says +that there should be a newline after the null byte. Much of the code in StompBuffer +actively expects it to be there, but I suspect that *relying* on a frame ending '\x00\n' +may well limit compatibility. It's not an issue with Stomper-to-Stomper communication, +of course, as the sender puts it, the receiver accepts it, and ActiveMQ happily sends it along. -- StompBuffer has had a few fixes; most notably, a fix that prevents a content-length "header" -in the *body* from being picked up and used (!). The biggest change is a new method, -syncBuffer(), which allows a corrupted buffer to recover from the corruption. Note that -I've never actually *seen* the buffer corruption when using Twisted, but the thought +- StompBuffer has had a few fixes; most notably, a fix that prevents a content-length "header" +in the *body* from being picked up and used (!). The biggest change is a new method, +syncBuffer(), which allows a corrupted buffer to recover from the corruption. Note that +I've never actually *seen* the buffer corruption when using Twisted, but the thought occurred to me that a single corrupt buffer could hang the entire message handling process. -- Fixed the typo "NO_REPONSE_NEEDED". I've changed it to NO_RESPONSE_NEEDED, but kept the +- Fixed the typo "NO_REPONSE_NEEDED". I've changed it to NO_RESPONSE_NEEDED, but kept the old variable for backwards compatibility; -- I've also modified the string format in send() to include the '\n\n' between the header +- I've also modified the string format in send() to include the '\n\n' between the header and the body, which I think is missing (it currently has only one '\n'). - Added CONNECTED to VALID_COMMANDS so syncBuffer() does not decide these messages are bogus. diff --git a/lib/stomper/tests/teststomper.py b/lib/stomper/tests/teststomper.py index a481194..ed40b49 100644 --- a/lib/stomper/tests/teststomper.py +++ b/lib/stomper/tests/teststomper.py @@ -6,7 +6,7 @@ * http://stomp.codehaus.org/Protocol I've looked and the stomp client by Jason R. Briggs and have based the message -generation on how his client did it. The client can be found at the follow +generation on how his client did it. The client can be found at the follow address however it isn't a dependancy. * http://www.briggs.net.nz/log/projects/stomppy @@ -47,10 +47,8 @@ def receipt(self, msg): return 'receipt' - class StomperTest(unittest.TestCase): - def testEngineToServerMessages(self): """Test the state machines reaction """ @@ -59,7 +57,10 @@ def testEngineToServerMessages(self): # React to a message which should be an ack: msg = stomper.Frame() msg.cmd = 'MESSAGE' - msg.headers = {'destination:':'/queue/a','message-id:':'some-message-id'} + msg.headers = { + 'destination:': '/queue/a', + 'message-id:': 'some-message-id' + } msg.body = "hello queue a" rc = e.react(msg.pack()) @@ -69,7 +70,7 @@ def testEngineToServerMessages(self): # React to an error: error = stomper.Frame() error.cmd = 'ERROR' - error.headers = {'mesage:':'malformed packet received!'} + error.headers = {'mesage:': 'malformed packet received!'} error.body = """The message: ----- MESSAGE @@ -77,7 +78,7 @@ def testEngineToServerMessages(self): Hello queue a! ----- -Did not contain a destination header, which is required for message propagation. +Did not contain a destination header, which is required for message propagation. \x00 """ @@ -88,13 +89,12 @@ def testEngineToServerMessages(self): # React to an receipt: receipt = stomper.Frame() receipt.cmd = 'RECEIPT' - receipt.headers = {'receipt-id:':'message-12345'} - + receipt.headers = {'receipt-id:': 'message-12345'} + rc = e.react(receipt.pack()) self.assertEquals(rc, 'receipt') self.assertEquals(e.receiptCalled, True) - def testEngine(self): """Test the basic state machine. """ @@ -111,7 +111,6 @@ def testEngine(self): returned = e.react(result) self.assertEquals(returned, correct) - # test message: msg = """MESSAGE destination: /queue/a @@ -125,7 +124,6 @@ def testEngine(self): correct = 'ACK\nmessage-id: some-message-id\n\n\x00\n' self.assertEquals(returned, correct) - # test error: msg = """ERROR message:some error @@ -137,7 +135,7 @@ def testEngine(self): returned = e.react(msg) correct = 'error' self.assertEquals(returned, correct) - + # test receipt: msg = """RECEIPT message-id: some-message-id @@ -148,39 +146,40 @@ def testEngine(self): correct = 'receipt' self.assertEquals(returned, correct) - def testFramepack1(self): """Testing pack, unpacking and the Frame class. """ # Check bad frame generation: - frame = stomper.Frame() + frame = stomper.Frame() + def bad(): frame.cmd = 'SOME UNNOWN CMD' + self.assertRaises(stomper.FrameError, bad) # Generate a MESSAGE frame: - frame = stomper.Frame() + frame = stomper.Frame() frame.cmd = 'MESSAGE' frame.headers['destination'] = '/queue/a' frame.headers['message-id'] = 'card_data' frame.body = "hello queue a" result = frame.pack() - + # print "\n-- result " + "----" * 10 # pprint.pprint(result) # print # Try bad message unpack catching: - bad_frame = stomper.Frame() + bad_frame = stomper.Frame() self.assertRaises(stomper.FrameError, bad_frame.unpack, None) - self.assertRaises(stomper.FrameError, bad_frame.unpack, '') + self.assertRaises(stomper.FrameError, bad_frame.unpack, '') # Try to read the generated frame back in # and then check the variables are set up # correctly: - frame2 = stomper.Frame() + frame2 = stomper.Frame() frame2.unpack(result) - + self.assertEquals(frame2.cmd, 'MESSAGE') self.assertEquals(frame2.headers['destination'], '/queue/a') self.assertEquals(frame2.headers['message-id'], 'card_data') @@ -188,24 +187,33 @@ def bad(): result = frame2.pack() correct = "MESSAGE\ndestination:/queue/a\nmessage-id:card_data\n\nhello queue a\x00\n" - + # print "result: " # pprint.pprint(result) # print # print "correct: " # pprint.pprint(correct) -# print +# print # self.assertEquals(result, correct) result = stomper.unpack_frame(result) - + self.assertEquals(result['cmd'], 'MESSAGE') self.assertEquals(result['headers']['destination'], '/queue/a') self.assertEquals(result['headers']['message-id'], 'card_data') self.assertEquals(result['body'], 'hello queue a') - - + + def testFramepack2(self): + """Testing pack, unpacking and the Frame class. + """ + # Check bad frame generation: + frame = stomper.Frame() + frame.cmd = 'DISCONNECT' + result = frame.pack() + correct = 'DISCONNECT\n\n\x00\n' + self.assertEquals(result, correct) + def testFrameUnpack2(self): """Testing unpack frame function against MESSAGE """ @@ -216,13 +224,12 @@ def testFrameUnpack2(self): hello queue a""" result = stomper.unpack_frame(msg) - + self.assertEquals(result['cmd'], 'MESSAGE') self.assertEquals(result['headers']['destination'], '/queue/a') self.assertEquals(result['headers']['message-id'], 'card_data') self.assertEquals(result['body'], 'hello queue a') - - + def testFrameUnpack3(self): """Testing unpack frame function against CONNECTED """ @@ -230,12 +237,11 @@ def testFrameUnpack3(self): session:ID:snorky.local-49191-1185461799654-3:18 """ result = stomper.unpack_frame(msg) - + self.assertEquals(result['cmd'], 'CONNECTED') self.assertEquals(result['headers']['session'], 'ID:snorky.local-49191-1185461799654-3:18') self.assertEquals(result['body'], '') - - + def testBugInFrameUnpack1(self): msg = """MESSAGE destination:/queue/a @@ -246,14 +252,13 @@ def testBugInFrameUnpack1(self): \x00 """ result = stomper.unpack_frame(msg) - + self.assertEquals(result['cmd'], 'MESSAGE') self.assertEquals(result['headers']['destination'], '/queue/a') self.assertEquals(result['headers']['message-id'], 'card_data') self.assertEquals(result['body'], 'hello queue a') - - def testCommit(self): + def testCommit(self): transactionid = '1234' correct = "COMMIT\ntransaction: %s\n\n\x00\n" % transactionid self.assertEquals(stomper.commit(transactionid), correct) @@ -267,11 +272,11 @@ def testBegin(self): transactionid = '1234' correct = "BEGIN\ntransaction: %s\n\n\x00\n" % transactionid self.assertEquals(stomper.begin(transactionid), correct) - + def testAck(self): messageid = '1234' transactionid = '9876' - header = 'message-id: %s\ntransaction: %s' % (messageid, transactionid) + header = 'message-id: %s\ntransaction: %s' % (messageid, transactionid) correct = "ACK\n%s\n\n\x00\n" % header self.assertEquals(stomper.ack(messageid, transactionid), correct) @@ -296,42 +301,32 @@ def testSubscribe(self): correct = "SUBSCRIBE\ndestination: %s\nack: %s\n\n\x00\n" % (dest, ack) self.assertEquals(stomper.subscribe(dest), correct) - def testConnect(self): username, password = 'bob', '123' correct = "CONNECT\nlogin:%s\npasscode:%s\n\n\x00\n" % (username, password) self.assertEquals(stomper.connect(username, password), correct) - def testDisconnect(self): correct = "DISCONNECT\n\n\x00\n" self.assertEquals(stomper.disconnect(), correct) - def testSend(self): dest, transactionid, msg = '/queue/myplace', '', '123 456 789' correct = "SEND\ndestination: %s\n\n%s\n%s\x00\n" % (dest, '', msg) result = stomper.send(dest, msg, transactionid) - + # print "result: " # pprint.pprint(result) # print # print "correct: " # pprint.pprint(correct) -# print - +# print self.assertEquals(result, correct) dest, transactionid, msg = '/queue/myplace', '987', '123 456 789' correct = "SEND\ndestination: %s\ntransaction: %s\n\n%s\x00\n" % (dest, transactionid, msg) self.assertEquals(stomper.send(dest, msg, transactionid), correct) - - - - if __name__ == "__main__": unittest.main() - - \ No newline at end of file diff --git a/setup.py b/setup.py index a284932..06ac177 100644 --- a/setup.py +++ b/setup.py @@ -9,16 +9,22 @@ from setuptools import setup, find_packages -Name='stomper' -ProjecUrl="http://code.google.com/p/stomper" -Version='0.2.2' # alpha release -Author='Oisin Mulvihill' -AuthorEmail='oisin dot mulvihill at gmail com' -Maintainer=' Oisin Mulvihill' -Summary='This is a transport neutral client implementation of the STOMP protocol.' -License='http://www.apache.org/licenses/LICENSE-2.0' -ShortDescription="This is a transport neutral client implementation of the STOMP protocol." -Classifiers=[ +Name = 'stomper' +ProjectUrl = "http://code.google.com/p/stomper" +Version = '0.2.5' +Author = 'Oisin Mulvihill' +AuthorEmail = 'oisin dot mulvihill at gmail com' +Maintainer = 'Oisin Mulvihill' +Summary = ( + 'This is a transport neutral client implementation ' + 'of the STOMP protocol.' +) +License = 'http://www.apache.org/licenses/LICENSE-2.0' +ShortDescription = ( + "This is a transport neutral client implementation of the " + "STOMP protocol." +) +Classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", @@ -27,7 +33,7 @@ # Recover the ReStructuredText docs: fd = file("lib/stomper/doc/stomper.stx") -Description=fd.read() +Description = fd.read() fd.close() TestSuite = 'stomper.tests' @@ -35,14 +41,11 @@ # stop any logger not found messages if tests are run. #stomper.utils.log_init(logging.CRITICAL) - -ProjectScripts = [ -# '', -] +ProjectScripts = [] PackageData = { # If any package contains *.txt or *.rst files, include them: - 'stomper': ['doc/*.stx',], + 'stomper': ['doc/*.stx'], } @@ -53,14 +56,13 @@ ] setup( -# url=ProjecUrl, name=Name, version=Version, author=Author, author_email=AuthorEmail, description=ShortDescription, long_description=Description, - url=ProjecUrl, + url=ProjectUrl, license=License, classifiers=Classifiers, install_requires=needed, @@ -68,7 +70,5 @@ scripts=ProjectScripts, packages=find_packages('lib'), package_data=PackageData, - package_dir = {'': 'lib'}, + package_dir={'': 'lib'}, ) - -