bunny 0.5.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -28,6 +28,9 @@ Sets up a Bunny::Client object ready for connection to a broker/server. _Client_
28
28
  * <tt>:logging => true or false (_default_)</tt> - If set to _true_, session information is sent
29
29
  to STDOUT if <tt>:logfile</tt> has not been specified. Otherwise, session information is written to
30
30
  <tt>:logfile</tt>.
31
+ * <tt>:frame_max => maximum frame size in bytes (default = 131072)</tt>
32
+ * <tt>:channel_max => maximum number of channels (default = 0 no maximum)</tt>
33
+ * <tt>:heartbeat => number of seconds (default = 0 no heartbeat)</tt>
31
34
 
32
35
  =end
33
36
 
@@ -38,15 +41,21 @@ Sets up a Bunny::Client object ready for connection to a broker/server. _Client_
38
41
  @user = opts[:user] || 'guest'
39
42
  @pass = opts[:pass] || 'guest'
40
43
  @vhost = opts[:vhost] || '/'
41
- @frame_max = opts[:frame_max] || 131072
42
- @channel_max = opts[:channel_max] || 5
43
44
  @logfile = opts[:logfile] || nil
44
45
  @logging = opts[:logging] || false
45
- @status = :not_connected
46
+ @status = :not_connected
47
+ @frame_max = opts[:frame_max] || 131072
48
+ @channel_max = opts[:channel_max] || 5
49
+ @heartbeat = opts[:heartbeat] || 0
46
50
  @logger = nil
47
51
  create_logger if @logging
52
+ @channels = []
48
53
  # Create channel 0
49
54
  @channel = Bunny::Channel09.new(self, true)
55
+ @exchanges = {}
56
+ @queues = {}
57
+ @heartbeat_in = false
58
+ @connecting = false
50
59
  end
51
60
 
52
61
  =begin rdoc
@@ -85,18 +94,6 @@ Exchange
85
94
 
86
95
  === DESCRIPTION:
87
96
 
88
- Returns hash of exchanges declared by Bunny.
89
-
90
- =end
91
-
92
- def exchanges
93
- @exchanges ||= {}
94
- end
95
-
96
- =begin rdoc
97
-
98
- === DESCRIPTION:
99
-
100
97
  Declares a queue to the broker/server. If the queue does not exist, a new one is created
101
98
  using the arguments passed in. If the queue already exists, a reference to it is created, provided
102
99
  that the arguments passed in do not conflict with the existing attributes of the queue. If an error
@@ -135,19 +132,16 @@ Queue
135
132
 
136
133
  return queues[name] if queues.has_key?(name)
137
134
 
138
- queue = Bunny::Queue09.new(self, name, opts)
135
+ Bunny::Queue09.new(self, name, opts)
139
136
  end
140
-
141
- =begin rdoc
142
-
143
- === DESCRIPTION:
144
-
145
- Returns hash of queues declared by Bunny.
146
-
147
- =end
148
137
 
149
- def queues
150
- @queues ||= {}
138
+ def send_heartbeat
139
+ # Create a new heartbeat frame
140
+ hb = Qrack::Transport09::Heartbeat.new('')
141
+ # Channel 0 must be used
142
+ switch_channel(0) if @channel.number > 0
143
+ # Send the heartbeat to server
144
+ send_frame(hb)
151
145
  end
152
146
 
153
147
  def send_frame(*args)
@@ -161,10 +155,27 @@ Returns hash of queues declared by Bunny.
161
155
  nil
162
156
  end
163
157
 
164
- def next_frame
165
- frame = Qrack::Transport09::Frame.parse(buffer)
166
- @logger.info("received") { frame } if @logging
167
- frame
158
+ def next_frame(opts = {})
159
+ secs = opts[:timeout] || 0
160
+
161
+ begin
162
+ Timeout::timeout(secs) do
163
+ @frame = Qrack::Transport09::Frame.parse(buffer)
164
+ end
165
+ rescue Timeout::Error
166
+ return :timed_out
167
+ end
168
+
169
+ @logger.info("received") { @frame } if @logging
170
+
171
+ raise Bunny::ConnectionError, 'No connection to server' if (@frame.nil? and !connecting?)
172
+
173
+ if @frame.is_a?(Qrack::Transport09::Heartbeat)
174
+ @heartbeat_in = true
175
+ next_frame
176
+ end
177
+
178
+ @frame
168
179
  end
