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.
@@ -1,4 +1,13 @@
1
- module API
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 = NOT_CONNECTED
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 and frame.payload
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 API::ProtocolError, "Error closing channel #{channel}" unless next_method.is_a?(Protocol::Channel::CloseOk)
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 API::ProtocolError, "Error closing connection" unless next_method.is_a?(Protocol::Connection::CloseOk)
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 API::ProtocolError, 'Connection initiation failed' unless next_method.is_a?(Protocol::Connection::Start)
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 API::ProtocolError, "Connection failed - user: #{@user}, pass: #{@pass}" if method.nil?
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 API::ProtocolError, 'Cannot open connection' unless next_method.is_a?(Protocol::Connection::OpenOk)
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 API::ProtocolError, "Cannot open channel #{channel}" unless next_method.is_a?(Protocol::Channel::OpenOk)
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 API::ProtocolError, 'Access denied' unless method.is_a?(Protocol::Access::RequestOk)
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 API::ServerDownError, e.message
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 = CONNECTED
295
+ @status = :connected
149
296
  rescue SocketError, SystemCallError, IOError, Timeout::Error => e
150
- raise API::ServerDownError, e.message
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 = NOT_CONNECTED
307
+ @status = :not_connected
161
308
  end
162
309
 
163
310
  def log(*args)
@@ -1,83 +1,158 @@
1
- module API
2
- class Exchange
3
-
4
- attr_reader :client, :type, :name, :opts, :key
1
+ module Bunny
2
+
3
+ =begin rdoc
5
4
 
6
- def initialize(client, name, opts = {})
7
- # check connection to server
8
- raise API::ConnectionError, 'Not connected to server' if client.status == NOT_CONNECTED
9
-
10
- @client, @name, @opts = client, name, opts
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
-
22
- @key = opts[:key]
23
- @client.exchanges[@name] ||= self
24
-
25
- # ignore the :nowait option if passed, otherwise program will hang waiting for a
26
- # response that will not be sent by the server
27
- opts.delete(:nowait)
28
-
29
- unless name == "amq.#{type}" or name == ''
30
- client.send_frame(
31
- Protocol::Exchange::Declare.new(
32
- { :exchange => name, :type => type, :nowait => false }.merge(opts)
33
- )
34
- )
35
-
36
- raise API::ProtocolError,
37
- "Error declaring exchange #{name}: type = #{type}" unless
38
- client.next_method.is_a?(Protocol::Exchange::DeclareOk)
39
- end
40
- end
41
-
42
- def publish(data, opts = {})
43
- out = []
44
-
45
- out << Protocol::Basic::Publish.new(
46
- { :exchange => name, :routing_key => opts.delete(:key) || key }.merge(opts)
47
- )
48
- data = data.to_s
49
- out << Protocol::Header.new(
50
- Protocol::Basic,
51
- data.length, {
52
- :content_type => 'application/octet-stream',
53
- :delivery_mode => (opts.delete(:persistent) ? 2 : 1),
54
- :priority => 0
55
- }.merge(opts)
56
- )
57
- out << Transport::Frame::Body.new(data)
58
-
59
- client.send_frame(*out)
60
- end
61
-
62
- def delete(opts = {})
63
- # ignore the :nowait option if passed, otherwise program will hang waiting for a
64
- # response that will not be sent by the server
65
- opts.delete(:nowait)
66
-
67
- client.send_frame(
68
- Protocol::Exchange::Delete.new({ :exchange => name, :nowait => false }.merge(opts))
69
- )
70
-
71
- raise API::ProtocolError,
72
- "Error deleting exchange #{name}" unless
73
- client.next_method.is_a?(Protocol::Exchange::DeleteOk)
74
-
75
- client.exchanges.delete(name)
76
-
77
- # return confirmation
78
- EXCHANGE_DELETED
79
- end
80
-
81
- end
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