bunny 0.5.3 → 0.6.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.
- data/{README → README.rdoc} +9 -2
- data/bunny.gemspec +12 -7
- data/examples/simple_08.rb +1 -1
- data/examples/simple_09.rb +1 -1
- data/examples/simple_ack_08.rb +1 -1
- data/examples/simple_ack_09.rb +1 -1
- data/examples/simple_consumer_08.rb +6 -14
- data/examples/simple_consumer_09.rb +6 -14
- data/examples/simple_fanout_08.rb +2 -2
- data/examples/simple_fanout_09.rb +2 -2
- data/examples/simple_headers_08.rb +1 -1
- data/examples/simple_headers_09.rb +1 -1
- data/examples/simple_topic_08.rb +4 -4
- data/examples/simple_topic_09.rb +4 -4
- data/lib/bunny.rb +23 -15
- data/lib/bunny/channel08.rb +8 -2
- data/lib/bunny/channel09.rb +8 -2
- data/lib/bunny/client08.rb +67 -24
- data/lib/bunny/client09.rb +88 -48
- data/lib/bunny/exchange08.rb +55 -43
- data/lib/bunny/exchange09.rb +67 -54
- data/lib/bunny/queue08.rb +79 -137
- data/lib/bunny/queue09.rb +79 -141
- data/lib/bunny/subscription08.rb +85 -0
- data/lib/bunny/subscription09.rb +85 -0
- data/lib/qrack/client.rb +29 -10
- data/lib/qrack/protocol/spec08.rb +1 -0
- data/lib/qrack/protocol/spec09.rb +1 -0
- data/lib/qrack/qrack08.rb +1 -0
- data/lib/qrack/qrack09.rb +1 -0
- data/lib/qrack/queue.rb +1 -1
- data/lib/qrack/subscription.rb +102 -0
- data/spec/spec_08/bunny_spec.rb +6 -0
- data/spec/spec_08/connection_spec.rb +12 -0
- data/spec/spec_08/exchange_spec.rb +19 -3
- data/spec/spec_08/queue_spec.rb +87 -13
- data/spec/spec_09/bunny_spec.rb +7 -1
- data/spec/spec_09/connection_spec.rb +12 -0
- data/spec/spec_09/exchange_spec.rb +19 -3
- data/spec/spec_09/queue_spec.rb +94 -21
- metadata +11 -6
@@ -0,0 +1,85 @@
|
|
1
|
+
module Bunny
|
2
|
+
|
3
|
+
=begin rdoc
|
4
|
+
|
5
|
+
=== DESCRIPTION:
|
6
|
+
|
7
|
+
Asks the server to start a "consumer", which is a transient request for messages from a specific
|
8
|
+
queue. Consumers last as long as the channel they were created on, or until the client cancels them
|
9
|
+
with an _unsubscribe_. Every time a message reaches the queue it is passed to the _blk_ for
|
10
|
+
processing. If error occurs, _Bunny_::_ProtocolError_ is raised.
|
11
|
+
|
12
|
+
==== OPTIONS:
|
13
|
+
* <tt>:consumer_tag => '_tag_'</tt> - Specifies the identifier for the consumer. The consumer tag is
|
14
|
+
local to a connection, so two clients can use the same consumer tags. If this option is not
|
15
|
+
specified a server generated name is used.
|
16
|
+
* <tt>:ack => false (_default_) or true</tt> - If set to _false_, the server does not expect an
|
17
|
+
acknowledgement message from the client. If set to _true_, the server expects an acknowledgement
|
18
|
+
message from the client and will re-queue the message if it does not receive one within a time specified
|
19
|
+
by the server.
|
20
|
+
* <tt>:exclusive => true or false (_default_)</tt> - Request exclusive consumer access, meaning
|
21
|
+
only this consumer can access the queue.
|
22
|
+
* <tt>:nowait => true or false (_default_)</tt> - Ignored by Bunny, always _false_.
|
23
|
+
* <tt>:timeout => number of seconds - The subscribe loop will continue to wait for
|
24
|
+
messages until terminated (Ctrl-C or kill command) or this timeout interval is reached.
|
25
|
+
* <tt>:message_max => max number messages to process</tt> - When the required number of messages
|
26
|
+
is processed subscribe loop is exited.
|
27
|
+
|
28
|
+
==== OPERATION:
|
29
|
+
|
30
|
+
Passes a hash of message information to the block, if one has been supplied. The hash contains
|
31
|
+
:header, :payload and :delivery_details. The structure of the data is as follows -
|
32
|
+
|
33
|
+
:header has instance variables -
|
34
|
+
@klass
|
35
|
+
@size
|
36
|
+
@weight
|
37
|
+
@properties is a hash containing -
|
38
|
+
:content_type
|
39
|
+
:delivery_mode
|
40
|
+
:priority
|
41
|
+
|
42
|
+
:payload contains the message contents
|
43
|
+
|
44
|
+
:delivery details is a hash containing -
|
45
|
+
:consumer_tag
|
46
|
+
:delivery_tag
|
47
|
+
:redelivered
|
48
|
+
:exchange
|
49
|
+
:routing_key
|
50
|
+
|
51
|
+
If the :timeout option is specified then Qrack::ClientTimeout is raised if method times out
|
52
|
+
waiting to receive the next message from the queue.
|
53
|
+
|
54
|
+
==== EXAMPLES
|
55
|
+
|
56
|
+
my_queue.subscribe(:timeout => 5) {|msg| puts msg[:payload]}
|
57
|
+
|
58
|
+
my_queue.subscribe(:message_max => 10, :ack => true) {|msg| puts msg[:payload]}
|
59
|
+
|
60
|
+
=end
|
61
|
+
|
62
|
+
class Subscription < Qrack::Subscription
|
63
|
+
|
64
|
+
|
65
|
+
def setup_consumer
|
66
|
+
client.send_frame(
|
67
|
+
Qrack::Protocol::Basic::Consume.new({ :queue => queue.name,
|
68
|
+
:consumer_tag => consumer_tag,
|
69
|
+
:no_ack => !ack,
|
70
|
+
:exclusive => exclusive,
|
71
|
+
:nowait => false }.merge(@opts))
|
72
|
+
)
|
73
|
+
|
74
|
+
method = client.next_method
|
75
|
+
|
76
|
+
client.check_response(method, Qrack::Protocol::Basic::ConsumeOk,
|
77
|
+
"Error subscribing to queue #{queue.name}")
|
78
|
+
|
79
|
+
@consumer_tag = method.consumer_tag
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Bunny
|
2
|
+
|
3
|
+
=begin rdoc
|
4
|
+
|
5
|
+
=== DESCRIPTION:
|
6
|
+
|
7
|
+
Asks the server to start a "consumer", which is a transient request for messages from a specific
|
8
|
+
queue. Consumers last as long as the channel they were created on, or until the client cancels them
|
9
|
+
with an _unsubscribe_. Every time a message reaches the queue it is passed to the _blk_ for
|
10
|
+
processing. If error occurs, _Bunny_::_ProtocolError_ is raised.
|
11
|
+
|
12
|
+
==== OPTIONS:
|
13
|
+
* <tt>:consumer_tag => '_tag_'</tt> - Specifies the identifier for the consumer. The consumer tag is
|
14
|
+
local to a connection, so two clients can use the same consumer tags. If this option is not
|
15
|
+
specified a server generated name is used.
|
16
|
+
* <tt>:ack => false (_default_) or true</tt> - If set to _false_, the server does not expect an
|
17
|
+
acknowledgement message from the client. If set to _true_, the server expects an acknowledgement
|
18
|
+
message from the client and will re-queue the message if it does not receive one within a time specified
|
19
|
+
by the server.
|
20
|
+
* <tt>:exclusive => true or false (_default_)</tt> - Request exclusive consumer access, meaning
|
21
|
+
only this consumer can access the queue.
|
22
|
+
* <tt>:nowait => true or false (_default_)</tt> - Ignored by Bunny, always _false_.
|
23
|
+
* <tt>:timeout => number of seconds - The subscribe loop will continue to wait for
|
24
|
+
messages until terminated (Ctrl-C or kill command) or this timeout interval is reached.
|
25
|
+
* <tt>:message_max => max number messages to process</tt> - When the required number of messages
|
26
|
+
is processed subscribe loop is exited.
|
27
|
+
|
28
|
+
==== OPERATION:
|
29
|
+
|
30
|
+
Passes a hash of message information to the block, if one has been supplied. The hash contains
|
31
|
+
:header, :payload and :delivery_details. The structure of the data is as follows -
|
32
|
+
|
33
|
+
:header has instance variables -
|
34
|
+
@klass
|
35
|
+
@size
|
36
|
+
@weight
|
37
|
+
@properties is a hash containing -
|
38
|
+
:content_type
|
39
|
+
:delivery_mode
|
40
|
+
:priority
|
41
|
+
|
42
|
+
:payload contains the message contents
|
43
|
+
|
44
|
+
:delivery details is a hash containing -
|
45
|
+
:consumer_tag
|
46
|
+
:delivery_tag
|
47
|
+
:redelivered
|
48
|
+
:exchange
|
49
|
+
:routing_key
|
50
|
+
|
51
|
+
If the :timeout option is specified then Qrack::ClientTimeout is raised if method times out
|
52
|
+
waiting to receive the next message from the queue.
|
53
|
+
|
54
|
+
==== EXAMPLES
|
55
|
+
|
56
|
+
my_queue.subscribe(:timeout => 5) {|msg| puts msg[:payload]}
|
57
|
+
|
58
|
+
my_queue.subscribe(:message_max => 10, :ack => true) {|msg| puts msg[:payload]}
|
59
|
+
|
60
|
+
=end
|
61
|
+
|
62
|
+
class Subscription09 < Qrack::Subscription
|
63
|
+
|
64
|
+
def setup_consumer
|
65
|
+
client.send_frame(
|
66
|
+
Qrack::Protocol09::Basic::Consume.new({ :reserved_1 => 0,
|
67
|
+
:queue => queue.name,
|
68
|
+
:consumer_tag => consumer_tag,
|
69
|
+
:no_ack => !ack,
|
70
|
+
:exclusive => exclusive,
|
71
|
+
:nowait => false}.merge(@opts))
|
72
|
+
)
|
73
|
+
|
74
|
+
method = client.next_method
|
75
|
+
|
76
|
+
client.check_response(method, Qrack::Protocol09::Basic::ConsumeOk,
|
77
|
+
"Error subscribing to queue #{queue.name}")
|
78
|
+
|
79
|
+
@consumer_tag = method.consumer_tag
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
data/lib/qrack/client.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
module Qrack
|
2
2
|
|
3
3
|
class ClientTimeout < Timeout::Error; end
|
4
|
+
class ConnectionTimeout < Timeout::Error; end
|
4
5
|
|
5
6
|
# Client ancestor class
|
6
7
|
class Client
|
7
8
|
|
8
|
-
CONNECT_TIMEOUT =
|
9
|
+
CONNECT_TIMEOUT = 5.0
|
9
10
|
RETRY_DELAY = 10.0
|
10
11
|
|
11
12
|
attr_reader :status, :host, :vhost, :port, :logging, :spec, :heartbeat
|
@@ -19,10 +20,13 @@ module Qrack
|
|
19
20
|
@vhost = opts[:vhost] || '/'
|
20
21
|
@logfile = opts[:logfile] || nil
|
21
22
|
@logging = opts[:logging] || false
|
23
|
+
@ssl = opts[:ssl] || false
|
24
|
+
@verify_ssl = opts[:verify_ssl].nil? || opts[:verify_ssl]
|
22
25
|
@status = :not_connected
|
23
26
|
@frame_max = opts[:frame_max] || 131072
|
24
27
|
@channel_max = opts[:channel_max] || 0
|
25
28
|
@heartbeat = opts[:heartbeat] || 0
|
29
|
+
@connect_timeout = opts[:connect_timeout] || CONNECT_TIMEOUT
|
26
30
|
@logger = nil
|
27
31
|
create_logger if @logging
|
28
32
|
@message_in = false
|
@@ -100,24 +104,30 @@ with the <tt>:immediate</tt> or <tt>:mandatory</tt> options.
|
|
100
104
|
|
101
105
|
==== RETURNS:
|
102
106
|
|
103
|
-
<tt
|
107
|
+
<tt>{:header => nil, :payload => :no_return, :return_details => nil}</tt> if message is
|
108
|
+
not returned before timeout.
|
104
109
|
<tt>{:header, :return_details, :payload}</tt> if message is returned. <tt>:return_details</tt> is
|
105
110
|
a hash <tt>{:reply_code, :reply_text, :exchange, :routing_key}</tt>.
|
106
111
|
|
107
112
|
=end
|
108
113
|
|
109
114
|
def returned_message(opts = {})
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
return :
|
115
|
+
|
116
|
+
begin
|
117
|
+
frame = next_frame(:timeout => opts[:timeout] || 0.1)
|
118
|
+
rescue Qrack::ClientTimeout
|
119
|
+
return {:header => nil, :payload => :no_return, :return_details => nil}
|
115
120
|
end
|
116
121
|
|
117
122
|
method = frame.payload
|
118
123
|
header = next_payload
|
119
|
-
|
120
|
-
|
124
|
+
|
125
|
+
# If maximum frame size is smaller than message payload body then message
|
126
|
+
# will have a message header and several message bodies
|
127
|
+
msg = ''
|
128
|
+
while msg.length < header.size
|
129
|
+
msg += next_payload
|
130
|
+
end
|
121
131
|
|
122
132
|
# Return the message and related info
|
123
133
|
{:header => header, :payload => msg, :return_details => method.arguments}
|
@@ -165,13 +175,22 @@ a hash <tt>{:reply_code, :reply_text, :exchange, :routing_key}</tt>.
|
|
165
175
|
|
166
176
|
begin
|
167
177
|
# Attempt to connect.
|
168
|
-
@socket = timeout(
|
178
|
+
@socket = timeout(@connect_timeout, ConnectionTimeout) do
|
169
179
|
TCPSocket.new(host, port)
|
170
180
|
end
|
171
181
|
|
172
182
|
if Socket.constants.include? 'TCP_NODELAY'
|
173
183
|
@socket.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
174
184
|
end
|
185
|
+
|
186
|
+
if @ssl
|
187
|
+
require 'openssl' unless defined? OpenSSL::SSL
|
188
|
+
@socket = OpenSSL::SSL::SSLSocket.new(@socket)
|
189
|
+
@socket.sync_close = true
|
190
|
+
@socket.connect
|
191
|
+
@socket.post_connection_check(host) if @verify_ssl
|
192
|
+
@socket
|
193
|
+
end
|
175
194
|
rescue => e
|
176
195
|
@status = :not_connected
|
177
196
|
raise Bunny::ServerDownError, e.message
|
data/lib/qrack/qrack08.rb
CHANGED
data/lib/qrack/qrack09.rb
CHANGED
data/lib/qrack/queue.rb
CHANGED
@@ -0,0 +1,102 @@
|
|
1
|
+
module Qrack
|
2
|
+
# Subscription ancestor class
|
3
|
+
class Subscription
|
4
|
+
|
5
|
+
attr_accessor :consumer_tag, :delivery_tag, :message_max, :timeout, :ack, :exclusive
|
6
|
+
attr_reader :client, :queue, :message_count
|
7
|
+
|
8
|
+
def initialize(client, queue, opts = {})
|
9
|
+
@client = client
|
10
|
+
@queue = queue
|
11
|
+
|
12
|
+
# Get timeout value
|
13
|
+
@timeout = opts[:timeout] || nil
|
14
|
+
|
15
|
+
# Get maximum amount of messages to process
|
16
|
+
@message_max = opts[:message_max] || nil
|
17
|
+
|
18
|
+
# If a consumer tag is not passed in the server will generate one
|
19
|
+
@consumer_tag = opts[:consumer_tag] || nil
|
20
|
+
|
21
|
+
# Ignore the :nowait option if passed, otherwise program will hang waiting for a
|
22
|
+
# response from the server causing an error.
|
23
|
+
opts.delete(:nowait)
|
24
|
+
|
25
|
+
# Do we want to have to provide an acknowledgement?
|
26
|
+
@ack = opts[:ack] || nil
|
27
|
+
|
28
|
+
# Does this consumer want exclusive use of the queue?
|
29
|
+
@exclusive = opts[:exclusive] || false
|
30
|
+
|
31
|
+
# Initialize message counter
|
32
|
+
@message_count = 0
|
33
|
+
|
34
|
+
# Give queue reference to this subscription
|
35
|
+
@queue.subscription = self
|
36
|
+
|
37
|
+
# Store options
|
38
|
+
@opts = opts
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
def start(&blk)
|
43
|
+
|
44
|
+
# Do not process any messages if zero message_max
|
45
|
+
if message_max == 0
|
46
|
+
return
|
47
|
+
end
|
48
|
+
|
49
|
+
# Notify server about new consumer
|
50
|
+
setup_consumer
|
51
|
+
|
52
|
+
# Start subscription loop
|
53
|
+
loop do
|
54
|
+
|
55
|
+
begin
|
56
|
+
method = client.next_method(:timeout => timeout)
|
57
|
+
rescue Qrack::ClientTimeout
|
58
|
+
queue.unsubscribe()
|
59
|
+
break
|
60
|
+
end
|
61
|
+
|
62
|
+
# Increment message counter
|
63
|
+
@message_count += 1
|
64
|
+
|
65
|
+
# get delivery tag to use for acknowledge
|
66
|
+
queue.delivery_tag = method.delivery_tag if @ack
|
67
|
+
|
68
|
+
header = client.next_payload
|
69
|
+
|
70
|
+
# If maximum frame size is smaller than message payload body then message
|
71
|
+
# will have a message header and several message bodies
|
72
|
+
msg = ''
|
73
|
+
while msg.length < header.size
|
74
|
+
msg += client.next_payload
|
75
|
+
end
|
76
|
+
|
77
|
+
# If block present, pass the message info to the block for processing
|
78
|
+
blk.call({:header => header, :payload => msg, :delivery_details => method.arguments}) if !blk.nil?
|
79
|
+
|
80
|
+
# Exit loop if message_max condition met
|
81
|
+
if (!message_max.nil? and message_count == message_max)
|
82
|
+
# Stop consuming messages
|
83
|
+
queue.unsubscribe()
|
84
|
+
# Acknowledge receipt of the final message
|
85
|
+
queue.ack() if @ack
|
86
|
+
# Quit the loop
|
87
|
+
break
|
88
|
+
end
|
89
|
+
|
90
|
+
# Have to do the ack here because the ack triggers the release of messages from the server
|
91
|
+
# if you are using Client#qos prefetch and you will get extra messages sent through before
|
92
|
+
# the unsubscribe takes effect to stop messages being sent to this consumer unless the ack is
|
93
|
+
# deferred.
|
94
|
+
queue.ack() if @ack
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
data/spec/spec_08/bunny_spec.rb
CHANGED
@@ -52,6 +52,12 @@ describe Bunny do
|
|
52
52
|
@b.queues.has_key?('test1').should be(true)
|
53
53
|
end
|
54
54
|
|
55
|
+
# Current RabbitMQ has not implemented some functionality
|
56
|
+
it "should raise an error if setting of QoS fails" do
|
57
|
+
lambda { @b.qos(:global => true) }.should raise_error(Bunny::ForcedConnectionCloseError)
|
58
|
+
@b.status.should == :not_connected
|
59
|
+
end
|
60
|
+
|
55
61
|
it "should be able to set QoS" do
|
56
62
|
@b.qos.should == :qos_ok
|
57
63
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# connection_spec.rb
|
2
|
+
|
3
|
+
require File.expand_path(File.join(File.dirname(__FILE__), %w[.. .. lib bunny]))
|
4
|
+
|
5
|
+
describe Bunny do
|
6
|
+
|
7
|
+
it "should raise an error if the wrong user name or password is used" do
|
8
|
+
b = Bunny.new(:user => 'wrong')
|
9
|
+
lambda { b.start}.should raise_error(Bunny::ProtocolError)
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
@@ -8,7 +8,7 @@
|
|
8
8
|
|
9
9
|
require File.expand_path(File.join(File.dirname(__FILE__), %w[.. .. lib bunny]))
|
10
10
|
|
11
|
-
describe
|
11
|
+
describe 'Exchange' do
|
12
12
|
|
13
13
|
before(:each) do
|
14
14
|
@b = Bunny.new
|
@@ -16,7 +16,8 @@ describe Bunny do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
it "should raise an error if instantiated as non-existent type" do
|
19
|
-
lambda { @b.exchange('bogus_ex', :type => :bogus) }.should raise_error(Bunny::
|
19
|
+
lambda { @b.exchange('bogus_ex', :type => :bogus) }.should raise_error(Bunny::ForcedConnectionCloseError)
|
20
|
+
@b.status.should == :not_connected
|
20
21
|
end
|
21
22
|
|
22
23
|
it "should allow a default direct exchange to be instantiated by specifying :type" do
|
@@ -124,13 +125,28 @@ describe Bunny do
|
|
124
125
|
end
|
125
126
|
|
126
127
|
it "should be able to return an undeliverable message" do
|
127
|
-
exch = @b.exchange('')
|
128
|
+
exch = @b.exchange('return_exch')
|
128
129
|
exch.publish('This message should be undeliverable', :immediate => true)
|
129
130
|
ret_msg = @b.returned_message
|
130
131
|
ret_msg.should be_an_instance_of(Hash)
|
131
132
|
ret_msg[:payload].should == 'This message should be undeliverable'
|
132
133
|
end
|
133
134
|
|
135
|
+
it "should be able to return a message that exceeds maximum frame size" do
|
136
|
+
exch = @b.exchange('return_exch')
|
137
|
+
lg_msg = 'z' * 142000
|
138
|
+
exch.publish(lg_msg, :immediate => true)
|
139
|
+
ret_msg = @b.returned_message
|
140
|
+
ret_msg.should be_an_instance_of(Hash)
|
141
|
+
ret_msg[:payload].should == lg_msg
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should report an error if delete fails" do
|
145
|
+
exch = @b.exchange('direct_exchange')
|
146
|
+
lambda { exch.delete(:exchange => 'bogus_ex') }.should raise_error(Bunny::ForcedChannelCloseError)
|
147
|
+
@b.channel.active.should == false
|
148
|
+
end
|
149
|
+
|
134
150
|
it "should be able to be deleted" do
|
135
151
|
exch = @b.exchange('direct_exchange')
|
136
152
|
res = exch.delete
|