169
180
 
170
181
  def next_payload
@@ -173,6 +184,43 @@ Returns hash of queues declared by Bunny.
173
184
  end
174
185
 
175
186
  alias next_method next_payload
187
+
188
+ =begin rdoc
189
+
190
+ === DESCRIPTION:
191
+
192
+ Checks to see whether or not an undeliverable message has been returned as a result of a publish
193
+ with the <tt>:immediate</tt> or <tt>:mandatory</tt> options.
194
+
195
+ ==== OPTIONS:
196
+
197
+ * <tt>:timeout => number of seconds (default = 0.1) - The method will wait for a return
198
+ message until this timeout interval is reached.
199
+
200
+ ==== RETURNS:
201
+
202
+ <tt>:no_return</tt> if message was not returned before timeout .
203
+ <tt>{:header, :return_details, :payload}</tt> if message is returned. <tt>:return_details</tt> is
204
+ a hash <tt>{:reply_code, :reply_text, :exchange, :routing_key}</tt>.
205
+
206
+ =end
207
+
208
+ def returned_message(opts = {})
209
+ secs = opts[:timeout] || 0.1
210
+ frame = next_frame(:timeout => secs)
211
+
212
+ if frame.is_a?(Symbol)
213
+ return :no_return if frame == :timed_out
214
+ end
215
+
216
+ method = frame.payload
217
+ header = next_payload
218
+ msg = next_payload
219
+ raise Bunny::MessageError, 'unexpected length' if msg.length < header.size
220
+
221
+ # Return the message and related info
222
+ {:header => header, :payload => msg, :return_details => method.arguments}
223
+ end
176
224
 
177
225
  =begin rdoc
178
226
 
@@ -189,12 +237,9 @@ _Bunny_::_ProtocolError_ is raised. If successful, _Client_._status_ is set to <
189
237
 
190
238
  def close
191
239
  # Close all active channels
192
- channels.each_value do |c|
240
+ channels.each do |c|
193
241
  c.close if c.open?
194
242
  end
195
-
196
- # Set client channel to zero
197
- self.channel = channels[0]
198
243
 
199
244
  # Close connection to AMQP server
200
245
  close_connection
@@ -227,6 +272,8 @@ _Bunny_::_ProtocolError_ is raised. If successful, _Client_._status_ is set to <
227
272
  =end
228
273
 
229
274
  def start_session
275
+ @connecting = true
276
+
230
277
  # Create/get socket
231
278
  socket
232
279
 
@@ -236,12 +283,14 @@ _Bunny_::_ProtocolError_ is raised. If successful, _Client_._status_ is set to <
236
283
  # Open connection
237
284
  open_connection
238
285
 
239
- # Open a channel
240
- self.channel = get_channel
241
- channel.open
286
+ # Open another channel because channel zero is used for specific purposes
287
+ c = create_channel()
288
+ c.open
242
289
 
290
+ @connecting = false
291
+
243
292
  # return status
244
- status
293
+ @status = :connected
245
294
  end
246
295
 
247
296
  alias start start_session
@@ -250,7 +299,7 @@ _Bunny_::_ProtocolError_ is raised. If successful, _Client_._status_ is set to <
250
299
 
251
300
  === DESCRIPTION:
252
301
 
253
- Asks the broker to redeliver all unacknowledged messages on a specifieid channel. Zero or
302
+ Asks the broker to redeliver all unacknowledged messages on a specified channel. Zero or
254
303
  more messages may be redelivered.
255
304
 
256
305
  ==== Options:
@@ -376,22 +425,35 @@ after a rollback.
376
425
  create_logger if @logging
