bunny 0.7.12 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. data/.gitignore +2 -2
  2. data/.travis.yml +7 -16
  3. data/CHANGELOG +3 -21
  4. data/Gemfile +2 -4
  5. data/README.textile +31 -9
  6. data/Rakefile +3 -3
  7. data/bunny.gemspec +6 -3
  8. data/examples/{simple_08.rb → simple.rb} +1 -1
  9. data/examples/{simple_ack_08.rb → simple_ack.rb} +1 -1
  10. data/examples/{simple_consumer_08.rb → simple_consumer.rb} +4 -4
  11. data/examples/{simple_fanout_08.rb → simple_fanout.rb} +1 -1
  12. data/examples/{simple_headers_08.rb → simple_headers.rb} +2 -2
  13. data/examples/{simple_publisher_09.rb → simple_publisher.rb} +1 -1
  14. data/examples/{simple_topic_09.rb → simple_topic.rb} +2 -2
  15. data/ext/amqp-0.9.1.json +1 -0
  16. data/ext/config.yml +3 -3
  17. data/ext/qparser.rb +9 -52
  18. data/lib/bunny.rb +15 -33
  19. data/lib/bunny/{channel08.rb → channel.rb} +0 -0
  20. data/lib/bunny/{client09.rb → client.rb} +34 -46
  21. data/lib/bunny/{exchange09.rb → exchange.rb} +16 -15
  22. data/lib/bunny/{queue09.rb → queue.rb} +26 -23
  23. data/lib/bunny/{subscription09.rb → subscription.rb} +11 -6
  24. data/lib/bunny/version.rb +1 -1
  25. data/lib/qrack/client.rb +30 -21
  26. data/lib/qrack/protocol/{protocol08.rb → protocol.rb} +2 -1
  27. data/lib/qrack/protocol/{spec09.rb → spec.rb} +8 -7
  28. data/lib/qrack/{qrack08.rb → qrack.rb} +4 -4
  29. data/lib/qrack/subscription.rb +58 -9
  30. data/lib/qrack/transport/{buffer08.rb → buffer.rb} +8 -0
  31. data/lib/qrack/transport/{frame08.rb → frame.rb} +7 -22
  32. data/spec/spec_09/bunny_spec.rb +10 -8
  33. data/spec/spec_09/connection_spec.rb +8 -3
  34. data/spec/spec_09/exchange_spec.rb +22 -19
  35. data/spec/spec_09/queue_spec.rb +32 -18
  36. metadata +69 -76
  37. checksums.yaml +0 -7
  38. data/examples/simple_09.rb +0 -32
  39. data/examples/simple_ack_09.rb +0 -35
  40. data/examples/simple_consumer_09.rb +0 -55
  41. data/examples/simple_fanout_09.rb +0 -41
  42. data/examples/simple_headers_09.rb +0 -42
  43. data/examples/simple_publisher_08.rb +0 -29
  44. data/examples/simple_topic_08.rb +0 -61
  45. data/ext/amqp-0.8.json +0 -616
  46. data/lib/bunny/channel09.rb +0 -39
  47. data/lib/bunny/client08.rb +0 -480
  48. data/lib/bunny/exchange08.rb +0 -177
  49. data/lib/bunny/queue08.rb +0 -403
  50. data/lib/bunny/subscription08.rb +0 -87
  51. data/lib/qrack/protocol/protocol09.rb +0 -135
  52. data/lib/qrack/protocol/spec08.rb +0 -828
  53. data/lib/qrack/qrack09.rb +0 -20
  54. data/lib/qrack/transport/buffer09.rb +0 -305
  55. data/lib/qrack/transport/frame09.rb +0 -97
  56. data/spec/spec_08/bunny_spec.rb +0 -75
  57. data/spec/spec_08/connection_spec.rb +0 -24
  58. data/spec/spec_08/exchange_spec.rb +0 -170
  59. data/spec/spec_08/queue_spec.rb +0 -239
@@ -31,6 +31,12 @@ module Bunny
31
31
  # @option opts [Integer] :message_max
