bunny 0.7.12 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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