377
426
  end
378
427
 
379
- def channels
380
- @channels ||= {}
381
- end
382
-
383
- def get_channel
384
- channels.each_value do |c|
428
+ def create_channel
429
+ channels.each do |c|
385
430
  return c if (!c.open? and c.number != 0)
386
431
  end
387
432
  # If no channel to re-use instantiate new one
388
433
  Bunny::Channel09.new(self)
389
434
  end
390
435
 
436
+ def switch_channel(chann)
437
+ if (0...channels.size).include? chann
438
+ @channel = channels[chann]
439
+ chann
440
+ else
441
+ raise RuntimeError, "Invalid channel number - #{chann}"
442
+ end
443
+ end
444
+
445
+ def connecting?
446
+ connecting
447
+ end
448
+
391
449
  def init_connection
392
450
  write(Qrack::Protocol09::HEADER)
393
451
  write([0, Qrack::Protocol09::VERSION_MAJOR, Qrack::Protocol09::VERSION_MINOR, Qrack::Protocol09::REVISION].pack('C4'))
394
- raise Bunny::ProtocolError, 'Connection initiation failed' unless next_method.is_a?(Qrack::Protocol09::Connection::Start)
452
+
453
+ frame = next_frame
454
+ if frame.nil? or !frame.payload.is_a?(Qrack::Protocol09::Connection::Start)
455
+ raise Bunny::ProtocolError, 'Connection initiation failed'
456
+ end
395
457
  end
396
458
 
397
459
  def open_connection
@@ -404,12 +466,14 @@ after a rollback.
404
466
  )
405
467
  )
406
468
 
407
- method = next_method
408
- raise Bunny::ProtocolError, "Connection failed - user: #{@user}, pass: #{@pass}" if method.nil?
409
-
469
+ frame = next_frame
470
+ raise Bunny::ProtocolError, "Connection failed - user: #{@user}, pass: #{@pass}" if frame.nil?
471
+
472
+ method = frame.payload
473
+
410
474
  if method.is_a?(Qrack::Protocol09::Connection::Tune)
411
475
  send_frame(
412
- Qrack::Protocol09::Connection::TuneOk.new( :channel_max => @channel_max, :frame_max => @frame_max, :heartbeat => 0)
476
+ Qrack::Protocol09::Connection::TuneOk.new( :channel_max => @channel_max, :frame_max => @frame_max, :heartbeat => @heartbeat)
413
477
  )
414
478
  end
415
479
 
@@ -421,6 +485,9 @@ after a rollback.
421
485
  end
422
486
 
423
487
  def close_connection
488
+ # Set client channel to zero
489
+ switch_channel(0)
490
+
424
491
  send_frame(
425
492
  Qrack::Protocol09::Connection::Close.new(:reply_code => 200, :reply_text => 'Goodbye', :class_id => 0, :method_id => 0)
426
493
  )
@@ -446,8 +513,6 @@ after a rollback.
446
513
  return @socket if @socket and (@status == :connected) and not @socket.closed?
447
514
 
448
515
  begin
449
- @status = :not_connected
450
-
451
516
  # Attempt to connect.
452
517
  @socket = timeout(CONNECT_TIMEOUT) do
453
518
  TCPSocket.new(host, port)
@@ -456,7 +521,6 @@ after a rollback.
456
521
  if Socket.constants.include? 'TCP_NODELAY'
457
522
  @socket.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
458
523
  end
459
- @status = :connected
460
524
  rescue => e
461
525
  @status = :not_connected
462
526
  raise Bunny::ServerDownError, e.message
@@ -214,17 +214,21 @@ processing. If error occurs, _Bunny_::_ProtocolError_ is raised.
214
214
  * <tt>:exclusive => true or false (_default_)</tt> - Request exclusive consumer access, meaning
215
215
  only this consumer can access the queue.
216
216
  * <tt>:nowait => true or false (_default_)</tt> - Ignored by Bunny, always _false_.
