Merge lp:~elmo/ubuntu/oneiric/libstomp-ruby/bug-707317 into lp:ubuntu/oneiric/libstomp-ruby
- Oneiric (11.10)
- bug-707317
- Merge into oneiric
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 |
Related bugs: |
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 |
Commit message
Description of the change
New upstream version, required for mcollective and also fixes LP
#707317 and Debian #598564.
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-
> E: libstomp-ruby: ruby-script-
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
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.
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.
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 :)
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-
> E: libstomp-ruby: ruby-script-
I've pushed r10 which fixes all of the above issues.
Dave Walker (davewalker) wrote : | # |
Thanks for addressing this, uploading now!
Preview Diff
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' |
1881 | Binary 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 |
I know little about Debian ruby packaging. But I can spot some issues: but-no- ruby-dep usr/bin/catstomp but-no- ruby-dep usr/bin/stompcat
* 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-
E: libstomp-ruby: ruby-script-