Merge lp:~elmo/ubuntu/oneiric/libstomp-ruby/bug-707317 into lp:ubuntu/oneiric/libstomp-ruby

Proposed by James Troup
Status: Merged
Merged at revision: 5
Proposed branch: lp:~elmo/ubuntu/oneiric/libstomp-ruby/bug-707317
Merge into: lp:ubuntu/oneiric/libstomp-ruby
Diff against target: 5091 lines (+3522/-1302)
39 files modified
.gitignore (+5/-0)
.pc/.version (+0/-1)
.pc/applied-patches (+0/-2)
.pc/case_statement_compatible_1.9.2.patch/lib/stomp.rb (+0/-422)
.pc/getc_returns_a_string_1.9.patch/lib/stomp.rb (+0/-416)
CHANGELOG.rdoc (+84/-0)
LICENSE (+202/-0)
README.rdoc (+107/-0)
Rakefile (+77/-0)
bin/catstomp (+55/-0)
bin/stompcat (+56/-0)
debian/changelog (+23/-0)
debian/control (+4/-4)
debian/patches/case_statement_compatible_1.9.2.patch (+0/-23)
debian/patches/getc_returns_a_string_1.9.patch (+0/-27)
debian/patches/series (+0/-2)
debian/rules (+12/-0)
debian/source/format (+0/-1)
examples/consumer.rb (+19/-0)
examples/logexamp.rb (+50/-0)
examples/publisher.rb (+17/-0)
examples/slogger.rb (+100/-0)
lib/stomp.rb (+7/-404)
lib/stomp/client.rb (+340/-0)
lib/stomp/connection.rb (+559/-0)
lib/stomp/errors.rb (+33/-0)
lib/stomp/ext/hash.rb (+24/-0)
lib/stomp/message.rb (+68/-0)
lib/stomp/version.rb (+8/-0)
spec/client_shared_examples.rb (+69/-0)
spec/client_spec.rb (+312/-0)
spec/connection_spec.rb (+365/-0)
spec/message_spec.rb (+56/-0)
spec/spec_helper.rb (+6/-0)
stomp.gemspec (+66/-0)
test/test_client.rb (+364/-0)
test/test_connection.rb (+278/-0)
test/test_helper.rb (+38/-0)
test/test_message.rb (+118/-0)
To merge this branch: bzr merge lp:~elmo/ubuntu/oneiric/libstomp-ruby/bug-707317
Reviewer Review Type Date Requested Status
Dave Walker (community) Approve
James Troup (community) Needs Resubmitting
Stefano Rivera Needs Fixing
Ubuntu branches Pending
Review via email: mp+67463@code.launchpad.net

Description of the change

New upstream version, required for mcollective and also fixes LP
#707317 and Debian #598564.

To post a comment you must log in.
Revision history for this message
Stefano Rivera (stefanor) wrote :

I know little about Debian ruby packaging. But I can spot some issues:
* The version number really should be 1.1.9-0ubuntu1.
* We shouldn't change source format just because we don't have any packages.
* E: libstomp-ruby: ruby-script-but-no-ruby-dep usr/bin/catstomp
  E: libstomp-ruby: ruby-script-but-no-ruby-dep usr/bin/stompcat

review: Needs Fixing
Revision history for this message
James Troup (elmo) wrote :

Stefano Rivera <email address hidden> writes:

> * The version number really should be 1.1.9-0ubuntu1.

Even if I NMU it into Debian? ;-)

> * We shouldn't change source format just because we don't have any
> packages.

I assume you mean patches? If so, sorry, but why not? There are no
upstream changes anymore so making it source format 1 makes it easier to
backport to older distributions and AFAICS doesn't have any negative
side effects. What am I missing?

> * E: libstomp-ruby: ruby-script-but-no-ruby-dep usr/bin/catstomp
> E: libstomp-ruby: ruby-script-but-no-ruby-dep usr/bin/stompcat

Well, it has an indirect dependency via libstomp-ruby1.8 |
libstomp-ruby1.9.1. I guess I can make it an explicit dependency, if
you want.

--
James

Revision history for this message
Stefano Rivera (stefanor) wrote :

> Even if I NMU it into Debian? ;-)

Then the changelog would say "Non-maintainer upload." :)

> I assume you mean patches? If so, sorry, but why not?

Yeah, that's what I meant. Just because we don't like to deviate unnecessarily from Debian. I wouldn't change source format in an NMU either (but then I guess I'm pretty conservative when touching other people's packages in Debian).

One of the advantages of 3.0 (quilt) is that you don't need to add a patch system to add a patch neatly, it's already there.

IIRC 3.0 (quilt) is supported all the way back to etch, so backporting shouldn't be a big deal either.

The indirect dependency is fine with me (esp. as I prefer not deviating unnecessarily in Ubuntu). The python helpers provide dependencies on python I'd have expected the same for ruby, but as I said I'm completely unfamiliar with it.

Revision history for this message
Dave Walker (davewalker) wrote :

Hi James,

I agree that dropping the patch system is a bit of a pain if we need to introduce patches; particularly for SRU's where *adding* a patch system is frowned upon (traditionally favouring directly applying the patches!). I also thought there was a drive to try and get Debian packages to 3.0 ($something).

Anyway, *I* would favour trying to stay with 3.0 (quilt).. However, I don't care that strongly. If you NMU this to Debian then i care even less; as i'll sync whatever you put there making this branch obsolete.

This is currently blocking mcollective installation on Oneiric, so please update on what you want to do; and lets get it done.

Thanks.

review: Needs Information
Revision history for this message
Chris Halse Rogers (raof) wrote :

I'm marking this as In Progress to drop it off the sponsorship queue - it sounds like this is going to be resolved by NMUing this package into Debian and then syncing to Ubuntu. If that's the case, this merge request can be deleted.

If that's not the case, then the comments above apply :)

Revision history for this message
James Troup (elmo) wrote :

> I know little about Debian ruby packaging. But I can spot some issues:
> * The version number really should be 1.1.9-0ubuntu1.
> * We shouldn't change source format just because we don't have any packages.
> * E: libstomp-ruby: ruby-script-but-no-ruby-dep usr/bin/catstomp
> E: libstomp-ruby: ruby-script-but-no-ruby-dep usr/bin/stompcat

I've pushed r10 which fixes all of the above issues.

review: Needs Resubmitting
Revision history for this message
Dave Walker (davewalker) wrote :