217
+ * <tt>:timeout => number of seconds (default = 0 no timeout) - The subscribe loop will continue to wait for
218
+ messages until terminated (Ctrl-C or kill command) or this timeout interval is reached.
217
219
 
218
220
  ==== RETURNS:
219
221
 
220
222
  If <tt>:header => true</tt> returns hash <tt>{:header, :delivery_details, :payload}</tt> for each message.
221
223
  <tt>:delivery_details</tt> is a hash <tt>{:consumer_tag, :delivery_tag, :redelivered, :exchange, :routing_key}</tt>.
222
224
  If <tt>:header => false</tt> only message payload is returned.
225
+ If <tt>:timeout => > 0</tt> is reached returns :timed_out
223
226
 
224
227
  =end
225
228
 
226
229
  def subscribe(opts = {}, &blk)
227
230
  consumer_tag = opts[:consumer_tag] || name
231
+ secs = opts[:timeout] || 0
228
232
 
229
233
  # ignore the :nowait option if passed, otherwise program will hang waiting for a
230
234
  # response from the server causing an error.
@@ -247,13 +251,19 @@ If <tt>:header => false</tt> only message payload is returned.
247
251
  "Error subscribing to queue #{name}" unless
248
252
  client.next_method.is_a?(Qrack::Protocol::Basic::ConsumeOk)
249
253
 
250
- while true
251
- method = client.next_method
252
-
253
- break if method.is_a?(Qrack::Protocol::Basic::CancelOk)
254
+ loop do
255
+ begin
256
+ Timeout::timeout(secs) do
257
+ @method = client.next_method
258
+ end
259
+ rescue Timeout::Error
260
+ return :timed_out
261
+ end
262
+
263
+ break if @method.is_a?(Qrack::Protocol::Basic::CancelOk)
254
264
 
255
265
  # get delivery tag to use for acknowledge
256
- self.delivery_tag = method.delivery_tag if ack
266
+ self.delivery_tag = @method.delivery_tag if ack
257
267
 
258
268
  header = client.next_payload
259
269
  msg = client.next_payload
@@ -287,6 +297,10 @@ the server will not send any more messages for that consumer.
287
297
  opts.delete(:nowait)
288
298
 
289
299
  client.send_frame( Qrack::Protocol::Basic::Cancel.new({ :consumer_tag => consumer_tag }.merge(opts)))
300
+
301
+ raise Bunny::ProtocolError,
302
+ "Error unsubscribing from queue #{name}" unless
303
+ client.next_method.is_a?(Qrack::Protocol::Basic::CancelOk)
290
304
 
291
305
  end
292
306
 
@@ -216,17 +216,21 @@ processing. If error occurs, _Bunny_::_ProtocolError_ is raised.
216
216
  * <tt>:exclusive => true or false (_default_)</tt> - Request exclusive consumer access, meaning
217
217
  only this consumer can access the queue.
218
218
  * <tt>:nowait => true or false (_default_)</tt> - Ignored by Bunny, always _false_.
219
+ * <tt>:timeout => number of seconds (default = 0 no timeout) - The subscribe loop will continue to wait for
220
+ messages until terminated (Ctrl-C or kill command) or this timeout interval is reached.
219
221
 
220
222
  ==== RETURNS:
221
223
 
222
224
  If <tt>:header => true</tt> returns hash <tt>{:header, :delivery_details, :payload}</tt> for each message.
223
225
  <tt>:delivery_details</tt> is a hash <tt>{:consumer_tag, :delivery_tag, :redelivered, :exchange, :routing_key}</tt>.
224
226
  If <tt>:header => false</tt> only message payload is returned.
227
+ If <tt>:timeout => > 0</tt> is reached returns :timed_out
225
228
 
226
229
  =end
227
230
 
228
231
  def subscribe(opts = {}, &blk)
229
232
  consumer_tag = opts[:consumer_tag] || name
233
+ secs = opts[:timeout] || 0
230
234
 
231
235
  # ignore the :nowait option if passed, otherwise program will hang waiting for a