32
32
  # When the required number of messages is processed subscribe loop is exited.
33
33
  #
34
+ # @option opts [IO] :cancellator (nil)
35
+ # A cancellator can be used to for cancelling the subscribe loop from another
36
+ # thread or from a signal handler. Whenever Bunny notices that this IO object has
37
+ # become readable, the subscribe loop will be exited after the current message
38
+ # has been processed.
39
+ #
34
40
  # h2. Operation
35
41
  #
36
42
  # Passes a hash of message information to the block, if one has been supplied. The hash contains
@@ -54,14 +60,13 @@ module Bunny
54
60
  # :exchange
55
61
  # :routing_key
56
62
  #
57
- # If the :timeout option is specified then the subscription will
58
- # automatically cease if the given number of seconds passes with no
59
- # message arriving.
63
+ # If the :timeout option is specified then the subscription will automatically
64
+ # cease if the given number of seconds passes with no message arriving.
60
65
  #
61
66
  # @example
62
67
  # my_queue.subscribe(timeout: 5) { |msg| puts msg[:payload] }
63
68
  # my_queue.subscribe(message_max: 10, ack: true) { |msg| puts msg[:payload] }
64
- class Subscription09 < Bunny::Consumer
69
+ class Subscription < Bunny::Consumer
65
70
 
66
71
  def setup_consumer
67
72
  subscription_options = {
@@ -73,11 +78,11 @@ module Bunny
73
78
  :nowait => false
74
79
  }.merge(@opts)
75
80
 
76
- client.send_frame(Qrack::Protocol09::Basic::Consume.new(subscription_options))
81
+ client.send_frame(Qrack::Protocol::Basic::Consume.new(subscription_options))
77
82
 
78
83
  method = client.next_method
79
84
 
80
- client.check_response(method, Qrack::Protocol09::Basic::ConsumeOk, "Error subscribing to queue #{queue.name}")
85
+ client.check_response(method, Qrack::Protocol::Basic::ConsumeOk, "Error subscribing to queue #{queue.name}, got #{method}")
81
86
 
82
87
  @consumer_tag = method.consumer_tag
83
88
  end
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Bunny
4
- VERSION = "0.7.12"
4
+ VERSION = "0.8.0"
5
5
  end
@@ -1,12 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- begin
4
- # try loading AMQ::Client::Setttings form the amq client gem, if it has been already loaded
5
- # this avoids "warning: already initialized constant AMQP_PORTS"
6
- require "amq/client/settings"
7
- rescue LoadError
8
- require "qrack/amq-client-url"
9
- end
3
+ require "qrack/amq-client-url"
10
4
 
11
5
  module Qrack
12
6
 
@@ -20,12 +14,9 @@ module Qrack
20
14
  CONNECT_TIMEOUT = 5.0
21
15
  RETRY_DELAY = 10.0
22
16
 
23
- attr_reader :status, :host, :vhost, :port, :logging, :spec, :heartbeat, :frame_max
17
+ attr_reader :status, :host, :vhost, :port, :logging, :spec, :heartbeat, :last_method
24
18
  attr_accessor :channel, :logfile, :exchanges, :queues, :channels, :message_in, :message_out, :connecting
25
19
 
26
- # Temporary hack to make Bunny 0.7 work with port number in AMQP URL.
27
- # This is not necessary on Bunny 0.8 as it removes support of AMQP 0.8.
28
- attr_reader :__opts__
29
20
 
30
21
  def initialize(connection_string_or_opts = Hash.new, opts = Hash.new)
31
22
  opts = case connection_string_or_opts
@@ -37,18 +28,18 @@ module Qrack
37
28
  Hash.new
38
29
  end.merge(opts)
39
30
 
40
- # Temporary hack to make Bunny 0.7 work with port number in AMQP URL.
41
- # This is not necessary on Bunny 0.8 as it removes support of AMQP 0.8.
42
- @__opts__ = opts
43
-
44
-
45
31
  @host = opts[:host] || 'localhost'