Thanks for addressing this, uploading now!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file '.gitignore'
2--- .gitignore 1970-01-01 00:00:00 +0000
3+++ .gitignore 2011-07-31 16:48:34 +0000
4@@ -0,0 +1,5 @@
5+pkg/*
6+doc/*
7+coverage/*
8+*.gem
9+
10
11=== added directory '.pc'
12=== removed directory '.pc'
13=== added file '.pc/.version'
14--- .pc/.version 1970-01-01 00:00:00 +0000
15+++ .pc/.version 2011-07-31 16:48:34 +0000
16@@ -0,0 +1,1 @@
17+2
18
19=== removed file '.pc/.version'
20--- .pc/.version 2010-09-28 00:25:39 +0000
21+++ .pc/.version 1970-01-01 00:00:00 +0000
22@@ -1,1 +0,0 @@
23-2
24
25=== removed file '.pc/applied-patches'
26--- .pc/applied-patches 2010-09-28 00:25:39 +0000
27+++ .pc/applied-patches 1970-01-01 00:00:00 +0000
28@@ -1,2 +0,0 @@
29-getc_returns_a_string_1.9.patch
30-case_statement_compatible_1.9.2.patch
31
32=== removed directory '.pc/case_statement_compatible_1.9.2.patch'
33=== removed directory '.pc/case_statement_compatible_1.9.2.patch/lib'
34=== removed file '.pc/case_statement_compatible_1.9.2.patch/lib/stomp.rb'
35--- .pc/case_statement_compatible_1.9.2.patch/lib/stomp.rb 2010-09-28 00:25:39 +0000
36+++ .pc/case_statement_compatible_1.9.2.patch/lib/stomp.rb 1970-01-01 00:00:00 +0000
37@@ -1,422 +0,0 @@
38-# Copyright 2005-2006 Brian McCallister
39-# Copyright 2006 LogicBlaze Inc.
40-#
41-# Licensed under the Apache License, Version 2.0 (the "License");
42-# you may not use this file except in compliance with the License.
43-# You may obtain a copy of the License at
44-#
45-# http://www.apache.org/licenses/LICENSE-2.0
46-#
47-# Unless required by applicable law or agreed to in writing, software
48-# distributed under the License is distributed on an "AS IS" BASIS,
49-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
50-# See the License for the specific language governing permissions and
51-# limitations under the License.
52-
53-require 'io/wait'
54-require 'socket'
55-require 'thread'
56-
57-module Stomp
58-
59- # Low level connection which maps commands and supports
60- # synchronous receives
61- class Connection
62-
63- def Connection.open(login = "", passcode = "", host='localhost', port=61613, reliable=FALSE, reconnectDelay=5)
64- Connection.new login, passcode, host, port, reliable, reconnectDelay
65- end
66-
67- # Create a connection, requires a login and passcode.
68- # Can accept a host (default is localhost), and port
69- # (default is 61613) to connect to
70- def initialize(login, passcode, host='localhost', port=61613, reliable=false, reconnectDelay=5)
71- @host = host
72- @port = port
73- @login = login
74- @passcode = passcode
75- @transmit_semaphore = Mutex.new
76- @read_semaphore = Mutex.new
77- @socket_semaphore = Mutex.new
78- @reliable = reliable
79- @reconnectDelay = reconnectDelay
80- @closed = FALSE
81- @subscriptions = {}
82- @failure = NIL
83- socket
84- end
85-
86- def socket
87- # Need to look into why the following synchronize does not work.
88- #@read_semaphore.synchronize do
89- s = @socket;
90- while s == NIL or @failure != NIL
91- @failure = NIL
92- begin
93- s = TCPSocket.open @host, @port
94- _transmit(s, "CONNECT", {:login => @login, :passcode => @passcode})
95- @connect = _receive(s)
96- # replay any subscriptions.
97- @subscriptions.each { |k,v| _transmit(s, "SUBSCRIBE", v) }
98- rescue
99- @failure = $!;
100- s=NIL;
101- raise unless @reliable
102- $stderr.print "connect failed: " + $! +" will retry in #{@reconnectDelay}\n";
103- sleep(@reconnectDelay);
104- end
105- end
106- @socket = s
107- return s;
108- #end
109- end
110-
111- # Is this connection open?
112- def open?
113- !@closed
114- end
115-
116- # Is this connection closed?
117- def closed?
118- @closed
119- end
120-
121- # Begin a transaction, requires a name for the transaction
122- def begin name, headers={}
123- headers[:transaction] = name
124- transmit "BEGIN", headers
125- end
126-
127- # Acknowledge a message, used then a subscription has specified
128- # client acknowledgement ( connection.subscribe "/queue/a", :ack => 'client'g
129- #
130- # Accepts a transaction header ( :transaction => 'some_transaction_id' )
131- def ack message_id, headers={}
132- headers['message-id'] = message_id
133- transmit "ACK", headers
134- end
135-
136- # Commit a transaction by name
137- def commit name, headers={}
138- headers[:transaction] = name
139- transmit "COMMIT", headers
140- end
141-
142- # Abort a transaction by name
143- def abort name, headers={}
144- headers[:transaction] = name
145- transmit "ABORT", headers
146- end
147-
148- # Subscribe to a destination, must specify a name
149- def subscribe(name, headers = {}, subId=NIL)
150- headers[:destination] = name
151- transmit "SUBSCRIBE", headers
152-
153- # Store the sub so that we can replay if we reconnect.
154- if @reliable
155- subId = name if subId==NIL
156- @subscriptions[subId]=headers
157- end
158- end
159-
160- # Unsubscribe from a destination, must specify a name
161- def unsubscribe(name, headers = {}, subId=NIL)
162- headers[:destination] = name
163- transmit "UNSUBSCRIBE", headers
164- if @reliable
165- subId = name if subId==NIL
166- @subscriptions.delete(subId)
167- end
168- end
169-
170- # Send message to destination
171- #
172- # Accepts a transaction header ( :transaction => 'some_transaction_id' )
173- def send(destination, message, headers={})
174- headers[:destination] = destination
175- transmit "SEND", headers, message
176- end
177-
178- # Close this connection
179- def disconnect(headers = {})
180- transmit "DISCONNECT", headers
181- end
182-
183- # Return a pending message if one is available, otherwise
184- # return nil
185- def poll
186- @read_semaphore.synchronize do
187- return nil if @socket==NIL or !@socket.ready?
188- return receive
189- end
190- end
191-
192- # Receive a frame, block until the frame is received
193- def __old_receive
194- # The recive my fail so we may need to retry.
195- while TRUE
196- begin
197- s = socket
198- return _receive(s)
199- rescue
200- @failure = $!;
201- raise unless @reliable
202- $stderr.print "receive failed: " + $!;
203- end
204- end
205- end
206-
207- def receive
208- super_result = __old_receive()
209- if super_result.nil? && @reliable
210- $stderr.print "connection.receive returning EOF as nil - resetting connection.\n"
211- @socket = nil
212- super_result = __old_receive()
213- end
214- return super_result
215- end
216-
217- private
218- def _receive( s )
219- line = ' '
220- @read_semaphore.synchronize do
221- line = s.gets while line =~ /^\s*$/
222- return NIL if line == NIL
223- Message.new do |m|
224- m.command = line.chomp
225- m.headers = {}
226- until (line = s.gets.chomp) == ''
227- k = (line.strip[0, line.strip.index(':')]).strip
228- v = (line.strip[line.strip.index(':') + 1, line.strip.length]).strip
229- m.headers[k] = v
230- end
231-
232- if (m.headers['content-length'])
233- m.body = s.read m.headers['content-length'].to_i
234- c = RUBY_VERSION > '1.9' ? s.getc.ord : s.getc
235- raise "Invalid content length received" unless c == 0
236- else
237- m.body = ''
238- if RUBY_VERSION > '1.9'
239- until (c = s.getc.ord) == 0
240- m.body << c.chr
241- end
242- else
243- until (c = s.getc) == 0
244- m.body << c.chr
245- end
246- end
247- end
248- #c = s.getc
249- #raise "Invalid frame termination received" unless c == 10
250- end
251- end
252- end
253-
254- private
255- def transmit(command, headers={}, body='')
256- # The transmit my fail so we may need to retry.
257- while TRUE
258- begin
259- s = socket
260- _transmit(s, command, headers, body)
261- return
262- rescue
263- @failure = $!;
264- raise unless @reliable
265- $stderr.print "transmit failed: " + $!+"\n";
266- end
267- end
268- end
269-
270- private
271- def _transmit(s, command, headers={}, body='')
272- @transmit_semaphore.synchronize do
273- s.puts command
274- headers.each {|k,v| s.puts "#{k}:#{v}" }
275- s.puts "content-length: #{body.length}"
276- s.puts "content-type: text/plain; charset=UTF-8"
277- s.puts
278- s.write body
279- s.write "\0"
280- end
281- end
282- end
283-
284- # Container class for frames, misnamed technically
285- class Message
286- attr_accessor :headers, :body, :command
287-
288- def initialize
289- yield(self) if block_given?
290- end
291-
292- def to_s
293- "<Stomp::Message headers=#{headers.inspect} body='#{body}' command='#{command}' >"
294- end
295- end
296-
297- # Typical Stomp client class. Uses a listener thread to receive frames
298- # from the server, any thread can send.
299- #
300- # Receives all happen in one thread, so consider not doing much processing
301- # in that thread if you have much message volume.
302- class Client
303-
304- # Accepts a username (default ""), password (default ""),
305- # host (default localhost), and port (default 61613)
306- def initialize user="", pass="", host="localhost", port=61613, reliable=false
307- if user =~ /stomp:\/\/(\w+):(\d+)/
308- user = ""
309- pass = ""
310- host = $1
311- port = $2
312- reliable = false
313- elsif user =~ /stomp:\/\/(\w+):(\w+)@(\w+):(\d+)/
314- user = $1
315- pass = $2
316- host = $3
317- port = $4
318- reliable = false
319- end
320-
321- @id_mutex = Mutex.new
322- @ids = 1
323- @connection = Connection.open user, pass, host, port, reliable
324- @listeners = {}
325- @receipt_listeners = {}
326- @running = true
327- @replay_messages_by_txn = Hash.new
328- @listener_thread = Thread.start do
329- while @running
330- message = @connection.receive
331- case
332- when message == NIL:
333- break
334- when message.command == 'MESSAGE':
335- if listener = @listeners[message.headers['destination']]
336- listener.call(message)
337- end
338- when message.command == 'RECEIPT':
339- if listener = @receipt_listeners[message.headers['receipt-id']]
340- listener.call(message)
341- end
342- end
343- end
344- end
345- end
346-
347- # Join the listener thread for this client,
348- # generally used to wait for a quit signal
349- def join
350- @listener_thread.join
351- end
352-
353- # Accepts a username (default ""), password (default ""),
354- # host (default localhost), and port (default 61613)
355- def self.open user="", pass="", host="localhost", port=61613, reliable=false
356- Client.new user, pass, host, port, reliable
357- end
358-
359- # Begin a transaction by name
360- def begin name, headers={}
361- @connection.begin name, headers
362- end
363-
364- # Abort a transaction by name
365- def abort name, headers={}
366- @connection.abort name, headers
367-
368- # lets replay any ack'd messages in this transaction
369- replay_list = @replay_messages_by_txn[name]
370- if replay_list
371- replay_list.each do |message|
372- if listener = @listeners[message.headers['destination']]
373- listener.call(message)
374- end
375- end
376- end
377- end
378-
379- # Commit a transaction by name
380- def commit name, headers={}
381- txn_id = headers[:transaction]
382- @replay_messages_by_txn.delete(txn_id)
383- @connection.commit name, headers
384- end
385-
386- # Subscribe to a destination, must be passed a block
387- # which will be used as a callback listener
388- #
389- # Accepts a transaction header ( :transaction => 'some_transaction_id' )
390- def subscribe destination, headers={}
391- raise "No listener given" unless block_given?
392- @listeners[destination] = lambda {|msg| yield msg}
393- @connection.subscribe destination, headers
394- end
395-
396- # Unsubecribe from a channel
397- def unsubscribe name, headers={}
398- @connection.unsubscribe name, headers
399- @listeners[name] = nil
400- end
401-
402- # Acknowledge a message, used then a subscription has specified
403- # client acknowledgement ( connection.subscribe "/queue/a", :ack => 'client'g
404- #
405- # Accepts a transaction header ( :transaction => 'some_transaction_id' )
406- def acknowledge message, headers={}
407- txn_id = headers[:transaction]
408- if txn_id
409- # lets keep around messages ack'd in this transaction in case we rollback
410- replay_list = @replay_messages_by_txn[txn_id]
411- if replay_list == nil
412- replay_list = []
413- @replay_messages_by_txn[txn_id] = replay_list
414- end
415- replay_list << message
416- end
417- if block_given?
418- headers['receipt'] = register_receipt_listener lambda {|r| yield r}
419- end
420- @connection.ack message.headers['message-id'], headers
421- end
422-
423- # Send message to destination
424- #
425- # If a block is given a receipt will be requested and passed to the
426- # block on receipt
427- #
428- # Accepts a transaction header ( :transaction => 'some_transaction_id' )
429- def send destination, message, headers = {}
430- if block_given?
431- headers['receipt'] = register_receipt_listener lambda {|r| yield r}
432- end
433- @connection.send destination, message, headers
434- end
435-
436- # Is this client open?
437- def open?
438- @connection.open?
439- end
440-
441- # Close out resources in use by this client
442- def close
443- @connection.disconnect
444- @running = false
445- end
446-
447- private
448- def register_receipt_listener listener
449- id = -1
450- @id_mutex.synchronize do
451- id = @ids.to_s
452- @ids = @ids.succ
453- end
454- @receipt_listeners[id] = listener
455- id
456- end
457-
458- end
459-end
460
461=== removed directory '.pc/getc_returns_a_string_1.9.patch'
462=== removed directory '.pc/getc_returns_a_string_1.9.patch/lib'
463=== removed file '.pc/getc_returns_a_string_1.9.patch/lib/stomp.rb'
464--- .pc/getc_returns_a_string_1.9.patch/lib/stomp.rb 2010-09-28 00:25:39 +0000
465+++ .pc/getc_returns_a_string_1.9.patch/lib/stomp.rb 1970-01-01 00:00:00 +0000
466@@ -1,416 +0,0 @@
467-# Copyright 2005-2006 Brian McCallister
468-# Copyright 2006 LogicBlaze Inc.
469-#
470-# Licensed under the Apache License, Version 2.0 (the "License");
471-# you may not use this file except in compliance with the License.
472-# You may obtain a copy of the License at
473-#
474-# http://www.apache.org/licenses/LICENSE-2.0
475-#
476-# Unless required by applicable law or agreed to in writing, software
477-# distributed under the License is distributed on an "AS IS" BASIS,
478-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
479-# See the License for the specific language governing permissions and
480-# limitations under the License.
481-
482-require 'io/wait'
483-require 'socket'
484-require 'thread'
485-
486-module Stomp
487-
488- # Low level connection which maps commands and supports
489- # synchronous receives
490- class Connection
491-
492- def Connection.open(login = "", passcode = "", host='localhost', port=61613, reliable=FALSE, reconnectDelay=5)
493- Connection.new login, passcode, host, port, reliable, reconnectDelay
494- end
495-
496- # Create a connection, requires a login and passcode.
497- # Can accept a host (default is localhost), and port
498- # (default is 61613) to connect to
499- def initialize(login, passcode, host='localhost', port=61613, reliable=false, reconnectDelay=5)
500- @host = host
501- @port = port
502- @login = login
503- @passcode = passcode
504- @transmit_semaphore = Mutex.new
505- @read_semaphore = Mutex.new
506- @socket_semaphore = Mutex.new
507- @reliable = reliable
508- @reconnectDelay = reconnectDelay
509- @closed = FALSE
510- @subscriptions = {}
511- @failure = NIL
512- socket
513- end
514-
515- def socket
516- # Need to look into why the following synchronize does not work.
517- #@read_semaphore.synchronize do
518- s = @socket;
519- while s == NIL or @failure != NIL
520- @failure = NIL
521- begin
522- s = TCPSocket.open @host, @port
523- _transmit(s, "CONNECT", {:login => @login, :passcode => @passcode})
524- @connect = _receive(s)
525- # replay any subscriptions.
526- @subscriptions.each { |k,v| _transmit(s, "SUBSCRIBE", v) }
527- rescue
528- @failure = $!;
529- s=NIL;
530- raise unless @reliable
531- $stderr.print "connect failed: " + $! +" will retry in #{@reconnectDelay}\n";
532- sleep(@reconnectDelay);
533- end
534- end
535- @socket = s
536- return s;
537- #end
538- end
539-
540- # Is this connection open?
541- def open?
542- !@closed
543- end
544-
545- # Is this connection closed?
546- def closed?
547- @closed
548- end
549-
550- # Begin a transaction, requires a name for the transaction
551- def begin name, headers={}
552- headers[:transaction] = name
553- transmit "BEGIN", headers
554- end
555-
556- # Acknowledge a message, used then a subscription has specified
557- # client acknowledgement ( connection.subscribe "/queue/a", :ack => 'client'g
558- #
559- # Accepts a transaction header ( :transaction => 'some_transaction_id' )
560- def ack message_id, headers={}
561- headers['message-id'] = message_id
562- transmit "ACK", headers
563- end
564-
565- # Commit a transaction by name
566- def commit name, headers={}
567- headers[:transaction] = name
568- transmit "COMMIT", headers
569- end
570-
571- # Abort a transaction by name
572- def abort name, headers={}
573- headers[:transaction] = name
574- transmit "ABORT", headers
575- end
576-
577- # Subscribe to a destination, must specify a name
578- def subscribe(name, headers = {}, subId=NIL)
579- headers[:destination] = name
580- transmit "SUBSCRIBE", headers
581-
582- # Store the sub so that we can replay if we reconnect.
583- if @reliable
584- subId = name if subId==NIL
585- @subscriptions[subId]=headers
586- end
587- end
588-
589- # Unsubscribe from a destination, must specify a name
590- def unsubscribe(name, headers = {}, subId=NIL)
591- headers[:destination] = name
592- transmit "UNSUBSCRIBE", headers
593- if @reliable
594- subId = name if subId==NIL
595- @subscriptions.delete(subId)
596- end
597- end
598-
599- # Send message to destination
600- #
601- # Accepts a transaction header ( :transaction => 'some_transaction_id' )
602- def send(destination, message, headers={})
603- headers[:destination] = destination
604- transmit "SEND", headers, message
605- end
606-
607- # Close this connection
608- def disconnect(headers = {})
609- transmit "DISCONNECT", headers
610- end
611-
612- # Return a pending message if one is available, otherwise
613- # return nil
614- def poll
615- @read_semaphore.synchronize do
616- return nil if @socket==NIL or !@socket.ready?
617- return receive
618- end
619- end
620-
621- # Receive a frame, block until the frame is received
622- def __old_receive
623- # The recive my fail so we may need to retry.
624- while TRUE
625- begin
626- s = socket
627- return _receive(s)
628- rescue
629- @failure = $!;
630- raise unless @reliable
631- $stderr.print "receive failed: " + $!;
632- end
633- end
634- end
635-
636- def receive
637- super_result = __old_receive()
638- if super_result.nil? && @reliable
639- $stderr.print "connection.receive returning EOF as nil - resetting connection.\n"
640- @socket = nil
641- super_result = __old_receive()
642- end
643- return super_result
644- end
645-
646- private
647- def _receive( s )
648- line = ' '
649- @read_semaphore.synchronize do
650- line = s.gets while line =~ /^\s*$/
651- return NIL if line == NIL
652- Message.new do |m|
653- m.command = line.chomp
654- m.headers = {}
655- until (line = s.gets.chomp) == ''
656- k = (line.strip[0, line.strip.index(':')]).strip
657- v = (line.strip[line.strip.index(':') + 1, line.strip.length]).strip
658- m.headers[k] = v
659- end
660-
661- if (m.headers['content-length'])
662- m.body = s.read m.headers['content-length'].to_i
663- c = s.getc
664- raise "Invalid content length received" unless c == 0
665- else
666- m.body = ''
667- until (c = s.getc) == 0
668- m.body << c.chr
669- end
670- end
671- #c = s.getc
672- #raise "Invalid frame termination received" unless c == 10
673- end
674- end
675- end
676-
677- private
678- def transmit(command, headers={}, body='')
679- # The transmit my fail so we may need to retry.
680- while TRUE
681- begin
682- s = socket
683- _transmit(s, command, headers, body)
684- return
685- rescue
686- @failure = $!;
687- raise unless @reliable
688- $stderr.print "transmit failed: " + $!+"\n";
689- end
690- end
691- end
692-
693- private
694- def _transmit(s, command, headers={}, body='')
695- @transmit_semaphore.synchronize do
696- s.puts command
697- headers.each {|k,v| s.puts "#{k}:#{v}" }
698- s.puts "content-length: #{body.length}"
699- s.puts "content-type: text/plain; charset=UTF-8"
700- s.puts
701- s.write body
702- s.write "\0"
703- end
704- end
705- end
706-
707- # Container class for frames, misnamed technically
708- class Message
709- attr_accessor :headers, :body, :command
710-
711- def initialize
712- yield(self) if block_given?
713- end
714-
715- def to_s
716- "<Stomp::Message headers=#{headers.inspect} body='#{body}' command='#{command}' >"
717- end
718- end
719-
720- # Typical Stomp client class. Uses a listener thread to receive frames
721- # from the server, any thread can send.
722- #
723- # Receives all happen in one thread, so consider not doing much processing
724- # in that thread if you have much message volume.
725- class Client
726-
727- # Accepts a username (default ""), password (default ""),
728- # host (default localhost), and port (default 61613)
729- def initialize user="", pass="", host="localhost", port=61613, reliable=false
730- if user =~ /stomp:\/\/(\w+):(\d+)/
731- user = ""
732- pass = ""
733- host = $1
734- port = $2
735- reliable = false
736- elsif user =~ /stomp:\/\/(\w+):(\w+)@(\w+):(\d+)/
737- user = $1
738- pass = $2
739- host = $3
740- port = $4
741- reliable = false
742- end
743-
744- @id_mutex = Mutex.new
745- @ids = 1
746- @connection = Connection.open user, pass, host, port, reliable
747- @listeners = {}
748- @receipt_listeners = {}
749- @running = true
750- @replay_messages_by_txn = Hash.new
751- @listener_thread = Thread.start do
752- while @running
753- message = @connection.receive
754- case
755- when message == NIL:
756- break
757- when message.command == 'MESSAGE':
758- if listener = @listeners[message.headers['destination']]
759- listener.call(message)
760- end
761- when message.command == 'RECEIPT':
762- if listener = @receipt_listeners[message.headers['receipt-id']]
763- listener.call(message)
764- end
765- end
766- end
767- end
768- end
769-
770- # Join the listener thread for this client,
771- # generally used to wait for a quit signal
772- def join
773- @listener_thread.join
774- end
775-
776- # Accepts a username (default ""), password (default ""),
777- # host (default localhost), and port (default 61613)
778- def self.open user="", pass="", host="localhost", port=61613, reliable=false
779- Client.new user, pass, host, port, reliable
780- end
781-
782- # Begin a transaction by name
783- def begin name, headers={}
784- @connection.begin name, headers
785- end
786-
787- # Abort a transaction by name
788- def abort name, headers={}
789- @connection.abort name, headers
790-
791- # lets replay any ack'd messages in this transaction
792- replay_list = @replay_messages_by_txn[name]
793- if replay_list
794- replay_list.each do |message|
795- if listener = @listeners[message.headers['destination']]
796- listener.call(message)
797- end
798- end
799- end
800- end
801-
802- # Commit a transaction by name
803- def commit name, headers={}
804- txn_id = headers[:transaction]
805- @replay_messages_by_txn.delete(txn_id)
806- @connection.commit name, headers
807- end
808-
809- # Subscribe to a destination, must be passed a block
810- # which will be used as a callback listener
811- #
812- # Accepts a transaction header ( :transaction => 'some_transaction_id' )
813- def subscribe destination, headers={}
814- raise "No listener given" unless block_given?
815- @listeners[destination] = lambda {|msg| yield msg}
816- @connection.subscribe destination, headers
817- end
818-
819- # Unsubecribe from a channel
820- def unsubscribe name, headers={}
821- @connection.unsubscribe name, headers
822- @listeners[name] = nil
823- end
824-
825- # Acknowledge a message, used then a subscription has specified
826- # client acknowledgement ( connection.subscribe "/queue/a", :ack => 'client'g
827- #
828- # Accepts a transaction header ( :transaction => 'some_transaction_id' )
829- def acknowledge message, headers={}
830- txn_id = headers[:transaction]
831- if txn_id
832- # lets keep around messages ack'd in this transaction in case we rollback
833- replay_list = @replay_messages_by_txn[txn_id]
834- if replay_list == nil
835- replay_list = []
836- @replay_messages_by_txn[txn_id] = replay_list
837- end
838- replay_list << message
839- end
840- if block_given?
841- headers['receipt'] = register_receipt_listener lambda {|r| yield r}
842- end
843- @connection.ack message.headers['message-id'], headers
844- end
845-
846- # Send message to destination
847- #
848- # If a block is given a receipt will be requested and passed to the
849- # block on receipt
850- #
851- # Accepts a transaction header ( :transaction => 'some_transaction_id' )
852- def send destination, message, headers = {}
853- if block_given?
854- headers['receipt'] = register_receipt_listener lambda {|r| yield r}
855- end
856- @connection.send destination, message, headers
857- end
858-
859- # Is this client open?
860- def open?
861- @connection.open?
862- end
863-
864- # Close out resources in use by this client
865- def close
866- @connection.disconnect
867- @running = false
868- end
869-
870- private
871- def register_receipt_listener listener
872- id = -1
873- @id_mutex.synchronize do
874- id = @ids.to_s
875- @ids = @ids.succ
876- end
877- @receipt_listeners[id] = listener
878- id
879- end
880-
881- end
882-end
883
884=== added file 'CHANGELOG.rdoc'
885--- CHANGELOG.rdoc 1970-01-01 00:00:00 +0000
886+++ CHANGELOG.rdoc 2011-07-31 16:48:34 +0000
887@@ -0,0 +1,84 @@
888+== 1.1.9 2011-15-06
889+
890+* Support wildcard destinations
891+* Handle subscribe with string or symbol ID
892+* Check for duplicate subscriptions in spec tests
893+* Support AMQ and Apollo servers in uinit tests
894+* Correct UTF-8 (Unicode) content-length calcualtion in Ruby 1.9
895+* Send of a nil body causes exception
896+* Add optional callback logging. See the examples install directory, files logexamp.rb and slogger.rb
897+* Correct date stamps in this file
898+
899+== 1.1.8 2011-16-03
900+
901+* Set KEEPALIVE on connection socket options
902+* Attempt to support JRuby more robustly (poll remains broken)
903+* Switch to ruby supplied IO#ready?
904+* Test enhancements for suppress_content_length header
905+* Miscellaneous small documentation updates
906+* Add parse_timeout parameter for use with hashed logins
907+* Allow connection to hosts with a - (dash) in the host name
908+* Add limit parameter to thread joins
909+
910+== 1.1.7 2011-09-01
911+
912+* Binary parse of raw STOMP frame
913+* Fix broken tests on Ruby 1.9.2
914+
915+== 1.1.6 2010-10-06
916+
917+* Fixed multi-thread app hanging
918+
919+== 1.1.5 2010-17-03
920+
921+* Added publish method (send is now deprecated)
922+* Changes on Rake File
923+* Added original_destination header to unreceive
924+* suppress content length header is send on the message for future handling (like unreceive)
925+
926+== 1.1.4 2010-21-01
927+
928+* Added unreceive message method that sends the message back to its queue or to the
929+ dead letter queue, depending on the :max_redeliveries option, similar to a13m one.
930+* Added environment variable option for running 'rake test' on any stomp server, using any port with any user.
931+* Added suppress_content_length header option for ActiveMQ knowing it is a text message (see:
932+ http://juretta.com/log/2009/05/24/activemq-jms-stomp/)
933+* Fixed some bugs with Ruby 1.9 (concatenate string + exception)
934+* Major changes on message parsing feature
935+* Fixed bug with old socket not being closed when using failover
936+* Fixed broken poll method on Connection
937+* Fixed broken close method on Client
938+* Added connection_frame accessor
939+* Added disconnect receipt
940+
941+== 1.1.3 2009-24-11
942+
943+* Failover support
944+* SSL support
945+* Stomp::Connection and Stomp::Client accept a hash on their constructor
946+
947+== 1.1 2009-27-02
948+
949+* Ruby 1.9 Support
950+* Add support for connect_headers, to control the CONNECT command.
951+* Refactored lib dir to separate concerns.
952+* Better test coverage
953+* General code cleanup.
954+
955+== 1.0.6 2008-05-08
956+
957+* Whitespace cleanup
958+* Refactored Rakefile and added stomp.gemspec for GitHub friendliness.
959+* Added .gitignore file
960+* Refactored layout of lib dir to separate concerns
961+* Cleanup of initializers, and provide Client accessors for reading values (for testing)
962+* Removed test/test_url_* files as they only differed from the test_client.rb in their
963+ setup. Super UnDry. Added URL tests to cover stomp URL as param.
964+* Created initial RSpec specs which stub/mock objects and should not require a running
965+ Stomp server instance.
966+
967+== v1.0.5
968+
969+SVN rev 86 clone from http://svn.codehaus.org/stomp/ruby/trunk
970+
971+git-svn-id: http://svn.codehaus.org/stomp/ruby/trunk@86 fd4e7336-3dff-0310-b68a-b6615a75f13b
972
973=== added file 'LICENSE'
974--- LICENSE 1970-01-01 00:00:00 +0000
975+++ LICENSE 2011-07-31 16:48:34 +0000
976@@ -0,0 +1,202 @@
977+
978+ Apache License
979+ Version 2.0, January 2004
980+ http://www.apache.org/licenses/
981+
982+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
983+
984+ 1. Definitions.
985+
986+ "License" shall mean the terms and conditions for use, reproduction,
987+ and distribution as defined by Sections 1 through 9 of this document.
988+
989+ "Licensor" shall mean the copyright owner or entity authorized by
990+ the copyright owner that is granting the License.
991+
992+ "Legal Entity" shall mean the union of the acting entity and all
993+ other entities that control, are controlled by, or are under common
994+ control with that entity. For the purposes of this definition,
995+ "control" means (i) the power, direct or indirect, to cause the
996+ direction or management of such entity, whether by contract or
997+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
998+ outstanding shares, or (iii) beneficial ownership of such entity.
999+
1000+ "You" (or "Your") shall mean an individual or Legal Entity
1001+ exercising permissions granted by this License.
1002+
1003+ "Source" form shall mean the preferred form for making modifications,
1004+ including but not limited to software source code, documentation
1005+ source, and configuration files.
1006+
1007+ "Object" form shall mean any form resulting from mechanical
1008+ transformation or translation of a Source form, including but
1009+ not limited to compiled object code, generated documentation,
1010+ and conversions to other media types.
1011+
1012+ "Work" shall mean the work of authorship, whether in Source or
1013+ Object form, made available under the License, as indicated by a
1014+ copyright notice that is included in or attached to the work
1015+ (an example is provided in the Appendix below).
1016+
1017+ "Derivative Works" shall mean any work, whether in Source or Object
1018+ form, that is based on (or derived from) the Work and for which the
1019+ editorial revisions, annotations, elaborations, or other modifications
1020+ represent, as a whole, an original work of authorship. For the purposes
1021+ of this License, Derivative Works shall not include works that remain
1022+ separable from, or merely link (or bind by name) to the interfaces of,
1023+ the Work and Derivative Works thereof.
1024+
1025+ "Contribution" shall mean any work of authorship, including
1026+ the original version of the Work and any modifications or additions
1027+ to that Work or Derivative Works thereof, that is intentionally
1028+ submitted to Licensor for inclusion in the Work by the copyright owner
1029+ or by an individual or Legal Entity authorized to submit on behalf of
1030+ the copyright owner. For the purposes of this definition, "submitted"
1031+ means any form of electronic, verbal, or written communication sent
1032+ to the Licensor or its representatives, including but not limited to
1033+ communication on electronic mailing lists, source code control systems,
1034+ and issue tracking systems that are managed by, or on behalf of, the
1035+ Licensor for the purpose of discussing and improving the Work, but
1036+ excluding communication that is conspicuously marked or otherwise
1037+ designated in writing by the copyright owner as "Not a Contribution."
1038+
1039+ "Contributor" shall mean Licensor and any individual or Legal Entity
1040+ on behalf of whom a Contribution has been received by Licensor and
1041+ subsequently incorporated within the Work.
1042+
1043+ 2. Grant of Copyright License. Subject to the terms and conditions of
1044+ this License, each Contributor hereby grants to You a perpetual,
1045+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
1046+ copyright license to reproduce, prepare Derivative Works of,
1047+ publicly display, publicly perform, sublicense, and distribute the
1048+ Work and such Derivative Works in Source or Object form.
1049+
1050+ 3. Grant of Patent License. Subject to the terms and conditions of
1051+ this License, each Contributor hereby grants to You a perpetual,
1052+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
1053+ (except as stated in this section) patent license to make, have made,
1054+ use, offer to sell, sell, import, and otherwise transfer the Work,
1055+ where such license applies only to those patent claims licensable
1056+ by such Contributor that are necessarily infringed by their
1057+ Contribution(s) alone or by combination of their Contribution(s)
1058+ with the Work to which such Contribution(s) was submitted. If You
1059+ institute patent litigation against any entity (including a
1060+ cross-claim or counterclaim in a lawsuit) alleging that the Work
1061+ or a Contribution incorporated within the Work constitutes direct
1062+ or contributory patent infringement, then any patent licenses
1063+ granted to You under this License for that Work shall terminate
1064+ as of the date such litigation is filed.
1065+
1066+ 4. Redistribution. You may reproduce and distribute copies of the
1067+ Work or Derivative Works thereof in any medium, with or without
1068+ modifications, and in Source or Object form, provided that You
1069+ meet the following conditions:
1070+
1071+ (a) You must give any other recipients of the Work or
1072+ Derivative Works a copy of this License; and
1073+
1074+ (b) You must cause any modified files to carry prominent notices
1075+ stating that You changed the files; and
1076+
1077+ (c) You must retain, in the Source form of any Derivative Works
1078+ that You distribute, all copyright, patent, trademark, and
1079+ attribution notices from the Source form of the Work,
1080+ excluding those notices that do not pertain to any part of
1081+ the Derivative Works; and
1082+
1083+ (d) If the Work includes a "NOTICE" text file as part of its
1084+ distribution, then any Derivative Works that You distribute must
1085+ include a readable copy of the attribution notices contained
1086+ within such NOTICE file, excluding those notices that do not
1087+ pertain to any part of the Derivative Works, in at least one
1088+ of the following places: within a NOTICE text file distributed
1089+ as part of the Derivative Works; within the Source form or
1090+ documentation, if provided along with the Derivative Works; or,
1091+ within a display generated by the Derivative Works, if and
1092+ wherever such third-party notices normally appear. The contents
1093+ of the NOTICE file are for informational purposes only and
1094+ do not modify the License. You may add Your own attribution
1095+ notices within Derivative Works that You distribute, alongside
1096+ or as an addendum to the NOTICE text from the Work, provided
1097+ that such additional attribution notices cannot be construed
1098+ as modifying the License.
1099+
1100+ You may add Your own copyright statement to Your modifications and
1101+ may provide additional or different license terms and conditions
1102+ for use, reproduction, or distribution of Your modifications, or
1103+ for any such Derivative Works as a whole, provided Your use,
1104+ reproduction, and distribution of the Work otherwise complies with
1105+ the conditions stated in this License.
1106+
1107+ 5. Submission of Contributions. Unless You explicitly state otherwise,
1108+ any Contribution intentionally submitted for inclusion in the Work
1109+ by You to the Licensor shall be under the terms and conditions of
1110+ this License, without any additional terms or conditions.
1111+ Notwithstanding the above, nothing herein shall supersede or modify
1112+ the terms of any separate license agreement you may have executed
1113+ with Licensor regarding such Contributions.
1114+
1115+ 6. Trademarks. This License does not grant permission to use the trade
1116+ names, trademarks, service marks, or product names of the Licensor,
1117+ except as required for reasonable and customary use in describing the
1118+ origin of the Work and reproducing the content of the NOTICE file.
1119+
1120+ 7. Disclaimer of Warranty. Unless required by applicable law or
1121+ agreed to in writing, Licensor provides the Work (and each
1122+ Contributor provides its Contributions) on an "AS IS" BASIS,
1123+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
1124+ implied, including, without limitation, any warranties or conditions
1125+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
1126+ PARTICULAR PURPOSE. You are solely responsible for determining the
1127+ appropriateness of using or redistributing the Work and assume any
1128+ risks associated with Your exercise of permissions under this License.
1129+
1130+ 8. Limitation of Liability. In no event and under no legal theory,
1131+ whether in tort (including negligence), contract, or otherwise,
1132+ unless required by applicable law (such as deliberate and grossly
1133+ negligent acts) or agreed to in writing, shall any Contributor be
1134+ liable to You for damages, including any direct, indirect, special,
1135+ incidental, or consequential damages of any character arising as a
1136+ result of this License or out of the use or inability to use the
1137+ Work (including but not limited to damages for loss of goodwill,
1138+ work stoppage, computer failure or malfunction, or any and all
1139+ other commercial damages or losses), even if such Contributor
1140+ has been advised of the possibility of such damages.
1141+
1142+ 9. Accepting Warranty or Additional Liability. While redistributing
1143+ the Work or Derivative Works thereof, You may choose to offer,
1144+ and charge a fee for, acceptance of support, warranty, indemnity,
1145+ or other liability obligations and/or rights consistent with this
1146+ License. However, in accepting such obligations, You may act only
1147+ on Your own behalf and on Your sole responsibility, not on behalf
1148+ of any other Contributor, and only if You agree to indemnify,
1149+ defend, and hold each Contributor harmless for any liability
1150+ incurred by, or claims asserted against, such Contributor by reason
1151+ of your accepting any such warranty or additional liability.
1152+
1153+ END OF TERMS AND CONDITIONS
1154+
1155+ APPENDIX: How to apply the Apache License to your work.
1156+
1157+ To apply the Apache License to your work, attach the following
1158+ boilerplate notice, with the fields enclosed by brackets "[]"
1159+ replaced with your own identifying information. (Don't include
1160+ the brackets!) The text should be enclosed in the appropriate
1161+ comment syntax for the file format. We also recommend that a
1162+ file or class name and description of purpose be included on the
1163+ same "printed page" as the copyright notice for easier
1164+ identification within third-party archives.
1165+
1166+ Copyright [yyyy] [name of copyright owner]
1167+
1168+ Licensed under the Apache License, Version 2.0 (the "License");
1169+ you may not use this file except in compliance with the License.
1170+ You may obtain a copy of the License at
1171+
1172+ http://www.apache.org/licenses/LICENSE-2.0
1173+
1174+ Unless required by applicable law or agreed to in writing, software
1175+ distributed under the License is distributed on an "AS IS" BASIS,
1176+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1177+ See the License for the specific language governing permissions and
1178+ limitations under the License.
1179
1180=== added file 'README.rdoc'
1181--- README.rdoc 1970-01-01 00:00:00 +0000
1182+++ README.rdoc 2011-07-31 16:48:34 +0000
1183@@ -0,0 +1,107 @@
1184+==README
1185+
1186+* (http://gitorious.org/projects/stomp/)
1187+* (https://github.com/morellon/stomp/)
1188+* (http://stomp.rubyforge.org/)
1189+
1190+===Overview
1191+
1192+An implementation of the Stomp protocol for Ruby. See:
1193+
1194+* [STOMP 1.0] (http://stomp.codehaus.org/Protocol)
1195+* [STOMP 1.0 and 1.1 Draft] (http://stomp.github.com/index.html)
1196+
1197+
1198+===Example Usage
1199+
1200+ client = Stomp::Client.new("test", "user", "localhost", 61613)
1201+ client.send("/my/queue", "hello world!")
1202+ client.subscribe("/my/queue") do |msg|
1203+ p msg
1204+ end
1205+
1206+===Failover + SSL Example URL Usage
1207+
1208+ options = "initialReconnectDelay=5000&randomize=false&useExponentialBackOff=false"
1209+
1210+ #remotehost1 uses SSL, remotehost2 doesn't
1211+ client = Stomp::Client.new("failover:(stomp+ssl://login1:passcode1@remotehost1:61612,stomp://login2:passcode2@remotehost2:61613)?#{options}")
1212+
1213+ client.send("/my/queue", "hello world!")
1214+ client.subscribe("/my/queue") do |msg|
1215+ p msg
1216+ end
1217+
1218+===Hash Login Example Usage
1219+
1220+ hash = {
1221+ :hosts => [
1222+ {:login => "login1", :passcode => "passcode1", :host => "remotehost1", :port => 61612, :ssl => true},
1223+ {:login => "login2", :passcode => "passcode2", :host => "remotehost2", :port => 61613, :ssl => false},
1224+
1225+ ],
1226+ # These are the default parameters, don't need to be set
1227+ :initial_reconnect_delay => 0.01,
1228+ :max_reconnect_delay => 30.0,
1229+ :use_exponential_back_off => true,
1230+ :back_off_multiplier => 2,
1231+ :max_reconnect_attempts => 0,
1232+ :randomize => false,
1233+ :backup => false,
1234+ :timeout => -1,
1235+ :connect_headers => {},
1236+ :parse_timeout => 5,
1237+ :logger => nil,
1238+ }
1239+
1240+ # for client
1241+ client = Stomp::Client.new(hash)
1242+
1243+ # for connection
1244+ connection = Stomp::Connection.new(hash)
1245+
1246+
1247+===Contact info
1248+
1249+Up until March 2009 the project was maintained and primarily developed by Brian McCallister.
1250+
1251+The project is now maintained by Johan Sørensen <johan@johansorensen.com> and others.
1252+
1253+===Source Code
1254+
1255+ https://github.com/morellon/stomp/
1256+ http://gitorious.org/projects/stomp/
1257+ http://github.com/js/stomp/
1258+
1259+===Project urls
1260+
1261+Project Home :
1262+
1263+ http://gitorious.org/projects/stomp/
1264+ http://rubyforge.org/projects/stomp/
1265+
1266+Stomp Protocol Info :
1267+
1268+ http://stomp.github.com/index.html
1269+ http://stomp.codehaus.org/Protocol
1270+
1271+= Contributors
1272+
1273+The following people have contributed to Stomp:
1274+
1275+* Brian McCaliister
1276+* Glenn Rempe <glenn@rempe.us>
1277+* jstrachan
1278+* Marius Mathiesen <marius.mathiesen@gmail.com>
1279+* Johan S√∏rensen <johan@johansorensen.com>
1280+* Thiago Morello <morellon@gmail.com>
1281+* Guy M. Allard
1282+* kookster
1283+* Tony Garnock-Jones <tonyg@lshift.net>
1284+* chirino
1285+* Stefan Saasen
1286+* Neil Wilson
1287+* Dinesh Majrekar
1288+* Kiall Mac Innes
1289+* Rob Skaggs
1290+
1291
1292=== added file 'Rakefile'
1293--- Rakefile 1970-01-01 00:00:00 +0000
1294+++ Rakefile 2011-07-31 16:48:34 +0000
1295@@ -0,0 +1,77 @@
1296+# Copyright 2005-2006 Brian McCallister
1297+#
1298+# Licensed under the Apache License, Version 2.0 (the "License");
1299+# you may not use this file except in compliance with the License.
1300+# You may obtain a copy of the License at
1301+#
1302+# http://www.apache.org/licenses/LICENSE-2.0
1303+#
1304+# Unless required by applicable law or agreed to in writing, software
1305+# distributed under the License is distributed on an "AS IS" BASIS,
1306+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1307+# See the License for the specific language governing permissions and
1308+# limitations under the License.
1309+$:.unshift(File.dirname(__FILE__) + "/lib")
1310+require 'rubygems'
1311+require 'rake'
1312+require 'rake/testtask'
1313+require 'rspec/core/rake_task'
1314+require "stomp/version"
1315+
1316+begin
1317+ require "hanna/rdoctask"
1318+rescue LoadError => e
1319+ require "rdoc/task"
1320+end
1321+
1322+begin
1323+ require 'jeweler'
1324+ Jeweler::Tasks.new do |gem|
1325+ gem.name = "stomp"
1326+ gem.version = Stomp::Version::STRING
1327+ gem.summary = %Q{Ruby client for the Stomp messaging protocol}
1328+ gem.description = %Q{Ruby client for the Stomp messaging protocol}
1329+ gem.email = ["brianm@apache.org", 'marius@stones.com', 'morellon@gmail.com',
1330+ 'allard.guy.m@gmail.com' ]
1331+ gem.homepage = "https://rubygems.org/gems/stomp"
1332+ gem.authors = ["Brian McCallister", 'Marius Mathiesen', 'Thiago Morello',
1333+ 'Guy M. Allard']
1334+ gem.add_development_dependency "rspec", '>= 2.3'
1335+ end
1336+ Jeweler::GemcutterTasks.new
1337+rescue LoadError
1338+ puts "Jeweler not available. Install it with: gem install jeweler"
1339+end
1340+
1341+desc 'Run the specs'
1342+RSpec::Core::RakeTask.new(:spec) do |t|
1343+ t.rspec_opts = ['--colour']
1344+ t.pattern = 'spec/**/*_spec.rb'
1345+end
1346+
1347+desc "Rspec : run all with RCov"
1348+RSpec::Core::RakeTask.new('spec:rcov') do |t|
1349+ t.pattern = 'spec/**/*_spec.rb'
1350+ t.rcov = true
1351+ t.rcov_opts = ['--exclude', 'gems', '--exclude', 'spec']
1352+end
1353+
1354+Rake::RDocTask.new do |rdoc|
1355+ rdoc.main = "README.rdoc"
1356+ rdoc.rdoc_dir = "doc"
1357+ rdoc.title = "Stomp"
1358+ rdoc.options += %w[ --line-numbers --inline-source --charset utf-8 ]
1359+ rdoc.rdoc_files.include("README.rdoc", "CHANGELOG.rdoc")
1360+ rdoc.rdoc_files.include("lib/**/*.rb")
1361+end
1362+
1363+Rake::TestTask.new do |t|
1364+ t.libs << "test"
1365+ t.test_files = FileList['test/test*.rb']
1366+ t.verbose = true
1367+end
1368+
1369+task :default => :spec
1370+
1371+
1372+
1373
1374=== added directory 'bin'
1375=== added file 'bin/catstomp'
1376--- bin/catstomp 1970-01-01 00:00:00 +0000
1377+++ bin/catstomp 2011-07-31 16:48:34 +0000
1378@@ -0,0 +1,55 @@
1379+#!/usr/bin/env ruby
1380+#
1381+# Copyright 2006 LogicBlaze Inc.
1382+#
1383+# Licensed under the Apache License, Version 2.0 (the "License");
1384+# you may not use this file except in compliance with the License.
1385+# You may obtain a copy of the License at
1386+#
1387+# http://www.apache.org/licenses/LICENSE-2.0
1388+#
1389+# Unless required by applicable law or agreed to in writing, software
1390+# distributed under the License is distributed on an "AS IS" BASIS,
1391+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1392+# See the License for the specific language governing permissions and
1393+# limitations under the License.
1394+#
1395+begin; require 'rubygems'; rescue; end
1396+require 'stomp'
1397+
1398+#
1399+# This simple script is inspired by the netcat utility. It allows you to send
1400+# input into this process to stomp destination.
1401+#
1402+# Usage: catstomp (destination-name)
1403+#
1404+# Example: ls | catstomp /topic/foo
1405+# Would send the output of the ls command to the stomp destination /topic/foo
1406+#
1407+begin
1408+
1409+ @port = 61613
1410+ @host = "localhost"
1411+ @user = ENV["STOMP_USER"];
1412+ @password = ENV["STOMP_PASSWORD"]
1413+
1414+ @host = ENV["STOMP_HOST"] if ENV["STOMP_HOST"] != nil
1415+ @port = ENV["STOMP_PORT"] if ENV["STOMP_PORT"] != nil
1416+
1417+ @destination = "/topic/default"
1418+ @destination = $*[0] if $*[0] != nil
1419+
1420+ $stderr.print "Connecting to stomp://#{@host}:#{@port} as #{@user}\n"
1421+ @conn = Stomp::Connection.open(@user, @password, @host, @port, true)
1422+ $stderr.print "Sending input to #{@destination}\n"
1423+
1424+ @headers = {'persistent'=>'false'}
1425+ @headers['reply-to'] = $*[1] if $*[1] != nil
1426+
1427+ STDIN.each_line { |line|
1428+ @conn.send @destination, line, @headers
1429+ }
1430+
1431+rescue
1432+end
1433+
1434
1435=== added file 'bin/stompcat'
1436--- bin/stompcat 1970-01-01 00:00:00 +0000
1437+++ bin/stompcat 2011-07-31 16:48:34 +0000
1438@@ -0,0 +1,56 @@
1439+#!/usr/bin/env ruby
1440+#
1441+# Copyright 2006 LogicBlaze Inc.
1442+#
1443+# Licensed under the Apache License, Version 2.0 (the "License");
1444+# you may not use this file except in compliance with the License.
1445+# You may obtain a copy of the License at
1446+#
1447+# http://www.apache.org/licenses/LICENSE-2.0
1448+#
1449+# Unless required by applicable law or agreed to in writing, software
1450+# distributed under the License is distributed on an "AS IS" BASIS,
1451+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1452+# See the License for the specific language governing permissions and
1453+# limitations under the License.
1454+#
1455+begin; require 'rubygems'; rescue; end
1456+require 'stomp'
1457+
1458+#
1459+# This simple script is inspired by the netcat utility. It allows you to receive
1460+# data from a stomp destination and output it.
1461+#
1462+# Usage: stompcat (destination-name)
1463+#
1464+# Example: stompcat /topic/foo
1465+# Would display output that arrives at the /topic/foo stomp destination
1466+#
1467+begin
1468+
1469+ @port = 61613
1470+ @host = "localhost"
1471+ @user = ENV["STOMP_USER"];
1472+ @password = ENV["STOMP_PASSWORD"]
1473+
1474+ @host = ENV["STOMP_HOST"] if ENV["STOMP_HOST"] != nil
1475+ @port = ENV["STOMP_PORT"] if ENV["STOMP_PORT"] != nil
1476+
1477+ @destination = "/topic/default"
1478+ @destination = $*[0] if $*[0] != nil
1479+
1480+ $stderr.print "Connecting to stomp://#{@host}:#{@port} as #{@user}\n"
1481+ @conn = Stomp::Connection.open(@user, @password, @host, @port, true)
1482+ $stderr.print "Getting output from #{@destination}\n"
1483+
1484+ @conn.subscribe(@destination, { :ack =>"client" })
1485+ while true
1486+ @msg = @conn.receive
1487+ $stdout.print @msg.body
1488+ $stdout.flush
1489+ @conn.ack @msg.headers["message-id"]
1490+ end
1491+
1492+rescue
1493+end
1494+
1495
1496=== modified file 'debian/changelog'
1497--- debian/changelog 2010-09-28 00:25:39 +0000
1498+++ debian/changelog 2011-07-31 16:48:34 +0000
1499@@ -1,3 +1,26 @@
1500+libstomp-ruby (1.1.9-0ubuntu1) oneiric; urgency=low
1501+
1502+ * New upstream release (Closes: #598564, LP: #707317).
1503+
1504+ * debian/patches/case_statement_compatible_1.9.2.patch,
1505+ debian/patches/getc_returns_a_string_1.9.patch: obsolete, removed.
1506+ * debian/patches/series: update accordingly.
1507+
1508+ * debian/rules (common-binary-indep): deal with the catstomp and
1509+ stompcat binaries by putting them in libstomp-ruby instead of the
1510+ (ruby-)versioned packages and fixing their hash-bang line to use the
1511+ generic /usr/bin/ruby binary/symlink.
1512+
1513+ * debian/control: have libstomp-ruby depend on libstomp-ruby1.8 or
1514+ libstomp-ruby1.9.1. Make libstomp-ruby$ver suggest libstomp-ruby.
1515+ Update the package description to describe libstomp-ruby's new
1516+ purpose.
1517+
1518+ * debian/control: have libstomp-ruby explicitly depend on ruby for
1519+ catstomp and stompcat to make lintian happy.
1520+
1521+ -- James Troup <james.troup@canonical.com> Sun, 31 Jul 2011 17:14:07 +0100
1522+
1523 libstomp-ruby (1.0.4-3) unstable; urgency=low
1524
1525 * Team upload.
1526
1527=== modified file 'debian/control'
1528--- debian/control 2010-09-28 00:25:39 +0000
1529+++ debian/control 2011-07-31 16:48:34 +0000
1530@@ -12,7 +12,7 @@
1531
1532 Package: libstomp-ruby
1533 Architecture: all
1534-Depends: libstomp-ruby1.8, ${misc:Depends}
1535+Depends: libstomp-ruby1.8 | libstomp-ruby1.9.1, ruby, ${misc:Depends}
1536 Suggests: libstomp-ruby-doc
1537 Description: Ruby bindings for the stomp messaging protocol
1538 Stomp is a text-oriented wire protocol for messaging (MOM/MQ/JMS)
1539@@ -21,13 +21,12 @@
1540 implementation, and Stomp::Client, which is designed as a higher
1541 level convenience API.
1542 .
1543- This package is a dependency package, which depends on the package
1544- containing actual Ruby stomp libraries for the default Ruby version
1545- (currently 1.8).
1546+ This package contains the catstomp and stompcat binaries.
1547
1548 Package: libstomp-ruby1.8
1549 Architecture: all
1550 Depends: ${shlibs:Depends}, ${misc:Depends}, ruby1.8
1551+Suggests: libstomp-ruby
1552 Description: Ruby 1.8 bindings for the stomp messaging protocol
1553 Stomp is a text-oriented wire protocol for messaging (MOM/MQ/JMS)
1554 type systems. This library provides two useful interfaces, a low-
1555@@ -40,6 +39,7 @@
1556 Package: libstomp-ruby1.9.1
1557 Architecture: all
1558 Depends: ${shlibs:Depends}, ${misc:Depends}, ruby1.9.1
1559+Suggests: libstomp-ruby
1560 Description: Ruby 1.9.1 bindings for the stomp messaging protocol
1561 Stomp is a text-oriented wire protocol for messaging (MOM/MQ/JMS)
1562 type systems. This library provides two useful interfaces, a low-
1563
1564=== added directory 'debian/patches'
1565=== removed directory 'debian/patches'
1566=== removed file 'debian/patches/case_statement_compatible_1.9.2.patch'
1567--- debian/patches/case_statement_compatible_1.9.2.patch 2010-09-28 00:25:39 +0000
1568+++ debian/patches/case_statement_compatible_1.9.2.patch 1970-01-01 00:00:00 +0000
1569@@ -1,23 +0,0 @@
1570-From: Marius Mathiesen
1571-Description: Making 1.9 compatible
1572-Origin: upstream, http://github.com/js/stomp/commit/a778661ce9c074ae5b415658d17dd2639f5c9c05
1573-Debian-Bug: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=593079
1574---- a/lib/stomp.rb
1575-+++ b/lib/stomp.rb
1576-@@ -286,13 +286,13 @@ module Stomp
1577- while @running
1578- message = @connection.receive
1579- case
1580-- when message == NIL:
1581-+ when message == NIL
1582- break
1583-- when message.command == 'MESSAGE':
1584-+ when message.command == 'MESSAGE'
1585- if listener = @listeners[message.headers['destination']]
1586- listener.call(message)
1587- end
1588-- when message.command == 'RECEIPT':
1589-+ when message.command == 'RECEIPT'
1590- if listener = @receipt_listeners[message.headers['receipt-id']]
1591- listener.call(message)
1592- end
1593
1594=== removed file 'debian/patches/getc_returns_a_string_1.9.patch'
1595--- debian/patches/getc_returns_a_string_1.9.patch 2010-09-28 00:25:39 +0000
1596+++ debian/patches/getc_returns_a_string_1.9.patch 1970-01-01 00:00:00 +0000
1597@@ -1,27 +0,0 @@
1598-From: Johan Sørensen <johan@johansorensen.com>
1599-Subject: Further Ruby 1.9 support: IO#getc returns a string, instead of a char in 1.9
1600-Origin: upstream, http://github.com/js/stomp/commit/2971a7922f64052c5b308f2a4a92080d7c8b046b
1601---- a/lib/stomp.rb
1602-+++ b/lib/stomp.rb
1603-@@ -194,12 +194,18 @@ module Stomp
1604-
1605- if (m.headers['content-length'])
1606- m.body = s.read m.headers['content-length'].to_i
1607-- c = s.getc
1608-+ c = RUBY_VERSION > '1.9' ? s.getc.ord : s.getc
1609- raise "Invalid content length received" unless c == 0
1610- else
1611- m.body = ''
1612-- until (c = s.getc) == 0
1613-- m.body << c.chr
1614-+ if RUBY_VERSION > '1.9'
1615-+ until (c = s.getc.ord) == 0
1616-+ m.body << c.chr
1617-+ end
1618-+ else
1619-+ until (c = s.getc) == 0
1620-+ m.body << c.chr
1621-+ end
1622- end
1623- end
1624- #c = s.getc
1625
1626=== added file 'debian/patches/series'
1627--- debian/patches/series 1970-01-01 00:00:00 +0000
1628+++ debian/patches/series 2011-07-31 16:48:34 +0000
1629@@ -0,0 +1,1 @@
1630+
1631
1632=== removed file 'debian/patches/series'
1633--- debian/patches/series 2010-09-28 00:25:39 +0000
1634+++ debian/patches/series 1970-01-01 00:00:00 +0000
1635@@ -1,2 +0,0 @@
1636-getc_returns_a_string_1.9.patch
1637-case_statement_compatible_1.9.2.patch
1638
1639=== modified file 'debian/rules'
1640--- debian/rules 2009-01-26 20:38:53 +0000
1641+++ debian/rules 2011-07-31 16:48:34 +0000
1642@@ -2,3 +2,15 @@
1643
1644 include /usr/share/cdbs/1/rules/debhelper.mk
1645 include /usr/share/ruby-pkg-tools/1/class/ruby-setup-rb.mk
1646+
1647+common-binary-indep::
1648+ # Deal with the catstomp and stompcat binaries by putting them
1649+ # into libstomp-ruby and fixing them to use /usr/bin/ruby
1650+ install -d -m 755 -o root -g root debian/libstomp-ruby/usr/
1651+ cp -a debian/$(firstword $(DEB_RUBY_REAL_LIB_PACKAGES))/usr/bin/ debian/libstomp-ruby/usr/bin/
1652+ for script in `find debian/libstomp-ruby/usr/bin/ -type f`; do \
1653+ sed -i -e "1s%.*%#! /usr/bin/ruby%" $$script ; \
1654+ done
1655+ for pkg in $(DEB_RUBY_REAL_LIB_PACKAGES); do \
1656+ rm -fr debian/$$pkg/usr/bin/; \
1657+ done
1658
1659=== added directory 'debian/source'
1660=== removed directory 'debian/source'
1661=== added file 'debian/source/format'
1662--- debian/source/format 1970-01-01 00:00:00 +0000
1663+++ debian/source/format 2011-07-31 16:48:34 +0000
1664@@ -0,0 +1,1 @@
1665+3.0 (quilt)
1666
1667=== removed file 'debian/source/format'
1668--- debian/source/format 2010-06-12 00:08:49 +0000
1669+++ debian/source/format 1970-01-01 00:00:00 +0000
1670@@ -1,1 +0,0 @@
1671-3.0 (quilt)
1672
1673=== added directory 'examples'
1674=== added file 'examples/consumer.rb'
1675--- examples/consumer.rb 1970-01-01 00:00:00 +0000
1676+++ examples/consumer.rb 2011-07-31 16:48:34 +0000
1677@@ -0,0 +1,19 @@
1678+require 'rubygems'
1679+require 'stomp'
1680+
1681+
1682+client = Stomp::Client.new("failover://(stomp://:@localhost:61613,stomp://:@remotehost:61613)?initialReconnectDelay=5000&randomize=false&useExponentialBackOff=false")
1683+puts "Subscribing ronaldo"
1684+client.subscribe("/queue/ronaldo", {:ack => "client", "activemq.prefetchSize" => 1, "activemq.exclusive" => true }) do |msg|
1685+ File.open("file", "a") do |f|
1686+ f.write(msg.body)
1687+ f.write("\n----------------\n")
1688+ end
1689+
1690+ client.acknowledge(msg)
1691+end
1692+
1693+loop do
1694+ sleep(1)
1695+ puts "."
1696+end
1697
1698=== added file 'examples/logexamp.rb'
1699--- examples/logexamp.rb 1970-01-01 00:00:00 +0000
1700+++ examples/logexamp.rb 2011-07-31 16:48:34 +0000
1701@@ -0,0 +1,50 @@
1702+require 'rubygems'
1703+require 'stomp'
1704+require 'logger' # for the 'local' logger
1705+#
1706+$:.unshift(File.dirname(__FILE__))
1707+#
1708+require 'slogger'
1709+#
1710+# A STOMP client program which uses the callback logging facility.
1711+#
1712+llog = Logger::new(STDOUT)
1713+llog.level = Logger::DEBUG
1714+llog.debug "LE Starting"
1715+
1716+# //////////////////////////////////////////////////////////////////////////////
1717+mylog = Slogger::new # The client provided STOMP callback logger
1718+
1719+# //////////////////////////////////////////////////////////////////////////////
1720+user = ENV['STOMP_USER'] ? ENV['STOMP_USER'] : 'guest'
1721+password = ENV['STOMP_PASSWORD'] ? ENV['STOMP_PASSWORD'] : 'guestpw'
1722+host = ENV['STOMP_HOST'] ? ENV['STOMP_HOST'] : 'localhost'
1723+port = ENV['STOMP_PORT'] ? ENV['STOMP_PORT'].to_i : 61613
1724+# //////////////////////////////////////////////////////////////////////////////
1725+# A hash type connect *MUST* be used to enable callback logging.
1726+# //////////////////////////////////////////////////////////////////////////////
1727+hash = { :hosts => [
1728+ {:login => user, :passcode => password, :host => 'noonehome', :port => 2525},
1729+ {:login => user, :passcode => password, :host => host, :port => port},
1730+ ],
1731+ :logger => mylog, # This enables callback logging!
1732+ :max_reconnect_attempts => 5,
1733+ }
1734+
1735+# //////////////////////////////////////////////////////////////////////////////
1736+# For a Connection:
1737+conn = Stomp::Connection.new(hash)
1738+conn.disconnect
1739+# //////////////////////////////////////////////////////////////////////////////
1740+llog.debug "LE Connection processing complete"
1741+
1742+# //////////////////////////////////////////////////////////////////////////////
1743+# For a Client:
1744+conn = Stomp::Client.new(hash)
1745+conn.close
1746+# //////////////////////////////////////////////////////////////////////////////
1747+# llog.debug "LE Client processing complete"
1748+
1749+# //////////////////////////////////////////////////////////////////////////////
1750+llog.debug "LE Ending"
1751+
1752
1753=== added file 'examples/publisher.rb'
1754--- examples/publisher.rb 1970-01-01 00:00:00 +0000
1755+++ examples/publisher.rb 2011-07-31 16:48:34 +0000
1756@@ -0,0 +1,17 @@
1757+require 'rubygems'
1758+require 'stomp'
1759+
1760+#client = Stomp::Client.new("", "", "localhost", 61613)
1761+
1762+client = Stomp::Client.new("failover://(stomp://:@localhost:61613,stomp://:@remotehost:61613)?initialReconnectDelay=5000&randomize=false&useExponentialBackOff=false")
1763+message = "ronaldo #{ARGV[0]}"
1764+
1765+for i in (1..300)
1766+ puts "Sending message"
1767+ client.send("/queue/ronaldo", "#{i}: #{message}", {:persistent => true})
1768+ puts "(#{Time.now})Message sent: #{i}"
1769+ sleep 1
1770+end
1771+
1772+
1773+
1774
1775=== added file 'examples/slogger.rb'
1776--- examples/slogger.rb 1970-01-01 00:00:00 +0000
1777+++ examples/slogger.rb 2011-07-31 16:48:34 +0000
1778@@ -0,0 +1,100 @@
1779+=begin
1780+
1781+Example STOMP call back logger class.
1782+
1783+Optional callback methods:
1784+
1785+ on_connecting: connection starting
1786+ on_connected: successful connect
1787+ on_connectfail: unsuccessful connect (will usually be retried)
1788+ on_disconnect: successful disconnect
1789+
1790+ on_miscerr: on miscellaneous xmit/recv errors
1791+
1792+All methods are optional, at the user's requirements.
1793+
1794+If a method is not provided, it is not called (of course.)
1795+
1796+IMPORTANT NOTE: call back logging methods *MUST* not raise exceptions,
1797+otherwise the underlying STOMP connection will fail in mysterious ways.
1798+
1799+Callback parameters: are a copy of the @parameters instance variable for
1800+the Stomp::Connection.
1801+
1802+=end
1803+
1804+require 'logger' # use the standard Ruby logger .....
1805+
1806+class Slogger
1807+ #
1808+ def initialize(init_parms = nil)
1809+ @log = Logger::new(STDOUT) # User preference
1810+ @log.level = Logger::DEBUG # User preference
1811+ @log.info("Logger initialization complete.")
1812+ end
1813+
1814+ # Log connecting events
1815+ def on_connecting(parms)
1816+ begin
1817+ @log.debug "Connecting: #{info(parms)}"
1818+ rescue
1819+ @log.debug "Connecting oops"
1820+ end
1821+ end
1822+
1823+ # Log connected events
1824+ def on_connected(parms)
1825+ begin
1826+ @log.debug "Connected: #{info(parms)}"
1827+ rescue
1828+ @log.debug "Connected oops"
1829+ end
1830+ end
1831+
1832+ # Log connectfail events
1833+ def on_connectfail(parms)
1834+ begin
1835+ @log.debug "Connect Fail #{info(parms)}"
1836+ rescue
1837+ @log.debug "Connect Fail oops"
1838+ end
1839+ end
1840+
1841+ # Log disconnect events
1842+ def on_disconnect(parms)
1843+ begin
1844+ @log.debug "Disconnected #{info(parms)}"
1845+ rescue
1846+ @log.debug "Disconnected oops"
1847+ end
1848+ end
1849+
1850+
1851+ # Log miscellaneous errors
1852+ def on_miscerr(parms, errstr)
1853+ begin
1854+ @log.debug "Miscellaneous Error #{info(parms)}"
1855+ @log.debug "Miscellaneous Error String #{errstr}"
1856+ rescue
1857+ @log.debug "Miscellaneous Error oops"
1858+ end
1859+ end
1860+
1861+ private
1862+
1863+ def info(parms)
1864+ #
1865+ # Available in the Hash:
1866+ # parms[:cur_host]
1867+ # parms[:cur_port]
1868+ # parms[:cur_login]
1869+ # parms[:cur_passcode]
1870+ # parms[:cur_ssl]
1871+ # parms[:cur_recondelay]
1872+ # parms[:cur_parseto]
1873+ # parms[:cur_conattempts]
1874+ #
1875+ "Host: #{parms[:cur_host]}, Port: #{parms[:cur_port]}, Login: Port: #{parms[:cur_login]}, Passcode: #{parms[:cur_passcode]}"
1876+ end
1877+end # of class
1878+
1879
1880=== removed file 'lib/._stomp.rb'
1881Binary files lib/._stomp.rb 2009-01-26 20:38:53 +0000 and lib/._stomp.rb 1970-01-01 00:00:00 +0000 differ
1882=== added directory 'lib/stomp'
1883=== modified file 'lib/stomp.rb'
1884--- lib/stomp.rb 2010-09-28 00:25:39 +0000
1885+++ lib/stomp.rb 2011-07-31 16:48:34 +0000
1886@@ -13,410 +13,13 @@
1887 # See the License for the specific language governing permissions and
1888 # limitations under the License.
1889
1890-require 'io/wait'
1891-require 'socket'
1892-require 'thread'
1893+require 'stomp/ext/hash'
1894+require 'stomp/connection'
1895+require 'stomp/client'
1896+require 'stomp/message'
1897+require 'stomp/version'
1898+require 'stomp/errors'
1899
1900 module Stomp
1901-
1902- # Low level connection which maps commands and supports
1903- # synchronous receives
1904- class Connection
1905-
1906- def Connection.open(login = "", passcode = "", host='localhost', port=61613, reliable=FALSE, reconnectDelay=5)
1907- Connection.new login, passcode, host, port, reliable, reconnectDelay
1908- end
1909-
1910- # Create a connection, requires a login and passcode.
1911- # Can accept a host (default is localhost), and port
1912- # (default is 61613) to connect to
1913- def initialize(login, passcode, host='localhost', port=61613, reliable=false, reconnectDelay=5)
1914- @host = host
1915- @port = port
1916- @login = login
1917- @passcode = passcode
1918- @transmit_semaphore = Mutex.new
1919- @read_semaphore = Mutex.new
1920- @socket_semaphore = Mutex.new
1921- @reliable = reliable
1922- @reconnectDelay = reconnectDelay
1923- @closed = FALSE
1924- @subscriptions = {}
1925- @failure = NIL
1926- socket
1927- end
1928-
1929- def socket
1930- # Need to look into why the following synchronize does not work.
1931- #@read_semaphore.synchronize do
1932- s = @socket;
1933- while s == NIL or @failure != NIL
1934- @failure = NIL
1935- begin
1936- s = TCPSocket.open @host, @port
1937- _transmit(s, "CONNECT", {:login => @login, :passcode => @passcode})
1938- @connect = _receive(s)
1939- # replay any subscriptions.
1940- @subscriptions.each { |k,v| _transmit(s, "SUBSCRIBE", v) }
1941- rescue
1942- @failure = $!;
1943- s=NIL;
1944- raise unless @reliable
1945- $stderr.print "connect failed: " + $! +" will retry in #{@reconnectDelay}\n";
1946- sleep(@reconnectDelay);
1947- end
1948- end
1949- @socket = s
1950- return s;
1951- #end
1952- end
1953-
1954- # Is this connection open?
1955- def open?
1956- !@closed
1957- end
1958-
1959- # Is this connection closed?
1960- def closed?
1961- @closed
1962- end
1963-
1964- # Begin a transaction, requires a name for the transaction
1965- def begin name, headers={}
1966- headers[:transaction] = name
1967- transmit "BEGIN", headers
1968- end
1969-
1970- # Acknowledge a message, used then a subscription has specified
1971- # client acknowledgement ( connection.subscribe "/queue/a", :ack => 'client'g
1972- #
1973- # Accepts a transaction header ( :transaction => 'some_transaction_id' )
1974- def ack message_id, headers={}
1975- headers['message-id'] = message_id
1976- transmit "ACK", headers
1977- end
1978-
1979- # Commit a transaction by name
1980- def commit name, headers={}
1981- headers[:transaction] = name
1982- transmit "COMMIT", headers
1983- end
1984-
1985- # Abort a transaction by name
1986- def abort name, headers={}
1987- headers[:transaction] = name
1988- transmit "ABORT", headers
1989- end
1990-
1991- # Subscribe to a destination, must specify a name
1992- def subscribe(name, headers = {}, subId=NIL)
1993- headers[:destination] = name
1994- transmit "SUBSCRIBE", headers
1995-
1996- # Store the sub so that we can replay if we reconnect.
1997- if @reliable
1998- subId = name if subId==NIL
1999- @subscriptions[subId]=headers
2000- end
2001- end
2002-
2003- # Unsubscribe from a destination, must specify a name
2004- def unsubscribe(name, headers = {}, subId=NIL)
2005- headers[:destination] = name
2006- transmit "UNSUBSCRIBE", headers
2007- if @reliable
2008- subId = name if subId==NIL
2009- @subscriptions.delete(subId)
2010- end
2011- end
2012-
2013- # Send message to destination
2014- #
2015- # Accepts a transaction header ( :transaction => 'some_transaction_id' )
2016- def send(destination, message, headers={})
2017- headers[:destination] = destination
2018- transmit "SEND", headers, message
2019- end
2020-
2021- # Close this connection
2022- def disconnect(headers = {})
2023- transmit "DISCONNECT", headers
2024- end
2025-
2026- # Return a pending message if one is available, otherwise
2027- # return nil
2028- def poll
2029- @read_semaphore.synchronize do
2030- return nil if @socket==NIL or !@socket.ready?
2031- return receive
2032- end
2033- end
2034-
2035- # Receive a frame, block until the frame is received
2036- def __old_receive
2037- # The recive my fail so we may need to retry.
2038- while TRUE
2039- begin
2040- s = socket
2041- return _receive(s)
2042- rescue
2043- @failure = $!;
2044- raise unless @reliable
2045- $stderr.print "receive failed: " + $!;
2046- end
2047- end
2048- end
2049-
2050- def receive
2051- super_result = __old_receive()
2052- if super_result.nil? && @reliable
2053- $stderr.print "connection.receive returning EOF as nil - resetting connection.\n"
2054- @socket = nil
2055- super_result = __old_receive()
2056- end
2057- return super_result
2058- end
2059-
2060- private
2061- def _receive( s )
2062- line = ' '
2063- @read_semaphore.synchronize do
2064- line = s.gets while line =~ /^\s*$/
2065- return NIL if line == NIL
2066- Message.new do |m|
2067- m.command = line.chomp
2068- m.headers = {}
2069- until (line = s.gets.chomp) == ''
2070- k = (line.strip[0, line.strip.index(':')]).strip
2071- v = (line.strip[line.strip.index(':') + 1, line.strip.length]).strip
2072- m.headers[k] = v
2073- end
2074-
2075- if (m.headers['content-length'])
2076- m.body = s.read m.headers['content-length'].to_i
2077- c = RUBY_VERSION > '1.9' ? s.getc.ord : s.getc
2078- raise "Invalid content length received" unless c == 0
2079- else
2080- m.body = ''
2081- if RUBY_VERSION > '1.9'
2082- until (c = s.getc.ord) == 0
2083- m.body << c.chr
2084- end
2085- else
2086- until (c = s.getc) == 0
2087- m.body << c.chr
2088- end
2089- end
2090- end
2091- #c = s.getc
2092- #raise "Invalid frame termination received" unless c == 10
2093- end
2094- end
2095- end
2096-
2097- private
2098- def transmit(command, headers={}, body='')
2099- # The transmit my fail so we may need to retry.
2100- while TRUE
2101- begin
2102- s = socket
2103- _transmit(s, command, headers, body)
2104- return
2105- rescue
2106- @failure = $!;
2107- raise unless @reliable
2108- $stderr.print "transmit failed: " + $!+"\n";
2109- end
2110- end
2111- end
2112-
2113- private
2114- def _transmit(s, command, headers={}, body='')
2115- @transmit_semaphore.synchronize do
2116- s.puts command
2117- headers.each {|k,v| s.puts "#{k}:#{v}" }
2118- s.puts "content-length: #{body.length}"
2119- s.puts "content-type: text/plain; charset=UTF-8"
2120- s.puts
2121- s.write body
2122- s.write "\0"
2123- end
2124- end
2125- end
2126-
2127- # Container class for frames, misnamed technically
2128- class Message
2129- attr_accessor :headers, :body, :command
2130-
2131- def initialize
2132- yield(self) if block_given?
2133- end
2134-
2135- def to_s
2136- "<Stomp::Message headers=#{headers.inspect} body='#{body}' command='#{command}' >"
2137- end
2138- end
2139-
2140- # Typical Stomp client class. Uses a listener thread to receive frames
2141- # from the server, any thread can send.
2142- #
2143- # Receives all happen in one thread, so consider not doing much processing
2144- # in that thread if you have much message volume.
2145- class Client
2146-
2147- # Accepts a username (default ""), password (default ""),
2148- # host (default localhost), and port (default 61613)
2149- def initialize user="", pass="", host="localhost", port=61613, reliable=false
2150- if user =~ /stomp:\/\/(\w+):(\d+)/
2151- user = ""
2152- pass = ""
2153- host = $1
2154- port = $2
2155- reliable = false
2156- elsif user =~ /stomp:\/\/(\w+):(\w+)@(\w+):(\d+)/
2157- user = $1
2158- pass = $2
2159- host = $3
2160- port = $4
2161- reliable = false
2162- end
2163-
2164- @id_mutex = Mutex.new
2165- @ids = 1
2166- @connection = Connection.open user, pass, host, port, reliable
2167- @listeners = {}
2168- @receipt_listeners = {}
2169- @running = true
2170- @replay_messages_by_txn = Hash.new
2171- @listener_thread = Thread.start do
2172- while @running
2173- message = @connection.receive
2174- case
2175- when message == NIL
2176- break
2177- when message.command == 'MESSAGE'
2178- if listener = @listeners[message.headers['destination']]
2179- listener.call(message)
2180- end
2181- when message.command == 'RECEIPT'
2182- if listener = @receipt_listeners[message.headers['receipt-id']]
2183- listener.call(message)
2184- end
2185- end
2186- end
2187- end
2188- end
2189-
2190- # Join the listener thread for this client,
2191- # generally used to wait for a quit signal
2192- def join
2193- @listener_thread.join
2194- end
2195-
2196- # Accepts a username (default ""), password (default ""),
2197- # host (default localhost), and port (default 61613)
2198- def self.open user="", pass="", host="localhost", port=61613, reliable=false
2199- Client.new user, pass, host, port, reliable
2200- end
2201-
2202- # Begin a transaction by name
2203- def begin name, headers={}
2204- @connection.begin name, headers
2205- end
2206-
2207- # Abort a transaction by name
2208- def abort name, headers={}
2209- @connection.abort name, headers
2210-
2211- # lets replay any ack'd messages in this transaction
2212- replay_list = @replay_messages_by_txn[name]
2213- if replay_list
2214- replay_list.each do |message|
2215- if listener = @listeners[message.headers['destination']]
2216- listener.call(message)
2217- end
2218- end
2219- end
2220- end
2221-
2222- # Commit a transaction by name
2223- def commit name, headers={}
2224- txn_id = headers[:transaction]
2225- @replay_messages_by_txn.delete(txn_id)
2226- @connection.commit name, headers
2227- end
2228-
2229- # Subscribe to a destination, must be passed a block
2230- # which will be used as a callback listener
2231- #
2232- # Accepts a transaction header ( :transaction => 'some_transaction_id' )
2233- def subscribe destination, headers={}
2234- raise "No listener given" unless block_given?
2235- @listeners[destination] = lambda {|msg| yield msg}
2236- @connection.subscribe destination, headers
2237- end
2238-
2239- # Unsubecribe from a channel
2240- def unsubscribe name, headers={}
2241- @connection.unsubscribe name, headers
2242- @listeners[name] = nil
2243- end
2244-
2245- # Acknowledge a message, used then a subscription has specified
2246- # client acknowledgement ( connection.subscribe "/queue/a", :ack => 'client'g
2247- #
2248- # Accepts a transaction header ( :transaction => 'some_transaction_id' )
2249- def acknowledge message, headers={}
2250- txn_id = headers[:transaction]
2251- if txn_id
2252- # lets keep around messages ack'd in this transaction in case we rollback
2253- replay_list = @replay_messages_by_txn[txn_id]
2254- if replay_list == nil
2255- replay_list = []
2256- @replay_messages_by_txn[txn_id] = replay_list
2257- end
2258- replay_list << message
2259- end
2260- if block_given?
2261- headers['receipt'] = register_receipt_listener lambda {|r| yield r}
2262- end
2263- @connection.ack message.headers['message-id'], headers
2264- end
2265-
2266- # Send message to destination
2267- #
2268- # If a block is given a receipt will be requested and passed to the
2269- # block on receipt
2270- #
2271- # Accepts a transaction header ( :transaction => 'some_transaction_id' )
2272- def send destination, message, headers = {}
2273- if block_given?
2274- headers['receipt'] = register_receipt_listener lambda {|r| yield r}
2275- end
2276- @connection.send destination, message, headers
2277- end
2278-
2279- # Is this client open?
2280- def open?
2281- @connection.open?
2282- end
2283-
2284- # Close out resources in use by this client
2285- def close
2286- @connection.disconnect
2287- @running = false
2288- end
2289-
2290- private
2291- def register_receipt_listener listener
2292- id = -1
2293- @id_mutex.synchronize do
2294- id = @ids.to_s
2295- @ids = @ids.succ
2296- end
2297- @receipt_listeners[id] = listener
2298- id
2299- end
2300-
2301- end
2302 end
2303+
2304
2305=== added file 'lib/stomp/client.rb'
2306--- lib/stomp/client.rb 1970-01-01 00:00:00 +0000
2307+++ lib/stomp/client.rb 2011-07-31 16:48:34 +0000
2308@@ -0,0 +1,340 @@
2309+require 'thread'
2310+require 'digest/sha1'
2311+
2312+module Stomp
2313+
2314+ # Typical Stomp client class. Uses a listener thread to receive frames
2315+ # from the server, any thread can send.
2316+ #
2317+ # Receives all happen in one thread, so consider not doing much processing
2318+ # in that thread if you have much message volume.
2319+ class Client
2320+
2321+ attr_reader :login, :passcode, :host, :port, :reliable, :parameters
2322+
2323+ #alias :obj_send :send
2324+
2325+ # A new Client object can be initialized using two forms:
2326+ #
2327+ # Standard positional parameters:
2328+ # login (String, default : '')
2329+ # passcode (String, default : '')
2330+ # host (String, default : 'localhost')
2331+ # port (Integer, default : 61613)
2332+ # reliable (Boolean, default : false)
2333+ #
2334+ # e.g. c = Client.new('login', 'passcode', 'localhost', 61613, true)
2335+ #
2336+ # Stomp URL :
2337+ # A Stomp URL must begin with 'stomp://' and can be in one of the following forms:
2338+ #
2339+ # stomp://host:port
2340+ # stomp://host.domain.tld:port
2341+ # stomp://login:passcode@host:port
2342+ # stomp://login:passcode@host.domain.tld:port
2343+ #
2344+ def initialize(login = '', passcode = '', host = 'localhost', port = 61613, reliable = false)
2345+
2346+ # Parse stomp:// URL's or set params
2347+ if login.is_a?(Hash)
2348+ @parameters = login
2349+
2350+ first_host = @parameters[:hosts][0]
2351+
2352+ @login = first_host[:login]
2353+ @passcode = first_host[:passcode]
2354+ @host = first_host[:host]
2355+ @port = first_host[:port] || Connection::default_port(first_host[:ssl])
2356+
2357+ @reliable = true
2358+
2359+ elsif login =~ /^stomp:\/\/#{url_regex}/ # e.g. stomp://login:passcode@host:port or stomp://host:port
2360+ @login = $2 || ""
2361+ @passcode = $3 || ""
2362+ @host = $4
2363+ @port = $5.to_i
2364+ @reliable = false
2365+ elsif login =~ /^failover:(\/\/)?\(stomp(\+ssl)?:\/\/#{url_regex}(,stomp(\+ssl)?:\/\/#{url_regex}\))+(\?(.*))?$/ # e.g. failover://(stomp://login1:passcode1@localhost:61616,stomp://login2:passcode2@remotehost:61617)?option1=param
2366+
2367+ first_host = {}
2368+ first_host[:ssl] = !$2.nil?
2369+ @login = first_host[:login] = $4 || ""
2370+ @passcode = first_host[:passcode] = $5 || ""
2371+ @host = first_host[:host] = $6
2372+ @port = first_host[:port] = $7.to_i || Connection::default_port(first_host[:ssl])
2373+
2374+ options = $16 || ""
2375+ parts = options.split(/&|=/)
2376+ options = Hash[*parts]
2377+
2378+ hosts = [first_host] + parse_hosts(login)
2379+
2380+ @parameters = {}
2381+ @parameters[:hosts] = hosts
2382+
2383+ @parameters.merge! filter_options(options)
2384+
2385+ @reliable = true
2386+ else
2387+ @login = login
2388+ @passcode = passcode
2389+ @host = host
2390+ @port = port.to_i
2391+ @reliable = reliable
2392+ end
2393+
2394+ check_arguments!
2395+
2396+ @id_mutex = Mutex.new
2397+ @ids = 1
2398+
2399+ if @parameters
2400+ @connection = Connection.new(@parameters)
2401+ else
2402+ @connection = Connection.new(@login, @passcode, @host, @port, @reliable)
2403+ end
2404+
2405+ start_listeners
2406+
2407+ end
2408+
2409+ # Syntactic sugar for 'Client.new' See 'initialize' for usage.
2410+ def self.open(login = '', passcode = '', host = 'localhost', port = 61613, reliable = false)
2411+ Client.new(login, passcode, host, port, reliable)
2412+ end
2413+
2414+ # Join the listener thread for this client,
2415+ # generally used to wait for a quit signal
2416+ def join(limit = nil)
2417+ @listener_thread.join(limit)
2418+ end
2419+
2420+ # Begin a transaction by name
2421+ def begin(name, headers = {})
2422+ @connection.begin(name, headers)
2423+ end
2424+
2425+ # Abort a transaction by name
2426+ def abort(name, headers = {})
2427+ @connection.abort(name, headers)
2428+
2429+ # lets replay any ack'd messages in this transaction
2430+ replay_list = @replay_messages_by_txn[name]
2431+ if replay_list
2432+ replay_list.each do |message|
2433+ if listener = find_listener(message)
2434+ listener.call(message)
2435+ end
2436+ end
2437+ end
2438+ end
2439+
2440+ # Commit a transaction by name
2441+ def commit(name, headers = {})
2442+ txn_id = headers[:transaction]
2443+ @replay_messages_by_txn.delete(txn_id)
2444+ @connection.commit(name, headers)
2445+ end
2446+
2447+ # Subscribe to a destination, must be passed a block
2448+ # which will be used as a callback listener
2449+ #
2450+ # Accepts a transaction header ( :transaction => 'some_transaction_id' )
2451+ def subscribe(destination, headers = {})
2452+ raise "No listener given" unless block_given?
2453+ # use subscription id to correlate messages to subscription. As described in
2454+ # the SUBSCRIPTION section of the protocol: http://stomp.codehaus.org/Protocol.
2455+ # If no subscription id is provided, generate one.
2456+ set_subscription_id_if_missing(destination, headers)
2457+ if @listeners[headers[:id]]
2458+ raise "attempting to subscribe to a queue with a previous subscription"
2459+ end
2460+ @listeners[headers[:id]] = lambda {|msg| yield msg}
2461+ @connection.subscribe(destination, headers)
2462+ end
2463+
2464+ # Unsubecribe from a channel
2465+ def unsubscribe(name, headers = {})
2466+ set_subscription_id_if_missing(name, headers)
2467+ @connection.unsubscribe(name, headers)
2468+ @listeners[headers[:id]] = nil
2469+ end
2470+
2471+ # Acknowledge a message, used when a subscription has specified
2472+ # client acknowledgement ( connection.subscribe "/queue/a", :ack => 'client'g
2473+ #
2474+ # Accepts a transaction header ( :transaction => 'some_transaction_id' )
2475+ def acknowledge(message, headers = {})
2476+ txn_id = headers[:transaction]
2477+ if txn_id
2478+ # lets keep around messages ack'd in this transaction in case we rollback
2479+ replay_list = @replay_messages_by_txn[txn_id]
2480+ if replay_list.nil?
2481+ replay_list = []
2482+ @replay_messages_by_txn[txn_id] = replay_list
2483+ end
2484+ replay_list << message
2485+ end
2486+ if block_given?
2487+ headers['receipt'] = register_receipt_listener lambda {|r| yield r}
2488+ end
2489+ @connection.ack message.headers['message-id'], headers
2490+ end
2491+
2492+ # Unreceive a message, sending it back to its queue or to the DLQ
2493+ #
2494+ def unreceive(message, options = {})
2495+ @connection.unreceive(message, options)
2496+ end
2497+
2498+ # Publishes message to destination
2499+ #
2500+ # If a block is given a receipt will be requested and passed to the
2501+ # block on receipt
2502+ #
2503+ # Accepts a transaction header ( :transaction => 'some_transaction_id' )
2504+ def publish(destination, message, headers = {})
2505+ if block_given?
2506+ headers['receipt'] = register_receipt_listener lambda {|r| yield r}
2507+ end
2508+ @connection.publish(destination, message, headers)
2509+ end
2510+
2511+ def obj_send(*args)
2512+ __send__(*args)
2513+ end
2514+
2515+ def send(*args)
2516+ warn("This method is deprecated and will be removed on the next release. Use 'publish' instead")
2517+ publish(*args)
2518+ end
2519+
2520+ def connection_frame
2521+ @connection.connection_frame
2522+ end
2523+
2524+ def disconnect_receipt
2525+ @connection.disconnect_receipt
2526+ end
2527+
2528+ # Is this client open?
2529+ def open?
2530+ @connection.open?
2531+ end
2532+
2533+ # Is this client closed?
2534+ def closed?
2535+ @connection.closed?
2536+ end
2537+
2538+ # Close out resources in use by this client
2539+ def close headers={}
2540+ @listener_thread.exit
2541+ @connection.disconnect headers
2542+ end
2543+
2544+ # Check if the thread was created and isn't dead
2545+ def running
2546+ @listener_thread && !!@listener_thread.status
2547+ end
2548+
2549+ private
2550+ # Set a subscription id in the headers hash if one does not already exist.
2551+ # For simplicities sake, all subscriptions have a subscription ID.
2552+ # setting an id in the SUBSCRIPTION header is described in the stomp protocol docs:
2553+ # http://stomp.codehaus.org/Protocol
2554+ def set_subscription_id_if_missing(destination, headers)
2555+ headers[:id] = headers[:id] ? headers[:id] : headers['id']
2556+ if headers[:id] == nil
2557+ headers[:id] = Digest::SHA1.hexdigest(destination)
2558+ end
2559+ end
2560+
2561+ def register_receipt_listener(listener)
2562+ id = -1
2563+ @id_mutex.synchronize do
2564+ id = @ids.to_s
2565+ @ids = @ids.succ
2566+ end
2567+ @receipt_listeners[id] = listener
2568+ id
2569+ end
2570+
2571+ # e.g. login:passcode@host:port or host:port
2572+ def url_regex
2573+ '(([\w\.\-]*):(\w*)@)?([\w\.\-]+):(\d+)'
2574+ end
2575+
2576+ def parse_hosts(url)
2577+ hosts = []
2578+
2579+ host_match = /stomp(\+ssl)?:\/\/(([\w\.]*):(\w*)@)?([\w\.]+):(\d+)\)/
2580+ url.scan(host_match).each do |match|
2581+ host = {}
2582+ host[:ssl] = !match[0].nil?
2583+ host[:login] = match[2] || ""
2584+ host[:passcode] = match[3] || ""
2585+ host[:host] = match[4]
2586+ host[:port] = match[5].to_i
2587+
2588+ hosts << host
2589+ end
2590+
2591+ hosts
2592+ end
2593+
2594+ def check_arguments!
2595+ raise ArgumentError if @host.nil? || @host.empty?
2596+ raise ArgumentError if @port.nil? || @port == '' || @port < 1 || @port > 65535
2597+ raise ArgumentError unless @reliable.is_a?(TrueClass) || @reliable.is_a?(FalseClass)
2598+ end
2599+
2600+ def filter_options(options)
2601+ new_options = {}
2602+ new_options[:initial_reconnect_delay] = (options["initialReconnectDelay"] || 10).to_f / 1000 # In ms
2603+ new_options[:max_reconnect_delay] = (options["maxReconnectDelay"] || 30000 ).to_f / 1000 # In ms
2604+ new_options[:use_exponential_back_off] = !(options["useExponentialBackOff"] == "false") # Default: true
2605+ new_options[:back_off_multiplier] = (options["backOffMultiplier"] || 2 ).to_i
2606+ new_options[:max_reconnect_attempts] = (options["maxReconnectAttempts"] || 0 ).to_i
2607+ new_options[:randomize] = options["randomize"] == "true" # Default: false
2608+ new_options[:backup] = false # Not implemented yet: I'm using a master X slave solution
2609+ new_options[:timeout] = -1 # Not implemented yet: a "timeout(5) do ... end" would do the trick, feel free
2610+
2611+ new_options
2612+ end
2613+
2614+ def find_listener(message)
2615+ subscription_id = message.headers['subscription']
2616+ if subscription_id == nil
2617+ # For backward compatibility, some messages may already exist with no
2618+ # subscription id, in which case we can attempt to synthesize one.
2619+ set_subscription_id_if_missing(message.headers['destination'], message.headers)
2620+ subscription_id = message.headers['id']
2621+ end
2622+ @listeners[subscription_id]
2623+ end
2624+
2625+ def start_listeners
2626+ @listeners = {}
2627+ @receipt_listeners = {}
2628+ @replay_messages_by_txn = {}
2629+
2630+ @listener_thread = Thread.start do
2631+ while true
2632+ message = @connection.receive
2633+ if message.command == 'MESSAGE'
2634+ if listener = find_listener(message)
2635+ listener.call(message)
2636+ end
2637+ elsif message.command == 'RECEIPT'
2638+ if listener = @receipt_listeners[message.headers['receipt-id']]
2639+ listener.call(message)
2640+ end
2641+ end
2642+ end
2643+ end
2644+
2645+ end
2646+ end
2647+end
2648+
2649
2650=== added file 'lib/stomp/connection.rb'
2651--- lib/stomp/connection.rb 1970-01-01 00:00:00 +0000
2652+++ lib/stomp/connection.rb 2011-07-31 16:48:34 +0000
2653@@ -0,0 +1,559 @@
2654+require 'socket'
2655+require 'timeout'
2656+require 'io/wait'
2657+
2658+module Stomp
2659+
2660+ # Low level connection which maps commands and supports
2661+ # synchronous receives
2662+ class Connection
2663+ attr_reader :connection_frame
2664+ attr_reader :disconnect_receipt
2665+ #alias :obj_send :send
2666+
2667+ def self.default_port(ssl)
2668+ ssl ? 61612 : 61613
2669+ end
2670+
2671+ # A new Connection object accepts the following parameters:
2672+ #
2673+ # login (String, default : '')
2674+ # passcode (String, default : '')
2675+ # host (String, default : 'localhost')
2676+ # port (Integer, default : 61613)
2677+ # reliable (Boolean, default : false)
2678+ # reconnect_delay (Integer, default : 5)
2679+ #
2680+ # e.g. c = Connection.new("username", "password", "localhost", 61613, true)
2681+ #
2682+ # Hash:
2683+ #
2684+ # hash = {
2685+ # :hosts => [
2686+ # {:login => "login1", :passcode => "passcode1", :host => "localhost", :port => 61616, :ssl => false},
2687+ # {:login => "login2", :passcode => "passcode2", :host => "remotehost", :port => 61617, :ssl => false}
2688+ # ],
2689+ # :initial_reconnect_delay => 0.01,
2690+ # :max_reconnect_delay => 30.0,
2691+ # :use_exponential_back_off => true,
2692+ # :back_off_multiplier => 2,
2693+ # :max_reconnect_attempts => 0,
2694+ # :randomize => false,
2695+ # :backup => false,
2696+ # :timeout => -1,
2697+ # :connect_headers => {},
2698+ # :parse_timeout => 5,
2699+ # :logger => nil,
2700+ # }
2701+ #
2702+ # e.g. c = Connection.new(hash)
2703+ #
2704+ # TODO
2705+ # Stomp URL :
2706+ # A Stomp URL must begin with 'stomp://' and can be in one of the following forms:
2707+ #
2708+ # stomp://host:port
2709+ # stomp://host.domain.tld:port
2710+ # stomp://user:pass@host:port
2711+ # stomp://user:pass@host.domain.tld:port
2712+ #
2713+ def initialize(login = '', passcode = '', host = 'localhost', port = 61613, reliable = false, reconnect_delay = 5, connect_headers = {})
2714+ @received_messages = []
2715+
2716+ if login.is_a?(Hash)
2717+ hashed_initialize(login)
2718+ else
2719+ @host = host
2720+ @port = port
2721+ @login = login
2722+ @passcode = passcode
2723+ @reliable = reliable
2724+ @reconnect_delay = reconnect_delay
2725+ @connect_headers = connect_headers
2726+ @ssl = false
2727+ @parameters = nil
2728+ @parse_timeout = 5 # To override, use hashed parameters
2729+ @logger = nil # To override, use hashed parameters
2730+ end
2731+
2732+ # Use Mutexes: only one lock per each thread
2733+ # Revert to original implementation attempt
2734+ @transmit_semaphore = Mutex.new
2735+ @read_semaphore = Mutex.new
2736+ @socket_semaphore = Mutex.new
2737+
2738+ @subscriptions = {}
2739+ @failure = nil
2740+ @connection_attempts = 0
2741+
2742+ socket
2743+ end
2744+
2745+ def hashed_initialize(params)
2746+
2747+ @parameters = refine_params(params)
2748+ @reliable = true
2749+ @reconnect_delay = @parameters[:initial_reconnect_delay]
2750+ @connect_headers = @parameters[:connect_headers]
2751+ @parse_timeout = @parameters[:parse_timeout]
2752+ @logger = @parameters[:logger]
2753+ #sets the first host to connect
2754+ change_host
2755+ if @logger && @logger.respond_to?(:on_connecting)
2756+ @logger.on_connecting(log_params)
2757+ end
2758+ end
2759+
2760+ # Syntactic sugar for 'Connection.new' See 'initialize' for usage.
2761+ def Connection.open(login = '', passcode = '', host = 'localhost', port = 61613, reliable = false, reconnect_delay = 5, connect_headers = {})
2762+ Connection.new(login, passcode, host, port, reliable, reconnect_delay, connect_headers)
2763+ end
2764+
2765+ def socket
2766+ @socket_semaphore.synchronize do
2767+ used_socket = @socket
2768+ used_socket = nil if closed?
2769+
2770+ while used_socket.nil? || !@failure.nil?
2771+ @failure = nil
2772+ begin
2773+ used_socket = open_socket
2774+ # Open complete
2775+
2776+ connect(used_socket)
2777+ if @logger && @logger.respond_to?(:on_connected)
2778+ @logger.on_connected(log_params)
2779+ end
2780+ @connection_attempts = 0
2781+ rescue
2782+ @failure = $!
2783+ used_socket = nil
2784+ raise unless @reliable
2785+ if @logger && @logger.respond_to?(:on_connectfail)
2786+ @logger.on_connectfail(log_params)
2787+ else
2788+ $stderr.print "connect to #{@host} failed: #{$!} will retry(##{@connection_attempts}) in #{@reconnect_delay}\n"
2789+ end
2790+ raise Stomp::Error::MaxReconnectAttempts if max_reconnect_attempts?
2791+
2792+ sleep(@reconnect_delay)
2793+
2794+ @connection_attempts += 1
2795+
2796+ if @parameters
2797+ change_host
2798+ increase_reconnect_delay
2799+ end
2800+ end
2801+ end
2802+ @socket = used_socket
2803+ end
2804+ end
2805+
2806+ def refine_params(params)
2807+ params = params.uncamelize_and_symbolize_keys
2808+
2809+ default_params = {
2810+ :connect_headers => {},
2811+ # Failover parameters
2812+ :initial_reconnect_delay => 0.01,
2813+ :max_reconnect_delay => 30.0,
2814+ :use_exponential_back_off => true,
2815+ :back_off_multiplier => 2,
2816+ :max_reconnect_attempts => 0,
2817+ :randomize => false,
2818+ :backup => false,
2819+ :timeout => -1,
2820+ # Parse Timeout
2821+ :parse_timeout => 5
2822+ }
2823+
2824+ default_params.merge(params)
2825+
2826+ end
2827+
2828+ def change_host
2829+ @parameters[:hosts] = @parameters[:hosts].sort_by { rand } if @parameters[:randomize]
2830+
2831+ # Set first as master and send it to the end of array
2832+ current_host = @parameters[:hosts].shift
2833+ @parameters[:hosts] << current_host
2834+
2835+ @ssl = current_host[:ssl]
2836+ @host = current_host[:host]
2837+ @port = current_host[:port] || Connection::default_port(@ssl)
2838+ @login = current_host[:login] || ""
2839+ @passcode = current_host[:passcode] || ""
2840+
2841+ end
2842+
2843+ def max_reconnect_attempts?
2844+ !(@parameters.nil? || @parameters[:max_reconnect_attempts].nil?) && @parameters[:max_reconnect_attempts] != 0 && @connection_attempts >= @parameters[:max_reconnect_attempts]
2845+ end
2846+
2847+ def increase_reconnect_delay
2848+
2849+ @reconnect_delay *= @parameters[:back_off_multiplier] if @parameters[:use_exponential_back_off]
2850+ @reconnect_delay = @parameters[:max_reconnect_delay] if @reconnect_delay > @parameters[:max_reconnect_delay]
2851+
2852+ @reconnect_delay
2853+ end
2854+
2855+ # Is this connection open?
2856+ def open?
2857+ !@closed
2858+ end
2859+
2860+ # Is this connection closed?
2861+ def closed?
2862+ @closed
2863+ end
2864+
2865+ # Begin a transaction, requires a name for the transaction
2866+ def begin(name, headers = {})
2867+ headers[:transaction] = name
2868+ transmit("BEGIN", headers)
2869+ end
2870+
2871+ # Acknowledge a message, used when a subscription has specified
2872+ # client acknowledgement ( connection.subscribe "/queue/a", :ack => 'client'g
2873+ #
2874+ # Accepts a transaction header ( :transaction => 'some_transaction_id' )
2875+ def ack(message_id, headers = {})
2876+ headers['message-id'] = message_id
2877+ transmit("ACK", headers)
2878+ end
2879+
2880+ # Commit a transaction by name
2881+ def commit(name, headers = {})
2882+ headers[:transaction] = name
2883+ transmit("COMMIT", headers)
2884+ end
2885+
2886+ # Abort a transaction by name
2887+ def abort(name, headers = {})
2888+ headers[:transaction] = name
2889+ transmit("ABORT", headers)
2890+ end
2891+
2892+ # Subscribe to a destination, must specify a name
2893+ def subscribe(name, headers = {}, subId = nil)
2894+ headers[:destination] = name
2895+ transmit("SUBSCRIBE", headers)
2896+
2897+ # Store the sub so that we can replay if we reconnect.
2898+ if @reliable
2899+ subId = name if subId.nil?
2900+ @subscriptions[subId] = headers
2901+ end
2902+ end
2903+
2904+ # Unsubscribe from a destination, must specify a name
2905+ def unsubscribe(name, headers = {}, subId = nil)
2906+ headers[:destination] = name
2907+ transmit("UNSUBSCRIBE", headers)
2908+ if @reliable
2909+ subId = name if subId.nil?
2910+ @subscriptions.delete(subId)
2911+ end
2912+ end
2913+
2914+ # Publish message to destination
2915+ #
2916+ # To disable content length header ( :suppress_content_length => true )
2917+ # Accepts a transaction header ( :transaction => 'some_transaction_id' )
2918+ def publish(destination, message, headers = {})
2919+ headers[:destination] = destination
2920+ transmit("SEND", headers, message)
2921+ end
2922+
2923+ def obj_send(*args)
2924+ __send__(*args)
2925+ end
2926+
2927+ def send(*args)
2928+ warn("This method is deprecated and will be removed on the next release. Use 'publish' instead")
2929+ publish(*args)
2930+ end
2931+
2932+ # Send a message back to the source or to the dead letter queue
2933+ #
2934+ # Accepts a dead letter queue option ( :dead_letter_queue => "/queue/DLQ" )
2935+ # Accepts a limit number of redeliveries option ( :max_redeliveries => 6 )
2936+ # Accepts a force client acknowledgement option (:force_client_ack => true)
2937+ def unreceive(message, options = {})
2938+ options = { :dead_letter_queue => "/queue/DLQ", :max_redeliveries => 6 }.merge options
2939+ # Lets make sure all keys are symbols
2940+ message.headers = message.headers.symbolize_keys
2941+
2942+ retry_count = message.headers[:retry_count].to_i || 0
2943+ message.headers[:retry_count] = retry_count + 1
2944+ transaction_id = "transaction-#{message.headers[:'message-id']}-#{retry_count}"
2945+ message_id = message.headers.delete(:'message-id')
2946+
2947+ begin
2948+ self.begin transaction_id
2949+
2950+ if client_ack?(message) || options[:force_client_ack]
2951+ self.ack(message_id, :transaction => transaction_id)
2952+ end
2953+
2954+ if retry_count <= options[:max_redeliveries]
2955+ self.publish(message.headers[:destination], message.body, message.headers.merge(:transaction => transaction_id))
2956+ else
2957+ # Poison ack, sending the message to the DLQ
2958+ self.publish(options[:dead_letter_queue], message.body, message.headers.merge(:transaction => transaction_id, :original_destination => message.headers[:destination], :persistent => true))
2959+ end
2960+ self.commit transaction_id
2961+ rescue Exception => exception
2962+ self.abort transaction_id
2963+ raise exception
2964+ end
2965+ end
2966+
2967+ def client_ack?(message)
2968+ headers = @subscriptions[message.headers[:destination]]
2969+ !headers.nil? && headers[:ack] == "client"
2970+ end
2971+
2972+ # Close this connection
2973+ def disconnect(headers = {})
2974+ transmit("DISCONNECT", headers)
2975+ headers = headers.symbolize_keys
2976+ @disconnect_receipt = receive if headers[:receipt]
2977+ if @logger && @logger.respond_to?(:on_disconnect)
2978+ @logger.on_disconnect(log_params)
2979+ end
2980+ close_socket
2981+ end
2982+
2983+ # Return a pending message if one is available, otherwise
2984+ # return nil
2985+ def poll
2986+ # No need for a read lock here. The receive method eventually fullfills
2987+ # that requirement.
2988+ return nil if @socket.nil? || !@socket.ready?
2989+ receive
2990+ end
2991+
2992+ # Receive a frame, block until the frame is received
2993+ def __old_receive
2994+ # The recive my fail so we may need to retry.
2995+ while TRUE
2996+ begin
2997+ used_socket = socket
2998+ return _receive(used_socket)
2999+ rescue
3000+ @failure = $!
3001+ raise unless @reliable
3002+ errstr = "receive failed: #{$!}"
3003+ if @logger && @logger.respond_to?(:on_miscerr)
3004+ @logger.on_miscerr(log_params, errstr)
3005+ else
3006+ $stderr.print errstr
3007+ end
3008+ end
3009+ end
3010+ end
3011+
3012+ def receive
3013+ super_result = __old_receive
3014+ if super_result.nil? && @reliable
3015+ errstr = "connection.receive returning EOF as nil - resetting connection.\n"
3016+ if @logger && @logger.respond_to?(:on_miscerr)
3017+ @logger.on_miscerr(log_params, errstr)
3018+ else
3019+ $stderr.print errstr
3020+ end
3021+ @socket = nil
3022+ super_result = __old_receive
3023+ end
3024+ return super_result
3025+ end
3026+
3027+ private
3028+
3029+ def _receive( read_socket )
3030+ @read_semaphore.synchronize do
3031+ line = read_socket.gets
3032+
3033+ return nil if line.nil?
3034+
3035+ # If the reading hangs for more than X seconds, abort the parsing process.
3036+ # X defaults to 5. Override allowed in connection hash parameters.
3037+ Timeout::timeout(@parse_timeout, Stomp::Error::PacketParsingTimeout) do
3038+ # Reads the beginning of the message until it runs into a empty line
3039+ message_header = ''
3040+ begin
3041+ message_header += line
3042+ line = read_socket.gets
3043+ end until line =~ /^\s?\n$/
3044+
3045+ # Checks if it includes content_length header
3046+ content_length = message_header.match /content-length\s?:\s?(\d+)\s?\n/
3047+ message_body = ''
3048+
3049+ # If it does, reads the specified amount of bytes
3050+ char = ''
3051+ if content_length
3052+ message_body = read_socket.read content_length[1].to_i
3053+ raise Stomp::Error::InvalidMessageLength unless parse_char(read_socket.getc) == "\0"
3054+ # Else reads, the rest of the message until the first \0
3055+ else
3056+ message_body += char while (char = parse_char(read_socket.getc)) != "\0"
3057+ end
3058+
3059+ # If the buffer isn't empty, reads trailing new lines.
3060+ # Note: experiments with JRuby seem to show that .ready? never
3061+ # returns true. This means that this code to drain trailing new
3062+ # lines never runs using JRuby.
3063+ while read_socket.ready?
3064+ last_char = read_socket.getc
3065+ break unless last_char
3066+ if parse_char(last_char) != "\n"
3067+ read_socket.ungetc(last_char)
3068+ break
3069+ end
3070+ end
3071+ # And so, a JRuby hack. Remove any new lines at the start of the
3072+ # next buffer.
3073+ message_header.gsub!(/^\n?/, "")
3074+
3075+ # Adds the excluded \n and \0 and tries to create a new message with it
3076+ Message.new(message_header + "\n" + message_body + "\0")
3077+ end
3078+ end
3079+ end
3080+
3081+ def parse_char(char)
3082+ RUBY_VERSION > '1.9' ? char : char.chr
3083+ end
3084+
3085+ def transmit(command, headers = {}, body = '')
3086+ # The transmit may fail so we may need to retry.
3087+ while TRUE
3088+ begin
3089+ used_socket = socket
3090+ _transmit(used_socket, command, headers, body)
3091+ return
3092+ rescue Stomp::Error::MaxReconnectAttempts => e
3093+ raise
3094+ rescue
3095+ @failure = $!
3096+ raise unless @reliable
3097+ errstr = "transmit to #{@host} failed: #{$!}\n"
3098+ if @logger && @logger.respond_to?(:on_miscerr)
3099+ @logger.on_miscerr(log_params, errstr)
3100+ else
3101+ $stderr.print errstr
3102+ end
3103+ end
3104+ end
3105+ end
3106+
3107+ def _transmit(used_socket, command, headers = {}, body = '')
3108+ @transmit_semaphore.synchronize do
3109+ # Handle nil body
3110+ body = '' if body.nil?
3111+ # The content-length should be expressed in bytes.
3112+ # Ruby 1.8: String#length => # of bytes; Ruby 1.9: String#length => # of characters
3113+ # With Unicode strings, # of bytes != # of characters. So, use String#bytesize when available.
3114+ body_length_bytes = body.respond_to?(:bytesize) ? body.bytesize : body.length
3115+
3116+ # ActiveMQ interprets every message as a BinaryMessage
3117+ # if content_length header is included.
3118+ # Using :suppress_content_length => true will suppress this behaviour
3119+ # and ActiveMQ will interpret the message as a TextMessage.
3120+ # For more information refer to http://juretta.com/log/2009/05/24/activemq-jms-stomp/
3121+ # Lets send this header in the message, so it can maintain state when using unreceive
3122+ headers['content-length'] = "#{body_length_bytes}" unless headers[:suppress_content_length]
3123+
3124+ used_socket.puts command
3125+ headers.each {|k,v| used_socket.puts "#{k}:#{v}" }
3126+ used_socket.puts "content-type: text/plain; charset=UTF-8"
3127+ used_socket.puts
3128+ used_socket.write body
3129+ used_socket.write "\0"
3130+ end
3131+ end
3132+
3133+ def open_tcp_socket
3134+ tcp_socket = TCPSocket.open @host, @port
3135+
3136+ tcp_socket
3137+ end
3138+
3139+ def open_ssl_socket
3140+ require 'openssl' unless defined?(OpenSSL)
3141+ ctx = OpenSSL::SSL::SSLContext.new
3142+
3143+ # For client certificate authentication:
3144+ # key_path = ENV["STOMP_KEY_PATH"] || "~/stomp_keys"
3145+ # ctx.cert = OpenSSL::X509::Certificate.new("#{key_path}/client.cer")
3146+ # ctx.key = OpenSSL::PKey::RSA.new("#{key_path}/client.keystore")
3147+
3148+ # For server certificate authentication:
3149+ # truststores = OpenSSL::X509::Store.new
3150+ # truststores.add_file("#{key_path}/client.ts")
3151+ # ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
3152+ # ctx.cert_store = truststores
3153+
3154+ ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
3155+
3156+ ssl = OpenSSL::SSL::SSLSocket.new(open_tcp_socket, ctx)
3157+ def ssl.ready?
3158+ ! @rbuffer.empty? || @io.ready?
3159+ end
3160+ ssl.connect
3161+ ssl
3162+ end
3163+
3164+ def close_socket
3165+ begin
3166+ @socket.close
3167+ rescue
3168+ #Ignoring if already closed
3169+ end
3170+
3171+ @closed = true
3172+ end
3173+
3174+ def open_socket
3175+ used_socket = @ssl ? open_ssl_socket : open_tcp_socket
3176+ # try to close the old connection if any
3177+ close_socket
3178+
3179+ @closed = false
3180+ # Use keepalive
3181+ used_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
3182+ used_socket
3183+ end
3184+
3185+ def connect(used_socket)
3186+ headers = @connect_headers.clone
3187+ headers[:login] = @login
3188+ headers[:passcode] = @passcode
3189+ _transmit(used_socket, "CONNECT", headers)
3190+ @connection_frame = _receive(used_socket)
3191+ @disconnect_receipt = nil
3192+ # replay any subscriptions.
3193+ @subscriptions.each { |k,v| _transmit(used_socket, "SUBSCRIBE", v) }
3194+ end
3195+
3196+ def log_params
3197+ lparms = @parameters.clone
3198+ lparms[:cur_host] = @host
3199+ lparms[:cur_port] = @port
3200+ lparms[:cur_login] = @login
3201+ lparms[:cur_passcode] = @passcode
3202+ lparms[:cur_ssl] = @ssl
3203+ lparms[:cur_recondelay] = @reconnect_delay
3204+ lparms[:cur_parseto] = @parse_timeout
3205+ lparms[:cur_conattempts] = @connection_attempts
3206+ #
3207+ lparms
3208+ end
3209+ end
3210+
3211+end
3212+
3213
3214=== added file 'lib/stomp/errors.rb'
3215--- lib/stomp/errors.rb 1970-01-01 00:00:00 +0000
3216+++ lib/stomp/errors.rb 2011-07-31 16:48:34 +0000
3217@@ -0,0 +1,33 @@
3218+module Stomp
3219+ module Error
3220+ class InvalidFormat < RuntimeError
3221+ def message
3222+ "Invalid message - invalid format"
3223+ end
3224+ end
3225+
3226+ class InvalidServerCommand < RuntimeError
3227+ def message
3228+ "Invalid command from server"
3229+ end
3230+ end
3231+
3232+ class InvalidMessageLength < RuntimeError
3233+ def message
3234+ "Invalid content length received"
3235+ end
3236+ end
3237+
3238+ class PacketParsingTimeout < RuntimeError
3239+ def message
3240+ "Packet parsing timeout"
3241+ end
3242+ end
3243+
3244+ class MaxReconnectAttempts < RuntimeError
3245+ def message
3246+ "Maximum number of reconnection attempts reached"
3247+ end
3248+ end
3249+ end
3250+end
3251
3252=== added directory 'lib/stomp/ext'
3253=== added file 'lib/stomp/ext/hash.rb'
3254--- lib/stomp/ext/hash.rb 1970-01-01 00:00:00 +0000
3255+++ lib/stomp/ext/hash.rb 2011-07-31 16:48:34 +0000
3256@@ -0,0 +1,24 @@
3257+class ::Hash
3258+ def uncamelize_and_symbolize_keys
3259+ self.uncamelize_and_stringify_keys.symbolize_keys
3260+ end
3261+
3262+ def uncamelize_and_stringify_keys
3263+ uncamelized = {}
3264+ self.each_pair do |key, value|
3265+ new_key = key.to_s.split(/(?=[A-Z])/).join('_').downcase
3266+ uncamelized[new_key] = value
3267+ end
3268+
3269+ uncamelized
3270+ end
3271+
3272+ def symbolize_keys
3273+ symbolized = {}
3274+ self.each_pair do |key, value|
3275+ symbolized[key.to_sym] = value
3276+ end
3277+
3278+ symbolized
3279+ end unless self.method_defined?(:symbolize_keys)
3280+end
3281\ No newline at end of file
3282
3283=== added file 'lib/stomp/message.rb'
3284--- lib/stomp/message.rb 1970-01-01 00:00:00 +0000
3285+++ lib/stomp/message.rb 2011-07-31 16:48:34 +0000
3286@@ -0,0 +1,68 @@
3287+module Stomp
3288+
3289+ # Container class for frames, misnamed technically
3290+ class Message
3291+ attr_accessor :command, :headers, :body, :original
3292+
3293+ @@allowed_commands = [ 'CONNECTED', 'MESSAGE', 'RECEIPT', 'ERROR' ]
3294+
3295+ def initialize(frame)
3296+ # p frame
3297+ # Set default empty values
3298+ self.command = ''
3299+ self.headers = {}
3300+ self.body = ''
3301+ self.original = frame
3302+ return self if is_blank?(frame)
3303+ # Figure out where individual parts of the frame begin and end.
3304+ command_index = frame.index("\n")
3305+ raise Stomp::Error::InvalidFormat, 'command index' unless command_index
3306+ #
3307+ headers_index = frame.index("\n\n", command_index+1)
3308+ raise Stomp::Error::InvalidFormat, 'headers index' unless headers_index
3309+ #
3310+ lastnull_index = frame.rindex("\0")
3311+ raise Stomp::Error::InvalidFormat, 'lastnull index' unless lastnull_index
3312+
3313+ # Extract working copies of each frame part
3314+ work_command = frame[0..command_index-1]
3315+ raise Stomp::Error::InvalidServerCommand, "invalid command: #{work_command.inspect}" unless @@allowed_commands.include?(work_command)
3316+ #
3317+ work_headers = frame[command_index+1..headers_index-1]
3318+ raise Stomp::Error::InvalidFormat, 'nil headers' unless work_headers
3319+ #
3320+ work_body = frame[headers_index+2..lastnull_index-1]
3321+ raise Stomp::Error::InvalidFormat, 'nil body' unless work_body
3322+ # Set the frame values
3323+ self.command = work_command
3324+ work_headers.split("\n").map do |value|
3325+ parsed_value = value.match /^([\w|-]*):(.*)$/
3326+ raise Stomp::Error::InvalidFormat, 'parsed header value' unless parsed_value
3327+ self.headers[parsed_value[1].strip] = parsed_value[2].strip if parsed_value
3328+ end
3329+
3330+ body_length = -1
3331+
3332+ if self.headers['content-length']
3333+ body_length = self.headers['content-length'].to_i
3334+ raise Stomp::Error::InvalidMessageLength if work_body.length != body_length
3335+ end
3336+ self.body = work_body[0..body_length]
3337+ end
3338+
3339+ def to_s
3340+ "<Stomp::Message headers=#{headers.inspect} body='#{body}' command='#{command}' >"
3341+ end
3342+
3343+ def empty?
3344+ is_blank?(command) && is_blank?(headers) && is_blank?(body)
3345+ end
3346+
3347+ private
3348+ def is_blank?(value)
3349+ value.nil? || (value.respond_to?(:empty?) && value.empty?)
3350+ end
3351+ end
3352+
3353+end
3354+
3355
3356=== added file 'lib/stomp/version.rb'
3357--- lib/stomp/version.rb 1970-01-01 00:00:00 +0000
3358+++ lib/stomp/version.rb 2011-07-31 16:48:34 +0000
3359@@ -0,0 +1,8 @@
3360+module Stomp
3361+ module Version #:nodoc: all
3362+ MAJOR = 1
3363+ MINOR = 1
3364+ PATCH = 9
3365+ STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
3366+ end
3367+end
3368
3369=== added directory 'spec'
3370=== added file 'spec/client_shared_examples.rb'
3371--- spec/client_shared_examples.rb 1970-01-01 00:00:00 +0000
3372+++ spec/client_shared_examples.rb 2011-07-31 16:48:34 +0000
3373@@ -0,0 +1,69 @@
3374+require 'spec_helper'
3375+
3376+shared_examples_for "standard Client" do
3377+
3378+ before(:each) do
3379+ @destination = "/queue/test/ruby/client"
3380+ @message_text = "test_client-#{Time.now.to_i}"
3381+ end
3382+
3383+ describe "the closed? method" do
3384+ it "should be false when the connection is open" do
3385+ @mock_connection.stub!(:closed?).and_return(false)
3386+ @client.closed?.should == false
3387+ end
3388+
3389+ it "should be true when the connection is closed" do
3390+ @mock_connection.stub!(:closed?).and_return(true)
3391+ @client.closed?.should == true
3392+ end
3393+ end
3394+
3395+ describe "the open? method" do
3396+ it "should be true when the connection is open" do
3397+ @mock_connection.stub!(:open?).and_return(true)
3398+ @client.open?.should == true
3399+ end
3400+
3401+ it "should be false when the connection is closed" do
3402+ @mock_connection.stub!(:open?).and_return(false)
3403+ @client.open?.should == false
3404+ end
3405+ end
3406+
3407+ describe "the subscribe method" do
3408+
3409+ before(:each) do
3410+ @mock_connection.stub!(:subscribe).and_return(true)
3411+ end
3412+
3413+ it "should raise RuntimeError if not passed a block" do
3414+ lambda {
3415+ @client.subscribe(@destination)
3416+ }.should raise_error
3417+ end
3418+
3419+ it "should not raise an error when passed a block" do
3420+ lambda {
3421+ @client.subscribe(@destination) {|msg| received = msg}
3422+ }.should_not raise_error
3423+ end
3424+
3425+ it "should raise RuntimeError on duplicate subscriptions" do
3426+ lambda {
3427+ @client.subscribe(@destination)
3428+ @client.subscribe(@destination)
3429+ }.should raise_error
3430+ end
3431+
3432+ it "should raise RuntimeError with duplicate id headers" do
3433+ lambda {
3434+ @client.subscribe(@destination, {'id' => 'abcdef'})
3435+ @client.subscribe(@destination, {'id' => 'abcdef'})
3436+ }.should raise_error
3437+ end
3438+
3439+ end
3440+
3441+end
3442+
3443
3444=== added file 'spec/client_spec.rb'
3445--- spec/client_spec.rb 1970-01-01 00:00:00 +0000
3446+++ spec/client_spec.rb 2011-07-31 16:48:34 +0000
3447@@ -0,0 +1,312 @@
3448+require 'spec_helper'
3449+require 'client_shared_examples'
3450+
3451+
3452+describe Stomp::Client do
3453+
3454+ before(:each) do
3455+ @mock_connection = mock('connection')
3456+ Stomp::Connection.stub!(:new).and_return(@mock_connection)
3457+ end
3458+
3459+ describe "(created with no params)" do
3460+
3461+ before(:each) do
3462+ @client = Stomp::Client.new
3463+ end
3464+
3465+ it "should not return any errors" do
3466+ lambda {
3467+ @client = Stomp::Client.new
3468+ }.should_not raise_error
3469+ end
3470+
3471+ it "should not return any errors when created with the open constructor" do
3472+ lambda {
3473+ @client = Stomp::Client.open
3474+ }.should_not raise_error
3475+ end
3476+
3477+ it_should_behave_like "standard Client"
3478+
3479+ end
3480+
3481+ describe "(created with invalid params)" do
3482+
3483+ it "should return ArgumentError if host is nil" do
3484+ lambda {
3485+ @client = Stomp::Client.new('login', 'passcode', nil)
3486+ }.should raise_error
3487+ end
3488+
3489+ it "should return ArgumentError if host is empty" do
3490+ lambda {
3491+ @client = Stomp::Client.new('login', 'passcode', '')
3492+ }.should raise_error
3493+ end
3494+
3495+ it "should return ArgumentError if port is nil" do
3496+ lambda {
3497+ @client = Stomp::Client.new('login', 'passcode', 'localhost', nil)
3498+ }.should raise_error
3499+ end
3500+
3501+ it "should return ArgumentError if port is < 1" do
3502+ lambda {
3503+ @client = Stomp::Client.new('login', 'passcode', 'localhost', 0)
3504+ }.should raise_error
3505+ end
3506+
3507+ it "should return ArgumentError if port is > 65535" do
3508+ lambda {
3509+ @client = Stomp::Client.new('login', 'passcode', 'localhost', 65536)
3510+ }.should raise_error
3511+ end
3512+
3513+ it "should return ArgumentError if port is empty" do
3514+ lambda {
3515+ @client = Stomp::Client.new('login', 'passcode', 'localhost', '')
3516+ }.should raise_error
3517+ end
3518+
3519+ it "should return ArgumentError if reliable is something other than true or false" do
3520+ lambda {
3521+ @client = Stomp::Client.new('login', 'passcode', 'localhost', '12345', 'foo')
3522+ }.should raise_error
3523+ end
3524+
3525+ end
3526+
3527+
3528+ describe "(created with positional params)" do
3529+
3530+ before(:each) do
3531+ @client = Stomp::Client.new('testlogin', 'testpassword', 'localhost', '12345', false)
3532+ end
3533+
3534+ it "should properly parse the URL provided" do
3535+ @client.login.should eql('testlogin')
3536+ @client.passcode.should eql('testpassword')
3537+ @client.host.should eql('localhost')
3538+ @client.port.should eql(12345)
3539+ @client.reliable.should be_false
3540+ end
3541+
3542+ it_should_behave_like "standard Client"
3543+
3544+ end
3545+
3546+ describe "(created with non-authenticating stomp:// URL and non-TLD host)" do
3547+
3548+ before(:each) do
3549+ @client = Stomp::Client.new('stomp://foobar:12345')
3550+ end
3551+
3552+ it "should properly parse the URL provided" do
3553+ @client.login.should eql('')
3554+ @client.passcode.should eql('')
3555+ @client.host.should eql('foobar')
3556+ @client.port.should eql(12345)
3557+ @client.reliable.should be_false
3558+ end
3559+
3560+ it_should_behave_like "standard Client"
3561+
3562+ end
3563+
3564+ describe "(created with non-authenticating stomp:// URL and a host with a '-')" do
3565+
3566+ before(:each) do
3567+ @client = Stomp::Client.new('stomp://foo-bar:12345')
3568+ end
3569+
3570+ it "should properly parse the URL provided" do
3571+ @client.login.should eql('')
3572+ @client.passcode.should eql('')
3573+ @client.host.should eql('foo-bar')
3574+ @client.port.should eql(12345)
3575+ @client.reliable.should be_false
3576+ end
3577+
3578+ it_should_behave_like "standard Client"
3579+
3580+ end
3581+
3582+ describe "(created with authenticating stomp:// URL and non-TLD host)" do
3583+
3584+ before(:each) do
3585+ @client = Stomp::Client.new('stomp://test-login:testpasscode@foobar:12345')
3586+ end
3587+
3588+ it "should properly parse the URL provided" do
3589+ @client.login.should eql('test-login')
3590+ @client.passcode.should eql('testpasscode')
3591+ @client.host.should eql('foobar')
3592+ @client.port.should eql(12345)
3593+ @client.reliable.should be_false
3594+ end
3595+
3596+ it_should_behave_like "standard Client"
3597+
3598+ end
3599+
3600+ describe "(created with authenticating stomp:// URL and a host with a '-')" do
3601+
3602+ before(:each) do
3603+ @client = Stomp::Client.new('stomp://test-login:testpasscode@foo-bar:12345')
3604+ end
3605+
3606+ it "should properly parse the URL provided" do
3607+ @client.login.should eql('test-login')
3608+ @client.passcode.should eql('testpasscode')
3609+ @client.host.should eql('foo-bar')
3610+ @client.port.should eql(12345)
3611+ @client.reliable.should be_false
3612+ end
3613+
3614+ it_should_behave_like "standard Client"
3615+
3616+ end
3617+
3618+ describe "(created with non-authenticating stomp:// URL and TLD host)" do
3619+
3620+ before(:each) do
3621+ @client = Stomp::Client.new('stomp://host.foobar.com:12345')
3622+ end
3623+
3624+ after(:each) do
3625+ end
3626+
3627+ it "should properly parse the URL provided" do
3628+ @client.login.should eql('')
3629+ @client.passcode.should eql('')
3630+ @client.host.should eql('host.foobar.com')
3631+ @client.port.should eql(12345)
3632+ @client.reliable.should be_false
3633+ end
3634+
3635+ it_should_behave_like "standard Client"
3636+
3637+ end
3638+
3639+ describe "(created with authenticating stomp:// URL and non-TLD host)" do
3640+
3641+ before(:each) do
3642+ @client = Stomp::Client.new('stomp://testlogin:testpasscode@host.foobar.com:12345')
3643+ end
3644+
3645+ it "should properly parse the URL provided" do
3646+ @client.login.should eql('testlogin')
3647+ @client.passcode.should eql('testpasscode')
3648+ @client.host.should eql('host.foobar.com')
3649+ @client.port.should eql(12345)
3650+ @client.reliable.should be_false
3651+ end
3652+
3653+ it_should_behave_like "standard Client"
3654+
3655+ end
3656+
3657+ describe "(created with failover URL)" do
3658+ before(:each) do
3659+ #default options
3660+ @parameters = {
3661+ :initial_reconnect_delay => 0.01,
3662+ :max_reconnect_delay => 30.0,
3663+ :use_exponential_back_off => true,
3664+ :back_off_multiplier => 2,
3665+ :max_reconnect_attempts => 0,
3666+ :randomize => false,
3667+ :backup => false,
3668+ :timeout => -1
3669+ }
3670+ end
3671+ it "should properly parse a URL with failover://" do
3672+ url = "failover://(stomp://login1:passcode1@localhost:61616,stomp://login2:passcode2@remotehost:61617)"
3673+
3674+ @parameters[:hosts] = [
3675+ {:login => "login1", :passcode => "passcode1", :host => "localhost", :port => 61616, :ssl => false},
3676+ {:login => "login2", :passcode => "passcode2", :host => "remotehost", :port => 61617, :ssl => false}
3677+ ]
3678+
3679+ Stomp::Connection.should_receive(:new).with(@parameters)
3680+
3681+ client = Stomp::Client.new(url)
3682+ client.parameters.should == @parameters
3683+ end
3684+
3685+ it "should properly parse a URL with failover:" do
3686+ url = "failover:(stomp://login1:passcode1@localhost:61616,stomp://login2:passcode2@remotehost1:61617),stomp://login3:passcode3@remotehost2:61618)"
3687+
3688+ @parameters[:hosts] = [
3689+ {:login => "login1", :passcode => "passcode1", :host => "localhost", :port => 61616, :ssl => false},
3690+ {:login => "login2", :passcode => "passcode2", :host => "remotehost1", :port => 61617, :ssl => false},
3691+ {:login => "login3", :passcode => "passcode3", :host => "remotehost2", :port => 61618, :ssl => false}
3692+ ]
3693+
3694+ Stomp::Connection.should_receive(:new).with(@parameters)
3695+
3696+ client = Stomp::Client.new(url)
3697+ client.parameters.should == @parameters
3698+ end
3699+
3700+ it "should properly parse a URL without user and password" do
3701+ url = "failover:(stomp://localhost:61616,stomp://remotehost:61617)"
3702+
3703+ @parameters[:hosts] = [
3704+ {:login => "", :passcode => "", :host => "localhost", :port => 61616, :ssl => false},
3705+ {:login => "", :passcode => "", :host => "remotehost", :port => 61617, :ssl => false}
3706+ ]
3707+
3708+ Stomp::Connection.should_receive(:new).with(@parameters)
3709+
3710+ client = Stomp::Client.new(url)
3711+ client.parameters.should == @parameters
3712+ end
3713+
3714+ it "should properly parse a URL with user and/or password blank" do
3715+ url = "failover:(stomp://:@localhost:61616,stomp://:@remotehost:61617)"
3716+
3717+ @parameters[:hosts] = [
3718+ {:login => "", :passcode => "", :host => "localhost", :port => 61616, :ssl => false},
3719+ {:login => "", :passcode => "", :host => "remotehost", :port => 61617, :ssl => false}
3720+ ]
3721+
3722+ Stomp::Connection.should_receive(:new).with(@parameters)
3723+
3724+ client = Stomp::Client.new(url)
3725+ client.parameters.should == @parameters
3726+ end
3727+
3728+ it "should properly parse a URL with the options query" do
3729+ query = "initialReconnectDelay=5000&maxReconnectDelay=60000&useExponentialBackOff=false&backOffMultiplier=3"
3730+ query += "&maxReconnectAttempts=4&randomize=true&backup=true&timeout=10000"
3731+
3732+ url = "failover:(stomp://login1:passcode1@localhost:61616,stomp://login2:passcode2@remotehost:61617)?#{query}"
3733+
3734+ #backup and timeout are not implemented yet
3735+ @parameters = {
3736+ :initial_reconnect_delay => 5.0,
3737+ :max_reconnect_delay => 60.0,
3738+ :use_exponential_back_off => false,
3739+ :back_off_multiplier => 3,
3740+ :max_reconnect_attempts => 4,
3741+ :randomize => true,
3742+ :backup => false,
3743+ :timeout => -1
3744+ }
3745+
3746+ @parameters[:hosts] = [
3747+ {:login => "login1", :passcode => "passcode1", :host => "localhost", :port => 61616, :ssl => false},
3748+ {:login => "login2", :passcode => "passcode2", :host => "remotehost", :port => 61617, :ssl => false}
3749+ ]
3750+
3751+ Stomp::Connection.should_receive(:new).with(@parameters)
3752+
3753+ client = Stomp::Client.new(url)
3754+ client.parameters.should == @parameters
3755+ end
3756+
3757+ end
3758+
3759+end
3760
3761=== added file 'spec/connection_spec.rb'
3762--- spec/connection_spec.rb 1970-01-01 00:00:00 +0000
3763+++ spec/connection_spec.rb 2011-07-31 16:48:34 +0000
3764@@ -0,0 +1,365 @@
3765+# encoding: UTF-8
3766+require 'spec_helper'
3767+
3768+describe Stomp::Connection do
3769+
3770+ before(:each) do
3771+ @parameters = {
3772+ :hosts => [
3773+ {:login => "login1", :passcode => "passcode1", :host => "localhost", :port => 61616, :ssl => false},
3774+ {:login => "login2", :passcode => "passcode2", :host => "remotehost", :port => 61617, :ssl => false}
3775+ ],
3776+ :initial_reconnect_delay => 0.01,
3777+ :max_reconnect_delay => 30.0,
3778+ :use_exponential_back_off => true,
3779+ :back_off_multiplier => 2,
3780+ :max_reconnect_attempts => 0,
3781+ :randomize => false,
3782+ :backup => false,
3783+ :timeout => -1,
3784+ :parse_timeout => 5,
3785+ :connect_headers => {}
3786+ }
3787+
3788+ #POG:
3789+ class Stomp::Connection
3790+ def _receive( s )
3791+ end
3792+ end
3793+
3794+ # clone() does a shallow copy, we want a deep one so we can garantee the hosts order
3795+ normal_parameters = Marshal::load(Marshal::dump(@parameters))
3796+
3797+ @tcp_socket = mock(:tcp_socket, :close => nil, :puts => nil, :write => nil, :setsockopt => nil)
3798+ TCPSocket.stub!(:open).and_return @tcp_socket
3799+ @connection = Stomp::Connection.new(normal_parameters)
3800+ end
3801+
3802+ describe "(created using a hash)" do
3803+ it "should uncamelize and symbolize the main hash keys" do
3804+ used_hash = {
3805+ "hosts" => [
3806+ {:login => "login2", :passcode => "passcode2", :host => "remotehost", :port => 61617, :ssl => false},
3807+ {:login => "login1", :passcode => "passcode1", :host => "localhost", :port => 61616, :ssl => false}
3808+ ],
3809+ "initialReconnectDelay" => 0.01,
3810+ "maxReconnectDelay" => 30.0,
3811+ "useExponentialBackOff" => true,
3812+ "backOffMultiplier" => 2,
3813+ "maxReconnectAttempts" => 0,
3814+ "randomize" => false,
3815+ "backup" => false,
3816+ "timeout" => -1,
3817+ "parse_timeout" => 5,
3818+ }
3819+
3820+ @connection = Stomp::Connection.new(used_hash)
3821+ @connection.instance_variable_get(:@parameters).should == @parameters
3822+ end
3823+
3824+ it "should be reliable" do
3825+ @connection.instance_variable_get(:@reliable).should be_true
3826+ end
3827+ it "should start with first host in array" do
3828+ @connection.instance_variable_get(:@host).should == "localhost"
3829+ end
3830+
3831+ it "should change host to next one with randomize false" do
3832+ @connection.change_host
3833+ @connection.instance_variable_get(:@host).should == "remotehost"
3834+ end
3835+
3836+ it "should use default port (61613) if none is given" do
3837+ hash = {:hosts => [{:login => "login2", :passcode => "passcode2", :host => "remotehost", :ssl => false}]}
3838+ @connection = Stomp::Connection.new hash
3839+ @connection.instance_variable_get(:@port).should == 61613
3840+ end
3841+
3842+ context "when dealing with content-length header" do
3843+ it "should not suppress it when receiving :suppress_content_length => false" do
3844+ @tcp_socket.should_receive(:puts).with("content-length:7")
3845+ @connection.publish "/queue", "message", :suppress_content_length => false
3846+ end
3847+
3848+ it "should not suppress it when :suppress_content_length is nil" do
3849+ @tcp_socket.should_receive(:puts).with("content-length:7")
3850+ @connection.publish "/queue", "message"
3851+ end
3852+
3853+ it "should suppress it when receiving :suppress_content_length => true" do
3854+ @tcp_socket.should_not_receive(:puts).with("content-length:7")
3855+ @connection.publish "/queue", "message", :suppress_content_length => true
3856+ end
3857+
3858+ it "should get the correct byte length when dealing with Unicode characters" do
3859+ @tcp_socket.should_receive(:puts).with("content-length:18")
3860+ @connection.publish "/queue", "сообщение" # 'сообщение' is 'message' in Russian
3861+ end
3862+ end
3863+
3864+ describe "when unacknowledging a message" do
3865+
3866+ before :each do
3867+ @message = Stomp::Message.new(nil)
3868+ @message.body = "message body"
3869+ @message.headers = {"destination" => "/queue/original", "message-id" => "ID"}
3870+
3871+ @transaction_id = "transaction-#{@message.headers["message-id"]}-0"
3872+
3873+ @retry_headers = {
3874+ :destination => @message.headers["destination"],
3875+ :transaction => @transaction_id,
3876+ :retry_count => 1
3877+ }
3878+ end
3879+
3880+ it "should use a transaction" do
3881+ @connection.should_receive(:begin).with(@transaction_id).ordered
3882+ @connection.should_receive(:commit).with(@transaction_id).ordered
3883+ @connection.unreceive @message
3884+ end
3885+
3886+ it "should acknowledge the original message if ack mode is client" do
3887+ @connection.should_receive(:ack).with(@message.headers["message-id"], :transaction => @transaction_id)
3888+ @connection.subscribe(@message.headers["destination"], :ack => "client")
3889+ @connection.unreceive @message
3890+ end
3891+
3892+ it "should acknowledge the original message if forced" do
3893+ @connection.subscribe(@message.headers["destination"])
3894+ @connection.should_receive(:ack)
3895+ @connection.unreceive(@message, :force_client_ack => true)
3896+ end
3897+
3898+ it "should not acknowledge the original message if ack mode is not client or it did not subscribe to the queue" do
3899+ @connection.subscribe(@message.headers["destination"], :ack => "client")
3900+ @connection.should_receive(:ack)
3901+ @connection.unreceive @message
3902+
3903+ # At this time the message headers are symbolized
3904+ @connection.unsubscribe(@message.headers[:destination])
3905+ @connection.should_not_receive(:ack)
3906+ @connection.unreceive @message
3907+ @connection.subscribe(@message.headers[:destination], :ack => "individual")
3908+ @connection.unreceive @message
3909+ end
3910+
3911+ it "should send the message back to the queue it came" do
3912+ @connection.subscribe(@message.headers["destination"], :ack => "client")
3913+ @connection.should_receive(:publish).with(@message.headers["destination"], @message.body, @retry_headers)
3914+ @connection.unreceive @message
3915+ end
3916+
3917+ it "should increment the retry_count header" do
3918+ @message.headers["retry_count"] = 4
3919+ @connection.unreceive @message
3920+ @message.headers[:retry_count].should == 5
3921+ end
3922+
3923+ it "should not send the message to the dead letter queue as persistent if redeliveries equal max redeliveries" do
3924+ max_redeliveries = 5
3925+ dead_letter_queue = "/queue/Dead"
3926+
3927+ @message.headers["retry_count"] = max_redeliveries
3928+ transaction_id = "transaction-#{@message.headers["message-id"]}-#{@message.headers["retry_count"]}"
3929+ @retry_headers = @retry_headers.merge :transaction => transaction_id, :retry_count => @message.headers["retry_count"] + 1
3930+ @connection.should_receive(:publish).with(@message.headers["destination"], @message.body, @retry_headers)
3931+ @connection.unreceive @message, :dead_letter_queue => dead_letter_queue, :max_redeliveries => max_redeliveries
3932+ end
3933+
3934+ it "should send the message to the dead letter queue as persistent if max redeliveries have been reached" do
3935+ max_redeliveries = 5
3936+ dead_letter_queue = "/queue/Dead"
3937+
3938+ @message.headers["retry_count"] = max_redeliveries + 1
3939+ transaction_id = "transaction-#{@message.headers["message-id"]}-#{@message.headers["retry_count"]}"
3940+ @retry_headers = @retry_headers.merge :persistent => true, :transaction => transaction_id, :retry_count => @message.headers["retry_count"] + 1, :original_destination=> @message.headers["destination"]
3941+ @connection.should_receive(:publish).with(dead_letter_queue, @message.body, @retry_headers)
3942+ @connection.unreceive @message, :dead_letter_queue => dead_letter_queue, :max_redeliveries => max_redeliveries
3943+ end
3944+
3945+ it "should rollback the transaction and raise the exception if happened during transaction" do
3946+ @connection.should_receive(:publish).and_raise "Error"
3947+ @connection.should_receive(:abort).with(@transaction_id)
3948+ lambda {@connection.unreceive @message}.should raise_error("Error")
3949+ end
3950+
3951+ end
3952+
3953+ describe "when sending a nil message body" do
3954+ it "should should not raise an error" do
3955+ @connection = Stomp::Connection.new("niluser", "nilpass", "localhost", 61613)
3956+ lambda {
3957+ @connection.publish("/queue/nilq", nil)
3958+ }.should_not raise_error
3959+ end
3960+ end
3961+
3962+ describe "when using ssl" do
3963+
3964+ # Mocking ruby's openssl extension, so we can test without requiring openssl
3965+ module ::OpenSSL
3966+ module SSL
3967+ VERIFY_NONE = 0
3968+
3969+ class SSLSocket
3970+ end
3971+
3972+ class SSLContext
3973+ attr_accessor :verify_mode
3974+ end
3975+ end
3976+ end
3977+
3978+ before(:each) do
3979+ ssl_parameters = {:hosts => [{:login => "login2", :passcode => "passcode2", :host => "remotehost", :ssl => true}]}
3980+ @ssl_socket = mock(:ssl_socket, :puts => nil, :write => nil, :setsockopt => nil)
3981+
3982+ TCPSocket.should_receive(:open).and_return @tcp_socket
3983+ OpenSSL::SSL::SSLSocket.should_receive(:new).and_return(@ssl_socket)
3984+ @ssl_socket.should_receive(:connect)
3985+
3986+ @connection = Stomp::Connection.new ssl_parameters
3987+ end
3988+
3989+ it "should use ssl socket if ssl use is enabled" do
3990+ @connection.instance_variable_get(:@socket).should == @ssl_socket
3991+ end
3992+
3993+ it "should use default port for ssl (61612) if none is given" do
3994+ @connection.instance_variable_get(:@port).should == 61612
3995+ end
3996+
3997+ end
3998+
3999+ describe "when called to increase reconnect delay" do
4000+ it "should exponentialy increase when use_exponential_back_off is true" do
4001+ @connection.increase_reconnect_delay.should == 0.02
4002+ @connection.increase_reconnect_delay.should == 0.04
4003+ @connection.increase_reconnect_delay.should == 0.08
4004+ end
4005+ it "should not increase when use_exponential_back_off is false" do
4006+ @parameters[:use_exponential_back_off] = false
4007+ @connection = Stomp::Connection.new(@parameters)
4008+ @connection.increase_reconnect_delay.should == 0.01
4009+ @connection.increase_reconnect_delay.should == 0.01
4010+ end
4011+ it "should not increase when max_reconnect_delay is reached" do
4012+ @parameters[:initial_reconnect_delay] = 8.0
4013+ @connection = Stomp::Connection.new(@parameters)
4014+ @connection.increase_reconnect_delay.should == 16.0
4015+ @connection.increase_reconnect_delay.should == 30.0
4016+ end
4017+
4018+ it "should change to next host on socket error" do
4019+ @connection.instance_variable_set(:@failure, "some exception")
4020+ #retries the same host
4021+ TCPSocket.should_receive(:open).and_raise "exception"
4022+ #tries the new host
4023+ TCPSocket.should_receive(:open).and_return @tcp_socket
4024+
4025+ @connection.socket
4026+ @connection.instance_variable_get(:@host).should == "remotehost"
4027+ end
4028+
4029+ it "should use default options if those where not given" do
4030+ expected_hash = {
4031+ :hosts => [
4032+ {:login => "login2", :passcode => "passcode2", :host => "remotehost", :port => 61617, :ssl => false},
4033+ # Once connected the host is sent to the end of array
4034+ {:login => "login1", :passcode => "passcode1", :host => "localhost", :port => 61616, :ssl => false}
4035+ ],
4036+ :initial_reconnect_delay => 0.01,
4037+ :max_reconnect_delay => 30.0,
4038+ :use_exponential_back_off => true,
4039+ :back_off_multiplier => 2,
4040+ :max_reconnect_attempts => 0,
4041+ :randomize => false,
4042+ :backup => false,
4043+ :timeout => -1,
4044+ :parse_timeout => 5,
4045+ :connect_headers => {}
4046+ }
4047+
4048+ used_hash = {
4049+ :hosts => [
4050+ {:login => "login1", :passcode => "passcode1", :host => "localhost", :port => 61616, :ssl => false},
4051+ {:login => "login2", :passcode => "passcode2", :host => "remotehost", :port => 61617, :ssl => false}
4052+ ]
4053+ }
4054+
4055+ @connection = Stomp::Connection.new(used_hash)
4056+ @connection.instance_variable_get(:@parameters).should == expected_hash
4057+ end
4058+
4059+ it "should use the given options instead of default ones" do
4060+ used_hash = {
4061+ :hosts => [
4062+ {:login => "login2", :passcode => "passcode2", :host => "remotehost", :port => 61617, :ssl => false},
4063+ {:login => "login1", :passcode => "passcode1", :host => "localhost", :port => 61616, :ssl => false}
4064+ ],
4065+ :initial_reconnect_delay => 5.0,
4066+ :max_reconnect_delay => 100.0,
4067+ :use_exponential_back_off => false,
4068+ :back_off_multiplier => 3,
4069+ :max_reconnect_attempts => 10,
4070+ :randomize => true,
4071+ :backup => false,
4072+ :timeout => -1,
4073+ :parse_timeout => 20,
4074+ :connect_headers => {:lerolero => "ronaldo"},
4075+ :dead_letter_queue => "queue/Error",
4076+ :max_redeliveries => 10
4077+ }
4078+
4079+ @connection = Stomp::Connection.new(used_hash)
4080+ received_hash = @connection.instance_variable_get(:@parameters)
4081+
4082+ #Using randomize we can't assure the hosts order
4083+ received_hash.delete(:hosts)
4084+ used_hash.delete(:hosts)
4085+
4086+ received_hash.should == used_hash
4087+ end
4088+
4089+ end
4090+
4091+ end
4092+
4093+ describe "when closing a socket" do
4094+ it "should close the tcp connection" do
4095+ @tcp_socket.should_receive(:close)
4096+ @connection.obj_send(:close_socket).should be_true
4097+ end
4098+ it "should ignore exceptions" do
4099+ @tcp_socket.should_receive(:close).and_raise "exception"
4100+ @connection.obj_send(:close_socket).should be_true
4101+ end
4102+ end
4103+
4104+ describe "when checking if max reconnect attempts have been reached" do
4105+ it "should return false if not using failover" do
4106+ host = @parameters[:hosts][0]
4107+ @connection = Stomp::Connection.new(host[:login], host[:passcode], host[:host], host[:port], reliable = true, 5, connect_headers = {})
4108+ @connection.instance_variable_set(:@connection_attempts, 10000)
4109+ @connection.max_reconnect_attempts?.should be_false
4110+ end
4111+ it "should return false if max_reconnect_attempts = 0" do
4112+ @connection.instance_variable_set(:@connection_attempts, 10000)
4113+ @connection.max_reconnect_attempts?.should be_false
4114+ end
4115+ it "should return true if connection attempts > max_reconnect_attempts" do
4116+ limit = 10000
4117+ @parameters[:max_reconnect_attempts] = limit
4118+ @connection = Stomp::Connection.new(@parameters)
4119+
4120+ @connection.instance_variable_set(:@connection_attempts, limit-1)
4121+ @connection.max_reconnect_attempts?.should be_false
4122+
4123+ @connection.instance_variable_set(:@connection_attempts, limit)
4124+ @connection.max_reconnect_attempts?.should be_true
4125+
4126+ end
4127+ end
4128+end
4129+
4130
4131=== added file 'spec/message_spec.rb'
4132--- spec/message_spec.rb 1970-01-01 00:00:00 +0000
4133+++ spec/message_spec.rb 2011-07-31 16:48:34 +0000
4134@@ -0,0 +1,56 @@
4135+require 'spec_helper'
4136+
4137+describe Stomp::Message do
4138+
4139+ context 'when initializing a new message' do
4140+
4141+ context 'with invalid parameters' do
4142+ it 'should return an empty message when receiving an empty string or nil parameter' do
4143+ message = Stomp::Message.new('')
4144+ message.should be_empty
4145+ end
4146+
4147+ it 'should raise Stomp::Error::InvalidFormat when receiving a invalid formated message' do
4148+ lambda{ Stomp::Message.new('any invalid format') }.should raise_error(Stomp::Error::InvalidFormat)
4149+ end
4150+ end
4151+
4152+ context 'with valid parameters' do
4153+ subject do
4154+ @message = ["CONNECTED\n", "session:host_address\n", "\n", "body value\n", "\000\n"]
4155+ Stomp::Message.new(@message.join)
4156+ end
4157+
4158+ it 'should parse the headers' do
4159+ subject.headers.should == {'session' => 'host_address'}
4160+ end
4161+
4162+ it 'should parse the body' do
4163+ subject.body.should == @message[3]
4164+ end
4165+
4166+ it 'should parse the command' do
4167+ subject.command.should == @message[0].chomp
4168+ end
4169+ end
4170+
4171+ context 'with multiple line ends on the body' do
4172+ subject do
4173+ @message = ["CONNECTED\n", "session:host_address\n", "\n", "body\n\n value\n\n\n", "\000\n"]
4174+ Stomp::Message.new(@message.join)
4175+ end
4176+
4177+ it 'should parse the headers' do
4178+ subject.headers.should == {'session' => 'host_address'}
4179+ end
4180+
4181+ it 'should parse the body' do
4182+ subject.body.should == @message[3]
4183+ end
4184+
4185+ it 'should parse the command' do
4186+ subject.command.should == @message[0].chomp
4187+ end
4188+ end
4189+ end
4190+end
4191
4192=== added file 'spec/spec_helper.rb'
4193--- spec/spec_helper.rb 1970-01-01 00:00:00 +0000
4194+++ spec/spec_helper.rb 2011-07-31 16:48:34 +0000
4195@@ -0,0 +1,6 @@
4196+require 'rspec'
4197+dir = File.dirname(__FILE__)
4198+lib_path = File.expand_path("#{dir}/../lib")
4199+$LOAD_PATH.unshift lib_path unless $LOAD_PATH.include?(lib_path)
4200+
4201+require 'stomp'
4202
4203=== added file 'stomp.gemspec'
4204--- stomp.gemspec 1970-01-01 00:00:00 +0000
4205+++ stomp.gemspec 2011-07-31 16:48:34 +0000
4206@@ -0,0 +1,66 @@
4207+# Generated by jeweler
4208+# DO NOT EDIT THIS FILE DIRECTLY
4209+# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4210+# -*- encoding: utf-8 -*-
4211+
4212+Gem::Specification.new do |s|
4213+ s.name = %q{stomp}
4214+ s.version = "1.1.9"
4215+
4216+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4217+ s.authors = [%q{Brian McCallister}, %q{Marius Mathiesen}, %q{Thiago Morello}, %q{Guy M. Allard}]
4218+ s.date = %q{2011-06-15}
4219+ s.description = %q{Ruby client for the Stomp messaging protocol}
4220+ s.email = [%q{brianm@apache.org}, %q{marius@stones.com}, %q{morellon@gmail.com}, %q{allard.guy.m@gmail.com}]
4221+ s.executables = [%q{catstomp}, %q{stompcat}]
4222+ s.extra_rdoc_files = [
4223+ "LICENSE",
4224+ "README.rdoc"
4225+ ]
4226+ s.files = [
4227+ "CHANGELOG.rdoc",
4228+ "LICENSE",
4229+ "README.rdoc",
4230+ "Rakefile",
4231+ "bin/catstomp",
4232+ "bin/stompcat",
4233+ "examples/consumer.rb",
4234+ "examples/logexamp.rb",
4235+ "examples/publisher.rb",
4236+ "examples/slogger.rb",
4237+ "lib/stomp.rb",
4238+ "lib/stomp/client.rb",
4239+ "lib/stomp/connection.rb",
4240+ "lib/stomp/errors.rb",
4241+ "lib/stomp/ext/hash.rb",
4242+ "lib/stomp/message.rb",
4243+ "lib/stomp/version.rb",
4244+ "spec/client_shared_examples.rb",
4245+ "spec/client_spec.rb",
4246+ "spec/connection_spec.rb",
4247+ "spec/message_spec.rb",
4248+ "spec/spec_helper.rb",
4249+ "stomp.gemspec",
4250+ "test/test_client.rb",
4251+ "test/test_connection.rb",
4252+ "test/test_helper.rb",
4253+ "test/test_message.rb"
4254+ ]
4255+ s.homepage = %q{https://rubygems.org/gems/stomp}
4256+ s.require_paths = [%q{lib}]
4257+ s.rubygems_version = %q{1.8.5}
4258+ s.summary = %q{Ruby client for the Stomp messaging protocol}
4259+
4260+ if s.respond_to? :specification_version then
4261+ s.specification_version = 3
4262+
4263+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
4264+ s.add_development_dependency(%q<rspec>, [">= 2.3"])
4265+ else
4266+ s.add_dependency(%q<rspec>, [">= 2.3"])
4267+ end
4268+ else
4269+ s.add_dependency(%q<rspec>, [">= 2.3"])
4270+ end
4271+end
4272+
4273
4274=== added directory 'test'
4275=== added file 'test/test_client.rb'
4276--- test/test_client.rb 1970-01-01 00:00:00 +0000
4277+++ test/test_client.rb 2011-07-31 16:48:34 +0000
4278@@ -0,0 +1,364 @@
4279+$:.unshift(File.dirname(__FILE__))
4280+
4281+require 'test_helper'
4282+
4283+class TestClient < Test::Unit::TestCase
4284+ include TestBase
4285+
4286+ def setup
4287+ @client = Stomp::Client.new(user, passcode, host, port)
4288+ # Multi_thread test data
4289+ @max_threads = 20
4290+ @max_msgs = 50
4291+ end
4292+
4293+ def teardown
4294+ @client.close if @client # allow tests to close
4295+ end
4296+
4297+ def test_ack_api_works
4298+ @client.publish destination, message_text, {:suppress_content_length => true}
4299+
4300+ received = nil
4301+ @client.subscribe(destination, {:ack => 'client'}) {|msg| received = msg}
4302+ sleep 0.01 until received
4303+ assert_equal message_text, received.body
4304+
4305+ receipt = nil
4306+ @client.acknowledge(received) {|r| receipt = r}
4307+ sleep 0.01 until receipt
4308+ assert_not_nil receipt.headers['receipt-id']
4309+ end
4310+
4311+ def test_asynch_subscribe
4312+ received = false
4313+ @client.subscribe(destination) {|msg| received = msg}
4314+ @client.publish destination, message_text
4315+ sleep 0.01 until received
4316+
4317+ assert_equal message_text, received.body
4318+ end
4319+
4320+ def test_noack
4321+ @client.publish destination, message_text
4322+
4323+ received = nil
4324+ @client.subscribe(destination, :ack => :client) {|msg| received = msg}
4325+ sleep 0.01 until received
4326+ assert_equal message_text, received.body
4327+ @client.close
4328+
4329+ # was never acked so should be resent to next client
4330+
4331+ @client = Stomp::Client.new(user, passcode, host, port)
4332+ received2 = nil
4333+ @client.subscribe(destination) {|msg| received2 = msg}
4334+ sleep 0.01 until received2
4335+
4336+ assert_equal message_text, received2.body
4337+ assert_equal received.body, received2.body
4338+ assert_equal received.headers['message-id'], received2.headers['message-id']
4339+ end
4340+
4341+ def test_receipts
4342+ receipt = false
4343+ @client.publish(destination, message_text) {|r| receipt = r}
4344+ sleep 0.1 until receipt
4345+
4346+ message = nil
4347+ @client.subscribe(destination) {|m| message = m}
4348+ sleep 0.1 until message
4349+ assert_equal message_text, message.body
4350+ end
4351+
4352+ def test_disconnect_receipt
4353+ @client.close :receipt => "xyz789"
4354+ assert_nothing_raised {
4355+ assert_not_nil(@client.disconnect_receipt, "should have a receipt")
4356+ assert_equal(@client.disconnect_receipt.headers['receipt-id'],
4357+ "xyz789", "receipt sent and received should match")
4358+ }
4359+ @client = nil
4360+ end
4361+
4362+ def test_publish_then_sub
4363+ @client.publish destination, message_text
4364+ message = nil
4365+ @client.subscribe(destination) {|m| message = m}
4366+ sleep 0.01 until message
4367+
4368+ assert_equal message_text, message.body
4369+ end
4370+
4371+ def test_subscribe_requires_block
4372+ assert_raise(RuntimeError) do
4373+ @client.subscribe destination
4374+ end
4375+ end
4376+
4377+ def test_transactional_publish
4378+ @client.begin 'tx1'
4379+ @client.publish destination, message_text, :transaction => 'tx1'
4380+ @client.commit 'tx1'
4381+
4382+ message = nil
4383+ @client.subscribe(destination) {|m| message = m}
4384+ sleep 0.01 until message
4385+
4386+ assert_equal message_text, message.body
4387+ end
4388+
4389+ def test_transaction_publish_then_rollback
4390+ @client.begin 'tx1'
4391+ @client.publish destination, "first_message", :transaction => 'tx1'
4392+ @client.abort 'tx1'
4393+
4394+ @client.begin 'tx1'
4395+ @client.publish destination, "second_message", :transaction => 'tx1'
4396+ @client.commit 'tx1'
4397+
4398+ message = nil
4399+ @client.subscribe(destination) {|m| message = m}
4400+ sleep 0.01 until message
4401+ assert_equal "second_message", message.body
4402+ end
4403+
4404+ def test_transaction_ack_rollback_with_new_client
4405+ @client.publish destination, message_text
4406+
4407+ @client.begin 'tx1'
4408+ message = nil
4409+ @client.subscribe(destination, :ack => 'client') {|m| message = m}
4410+ sleep 0.01 until message
4411+ assert_equal message_text, message.body
4412+ @client.acknowledge message, :transaction => 'tx1'
4413+ message = nil
4414+ @client.abort 'tx1'
4415+
4416+ # lets recreate the connection
4417+ teardown
4418+ setup
4419+ @client.subscribe(destination, :ack => 'client') {|m| message = m}
4420+
4421+ Timeout::timeout(4) do
4422+ sleep 0.01 until message
4423+ end
4424+ assert_not_nil message
4425+ assert_equal message_text, message.body
4426+
4427+ @client.begin 'tx2'
4428+ @client.acknowledge message, :transaction => 'tx2'
4429+ @client.commit 'tx2'
4430+ end
4431+
4432+ def test_raise_on_multiple_subscriptions_to_same_destination
4433+ subscribe_dest = destination
4434+ @client.subscribe(subscribe_dest) {|m| nil }
4435+ assert_raise(RuntimeError) do
4436+ @client.subscribe(subscribe_dest) {|m| nil }
4437+ end
4438+ end
4439+
4440+ def test_raise_on_multiple_subscriptions_to_same_id
4441+ subscribe_dest = destination
4442+ @client.subscribe(subscribe_dest, {'id' => 'myid'}) {|m| nil }
4443+ assert_raise(RuntimeError) do
4444+ @client.subscribe(subscribe_dest, {'id' => 'myid'}) {|m| nil }
4445+ end
4446+ end
4447+
4448+ def test_raise_on_multiple_subscriptions_to_same_id_mixed
4449+ subscribe_dest = destination
4450+ @client.subscribe(subscribe_dest, {'id' => 'myid'}) {|m| nil }
4451+ assert_raise(RuntimeError) do
4452+ @client.subscribe(subscribe_dest, {:id => 'myid'}) {|m| nil }
4453+ end
4454+ end
4455+
4456+ def test_asterisk_wildcard_subscribe
4457+ queue_base_name = destination
4458+ queue1 = queue_base_name + ".a"
4459+ queue2 = queue_base_name + ".b"
4460+ send_message = message_text
4461+ @client.publish queue1, send_message
4462+ @client.publish queue2, send_message
4463+ messages = []
4464+ @client.subscribe(queue_base_name + ".*", :ack => 'client') do |m|
4465+ messages << m
4466+ @client.acknowledge(m)
4467+ end
4468+ Timeout::timeout(4) do
4469+ sleep 0.1 while messages.size < 2
4470+ end
4471+
4472+ messages.each do |message|
4473+ assert_not_nil message
4474+ assert_equal send_message, message.body
4475+ end
4476+ results = [queue1, queue2].collect do |queue|
4477+ messages.any? do |message|
4478+ message_source = message.headers['destination']
4479+ message_source == queue
4480+ end
4481+ end
4482+ assert results.all?{|a| a == true }
4483+
4484+ end unless ENV['STOMP_NOWILD']
4485+
4486+ def test_greater_than_wildcard_subscribe
4487+ queue_base_name = destination + "."
4488+ queue1 = queue_base_name + "foo.a"
4489+ queue2 = queue_base_name + "bar.a"
4490+ queue3 = queue_base_name + "foo.b"
4491+ send_message = message_text
4492+ @client.publish queue1, send_message
4493+ @client.publish queue2, send_message
4494+ @client.publish queue3, send_message
4495+ messages = []
4496+ # should subscribe to all three queues
4497+ @client.subscribe(queue_base_name + ">", :ack => 'client') do |m|
4498+ messages << m
4499+ @client.acknowledge(m)
4500+ end
4501+ Timeout::timeout(4) do
4502+ sleep 0.1 while messages.size < 3
4503+ end
4504+
4505+ messages.each do |message|
4506+ assert_not_nil message
4507+ assert_equal send_message, message.body
4508+ end
4509+ # make sure that the messages received came from the expected queues
4510+ results = [queue1, queue2, queue3].collect do |queue|
4511+ messages.any? do |message|
4512+ message_source = message.headers['destination']
4513+ message_source == queue
4514+ end
4515+ end
4516+ assert results.all?{|a| a == true }
4517+ end unless ENV['STOMP_NOWILD'] || ENV['STOMP_APOLLO']
4518+
4519+ def test_transaction_with_client_side_redelivery
4520+ @client.publish destination, message_text
4521+
4522+ @client.begin 'tx1'
4523+ message = nil
4524+ @client.subscribe(destination, :ack => 'client') { |m| message = m }
4525+
4526+ sleep 0.1 while message.nil?
4527+
4528+ assert_equal message_text, message.body
4529+ @client.acknowledge message, :transaction => 'tx1'
4530+ message = nil
4531+ @client.abort 'tx1'
4532+
4533+ sleep 0.1 while message.nil?
4534+
4535+ assert_not_nil message
4536+ assert_equal message_text, message.body
4537+
4538+ @client.begin 'tx2'
4539+ @client.acknowledge message, :transaction => 'tx2'
4540+ @client.commit 'tx2'
4541+ end
4542+
4543+ def test_connection_frame
4544+ assert_not_nil @client.connection_frame
4545+ end
4546+
4547+ def test_unsubscribe
4548+ message = nil
4549+ dest = destination
4550+ to_send = message_text
4551+ client = Stomp::Client.new(user, passcode, host, port, true)
4552+ assert_nothing_raised {
4553+ client.subscribe(dest, :ack => 'client') { |m| message = m }
4554+ @client.publish dest, to_send
4555+ Timeout::timeout(4) do
4556+ sleep 0.01 until message
4557+ end
4558+ }
4559+ assert_equal to_send, message.body, "first body check"
4560+ assert_nothing_raised {
4561+ client.unsubscribe dest # was throwing exception on unsub at one point
4562+ client.close
4563+ }
4564+ # Same message should remain on the queue. Receive it again with ack=>auto.
4565+ message_copy = nil
4566+ client = Stomp::Client.new(user, passcode, host, port, true)
4567+ assert_nothing_raised {
4568+ client.subscribe(dest, :ack => 'auto') { |m| message_copy = m }
4569+ Timeout::timeout(4) do
4570+ sleep 0.01 until message_copy
4571+ end
4572+ }
4573+ assert_equal to_send, message_copy.body, "second body check"
4574+ assert_equal message.headers['message-id'], message_copy.headers['message-id'], "header check"
4575+ end
4576+
4577+ def test_thread_one_subscribe
4578+ msg = nil
4579+ dest = destination
4580+ Thread.new(@client) do |acli|
4581+ assert_nothing_raised {
4582+ acli.subscribe(dest) { |m| msg = m }
4583+ Timeout::timeout(4) do
4584+ sleep 0.01 until msg
4585+ end
4586+ }
4587+ end
4588+ #
4589+ @client.publish(dest, message_text)
4590+ sleep 1
4591+ assert_not_nil msg
4592+ end
4593+
4594+ def test_thread_multi_subscribe
4595+ #
4596+ lock = Mutex.new
4597+ msg_ctr = 0
4598+ dest = destination
4599+ 1.upto(@max_threads) do |tnum|
4600+ # Threads within threads .....
4601+ Thread.new(@client) do |acli|
4602+ assert_nothing_raised {
4603+ acli.subscribe(dest) { |m|
4604+ msg = m
4605+ lock.synchronize do
4606+ msg_ctr += 1
4607+ end
4608+ # Simulate message processing
4609+ sleep 0.05
4610+ }
4611+ }
4612+ end
4613+ end
4614+ #
4615+ 1.upto(@max_msgs) do |mnum|
4616+ msg = Time.now.to_s + " #{mnum}"
4617+ @client.publish(dest, message_text)
4618+ end
4619+ #
4620+ max_sleep = (RUBY_VERSION =~ /1\.8\.6/) ? 30 : 5
4621+ sleep_incr = 0.10
4622+ total_slept = 0
4623+ while true
4624+ break if @max_msgs == msg_ctr
4625+ total_slept += sleep_incr
4626+ break if total_slept > max_sleep
4627+ sleep sleep_incr
4628+ end
4629+ assert_equal @max_msgs, msg_ctr
4630+ end
4631+
4632+ private
4633+ def message_text
4634+ name = caller_method_name unless name
4635+ "test_client#" + name
4636+ end
4637+
4638+ def destination
4639+ name = caller_method_name unless name
4640+ qname = ENV['STOMP_APOLLO'] ? "/queue/test.ruby.stomp." + name : "/queue/test/ruby/stomp/" + name
4641+ end
4642+end
4643
4644=== added file 'test/test_connection.rb'
4645--- test/test_connection.rb 1970-01-01 00:00:00 +0000
4646+++ test/test_connection.rb 2011-07-31 16:48:34 +0000
4647@@ -0,0 +1,278 @@
4648+$:.unshift(File.dirname(__FILE__))
4649+
4650+require 'test_helper'
4651+
4652+class TestStomp < Test::Unit::TestCase
4653+ include TestBase
4654+
4655+ def setup
4656+ @conn = Stomp::Connection.open(user, passcode, host, port)
4657+ # Data for multi_thread tests
4658+ @max_threads = 20
4659+ @max_msgs = 100
4660+ end
4661+
4662+ def teardown
4663+ @conn.disconnect if @conn # allow tests to disconnect
4664+ end
4665+
4666+ def test_connection_exists
4667+ assert_not_nil @conn
4668+ end
4669+
4670+ def test_no_length
4671+ @conn.subscribe make_destination
4672+ #
4673+ @conn.publish make_destination, "test_stomp#test_no_length",
4674+ { :suppress_content_length => true }
4675+ msg = @conn.receive
4676+ assert_equal "test_stomp#test_no_length", msg.body
4677+ #
4678+ @conn.publish make_destination, "test_stomp#test_\000_length",
4679+ { :suppress_content_length => true }
4680+ msg2 = @conn.receive
4681+ assert_equal "test_stomp#test_", msg2.body
4682+ end
4683+
4684+ def test_explicit_receive
4685+ @conn.subscribe make_destination
4686+ @conn.publish make_destination, "test_stomp#test_explicit_receive"
4687+ msg = @conn.receive
4688+ assert_equal "test_stomp#test_explicit_receive", msg.body
4689+ end
4690+
4691+ def test_receipt
4692+ @conn.subscribe make_destination, :receipt => "abc"
4693+ msg = @conn.receive
4694+ assert_equal "abc", msg.headers['receipt-id']
4695+ end
4696+
4697+ def test_disconnect_receipt
4698+ @conn.disconnect :receipt => "abc123"
4699+ assert_nothing_raised {
4700+ assert_not_nil(@conn.disconnect_receipt, "should have a receipt")
4701+ assert_equal(@conn.disconnect_receipt.headers['receipt-id'],
4702+ "abc123", "receipt sent and received should match")
4703+ }
4704+ @conn = nil
4705+ end
4706+
4707+ def test_client_ack_with_symbol
4708+ @conn.subscribe make_destination, :ack => :client
4709+ @conn.publish make_destination, "test_stomp#test_client_ack_with_symbol"
4710+ msg = @conn.receive
4711+ @conn.ack msg.headers['message-id']
4712+ end
4713+
4714+ def test_embedded_null
4715+ @conn.subscribe make_destination
4716+ @conn.publish make_destination, "a\0"
4717+ msg = @conn.receive
4718+ assert_equal "a\0" , msg.body
4719+ end
4720+
4721+ def test_connection_open?
4722+ assert_equal true , @conn.open?
4723+ @conn.disconnect
4724+ assert_equal false, @conn.open?
4725+ end
4726+
4727+ def test_connection_closed?
4728+ assert_equal false, @conn.closed?
4729+ @conn.disconnect
4730+ assert_equal true, @conn.closed?
4731+ end
4732+
4733+ def test_response_is_instance_of_message_class
4734+ @conn.subscribe make_destination
4735+ @conn.publish make_destination, "a\0"
4736+ msg = @conn.receive
4737+ assert_instance_of Stomp::Message , msg
4738+ end
4739+
4740+ def test_message_to_s
4741+ @conn.subscribe make_destination
4742+ @conn.publish make_destination, "a\0"
4743+ msg = @conn.receive
4744+ assert_match /^<Stomp::Message headers=/ , msg.to_s
4745+ end
4746+
4747+ def test_connection_frame
4748+ assert_not_nil @conn.connection_frame
4749+ end
4750+
4751+ def test_messages_with_multipleLine_ends
4752+ @conn.subscribe make_destination
4753+ @conn.publish make_destination, "a\n\n"
4754+ @conn.publish make_destination, "b\n\na\n\n"
4755+
4756+ msg_a = @conn.receive
4757+ msg_b = @conn.receive
4758+
4759+ assert_equal "a\n\n", msg_a.body
4760+ assert_equal "b\n\na\n\n", msg_b.body
4761+ end
4762+
4763+ def test_publish_two_messages
4764+ @conn.subscribe make_destination
4765+ @conn.publish make_destination, "a\0"
4766+ @conn.publish make_destination, "b\0"
4767+ msg_a = @conn.receive
4768+ msg_b = @conn.receive
4769+
4770+ assert_equal "a\0", msg_a.body
4771+ assert_equal "b\0", msg_b.body
4772+ end
4773+
4774+ def test_thread_hang_one
4775+ received = nil
4776+ Thread.new(@conn) do |amq|
4777+ while true
4778+ received = amq.receive
4779+ end
4780+ end
4781+ #
4782+ @conn.subscribe( make_destination )
4783+ message = Time.now.to_s
4784+ @conn.publish(make_destination, message)
4785+ sleep 1
4786+ assert_not_nil received
4787+ assert_equal message, received.body
4788+ end
4789+
4790+ def test_thread_poll_one
4791+ received = nil
4792+ max_sleep = (RUBY_VERSION =~ /1\.8\.6/) ? 5 : 1
4793+ Thread.new(@conn) do |amq|
4794+ while true
4795+ received = amq.poll
4796+ # One message is needed
4797+ Thread.exit if received
4798+ sleep max_sleep
4799+ end
4800+ end
4801+ #
4802+ @conn.subscribe( make_destination )
4803+ message = Time.now.to_s
4804+ @conn.publish(make_destination, message)
4805+ sleep max_sleep+1
4806+ assert_not_nil received
4807+ assert_equal message, received.body
4808+ end
4809+
4810+ def test_multi_thread_receive
4811+ lock = Mutex.new
4812+ msg_ctr = 0
4813+ dest = make_destination
4814+ #
4815+ 1.upto(@max_threads) do |tnum|
4816+ Thread.new(@conn) do |amq|
4817+ while true
4818+ received = amq.receive
4819+ lock.synchronize do
4820+ msg_ctr += 1
4821+ end
4822+ # Simulate message processing
4823+ sleep 0.05
4824+ end
4825+ end
4826+ end
4827+ #
4828+ @conn.subscribe( dest )
4829+ 1.upto(@max_msgs) do |mnum|
4830+ msg = Time.now.to_s + " #{mnum}"
4831+ @conn.publish(dest, msg)
4832+ end
4833+ #
4834+ max_sleep = (RUBY_VERSION =~ /1\.8\.6/) ? 30 : 5
4835+ sleep_incr = 0.10
4836+ total_slept = 0
4837+ while true
4838+ break if @max_msgs == msg_ctr
4839+ total_slept += sleep_incr
4840+ break if total_slept > max_sleep
4841+ sleep sleep_incr
4842+ end
4843+ assert_equal @max_msgs, msg_ctr
4844+ end
4845+
4846+ def test_multi_thread_poll
4847+ #
4848+ lock = Mutex.new
4849+ msg_ctr = 0
4850+ dest = make_destination
4851+ #
4852+ 1.upto(@max_threads) do |tnum|
4853+ Thread.new(@conn) do |amq|
4854+ while true
4855+ received = amq.poll
4856+ if received
4857+ lock.synchronize do
4858+ msg_ctr += 1
4859+ end
4860+ # Simulate message processing
4861+ sleep 0.05
4862+ else
4863+ # Wait a bit for more work
4864+ sleep 0.05
4865+ end
4866+ end
4867+ end
4868+ end
4869+ #
4870+ @conn.subscribe( dest )
4871+ 1.upto(@max_msgs) do |mnum|
4872+ msg = Time.now.to_s + " #{mnum}"
4873+ @conn.publish(dest, msg)
4874+ end
4875+ #
4876+ max_sleep = (RUBY_VERSION =~ /1\.8\.6/) ? 30 : 5
4877+ sleep_incr = 0.10
4878+ total_slept = 0
4879+ while true
4880+ break if @max_msgs == msg_ctr
4881+ total_slept += sleep_incr
4882+ break if total_slept > max_sleep
4883+ sleep sleep_incr
4884+ end
4885+ assert_equal @max_msgs, msg_ctr
4886+ end
4887+
4888+ def test_nil_body
4889+ dest = make_destination
4890+ assert_nothing_raised {
4891+ @conn.publish dest, nil
4892+ }
4893+ @conn.subscribe dest
4894+ msg = @conn.receive
4895+ assert_equal "", msg.body
4896+ end
4897+
4898+ private
4899+ def make_destination
4900+ name = caller_method_name unless name
4901+ qname = ENV['STOMP_APOLLO'] ? "/queue/test.ruby.stomp." + name : "/queue/test/ruby/stomp/" + name
4902+ end
4903+
4904+ def _test_transaction
4905+ @conn.subscribe make_destination
4906+
4907+ # Drain the destination.
4908+ sleep 0.01 while
4909+ sleep 0.01 while @conn.poll!=nil
4910+
4911+ @conn.begin "tx1"
4912+ @conn.publish make_destination, "txn message", 'transaction' => "tx1"
4913+
4914+ @conn.publish make_destination, "first message"
4915+
4916+ sleep 0.01
4917+ msg = @conn.receive
4918+ assert_equal "first message", msg.body
4919+
4920+ @conn.commit "tx1"
4921+ msg = @conn.receive
4922+ assert_equal "txn message", msg.body
4923+ end
4924+end
4925+
4926
4927=== added file 'test/test_helper.rb'
4928--- test/test_helper.rb 1970-01-01 00:00:00 +0000
4929+++ test/test_helper.rb 2011-07-31 16:48:34 +0000
4930@@ -0,0 +1,38 @@
4931+$:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
4932+
4933+require 'test/unit'
4934+require 'timeout'
4935+require 'stomp'
4936+
4937+# Helper routines
4938+module TestBase
4939+ def user
4940+ ENV['STOMP_USER'] || "test"
4941+ end
4942+ def passcode
4943+ ENV['STOMP_PASSCODE'] || "user"
4944+ end
4945+ # Get host
4946+ def host
4947+ ENV['STOMP_HOST'] || "localhost"
4948+ end
4949+ # Get port
4950+ def port
4951+ (ENV['STOMP_PORT'] || 61613).to_i
4952+ end
4953+ # Helper for minitest on 1.9
4954+ def caller_method_name
4955+ parse_caller(caller(2).first).last
4956+ end
4957+ # Helper for minitest on 1.9
4958+ def parse_caller(at)
4959+ if /^(.+?):(\d+)(?::in `(.*)')?/ =~ at
4960+ file = Regexp.last_match[1]
4961+ line = Regexp.last_match[2].to_i
4962+ method = Regexp.last_match[3]
4963+ method.gsub!(" ","_")
4964+ [file, line, method]
4965+ end
4966+ end
4967+end
4968+
4969
4970=== added file 'test/test_message.rb'
4971--- test/test_message.rb 1970-01-01 00:00:00 +0000
4972+++ test/test_message.rb 2011-07-31 16:48:34 +0000
4973@@ -0,0 +1,118 @@
4974+$:.unshift(File.dirname(__FILE__))
4975+#
4976+# Test Ruby 1.8 with $KCODE='U'
4977+#
4978+require 'test_helper'
4979+#
4980+class TestMessageKcode < Test::Unit::TestCase
4981+ include TestBase
4982+ #
4983+ def setup
4984+ $KCODE = 'U' if RUBY_VERSION =~ /1\.8/
4985+ @conn = Stomp::Connection.open(user, passcode, host, port)
4986+ # Message body data
4987+ @messages = [
4988+ "normal text message",
4989+ "bad byte: \372",
4990+ "\004\b{\f:\tbody\"\001\207\004\b{\b:\016statusmsg\"\aOK:\017statuscodei\000:\tdata{\t:\voutput\"3Enabled, not running, last run 693 seconds ago:\frunningi\000:\fenabledi\006:\flastrunl+\aE\021\022M:\rsenderid\"\032xx.xx.xx.xx:\016requestid\"%849d647bbe3e421ea19ac9f947bbdde4:\020senderagent\"\fpuppetd:\016msgtarget\"%/topic/mcollective.puppetd.reply:\thash\"\001\257ZdQqtaDmmdD0jZinnEcpN+YbkxQDn8uuCnwsQdvGHau6d+gxnnfPLUddWRSb\nZNMs+sQUXgJNfcV1eVBn1H+Z8QQmzYXVDMqz7J43jmgloz5PsLVbN9K3PmX/\ngszqV/WpvIyAqm98ennWqSzpwMuiCC4q2Jr3s3Gm6bUJ6UkKXnY=\n:\fmsgtimel+\a\372\023\022M"
4991+ ]
4992+ #
4993+ end
4994+
4995+ def teardown
4996+ @conn.disconnect if @conn # allow tests to disconnect
4997+ end
4998+
4999+ # Various message bodies, including the failing test case reported
5000+ def test_kcode_001
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: