bunny 0.2.0 → 0.3.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/LICENSE +20 -0
- data/README +49 -0
- data/Rakefile +3 -4
- data/bunny.gemspec +37 -0
- data/examples/simple.rb +1 -1
- data/examples/simple_ack.rb +1 -1
- data/examples/simple_consumer.rb +1 -1
- data/examples/simple_fanout.rb +1 -1
- data/examples/simple_publisher.rb +1 -1
- data/examples/simple_topic.rb +3 -3
- data/{protocol → ext}/amqp-0.8.json +0 -0
- data/ext/codegen.rb +177 -0
- data/lib/bunny.rb +20 -62
- data/lib/bunny/client.rb +161 -14
- data/lib/bunny/exchange.rb +155 -80
- data/lib/bunny/protocol/protocol.rb +135 -0
- data/lib/bunny/protocol/spec.rb +836 -0
- data/lib/bunny/queue.rb +223 -23
- data/lib/bunny/transport/buffer.rb +266 -0
- data/lib/bunny/transport/frame.rb +62 -0
- data/spec/bunny_spec.rb +2 -2
- data/spec/exchange_spec.rb +3 -3
- data/spec/queue_spec.rb +10 -9
- metadata +22 -22
- data/README.markdown +0 -126
- data/lib/api_messages.rb +0 -18
- data/lib/bunny/header.rb +0 -30
- data/lib/engineroom/buffer.rb +0 -274
- data/lib/engineroom/frame.rb +0 -61
- data/lib/engineroom/protocol.rb +0 -156
- data/lib/engineroom/spec.rb +0 -830
- data/protocol/codegen.rb +0 -171
data/lib/bunny/client.rb
CHANGED
@@ -1,4 +1,13 @@
|
|
1
|
-
module
|
1
|
+
module Bunny
|
2
|
+
|
3
|
+
=begin rdoc
|
4
|
+
|
5
|
+
=== DESCRIPTION:
|
6
|
+
|
7
|
+
The Client class provides the major Bunny API methods.
|
8
|
+
|
9
|
+
=end
|
10
|
+
|
2
11
|
class Client
|
3
12
|
CONNECT_TIMEOUT = 1.0
|
4
13
|
RETRY_DELAY = 10.0
|
@@ -6,6 +15,29 @@ module API
|
|
6
15
|
attr_reader :status, :host, :vhost, :port
|
7
16
|
attr_accessor :channel, :logging, :exchanges, :queues, :ticket
|
8
17
|
|
18
|
+
=begin rdoc
|
19
|
+
|
20
|
+
=== DESCRIPTION:
|
21
|
+
|
22
|
+
Sets up a Bunny::Client object ready for connection to a broker/server. _Client_._status_ is set to
|
23
|
+
<tt>:not_connected</tt>.
|
24
|
+
|
25
|
+
==== OPTIONS:
|
26
|
+
|
27
|
+
* <tt>:host => '_hostname_' (default = 'localhost')</tt>
|
28
|
+
* <tt>:port => _portno_ (default = 5672)</tt>
|
29
|
+
* <tt>:vhost => '_vhostname_' (default = '/')</tt>
|
30
|
+
* <tt>:user => '_username_' (default = 'guest')</tt>
|
31
|
+
* <tt>:pass => '_password_' (default = 'guest')</tt>
|
32
|
+
* <tt>:logging => true or false (_default_)</tt> - If set to _true_, session information is sent
|
33
|
+
to STDOUT.
|
34
|
+
* <tt>:insist => true or false (_default_)</tt> - In a configuration with multiple load-sharing
|
35
|
+
servers, the server may respond to a Connection.Open method with a Connection.Redirect. The insist
|
36
|
+
option, if set to _true_, tells the server that the client is insisting on a connection to the
|
37
|
+
specified server.
|
38
|
+
|
39
|
+
=end
|
40
|
+
|
9
41
|
def initialize(opts = {})
|
10
42
|
@host = opts[:host] || 'localhost'
|
11
43
|
@port = opts[:port] || Protocol::PORT
|
@@ -14,12 +46,97 @@ module API
|
|
14
46
|
@vhost = opts[:vhost] || '/'
|
15
47
|
@logging = opts[:logging] || false
|
16
48
|
@insist = opts[:insist]
|
17
|
-
@status =
|
49
|
+
@status = :not_connected
|
18
50
|
end
|
19
51
|
|
52
|
+
=begin rdoc
|
53
|
+
|
54
|
+
=== DESCRIPTION:
|
55
|
+
|
56
|
+
Declares an exchange to the broker/server. If the exchange does not exist, a new one is created
|
57
|
+
using the arguments passed in. If the exchange already exists, a reference to it is created, provided
|
58
|
+
that the arguments passed in do not conflict with the existing attributes of the exchange. If an error
|
59
|
+
occurs a _Bunny_::_ProtocolError_ is raised.
|
60
|
+
|
61
|
+
==== OPTIONS:
|
62
|
+
|
63
|
+
* <tt>:type => one of :direct (_default_), :fanout, :topic, :headers</tt>
|
64
|
+
* <tt>:passive => true or false</tt> - If set to _true_, the server will not create the exchange.
|
65
|
+
The client can use this to check whether an exchange exists without modifying the server state.
|
66
|
+
* <tt>:durable => true or false (_default_)</tt> - If set to _true_ when creating a new exchange, the exchange
|
67
|
+
will be marked as durable. Durable exchanges remain active when a server restarts. Non-durable
|
68
|
+
exchanges (transient exchanges) are purged if/when a server restarts.
|
69
|
+
* <tt>:auto_delete => true or false (_default_)</tt> - If set to _true_, the exchange is deleted
|
70
|
+
when all queues have finished using it.
|
71
|
+
* <tt>:nowait => true or false (_default_)</tt> - Ignored by Bunny, always _false_.
|
72
|
+
|
73
|
+
==== RETURNS:
|
74
|
+
|
75
|
+
Exchange
|
76
|
+
|
77
|
+
=end
|
78
|
+
|
79
|
+
def exchange(name, opts = {})
|
80
|
+
exchanges[name] ||= Bunny::Exchange.new(self, name, opts)
|
81
|
+
end
|
82
|
+
|
83
|
+
=begin rdoc
|
84
|
+
|
85
|
+
=== DESCRIPTION:
|
86
|
+
|
87
|
+
Returns hash of exchanges declared by Bunny.
|
88
|
+
|
89
|
+
=end
|
90
|
+
|
20
91
|
def exchanges
|
21
92
|
@exchanges ||= {}
|
22
93
|
end
|
94
|
+
|
95
|
+
=begin rdoc
|
96
|
+
|
97
|
+
=== DESCRIPTION:
|
98
|
+
|
99
|
+
Declares a queue to the broker/server. If the queue does not exist, a new one is created
|
100
|
+
using the arguments passed in. If the queue already exists, a reference to it is created, provided
|
101
|
+
that the arguments passed in do not conflict with the existing attributes of the queue. If an error
|
102
|
+
occurs a _Bunny_::_ProtocolError_ is raised.
|
103
|
+
|
104
|
+
==== OPTIONS:
|
105
|
+
|
106
|
+
* <tt>:passive => true or false (_default_)</tt> - If set to _true_, the server will not create
|
107
|
+
the queue. The client can use this to check whether a queue exists without modifying the server
|
108
|
+
state.
|
109
|
+
* <tt>:durable => true or false (_default_)</tt> - If set to _true_ when creating a new queue, the
|
110
|
+
queue will be marked as durable. Durable queues remain active when a server restarts. Non-durable
|
111
|
+
queues (transient queues) are purged if/when a server restarts. Note that durable queues do not
|
112
|
+
necessarily hold persistent messages, although it does not make sense to send persistent messages
|
113
|
+
to a transient queue.
|
114
|
+
* <tt>:exclusive => true or false (_default_)</tt> - If set to _true_, requests an exclusive queue.
|
115
|
+
Exclusive queues may only be consumed from by the current connection. Setting the 'exclusive'
|
116
|
+
flag always implies 'auto-delete'.
|
117
|
+
* <tt>:auto_delete => true or false (_default_)</tt> - If set to _true_, the queue is deleted
|
118
|
+
when all consumers have finished using it. Last consumer can be cancelled either explicitly
|
119
|
+
or because its channel is closed. If there has never been a consumer on the queue, it is not
|
120
|
+
deleted.
|
121
|
+
* <tt>:nowait => true or false (_default_)</tt> - Ignored by Bunny, always _false_.
|
122
|
+
|
123
|
+
==== RETURNS:
|
124
|
+
|
125
|
+
Queue
|
126
|
+
|
127
|
+
=end
|
128
|
+
|
129
|
+
def queue(name, opts = {})
|
130
|
+
queues[name] ||= Bunny::Queue.new(self, name, opts)
|
131
|
+
end
|
132
|
+
|
133
|
+
=begin rdoc
|
134
|
+
|
135
|
+
=== DESCRIPTION:
|
136
|
+
|
137
|
+
Returns hash of queues declared by Bunny.
|
138
|
+
|
139
|
+
=end
|
23
140
|
|
24
141
|
def queues
|
25
142
|
@queues ||= {}
|
@@ -49,24 +166,39 @@ module API
|
|
49
166
|
|
50
167
|
def next_payload
|
51
168
|
frame = next_frame
|
52
|
-
frame
|
169
|
+
frame.payload
|
53
170
|
end
|
54
171
|
|
172
|
+
=begin rdoc
|
173
|
+
|
174
|
+
=== DESCRIPTION:
|
175
|
+
|
176
|
+
Closes the current communication channel and connection. If an error occurs a
|
177
|
+
_Bunny_::_ProtocolError_ is raised. If successful, _Client_._status_ is set to <tt>:not_connected</tt>.
|
178
|
+
|
179
|
+
==== RETURNS:
|
180
|
+
|
181
|
+
<tt>:not_connected</tt> if successful.
|
182
|
+
|
183
|
+
=end
|
184
|
+
|
55
185
|
def close
|
56
186
|
send_frame(
|
57
187
|
Protocol::Channel::Close.new(:reply_code => 200, :reply_text => 'bye', :method_id => 0, :class_id => 0)
|
58
188
|
)
|
59
|
-
raise
|
189
|
+
raise Bunny::ProtocolError, "Error closing channel #{channel}" unless next_method.is_a?(Protocol::Channel::CloseOk)
|
60
190
|
|
61
191
|
self.channel = 0
|
62
192
|
send_frame(
|
63
193
|
Protocol::Connection::Close.new(:reply_code => 200, :reply_text => 'Goodbye', :class_id => 0, :method_id => 0)
|
64
194
|
)
|
65
|
-
raise
|
195
|
+
raise Bunny::ProtocolError, "Error closing connection" unless next_method.is_a?(Protocol::Connection::CloseOk)
|
66
196
|
|
67
197
|
close_socket
|
68
198
|
end
|
69
199
|
|
200
|
+
alias stop close
|
201
|
+
|
70
202
|
def read(*args)
|
71
203
|
send_command(:read, *args)
|
72
204
|
end
|
@@ -75,11 +207,24 @@ module API
|
|
75
207
|
send_command(:write, *args)
|
76
208
|
end
|
77
209
|
|
210
|
+
=begin rdoc
|
211
|
+
|
212
|
+
=== DESCRIPTION:
|
213
|
+
|
214
|
+
Opens a communication channel and starts a connection. If an error occurs, a
|
215
|
+
_Bunny_::_ProtocolError_ is raised. If successful, _Client_._status_ is set to <tt>:connected</tt>.
|
216
|
+
|
217
|
+
==== RETURNS:
|
218
|
+
|
219
|
+
<tt>:connected</tt> if successful.
|
220
|
+
|
221
|
+
=end
|
222
|
+
|
78
223
|
def start_session
|
79
224
|
@channel = 0
|
80
225
|
write(Protocol::HEADER)
|
81
226
|
write([1, 1, Protocol::VERSION_MAJOR, Protocol::VERSION_MINOR].pack('C4'))
|
82
|
-
raise
|
227
|
+
raise Bunny::ProtocolError, 'Connection initiation failed' unless next_method.is_a?(Protocol::Connection::Start)
|
83
228
|
|
84
229
|
send_frame(
|
85
230
|
Protocol::Connection::StartOk.new(
|
@@ -91,7 +236,7 @@ module API
|
|
91
236
|
)
|
92
237
|
|
93
238
|
method = next_method
|
94
|
-
raise
|
239
|
+
raise Bunny::ProtocolError, "Connection failed - user: #{@user}, pass: #{@pass}" if method.nil?
|
95
240
|
|
96
241
|
if method.is_a?(Protocol::Connection::Tune)
|
97
242
|
send_frame(
|
@@ -102,23 +247,25 @@ module API
|
|
102
247
|
send_frame(
|
103
248
|
Protocol::Connection::Open.new(:virtual_host => @vhost, :capabilities => '', :insist => @insist)
|
104
249
|
)
|
105
|
-
raise
|
250
|
+
raise Bunny::ProtocolError, 'Cannot open connection' unless next_method.is_a?(Protocol::Connection::OpenOk)
|
106
251
|
|
107
252
|
@channel = 1
|
108
253
|
send_frame(Protocol::Channel::Open.new)
|
109
|
-
raise
|
254
|
+
raise Bunny::ProtocolError, "Cannot open channel #{channel}" unless next_method.is_a?(Protocol::Channel::OpenOk)
|
110
255
|
|
111
256
|
send_frame(
|
112
257
|
Protocol::Access::Request.new(:realm => '/data', :read => true, :write => true, :active => true, :passive => true)
|
113
258
|
)
|
114
259
|
method = next_method
|
115
|
-
raise
|
260
|
+
raise Bunny::ProtocolError, 'Access denied' unless method.is_a?(Protocol::Access::RequestOk)
|
116
261
|
self.ticket = method.ticket
|
117
262
|
|
118
263
|
# return status
|
119
264
|
status
|
120
265
|
end
|
121
266
|
|
267
|
+
alias start start_session
|
268
|
+
|
122
269
|
private
|
123
270
|
|
124
271
|
def buffer
|
@@ -129,7 +276,7 @@ module API
|
|
129
276
|
begin
|
130
277
|
socket.__send__(cmd, *args)
|
131
278
|
rescue Errno::EPIPE, IOError => e
|
132
|
-
raise
|
279
|
+
raise Bunny::ServerDownError, e.message
|
133
280
|
end
|
134
281
|
end
|
135
282
|
|
@@ -145,9 +292,9 @@ module API
|
|
145
292
|
if Socket.constants.include? 'TCP_NODELAY'
|
146
293
|
@socket.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
147
294
|
end
|
148
|
-
@status =
|
295
|
+
@status = :connected
|
149
296
|
rescue SocketError, SystemCallError, IOError, Timeout::Error => e
|
150
|
-
raise
|
297
|
+
raise Bunny::ServerDownError, e.message
|
151
298
|
end
|
152
299
|
|
153
300
|
@socket
|
@@ -157,7 +304,7 @@ module API
|
|
157
304
|
# Close the socket. The server is not considered dead.
|
158
305
|
@socket.close if @socket and not @socket.closed?
|
159
306
|
@socket = nil
|
160
|
-
@status =
|
307
|
+
@status = :not_connected
|
161
308
|
end
|
162
309
|
|
163
310
|
def log(*args)
|
data/lib/bunny/exchange.rb
CHANGED
@@ -1,83 +1,158 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
attr_reader :client, :type, :name, :opts, :key
|
1
|
+
module Bunny
|
2
|
+
|
3
|
+
=begin rdoc
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
5
|
+
=== DESCRIPTION:
|
6
|
+
|
7
|
+
*Exchanges* are the routing and distribution hub of AMQP. All messages that Bunny sends
|
8
|
+
to an AMQP broker/server _have_ to pass through an exchange in order to be routed to a
|
9
|
+
destination queue. The AMQP specification defines the types of exchange that you can create.
|
10
|
+
|
11
|
+
At the time of writing there are four (4) types of exchange defined -
|
12
|
+
|
13
|
+
* <tt>:direct</tt>
|
14
|
+
* <tt>:fanout</tt>
|
15
|
+
* <tt>:topic</tt>
|
16
|
+
* <tt>:headers</tt>
|
17
|
+
|
18
|
+
AMQP-compliant brokers/servers are required to provide default exchanges for the _direct_ and
|
19
|
+
_fanout_ exchange types. All default exchanges are prefixed with <tt>'amq.'</tt>, for example -
|
20
|
+
|
21
|
+
* <tt>amq.direct</tt>
|
22
|
+
* <tt>amq.fanout</tt>
|
23
|
+
* <tt>amq.topic</tt>
|
24
|
+
* <tt>amq.match</tt> or <tt>amq.headers</tt>
|
25
|
+
|
26
|
+
If you want more information about exchanges, please consult the documentation for your
|
27
|
+
target broker/server or visit the {AMQP website}[http://www.amqp.org] to find the version of the
|
28
|
+
specification that applies to your target broker/server.
|
29
|
+
|
30
|
+
=end
|
31
|
+
|
32
|
+
class Exchange
|
33
|
+
|
34
|
+
attr_reader :client, :type, :name, :opts, :key
|
35
|
+
|
36
|
+
def initialize(client, name, opts = {})
|
37
|
+
# check connection to server
|
38
|
+
raise Bunny::ConnectionError, 'Not connected to server' if client.status == :not_connected
|
39
|
+
|
40
|
+
@client, @name, @opts = client, name, opts
|
41
|
+
|
42
|
+
# set up the exchange type catering for default names
|
43
|
+
if name.match(/^amq\./)
|
44
|
+
new_type = name.sub(/amq\./, '')
|
45
|
+
# handle 'amq.match' default
|
46
|
+
new_type = 'headers' if new_type == 'match'
|
47
|
+
@type = new_type.to_sym
|
48
|
+
else
|
49
|
+
@type = opts[:type] || :direct
|
50
|
+
end
|
51
|
+
|
52
|
+
@key = opts[:key]
|
53
|
+
@client.exchanges[@name] ||= self
|
54
|
+
|
55
|
+
# ignore the :nowait option if passed, otherwise program will hang waiting for a
|
56
|
+
# response that will not be sent by the server
|
57
|
+
opts.delete(:nowait)
|
58
|
+
|
59
|
+
unless name == "amq.#{type}" or name == ''
|
60
|
+
client.send_frame(
|
61
|
+
Protocol::Exchange::Declare.new(
|
62
|
+
{ :exchange => name, :type => type, :nowait => false }.merge(opts)
|
63
|
+
)
|
64
|
+
)
|
65
|
+
|
66
|
+
raise Bunny::ProtocolError,
|
67
|
+
"Error declaring exchange #{name}: type = #{type}" unless
|
68
|
+
client.next_method.is_a?(Protocol::Exchange::DeclareOk)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
=begin rdoc
|
73
|
+
|
74
|
+
=== DESCRIPTION:
|
75
|
+
|
76
|
+
Publishes a message to a specific exchange. The message will be routed to queues as defined
|
77
|
+
by the exchange configuration and distributed to any active consumers when the transaction,
|
78
|
+
if any, is committed.
|
79
|
+
|
80
|
+
==== OPTIONS:
|
81
|
+
|
82
|
+
* <tt>:key => 'routing_key'</tt> - Specifies the routing key for the message. The routing key is
|
83
|
+
used for routing messages depending on the exchange configuration.
|
84
|
+
* <tt>:mandatory => true or false (_default_)</tt> - Tells the server how to react if the message
|
85
|
+
cannot be routed to a queue. If set to _true_, the server will return an unroutable message
|
86
|
+
with a Return method. If this flag is zero, the server silently drops the message.
|
87
|
+
* <tt>:immediate => true or false (_default_)</tt> - Tells the server how to react if the message
|
88
|
+
cannot be routed to a queue consumer immediately. If set to _true_, the server will return an
|
89
|
+
undeliverable message with a Return method. If set to _false_, the server will queue the message,
|
90
|
+
but with no guarantee that it will ever be consumed.
|
91
|
+
|
92
|
+
==== RETURNS:
|
93
|
+
|
94
|
+
nil
|
95
|
+
|
96
|
+
=end
|
97
|
+
|
98
|
+
def publish(data, opts = {})
|
99
|
+
out = []
|
100
|
+
|
101
|
+
out << Protocol::Basic::Publish.new(
|
102
|
+
{ :exchange => name, :routing_key => opts.delete(:key) || key }.merge(opts)
|
103
|
+
)
|
104
|
+
data = data.to_s
|
105
|
+
out << Protocol::Header.new(
|
106
|
+
Protocol::Basic,
|
107
|
+
data.length, {
|
108
|
+
:content_type => 'application/octet-stream',
|
109
|
+
:delivery_mode => (opts.delete(:persistent) ? 2 : 1),
|
110
|
+
:priority => 0
|
111
|
+
}.merge(opts)
|
112
|
+
)
|
113
|
+
out << Transport::Frame::Body.new(data)
|
114
|
+
|
115
|
+
client.send_frame(*out)
|
116
|
+
end
|
117
|
+
|
118
|
+
=begin rdoc
|
119
|
+
|
120
|
+
=== DESCRIPTION:
|
121
|
+
|
122
|
+
Requests that an exchange is deleted from broker/server. Removes reference from exchanges
|
123
|
+
if successful. If an error occurs raises _Bunny_::_ProtocolError_.
|
82
124
|
|
125
|
+
==== Options:
|
126
|
+
|
127
|
+
* <tt>:if_unused => true or false (_default_)</tt> - If set to _true_, the server will only
|
128
|
+
delete the exchange if it has no queue bindings. If the exchange has queue bindings the
|
129
|
+
server does not delete it but raises a channel exception instead.
|
130
|
+
* <tt>:nowait => true or false (_default_)</tt> - Ignored by Bunny, always _false_.
|
131
|
+
|
132
|
+
==== Returns:
|
133
|
+
|
134
|
+
<tt>:delete_ok</tt> if successful
|
135
|
+
=end
|
136
|
+
|
137
|
+
def delete(opts = {})
|
138
|
+
# ignore the :nowait option if passed, otherwise program will hang waiting for a
|
139
|
+
# response that will not be sent by the server
|
140
|
+
opts.delete(:nowait)
|
141
|
+
|
142
|
+
client.send_frame(
|
143
|
+
Protocol::Exchange::Delete.new({ :exchange => name, :nowait => false }.merge(opts))
|
144
|
+
)
|
145
|
+
|
146
|
+
raise Bunny::ProtocolError,
|
147
|
+
"Error deleting exchange #{name}" unless
|
148
|
+
client.next_method.is_a?(Protocol::Exchange::DeleteOk)
|
149
|
+
|
150
|
+
client.exchanges.delete(name)
|
151
|
+
|
152
|
+
# return confirmation
|
153
|
+
:delete_ok
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
83
158
|
end
|