32
+ @port = opts[:port] || (opts[:ssl] ? Qrack::Protocol::SSL_PORT : Qrack::Protocol::PORT)
46
33
  @user = opts[:user] || 'guest'
47
34
  @pass = opts[:pass] || 'guest'
48
35
  @vhost = opts[:vhost] || '/'
49
36
  @logfile = opts[:logfile] || nil
50
37
  @logging = opts[:logging] || false
51
38
  @ssl = opts[:ssl] || false
39
+ @ssl_cert = opts[:ssl_cert] || nil
40
+ @ssl_key = opts[:ssl_key] || nil
41
+ @ssl_cert_string = opts[:ssl_cert_string] || nil
42
+ @ssl_key_string = opts[:ssl_key_string] || nil
52
43
  @verify_ssl = opts[:verify_ssl].nil? || opts[:verify_ssl]
53
44
  @status = :not_connected
54
45
  @frame_max = opts[:frame_max] || 131072
@@ -62,6 +53,7 @@ module Qrack
62
53
  create_logger if @logging
63
54
  @message_in = false
64
55
  @message_out = false
56
+ @last_method = nil
65
57
  @connecting = false
66
58
  @channels ||= []
67
59
  # Create channel 0
@@ -79,7 +71,7 @@ module Qrack
79
71
 
80
72
  # Close all active channels
81
73
  channels.each do |c|
82
- Bunny::Timer::timeout(@read_write_timeout) { c.close } if c.open?
74
+ Bunny::Timer::timeout(@disconnect_timeout) { c.close } if c.open?
83
75
  end
84
76
 
85
77
  # Close connection to AMQP server
@@ -142,9 +134,6 @@ module Qrack
142
134
  end
143
135
 
144
136
  method = frame.payload
145
- # at this point, we can receive a channel or connection close, which we need to check for
146
- check_returned_message(method)
147
-
148
137
  header = next_payload
149
138
 
150
139
  # If maximum frame size is smaller than message payload body then message
@@ -171,6 +160,11 @@ module Qrack
171
160
  send_command(:write, *args)
172
161
  end
173
162
 
163
+ def read_ready?(timeout, cancelator = nil)
164
+ io = IO.select([ @socket, cancelator ].compact, nil, nil, timeout)
165
+ io and io[0].include?(@socket)
166
+ end
167
+
174
168
  private
175
169
 
176
170
  def close_socket(reason=nil)
@@ -219,7 +213,9 @@ module Qrack
219
213
 
220
214
  if @ssl
221
215
  require 'openssl' unless defined? OpenSSL::SSL
222
- @socket = OpenSSL::SSL::SSLSocket.new(@socket)
216
+ sslctx = OpenSSL::SSL::SSLContext.new
217
+ initialize_client_pair(sslctx)
218
+ @socket = OpenSSL::SSL::SSLSocket.new(@socket, sslctx)
223
219
  @socket.sync_close = true
224
220
  @socket.connect
225
221
  @socket.post_connection_check(host) if @verify_ssl
@@ -233,6 +229,19 @@ module Qrack
233
229
  @socket
234
230
  end
235
231
 
232
+ def initialize_client_pair(sslctx)
233
+ if @ssl_cert
234
+ @ssl_cert_string = File.read(@ssl_cert)
235
+ end
236
+ if @ssl_key
237
+ @ssl_key_string = File.read(@ssl_key)
238
+ end
239
+
240
+ sslctx.cert = OpenSSL::X509::Certificate.new(@ssl_cert_string) if @ssl_cert_string
241
+ sslctx.key = OpenSSL::PKey::RSA.new(@ssl_key_string) if @ssl_key_string
242
+ sslctx
243
+ end
244
+
236
245
  end
237
246
 
238
247
  end
@@ -120,7 +120,8 @@ module Qrack
120
120
  end
121
121
 
122
122
  def method_missing meth, *args, &blk