232
236
  # response from the server causing an error.
@@ -250,13 +254,19 @@ If <tt>:header => false</tt> only message payload is returned.
250
254
  "Error subscribing to queue #{name}" unless
251
255
  client.next_method.is_a?(Qrack::Protocol09::Basic::ConsumeOk)
252
256
 
253
- while true
254
- method = client.next_method
255
-
256
- break if method.is_a?(Qrack::Protocol09::Basic::CancelOk)
257
+ loop do
258
+ begin
259
+ Timeout::timeout(secs) do
260
+ @method = client.next_method
261
+ end
262
+ rescue Timeout::Error
263
+ return :timed_out
264
+ end
265
+
266
+ break if @method.is_a?(Qrack::Protocol09::Basic::CancelOk)
257
267
 
258
268
  # get delivery tag to use for acknowledge
259
- self.delivery_tag = method.delivery_tag if ack
269
+ self.delivery_tag = @method.delivery_tag if ack
260
270
 
261
271
  header = client.next_payload
262
272
  msg = client.next_payload
@@ -290,6 +300,10 @@ the server will not send any more messages for that consumer.
290
300
  opts.delete(:nowait)
291
301
 
292
302
  client.send_frame( Qrack::Protocol09::Basic::Cancel.new({ :consumer_tag => consumer_tag }.merge(opts)))
303
+
304
+ raise Bunny::ProtocolError,
305
+ "Error unsubscribing from queue #{name}" unless
306
+ client.next_method.is_a?(Qrack::Protocol09::Basic::CancelOk)
293
307
 
294
308
  end
295
309
 
@@ -5,8 +5,8 @@ module Qrack
5
5
  CONNECT_TIMEOUT = 1.0
6
6
  RETRY_DELAY = 10.0
7
7
 
8
- attr_reader :status, :host, :vhost, :port, :logging, :spec
9
- attr_accessor :channel, :logfile, :exchanges, :queues, :channels
8
+ attr_reader :status, :host, :vhost, :port, :logging, :spec, :heartbeat
9
+ attr_accessor :channel, :logfile, :exchanges, :queues, :channels, :heartbeat_in, :connecting
10
10
 
11
11
  end
12
12
  end
@@ -19,18 +19,37 @@ describe Bunny do
19
19
  @b.status.should == :connected
20
20
  end
21
21
 
22
+ it "should be able to create and open a new channel" do
23
+ c = @b.create_channel
24
+ c.number.should == 2
25
+ c.should be_an_instance_of(Bunny::Channel)
26
+ @b.channels.size.should == 3
27
+ c.open.should == :open_ok
28
+ @b.channel.number.should == 2
29
+ end
30
+
31
+ it "should be able to switch between channels" do
32
+ @b.channel.number.should == 1
33
+ @b.switch_channel(0)
34
+ @b.channel.number.should == 0
35
+ end
36
+
37
+ it "should raise an error if trying to switch to a non-existent channel" do
38
+ lambda { @b.switch_channel(5)}.should raise_error(RuntimeError)
39
+ end
40
+
22
41
  it "should be able to create an exchange" do
23
42
  exch = @b.exchange('test_exchange')
24
- exch.should be_an_instance_of Bunny::Exchange
43
+ exch.should be_an_instance_of(Bunny::Exchange)
25
44
  exch.name.should == 'test_exchange'
26
- @b.exchanges.has_key?('test_exchange').should be true
45
+ @b.exchanges.has_key?('test_exchange').should be(true)
27
46
  end
28
47
 
29
48
  it "should be able to create a queue" do
30
49
  q = @b.queue('test1')
31
- q.should be_an_instance_of Bunny::Queue
50
+ q.should be_an_instance_of(Bunny::Queue)
32
51
  q.name.should == 'test1'
33
- @b.queues.has_key?('test1').should be true
52
+ @b.queues.has_key?('test1').should be(true)
34
53
  end
35
54
 
36
55
  it "should be able to set QoS" do