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