123
- @properties.has_key?(meth) || @klass.properties.find{|_,name| name == meth } ? @properties[meth] : super
123
+ @properties.has_key?(meth) || @klass.properties.find{|_,name| name == meth } ? @properties[meth] :
124
+ super
124
125
  end
125
126
  end
126
127
 
@@ -3,18 +3,19 @@
3
3
 
4
4
 
5
5
  #:stopdoc:
6
- # this file was autogenerated on 2011-07-21 07:07:06 +0100
7
- # using amqp-0.9.1.json (mtime: 2011-07-20 19:10:34 +0100)
6
+ # this file was autogenerated on 2012-02-28 11:22:27 -0500
7
+ # using amqp-0.9.1.json (mtime: 2012-02-28 10:50:44 -0500)
8
8
  #
9
9
  # DO NOT EDIT! (edit ext/qparser.rb and config.yml instead, and run 'ruby qparser.rb')
10
10
 
11
11
  module Qrack
12
- module Protocol09
12
+ module Protocol
13
13
  HEADER = "AMQP".freeze
14
14
  VERSION_MAJOR = 0
15
15
  VERSION_MINOR = 9
16
16
  REVISION = 1
17
17
  PORT = 5672
18
+ SSL_PORT = 5671
18
19
 
19
20
  RESPONSES = {
20
21
  200 => :REPLY_SUCCESS,
@@ -79,7 +80,7 @@ module Qrack
79
80
 
80
81
  def arguments() @arguments ||= [] end
81
82
 
82
- def parent() Protocol09.const_get(self.to_s[/Protocol09::(.+?)::/,1]) end
83
+ def parent() Protocol.const_get(self.to_s[/Protocol::(.+?)::/,1]) end
83
84
  def id() self::ID end
84
85
  def name() self::NAME.to_s end
85
86
  end
@@ -117,8 +118,8 @@ module Qrack
117
118
  def self.inherited klass
118
119
  klass.const_set(:ID, #{id})
119
120
  klass.const_set(:NAME, :#{name.to_s})
120
- Protocol09.classes[#{id}] = klass
121
- Protocol09.classes[klass::NAME] = klass
121
+ Protocol.classes[#{id}] = klass
122
+ Protocol.classes[klass::NAME] = klass
122
123
  end
123
124
  ]
124
125
  end
@@ -127,7 +128,7 @@ module Qrack
127
128
  end
128
129
 
129
130
  module Qrack
130
- module Protocol09
131
+ module Protocol
131
132
  class Connection < Class( 10, :connection ); end
132
133
  class Channel < Class( 20, :channel ); end
133
134
  class Exchange < Class( 40, :exchange ); end
@@ -2,11 +2,11 @@
2
2
 
3
3
  $: << File.expand_path(File.dirname(__FILE__))
4
4
 
5
- require 'protocol/spec08'
6
- require 'protocol/protocol08'
5
+ require 'protocol/spec'
6
+ require 'protocol/protocol'
7
7
 
8
- require 'transport/buffer08'
9
- require 'transport/frame08'
8
+ require 'transport/buffer'
9
+ require 'transport/frame'
10
10
 
11
11
  require 'qrack/client'
12
12
  require 'qrack/channel'
@@ -39,6 +39,9 @@ module Qrack
39
39
  # Initialize message counter
40
40
  @message_count = 0
41
41
 
42
+ # Store cancellator
43
+ @cancellator = opts[:cancellator]
44
+
42
45
  # Store options
43
46
  @opts = opts
44
47
  end
@@ -52,14 +55,30 @@ module Qrack
52
55
  # Notify server about new consumer
53
56
  setup_consumer
54
57
 
58
+ # We need to keep track of three possible subscription states
59
+ # :subscribed, :pending, and :unsubscribed
60
+ # 'pending' occurs because of network latency, where we tried to unsubscribe but were already given a message
61
+ subscribe_state = :subscribed
62
+
55
63
  # Start subscription loop
56
64
  loop do
57
65
 
58
66
  begin
59
- method = client.next_method(:timeout => timeout)
67
+ method = client.next_method(:timeout => timeout, :cancellator => @cancellator)
60
68
  rescue Qrack::FrameTimeout
61
- queue.unsubscribe
62
- break
69
+ begin
70
+ queue.unsubscribe
71
+ subscribe_state = :unsubscribed
72
+
73
+ break
74
+ rescue Bunny::ProtocolError
75
+ # Unsubscribe failed because we actually got a message, so we're in a weird state.
76
+ # We have to keep processing the message or else it may be lost...
77
+ # ...and there is also a CancelOk method floating around that we need to consume from the socket
78
+
79
+ method = client.last_method
80
+ subscribe_state = :pending
81
+ end
63
82
  end
64
83
 
65
84
  # Increment message counter
@@ -67,25 +86,55 @@ module Qrack
67
86
 
68
87
  # get delivery tag to use for acknowledge
69
88
  queue.delivery_tag = method.delivery_tag if @ack
70
-
71
89
  header = client.next_payload
72
90
 
91
+ # The unsubscribe ok may be sprinked into the payload
92
+ if subscribe_state == :pending and header.is_a?(Qrack::Protocol::Basic::CancelOk)
93
+ # We popped off the CancelOk, so we don't have to keep looking for it
94
+ subscribe_state = :unsubscribed
95
+
96
+ # Get the actual header now
97
+ header = client.next_payload
98
+ end
99
+
73
100
  # If maximum frame size is smaller than message payload body then message
74
101
  # will have a message header and several message bodies
75
102
  msg = ''
76
103
  while msg.length < header.size
77
- msg << client.next_payload
104
+ message = client.next_payload
105
+
106
+ # The unsubscribe ok may be sprinked into the payload
107
+ if subscribe_state == :pending and message.is_a?(Qrack::Protocol::Basic::CancelOk)
108
+ # We popped off the CancelOk, so we don't have to keep looking for it
109
+ subscribe_state = :unsubscribed
110
+ next
111
+ end
112
+
113
+ msg << message
78
114
  end
79
115
 
80
116
  # If block present, pass the message info to the block for processing
81
117
  blk.call({:header => header, :payload => msg, :delivery_details => method.arguments}) if !blk.nil?
82
118
 
83
- # Exit loop if message_max condition met
84
- if (!message_max.nil? and message_count == message_max)
85
- # Stop consuming messages
86
- queue.unsubscribe()
119
+ # Unsubscribe if we've encountered the maximum number of messages
120
+ if subscribe_state == :subscribed and !message_max.nil? and message_count == message_max
121
+ queue.unsubscribe
122
+ subscribe_state = :unsubscribed
123
+ end
124
+
125
+ # Exit the loop if we've unsubscribed
126
+ if subscribe_state != :subscribed
127
+ # We still haven't found the CancelOk, so it's the next method
128
+ if subscribe_state == :pending
129
+ method = client.next_method
130
+ client.check_response(method, Qrack::Protocol::Basic::CancelOk, "Error unsubscribing from queue #{queue.name}, got #{method.class}")
131
+
132
+ subscribe_state = :unsubscribed
133
+ end
134
+
87
135
  # Acknowledge receipt of the final message
88
136
  queue.ack() if @ack
137
+
89
138
  # Quit the loop
90
139
  break
91
140
  end
@@ -267,6 +267,14 @@ module Qrack
267
267
  end
268
268
  end
269
269
 
270
+ def read_ready?(timeout, cancellator)
271
+ if @data.is_a?(Qrack::Client)
272
+ @data.read_ready?(timeout, cancellator)
273
+ else
274
+ true
275
+ end
276
+ end
277
+
270
278
  def _read(size, pack = nil)
271
279
  if @data.is_a?(Qrack::Client)
272
280
  raw = @data.read(size)
@@ -3,7 +3,7 @@
3
3
 
4
4
 
5
5
  #:stopdoc:
6
- # this file was autogenerated on 2011-07-21 07:15:33 +0100
6
+ # this file was autogenerated on 2012-02-28 11:22:27 -0500
7
7
  #
8
8
  # DO NOT EDIT! (edit ext/qparser.rb and config.yml instead, and run 'ruby qparser.rb')
9
9
 
@@ -18,10 +18,6 @@ module Qrack
18
18
  1 => 'Method',
19
19
  2 => 'Header',
20
20
  3 => 'Body',
21
- 4 => 'OobMethod',
22
- 5 => 'OobHeader',
23
- 6 => 'OobBody',
24
- 7 => 'Trace',
25
21
  8 => 'Heartbeat',
26
22
  }
27
23
 
@@ -55,8 +51,13 @@ module Qrack
55
51
  end
56
52
  end
57
53
 
58
- def self.parse buf
54
+ def self.parse(buf, opts)
59
55
  buf = Transport::Buffer.new(buf) unless buf.is_a? Transport::Buffer
56
+
57
+ if opts[:timeout] or opts[:cancellator]
58
+ raise Qrack::FrameTimeout unless buf.read_ready?(opts[:timeout], opts[:cancellator])
59
+ end
60
+
60
61
  buf.extract do
61
62
  id, channel, payload, footer = buf.read(:octet, :short, :longstr, :octet)
62
63
  Qrack::Transport.const_get(@types[id]).new(payload, channel) if footer == FOOTER
@@ -93,22 +94,6 @@ module Qrack
93
94
  ID = 3
94
95
  end
95
96
 
96
- class OobMethod < Frame
97
- ID = 4
98
- end
99
-
100
- class OobHeader < Frame
101
- ID = 5
102
- end
103
-
104
- class OobBody < Frame
105
- ID = 6
106
- end
107
-
108
- class Trace < Frame
109
- ID = 7
110
- end
111
-
112
97
  class Heartbeat < Frame
113
98
  ID = 8
114
99
  end
@@ -13,7 +13,7 @@ require "bunny"
13
13
  describe Bunny do
14
14
 
15
15
  before(:each) do
16
- @b = Bunny.new(:spec => '09')
16
+ @b = Bunny.new
17
17
  @b.start
18
18
  end
19
19
 
@@ -33,7 +33,7 @@ describe Bunny do
33
33
  it "should be able to create and open a new channel" do
34
34
  c = @b.create_channel
35
35
  c.number.should == 2
36
- c.should be_an_instance_of(Bunny::Channel09)
36
+ c.should be_an_instance_of(Bunny::Channel)
37
37
  @b.channels.size.should == 3
38
38
  c.open.should == :open_ok
39
39
  @b.channel.number.should == 2
@@ -51,24 +51,26 @@ describe Bunny do
51
51
 
52
52
  it "should be able to create an exchange" do
53
53
  exch = @b.exchange('test_exchange')
54
- exch.should be_an_instance_of(Bunny::Exchange09)
54
+ exch.should be_an_instance_of(Bunny::Exchange)
55
55
  exch.name.should == 'test_exchange'
56
56
  @b.exchanges.has_key?('test_exchange').should be(true)
57
57
  end
58
58
 
59
59
  it "should be able to create a queue" do
60
60
  q = @b.queue('test1')
61
- q.should be_an_instance_of(Bunny::Queue09)
61
+ q.should be_an_instance_of(Bunny::Queue)
62
62
  q.name.should == 'test1'
63
63
  @b.queues.has_key?('test1').should be(true)
64
64
  end
65
65
 
66
- it "should be able to set QoS" do
67
- @b.qos.should == :qos_ok
66
+ # Current RabbitMQ has not implemented some functionality
67
+ it "should raise an error if setting of QoS fails" do
68
+ lambda { @b.qos(:global => true) }.should raise_error(Bunny::ForcedConnectionCloseError)
69
+ @b.status.should == :not_connected
68
70
  end
69
71
 
70
- it "should be able to set QoS (with global:true)" do
71
- @b.qos(:global => true).should == :qos_ok
72
+ it "should be able to set QoS" do
73
+ @b.qos.should == :qos_ok
72
74
  end
73
75
 
74
76
  end