bunny 0.1.1 → 0.2.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.
@@ -1,19 +1,20 @@
1
1
  # Bunny: A synchronous Ruby AMQP client
2
2
 
3
- Google Group: [bunny-amqp](http://groups.google.com/group/bunny-amqp)
3
+ Google Group: [http://groups.google.com/group/bunny-amqp](http://groups.google.com/group/bunny-amqp)
4
4
 
5
- Mailing List: [bunny-amqp-devel](http://rubyforge.org/mailman/listinfo/bunny-amqp-devel)
5
+ Mailing List: [http://rubyforge.org/mailman/listinfo/bunny-amqp-devel](http://rubyforge.org/mailman/listinfo/bunny-amqp-devel)
6
6
 
7
- Rubyforge: [bunny-amqp](http://rubyforge.org/projects/bunny-amqp)
7
+ Rubyforge: [http://rubyforge.org/projects/bunny-amqp](http://rubyforge.org/projects/bunny-amqp)
8
+
9
+ Twitter: [http://twitter.com/bunny_amqp](https://twitter.com/bunny_amqp)
8
10
 
9
11
  ## Announcements
10
12
 
11
- Bunny v0.1.1 has been released. It contains the following changes -
13
+ Bunny v0.2.0 is now available. The highlights are -
12
14
 
13
- * Queue#delete method returns ‘QUEUE DELETED’, Exchange#delete method returns ‘EXCHANGE DELETED’, Queue#bind returns ‘BIND SUCCEEDED’, Queue#unbind returns ‘UNBIND SUCCEEDED’
14
- * Queue#subscribe method available (see example bunny/examples/simple_consumer.rb)
15
- * Queue#status now returns a hash {:message_count, :consumer_count}
16
- * Queue#ack works after a Queue#subscribe or Queue#pop if :ack => true was specified
15
+ * Code has been re-organised enabling Bunny to play nicely with [amqp](http://github.com/tmm1/amqp) (thanks [Dan](http://github.com/danielsdeleo))
16
+ * When instantiating a default exchange (one beginning with ‘amq.’) the type will be inferred from the name.
17
+ * Fixed Queue#subscribe and included Queue#unsubscribe method. See examples/simple_consumer.rb for details
17
18
 
18
19
  ## About
19
20
 
@@ -92,6 +93,12 @@ Queue#publish(_**data**_, {_options_})
92
93
  ### Pop a message off of a queue
93
94
  Queue#pop({_options_})
94
95
 
96
+ ### Subscribe to a queue
97
+ Queue#subscribe({_options_}, &blk)
98
+
99
+ ### Unsubscribe from a queue
100
+ Queue#unsubscribe({_options_})
101
+
95
102
  ### Return queue message count
96
103
  Queue#message_count
97
104
 
@@ -1,6 +1,6 @@
1
1
  # consumer.rb
2
2
 
3
- # N.B. To be used in conjunction with publisher.rb - RUN THIS BEFORE simple_publisher.rb
3
+ # N.B. To be used in conjunction with simple_publisher.rb
4
4
 
5
5
  # Assumes that target message broker/server has a user called 'guest' with a password 'guest'
6
6
  # and that it is running on 'localhost'.
@@ -8,6 +8,21 @@
8
8
  # If this is not the case, please change the 'Bunny.new' call below to include
9
9
  # the relevant arguments e.g. b = Bunny.new(:user => 'john', :pass => 'doe', :host => 'foobar')
10
10
 
11
+ # How this example works
12
+ #=======================
13
+ #
14
+ # Open up two console windows start this program in one of them by typing -
15
+ #
16
+ # ruby simple_consumer.rb
17
+ #
18
+ # Then switch to the other console window and type -
19
+ #
20
+ # ruby simple_publisher.rb
21
+ #
22
+ # A message will be printed out by the simple_consumer and it will wait for the next message
23
+ #
24
+ # Run simple_publisher 3 more times. After the last run simple_consumer will stop.
25
+
11
26
  $:.unshift File.dirname(__FILE__) + '/../lib'
12
27
 
13
28
  require 'bunny'
@@ -26,11 +41,15 @@ exch = b.exchange('sorting_room')
26
41
  # bind queue to exchange
27
42
  q.bind(exch, :key => 'fred')
28
43
 
29
- # subscribe to queue
30
- msg = q.subscribe(:consumer_tag => 'testtag1')
44
+ # initialize counter
45
+ i = 1
31
46
 
32
- # output received message
33
- puts msg
47
+ # subscribe to queue
48
+ q.subscribe(:consumer_tag => 'testtag1') do |msg|
49
+ puts i.to_s + ': ' + msg
50
+ i+=1
51
+ q.unsubscribe(:consumer_tag => 'testtag1') if i == 5
52
+ end
34
53
 
35
54
  # close the connection
36
55
  b.stop
@@ -13,12 +13,7 @@ require 'bunny'
13
13
  b = Bunny.new(:logging => true)
14
14
 
15
15
  # start a communication session with the amqp server
16
- begin
17
- b.start
18
- rescue Exception => e
19
- puts 'ERROR - Could not start a session: ' + e
20
- exit
21
- end
16
+ b.start
22
17
 
23
18
  # declare queues
24
19
  q1 = b.queue('test_fan1')
@@ -1,6 +1,6 @@
1
1
  # simple_publisher.rb
2
2
 
3
- # N.B. To be used in conjunction with simple_consumer.rb
3
+ # N.B. To be used in conjunction with simple_consumer.rb. See simple_consumer.rb for explanation.
4
4
 
5
5
  # Assumes that target message broker/server has a user called 'guest' with a password 'guest'
6
6
  # and that it is running on 'localhost'.
@@ -17,15 +17,9 @@ b = Bunny.new(:logging => true)
17
17
  # start a communication session with the amqp server
18
18
  b.start
19
19
 
20
- # create/get queue
21
- q = b.queue('po_box')
22
-
23
20
  # create/get exchange
24
21
  exch = b.exchange('sorting_room')
25
22
 
26
- # bind queue to exchange
27
- q.bind(exch, :key => 'fred')
28
-
29
23
  # publish message to exchange
30
24
  exch.publish('This is a message from the publisher', :key => 'fred')
31
25
 
@@ -0,0 +1,59 @@
1
+ # simple_topic.rb
2
+
3
+ # Assumes that target message broker/server has a user called 'guest' with a password 'guest'
4
+ # and that it is running on 'localhost'.
5
+
6
+ # If this is not the case, please change the 'Bunny.new' call below to include
7
+ # the relevant arguments e.g. b = Bunny.new(:user => 'john', :pass => 'doe', :host => 'foobar')
8
+
9
+ $:.unshift File.dirname(__FILE__) + '/../lib'
10
+
11
+ require 'bunny'
12
+
13
+ b = Bunny.new
14
+
15
+ # start a communication session with the amqp server
16
+ b.start
17
+
18
+ # declare queues
19
+ soccer = b.queue('topic_soccer')
20
+ cricket = b.queue('topic_cricket')
21
+ rugby = b.queue('topic_rugby')
22
+ allsport = b.queue('topic_allsport')
23
+
24
+ # create a topic exchange
25
+ sports_results = b.exchange('sports_results', :type => :topic)
26
+
27
+ # bind the queues to the exchange
28
+ soccer.bind(sports_results, :key => 'soccer.*')
29
+ cricket.bind(sports_results, :key => 'cricket.*')
30
+ rugby.bind(sports_results, :key => 'rugby.*')
31
+ allsport.bind(sports_results, :key => '*.result')
32
+
33
+ # publish messages to the exchange
34
+ sports_results.publish('Manchester United 1 : Hull City 4', :key => 'soccer.result')
35
+ sports_results.publish('England beat Australia by 5 wickets in first test', :key => 'cricket.result')
36
+ sports_results.publish('British Lions 15 : South Africa 12', :key => 'rugby.result')
37
+
38
+ # get message from the queues
39
+
40
+ # soccer queue got the soccer message
41
+ msg = soccer.pop
42
+ puts 'This is a message from the soccer q: ' + msg + "\n\n"
43
+
44
+ # cricket queue got the cricket message
45
+ msg = cricket.pop
46
+ puts 'This is a message from the cricket q: ' + msg + "\n\n"
47
+
48
+ # rugby queue got the rugby message
49
+ msg = rugby.pop
50
+ puts 'This is a message from the rugby q: ' + msg + "\n\n"
51
+
52
+ # allsport queue got all of the messages
53
+ until msg == 'QUEUE EMPTY' do
54
+ msg = allsport.pop
55
+ puts 'This is a message from the allsport q: ' + msg + "\n\n" unless msg == 'QUEUE EMPTY'
56
+ end
57
+
58
+ # close the client connection
59
+ b.stop
@@ -1,8 +1,4 @@
1
- module AMQP
2
- %w[ spec buffer protocol frame client ].each do |file|
3
- require "amqp/#{file}"
4
- end
5
-
1
+ module API
6
2
  # return messages
7
3
  CONNECTED = 'CONNECTED'
8
4
  NOT_CONNECTED = 'NOT CONNECTED'
@@ -1,19 +1,32 @@
1
1
  $:.unshift File.expand_path(File.dirname(__FILE__))
2
2
 
3
- %w[socket thread timeout amqp].each do |file|
3
+ # Ruby standard libraries
4
+ %w[socket thread timeout].each do |file|
4
5
  require file
5
6
  end
6
7
 
7
- %w[ exchange queue header ].each do |file|
8
- require "bunny/#{file}"
8
+ # AMQP protocol and transport
9
+ %w[spec protocol buffer frame].each do |file|
10
+ require 'engineroom/' + file
9
11
  end
10
12
 
13
+ # Bunny API
14
+ %w[client exchange header queue].each do |file|
15
+ require 'bunny/' + file
16
+ end
17
+
18
+ # Error and return message definitions
19
+ require 'api_messages'
20
+
11
21
  class Bunny
12
-
22
+ include Protocol
23
+ include Transport
24
+ include API
25
+
13
26
  attr_reader :client
14
27
 
15
28
  def initialize(opts = {})
16
- @client = AMQP::Client.new(opts)
29
+ @client = API::Client.new(opts)
17
30
  end
18
31
 
19
32
  def logging=(bool)
@@ -33,11 +46,11 @@ class Bunny
33
46
  end
34
47
 
35
48
  def exchange(name, opts = {})
36
- client.exchanges[name] ||= Exchange.new(client, name, opts)
49
+ client.exchanges[name] ||= API::Exchange.new(client, name, opts)
37
50
  end
38
51
 
39
52
  def queue(name, opts = {})
40
- client.queues[name] ||= Queue.new(client, name, opts)
53
+ client.queues[name] ||= API::Queue.new(client, name, opts)
41
54
  end
42
55
 
43
56
  def stop
@@ -63,5 +76,5 @@ class Bunny
63
76
  def port
64
77
  client.port
65
78
  end
66
-
79
+
67
80
  end
@@ -1,4 +1,4 @@
1
- module AMQP
1
+ module API
2
2
  class Client
3
3
  CONNECT_TIMEOUT = 1.0
4
4
  RETRY_DELAY = 10.0
@@ -8,7 +8,7 @@ module AMQP
8
8
 
9
9
  def initialize(opts = {})
10
10
  @host = opts[:host] || 'localhost'
11
- @port = opts[:port] || AMQP::PORT
11
+ @port = opts[:port] || Protocol::PORT
12
12
  @user = opts[:user] || 'guest'
13
13
  @pass = opts[:pass] || 'guest'
14
14
  @vhost = opts[:vhost] || '/'
@@ -28,7 +28,7 @@ module AMQP
28
28
  def send_frame(*args)
29
29
  args.each do |data|
30
30
  data.ticket = ticket if ticket and data.respond_to?(:ticket=)
31
- data = data.to_frame(channel) unless data.is_a?(Frame)
31
+ data = data.to_frame(channel) unless data.is_a?(Transport::Frame)
32
32
  data.channel = channel
33
33
 
34
34
  log :send, data
@@ -38,7 +38,7 @@ module AMQP
38
38
  end
39
39
 
40
40
  def next_frame
41
- frame = Frame.parse(buffer)
41
+ frame = Transport::Frame.parse(buffer)
42
42
  log :received, frame
43
43
  frame
44
44
  end
@@ -56,13 +56,13 @@ module AMQP
56
56
  send_frame(
57
57
  Protocol::Channel::Close.new(:reply_code => 200, :reply_text => 'bye', :method_id => 0, :class_id => 0)
58
58
  )
59
- raise ProtocolError, "Error closing channel #{channel}" unless next_method.is_a?(Protocol::Channel::CloseOk)
59
+ raise API::ProtocolError, "Error closing channel #{channel}" unless next_method.is_a?(Protocol::Channel::CloseOk)
60
60
 
61
61
  self.channel = 0
62
62
  send_frame(
63
63
  Protocol::Connection::Close.new(:reply_code => 200, :reply_text => 'Goodbye', :class_id => 0, :method_id => 0)
64
64
  )
65
- raise ProtocolError, "Error closing connection" unless next_method.is_a?(Protocol::Connection::CloseOk)
65
+ raise API::ProtocolError, "Error closing connection" unless next_method.is_a?(Protocol::Connection::CloseOk)
66
66
 
67
67
  close_socket
68
68
  end
@@ -77,9 +77,9 @@ module AMQP
77
77
 
78
78
  def start_session
79
79
  @channel = 0
80
- write(HEADER)
81
- write([1, 1, VERSION_MAJOR, VERSION_MINOR].pack('C4'))
82
- raise ProtocolError, 'Connection initiation failed' unless next_method.is_a?(Protocol::Connection::Start)
80
+ write(Protocol::HEADER)
81
+ write([1, 1, Protocol::VERSION_MAJOR, Protocol::VERSION_MINOR].pack('C4'))
82
+ raise API::ProtocolError, 'Connection initiation failed' unless next_method.is_a?(Protocol::Connection::Start)
83
83
 
84
84
  send_frame(
85
85
  Protocol::Connection::StartOk.new(
@@ -91,7 +91,7 @@ module AMQP
91
91
  )
92
92
 
93
93
  method = next_method
94
- raise ProtocolError, "Connection failed - user: #{@user}, pass: #{@pass}" if method.nil?
94
+ raise API::ProtocolError, "Connection failed - user: #{@user}, pass: #{@pass}" if method.nil?
95
95
 
96
96
  if method.is_a?(Protocol::Connection::Tune)
97
97
  send_frame(
@@ -102,17 +102,17 @@ module AMQP
102
102
  send_frame(
103
103
  Protocol::Connection::Open.new(:virtual_host => @vhost, :capabilities => '', :insist => @insist)
104
104
  )
105
- raise ProtocolError, 'Cannot open connection' unless next_method.is_a?(Protocol::Connection::OpenOk)
105
+ raise API::ProtocolError, 'Cannot open connection' unless next_method.is_a?(Protocol::Connection::OpenOk)
106
106
 
107
107
  @channel = 1
108
108
  send_frame(Protocol::Channel::Open.new)
109
- raise ProtocolError, "Cannot open channel #{channel}" unless next_method.is_a?(Protocol::Channel::OpenOk)
109
+ raise API::ProtocolError, "Cannot open channel #{channel}" unless next_method.is_a?(Protocol::Channel::OpenOk)
110
110
 
111
111
  send_frame(
112
112
  Protocol::Access::Request.new(:realm => '/data', :read => true, :write => true, :active => true, :passive => true)
113
113
  )
114
114
  method = next_method
115
- raise ProtocolError, 'Access denied' unless method.is_a?(Protocol::Access::RequestOk)
115
+ raise API::ProtocolError, 'Access denied' unless method.is_a?(Protocol::Access::RequestOk)
116
116
  self.ticket = method.ticket
117
117
 
118
118
  # return status
@@ -122,14 +122,14 @@ module AMQP
122
122
  private
123
123
 
124
124
  def buffer
125
- @buffer ||= Buffer.new(self)
125
+ @buffer ||= Transport::Buffer.new(self)
126
126
  end
127
127
 
128
128
  def send_command(cmd, *args)
129
129
  begin
130
130
  socket.__send__(cmd, *args)
131
131
  rescue Errno::EPIPE, IOError => e
132
- raise ServerDownError, e.message
132
+ raise API::ServerDownError, e.message
133
133
  end
134
134
  end
135
135
 
@@ -147,7 +147,7 @@ module AMQP
147
147
  end
148
148
  @status = CONNECTED
149
149
  rescue SocketError, SystemCallError, IOError, Timeout::Error => e
150
- raise ServerDownError, e.message
150
+ raise API::ServerDownError, e.message
151
151
  end
152
152
 
153
153
  @socket
@@ -1,16 +1,24 @@
1
- class Bunny
1
+ module API
2
2
  class Exchange
3
3
 
4
- include AMQP
5
-
6
4
  attr_reader :client, :type, :name, :opts, :key
7
5
 
8
6
  def initialize(client, name, opts = {})
9
7
  # check connection to server
10
- raise ConnectionError, 'Not connected to server' if client.status == NOT_CONNECTED
8
+ raise API::ConnectionError, 'Not connected to server' if client.status == NOT_CONNECTED
11
9
 
12
10
  @client, @name, @opts = client, name, opts
13
- @type = opts[:type] || :direct
11
+
12
+ # set up the exchange type catering for default names
13
+ if name.match(/^amq\./)
14
+ new_type = name.sub(/amq\./, '')
15
+ # handle 'amq.match' default
16
+ new_type = 'headers' if new_type == 'match'
17
+ @type = new_type.to_sym
18
+ else
19
+ @type = opts[:type] || :direct
20
+ end
21
+
14
22
  @key = opts[:key]
15
23
  @client.exchanges[@name] ||= self
16
24
 
@@ -25,7 +33,7 @@ class Bunny
25
33
  )
26
34
  )
27
35
 
28
- raise ProtocolError,
36
+ raise API::ProtocolError,
29
37
  "Error declaring exchange #{name}: type = #{type}" unless
30
38
  client.next_method.is_a?(Protocol::Exchange::DeclareOk)
31
39
  end
@@ -46,7 +54,7 @@ class Bunny
46
54
  :priority => 0
47
55
  }.merge(opts)
48
56
  )
49
- out << Frame::Body.new(data)
57
+ out << Transport::Frame::Body.new(data)
50
58
 
51
59
  client.send_frame(*out)
52
60
  end
@@ -60,7 +68,7 @@ class Bunny
60
68
  Protocol::Exchange::Delete.new({ :exchange => name, :nowait => false }.merge(opts))
61
69
  )
62
70
 
63
- raise ProtocolError,
71
+ raise API::ProtocolError,
64
72
  "Error deleting exchange #{name}" unless
65
73
  client.next_method.is_a?(Protocol::Exchange::DeleteOk)
66
74
 
@@ -1,8 +1,6 @@
1
- class Bunny
1
+ module API
2
2
  class Header
3
3
 
4
- include AMQP
5
-
6
4
  attr_reader :client
7
5
 
8
6
  def initialize(client, header_obj)
@@ -1,18 +1,17 @@
1
- class Bunny
1
+ module API
2
2
  class Queue
3
3
 
4
- include AMQP
5
-
6
4
  attr_reader :name, :client
7
5
  attr_accessor :delivery_tag
8
6
 
9
7
  def initialize(client, name, opts = {})
10
8
  # check connection to server
11
- raise ConnectionError, 'Not connected to server' if client.status == NOT_CONNECTED
9
+ raise API::ConnectionError, 'Not connected to server' if client.status == NOT_CONNECTED
12
10
 
13
11
  @client = client
14
12
  @opts = opts
15
13
  @name = name
14
+ @delivery_tag = nil
16
15
 
17
16
  # ignore the :nowait option if passed, otherwise program will hang waiting for a
18
17
  # response that will not be sent by the server
@@ -22,7 +21,7 @@ class Bunny
22
21
  Protocol::Queue::Declare.new({ :queue => name, :nowait => false }.merge(opts))
23
22
  )
24
23
 
25
- raise ProtocolError, "Error declaring queue #{name}" unless client.next_method.is_a?(Protocol::Queue::DeclareOk)
24
+ raise API::ProtocolError, "Error declaring queue #{name}" unless client.next_method.is_a?(Protocol::Queue::DeclareOk)
26
25
  end
27
26
 
28
27
  def ack
@@ -54,7 +53,7 @@ class Bunny
54
53
  if method.is_a?(Protocol::Basic::GetEmpty) then
55
54
  return QUEUE_EMPTY
56
55
  elsif !method.is_a?(Protocol::Basic::GetOk)
57
- raise ProtocolError, "Error getting message from queue #{name}"
56
+ raise API::ProtocolError, "Error getting message from queue #{name}"
58
57
  end
59
58
 
60
59
  # get delivery tag to use for acknowledge
@@ -62,7 +61,7 @@ class Bunny
62
61
 
63
62
  header = client.next_payload
64
63
  msg = client.next_payload
65
- raise MessageError, 'unexpected length' if msg.length < header.size
64
+ raise API::MessageError, 'unexpected length' if msg.length < header.size
66
65
 
67
66
  hdr ? {:header => header, :payload => msg} : msg
68
67
 
@@ -90,7 +89,7 @@ class Bunny
90
89
  {:message_count => method.message_count, :consumer_count => method.consumer_count}
91
90
  end
92
91
 
93
- def subscribe(opts = {})
92
+ def subscribe(opts = {}, &blk)
94
93
  consumer_tag = opts[:consumer_tag] || name
95
94
 
96
95
  # ignore the :nowait option if passed, otherwise program will not wait for a
@@ -110,21 +109,31 @@ class Bunny
110
109
  :nowait => false }.merge(opts))
111
110
  )
112
111
 
113
- raise ProtocolError,
112
+ raise API::ProtocolError,
114
113
  "Error subscribing to queue #{name}" unless
115
114
  client.next_method.is_a?(Protocol::Basic::ConsumeOk)
116
115
 
117
- method = client.next_method
116
+ while true
117
+ method = client.next_method
118
+ break if method.is_a?(Protocol::Basic::CancelOk)
118
119
 
119
- # get delivery tag to use for acknowledge
120
- self.delivery_tag = method.delivery_tag if ack
120
+ # get delivery tag to use for acknowledge
121
+ self.delivery_tag = method.delivery_tag if ack
122
+
123
+ header = client.next_payload
124
+ msg = client.next_payload
125
+ raise API::MessageError, 'unexpected length' if msg.length < header.size
126
+
127
+ # pass the message to the block for processing
128
+ blk.call(hdr ? {:header => header, :payload => msg} : msg)
129
+ end
121
130
 
122
- header = client.next_payload
123
- msg = client.next_payload
124
- raise MessageError, 'unexpected length' if msg.length < header.size
125
-
126
- hdr ? {:header => header, :payload => msg} : msg
127
131
  end
132
+
133
+ def unsubscribe(opts = {})
134
+ consumer_tag = opts[:consumer_tag] || name
135
+ client.send_frame( Protocol::Basic::Cancel.new({ :consumer_tag => consumer_tag }.merge(opts)))
136
+ end
128
137
 
129
138
  def bind(exchange, opts = {})
130
139
  exchange = exchange.respond_to?(:name) ? exchange.name : exchange
@@ -141,7 +150,7 @@ class Bunny
141
150
  :nowait => false }.merge(opts))
142
151
  )
143
152
 
144
- raise ProtocolError,
153
+ raise API::ProtocolError,
145
154
  "Error binding queue #{name}" unless
146
155
  client.next_method.is_a?(Protocol::Queue::BindOk)
147
156
 
@@ -161,7 +170,7 @@ class Bunny
161
170
  )
162
171
  )
163
172
 
164
- raise ProtocolError,
173
+ raise API::ProtocolError,
165
174
  "Error unbinding queue #{name}" unless
166
175
  client.next_method.is_a?(Protocol::Queue::UnbindOk)
167
176
 
@@ -178,7 +187,7 @@ class Bunny
178
187
  Protocol::Queue::Delete.new({ :queue => name, :nowait => false }.merge(opts))
179
188
  )
180
189
 
181
- raise ProtocolError,
190
+ raise API::ProtocolError,
182
191
  "Error deleting queue #{name}" unless
183
192
  client.next_method.is_a?(Protocol::Queue::DeleteOk)
184
193