bunny 0.2.0 → 0.3.0

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