amqp 0.5.9 → 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/lib/mq/exchange.rb CHANGED
@@ -1,13 +1,199 @@
1
1
  class MQ
2
+ # An Exchange acts as an ingress point for all published messages. An
3
+ # exchange may also be described as a router or a matcher. Every
4
+ # published message is received by an exchange which, depending on its
5
+ # type (described below), determines how to deliver the message.
6
+ #
7
+ # It determines the next delivery hop by examining the bindings associated
8
+ # with the exchange.
9
+ #
10
+ # There are three (3) supported Exchange types: direct, fanout and topic.
11
+ #
12
+ # As part of the standard, the server _must_ predeclare the direct exchange
13
+ # 'amq.direct' and the fanout exchange 'amq.fanout' (all exchange names
14
+ # starting with 'amq.' are reserved). Attempts to declare an exchange using
15
+ # 'amq.' as the name will raise an MQ:Error and fail. In practice these
16
+ # default exchanges are never used directly by client code.
17
+ #
18
+ # These predececlared exchanges are used when the client code declares
19
+ # an exchange without a name. In these cases the library will use
20
+ # the default exchange for publishing the messages.
21
+ #
2
22
  class Exchange
3
23
  include AMQP
4
24
 
25
+ # Defines, intializes and returns an Exchange to act as an ingress
26
+ # point for all published messages.
27
+ #
28
+ # There are three (3) supported Exchange types: direct, fanout and topic.
29
+ #
30
+ # As part of the standard, the server _must_ predeclare the direct exchange
31
+ # 'amq.direct' and the fanout exchange 'amq.fanout' (all exchange names
32
+ # starting with 'amq.' are reserved). Attempts to declare an exchange using
33
+ # 'amq.' as the name will raise an MQ:Error and fail. In practice these
34
+ # default exchanges are never used directly by client code.
35
+ #
36
+ # == Direct
37
+ # A direct exchange is useful for 1:1 communication between a publisher and
38
+ # subscriber. Messages are routed to the queue with a binding that shares
39
+ # the same name as the exchange. Alternately, the messages are routed to
40
+ # the bound queue that shares the same name as the routing key used for
41
+ # defining the exchange. This exchange type does not honor the :key option
42
+ # when defining a new instance with a name. It _will_ honor the :key option
43
+ # if the exchange name is the empty string. This is because an exchange
44
+ # defined with the empty string uses the default pre-declared exchange
45
+ # called 'amq.direct'. In this case it needs to use :key to do its matching.
46
+ #
47
+ # # exchange is named 'foo'
48
+ # exchange = MQ::Exchange.new(MQ.new, :direct, 'foo')
49
+ #
50
+ # # or, the exchange can use the default name (amq.direct) and perform
51
+ # # routing comparisons using the :key
52
+ # exchange = MQ::Exchange.new(MQ.new, :direct, "", :key => 'foo')
53
+ # exchange.publish('some data') # will be delivered to queue bound to 'foo'
54
+ #
55
+ # queue = MQ::Queue.new(MQ.new, 'foo')
56
+ # # can receive data since the queue name and the exchange key match exactly
57
+ # queue.pop { |data| puts "received data [#{data}]" }
58
+ #
59
+ # == Fanout
60
+ # A fanout exchange is useful for 1:N communication where one publisher
61
+ # feeds multiple subscribers. Like direct exchanges, messages published
62
+ # to a fanout exchange are delivered to queues whose name matches the
63
+ # exchange name (or are bound to that exchange name). Each queue gets
64
+ # its own copy of the message.
65
+ #
66
+ # Like the direct exchange type, this exchange type does not honor the
67
+ # :key option when defining a new instance with a name. It _will_ honor
68
+ # the :key option if the exchange name is the empty string. Fanout exchanges
69
+ # defined with the empty string as the name use the default 'amq.fanout'.
70
+ # In this case it needs to use :key to do its matching.
71
+ #
72
+ # EM.run do
73
+ # clock = MQ::Exchange.new(MQ.new, :fanout, 'clock')
74
+ # EM.add_periodic_timer(1) do
75
+ # puts "\npublishing #{time = Time.now}"
76
+ # clock.publish(Marshal.dump(time))
77
+ # end
78
+ #
79
+ # # one way of defining a queue
80
+ # amq = MQ::Queue.new(MQ.new, 'every second')
81
+ # amq.bind(MQ.fanout('clock')).subscribe do |time|
82
+ # puts "every second received #{Marshal.load(time)}"
83
+ # end
84
+ #
85
+ # # defining a queue using the convenience method
86
+ # # note the string passed to #bind
87
+ # MQ.queue('every 5 seconds').bind('clock').subscribe do |time|
88
+ # time = Marshal.load(time)
89
+ # puts "every 5 seconds received #{time}" if time.strftime('%S').to_i%5 == 0
90
+ # end
91
+ # end
92
+ #
93
+ # == Topic
94
+ # A topic exchange allows for messages to be published to an exchange
95
+ # tagged with a specific routing key. The Exchange uses the routing key
96
+ # to determine which queues to deliver the message. Wildcard matching
97
+ # is allowed. The topic must be declared using dot notation to separate
98
+ # each subtopic.
99
+ #
100
+ # This is the only exchange type to honor the :key parameter.
101
+ #
102
+ # As part of the AMQP standard, each server _should_ predeclare a topic
103
+ # exchange called 'amq.topic' (this is not required by the standard).
104
+ #
105
+ # The classic example is delivering market data. When publishing market
106
+ # data for stocks, we may subdivide the stream based on 2
107
+ # characteristics: nation code and trading symbol. The topic tree for
108
+ # Apple Computer would look like:
109
+ # 'stock.us.aapl'
110
+ # For a foreign stock, it may look like:
111
+ # 'stock.de.dax'
112
+ #
113
+ # When publishing data to the exchange, bound queues subscribing to the
114
+ # exchange indicate which data interests them by passing a routing key
115
+ # for matching against the published routing key.
116
+ #
117
+ # EM.run do
118
+ # exch = MQ::Exchange.new(MQ.new, :topic, "stocks")
119
+ # keys = ['stock.us.aapl', 'stock.de.dax']
120
+ #
121
+ # EM.add_periodic_timer(1) do # every second
122
+ # puts
123
+ # exch.publish(10+rand(10), :routing_key => keys[rand(2)])
124
+ # end
125
+ #
126
+ # # match against one dot-separated item
127
+ # MQ.queue('us stocks').bind(exch, :key => 'stock.us.*').subscribe do |price|
128
+ # puts "us stock price [#{price}]"
129
+ # end
130
+ #
131
+ # # match against multiple dot-separated items
132
+ # MQ.queue('all stocks').bind(exch, :key => 'stock.#').subscribe do |price|
133
+ # puts "all stocks: price [#{price}]"
134
+ # end
135
+ #
136
+ # # require exact match
137
+ # MQ.queue('only dax').bind(exch, :key => 'stock.de.dax').subscribe do |price|
138
+ # puts "dax price [#{price}]"
139
+ # end
140
+ # end
141
+ #
142
+ # For matching, the '*' (asterisk) wildcard matches against one
143
+ # dot-separated item only. The '#' wildcard (hash or pound symbol)
144
+ # matches against 0 or more dot-separated items. If none of these
145
+ # symbols are used, the exchange performs a comparison looking for an
146
+ # exact match.
147
+ #
148
+ # == Options
149
+ # * :passive => true | false (default false)
150
+ # If set, the server will not create the exchange if it does not
151
+ # already exist. The client can use this to check whether an exchange
152
+ # exists without modifying the server state.
153
+ #
154
+ # * :durable => true | false (default false)
155
+ # If set when creating a new exchange, the exchange will be marked as
156
+ # durable. Durable exchanges remain active when a server restarts.
157
+ # Non-durable exchanges (transient exchanges) are purged if/when a
158
+ # server restarts.
159
+ #
160
+ # A transient exchange (the default) is stored in memory-only
161
+ # therefore it is a good choice for high-performance and low-latency
162
+ # message publishing.
163
+ #
164
+ # Durable exchanges cause all messages to be written to non-volatile
165
+ # backing store (i.e. disk) prior to routing to any bound queues.
166
+ #
167
+ # * :auto_delete => true | false (default false)
168
+ # If set, the exchange is deleted when all queues have finished
169
+ # using it. The server waits for a short period of time before
170
+ # determining the exchange is unused to give time to the client code
171
+ # to bind a queue to it.
172
+ #
173
+ # If the exchange has been previously declared, this option is ignored
174
+ # on subsequent declarations.
175
+ #
176
+ # * :internal => true | false (default false)
177
+ # If set, the exchange may not be used directly by publishers, but
178
+ # only when bound to other exchanges. Internal exchanges are used to
179
+ # construct wiring that is not visible to applications.
180
+ #
181
+ # * :nowait => true | false (default true)
182
+ # If set, the server will not respond to the method. The client should
183
+ # not wait for a reply method. If the server could not complete the
184
+ # method it will raise a channel or connection exception.
185
+ #
186
+ # == Exceptions
187
+ # Doing any of these activities are illegal and will raise MQ:Error.
188
+ # * redeclare an already-declared exchange to a different type
189
+ # * :passive => true and the exchange does not exist (NOT_FOUND)
190
+ #
5
191
  def initialize mq, type, name, opts = {}
6
192
  @mq = mq
7
- @type, @name = type, name
193
+ @type, @name, @opts = type, name, opts
8
194
  @mq.exchanges[@name = name] ||= self
9
195
  @key = opts[:key]
10
-
196
+
11
197
  @mq.callback{
12
198
  @mq.send Protocol::Exchange::Declare.new({ :exchange => name,
13
199
  :type => type,
@@ -16,13 +202,53 @@ class MQ
16
202
  end
17
203
  attr_reader :name, :type, :key
18
204
 
205
+ # This method publishes a staged file message to a specific exchange.
206
+ # The file message will be routed to queues as defined by the exchange
207
+ # configuration and distributed to any active consumers when the
208
+ # transaction, if any, is committed.
209
+ #
210
+ # exchange = MQ.direct('name', :key => 'foo.bar')
211
+ # exchange.publish("some data")
212
+ #
213
+ # The method takes several hash key options which modify the behavior or
214
+ # lifecycle of the message.
215
+ #
216
+ # * :routing_key => 'string'
217
+ #
218
+ # Specifies the routing key for the message. The routing key is
219
+ # used for routing messages depending on the exchange configuration.
220
+ #
221
+ # * :mandatory => true | false (default false)
222
+ #
223
+ # This flag tells the server how to react if the message cannot be
224
+ # routed to a queue. If this flag is set, the server will return an
225
+ # unroutable message with a Return method. If this flag is zero, the
226
+ # server silently drops the message.
227
+ #
228
+ # * :immediate => true | false (default false)
229
+ #
230
+ # This flag tells the server how to react if the message cannot be
231
+ # routed to a queue consumer immediately. If this flag is set, the
232
+ # server will return an undeliverable message with a Return method.
233
+ # If this flag is zero, the server will queue the message, but with
234
+ # no guarantee that it will ever be consumed.
235
+ #
236
+ # * :persistent
237
+ # True or False. When true, this message will remain in the queue until
238
+ # it is consumed (if the queue is durable). When false, the message is
239
+ # lost if the server restarts and the queue is recreated.
240
+ #
241
+ # For high-performance and low-latency, set :persistent => false so the
242
+ # message stays in memory and is never persisted to non-volatile (slow)
243
+ # storage.
244
+ #
19
245
  def publish data, opts = {}
20
246
  @mq.callback{
21
247
  out = []
22
248
 
23
249
  out << Protocol::Basic::Publish.new({ :exchange => name,
24
250
  :routing_key => opts.delete(:key) || @key }.merge(opts))
25
-
251
+
26
252
  data = data.to_s
27
253
 
28
254
  out << Protocol::Header.new(Protocol::Basic,
@@ -36,5 +262,41 @@ class MQ
36
262
  }
37
263
  self
38
264
  end
265
+
266
+ # This method deletes an exchange. When an exchange is deleted all queue
267
+ # bindings on the exchange are cancelled.
268
+ #
269
+ # Further attempts to publish messages to a deleted exchange will raise
270
+ # an MQ::Error due to a channel close exception.
271
+ #
272
+ # exchange = MQ.direct('name', :key => 'foo.bar')
273
+ # exchange.delete
274
+ #
275
+ # == Options
276
+ # * :nowait => true | false (default true)
277
+ # If set, the server will not respond to the method. The client should
278
+ # not wait for a reply method. If the server could not complete the
279
+ # method it will raise a channel or connection exception.
280
+ #
281
+ # exchange.delete(:nowait => false)
282
+ #
283
+ # * :if_unused => true | false (default false)
284
+ # If set, the server will only delete the exchange if it has no queue
285
+ # bindings. If the exchange has queue bindings the server does not
286
+ # delete it but raises a channel exception instead (MQ:Error).
287
+ #
288
+ def delete opts = {}
289
+ @mq.callback{
290
+ @mq.send Protocol::Exchange::Delete.new({ :exchange => name,
291
+ :nowait => true }.merge(opts))
292
+ @mq.exchanges.delete name
293
+ }
294
+ nil
295
+ end
296
+
297
+ def reset
298
+ @deferred_status = nil
299
+ initialize @mq, @type, @name, @opts
300
+ end
39
301
  end
40
302
  end
data/lib/mq/header.rb ADDED
@@ -0,0 +1,33 @@
1
+ class MQ
2
+ class Header
3
+ include AMQP
4
+
5
+ def initialize(mq, header_obj)
6
+ @mq = mq
7
+ @header = header_obj
8
+ end
9
+
10
+ # Acknowledges the receipt of this message with the server.
11
+ def ack
12
+ @mq.callback{
13
+ @mq.send Protocol::Basic::Ack.new(:delivery_tag => properties[:delivery_tag])
14
+ }
15
+ end
16
+
17
+ # Reject this message (XXX currently unimplemented in rabbitmq)
18
+ # * :requeue => true | false (default false)
19
+ def reject opts = {}
20
+ @mq.callback{
21
+ @mq.send Protocol::Basic::Reject.new(opts.merge(:delivery_tag => properties[:delivery_tag]))
22
+ }
23
+ end
24
+
25
+ def method_missing meth, *args, &blk
26
+ @header.send meth, *args, &blk
27
+ end
28
+
29
+ def inspect
30
+ @header.inspect
31
+ end
32
+ end
33
+ end
data/lib/mq/logger.rb CHANGED
@@ -48,7 +48,9 @@ class MQ
48
48
  :msg => data)
49
49
 
50
50
  print(opts)
51
- MQ.fanout('logging', :durable => true).publish Marshal.dump(opts)
51
+ unless Logger.disabled?
52
+ MQ.fanout('logging', :durable => true).publish Marshal.dump(opts)
53
+ end
52
54
 
53
55
  opts
54
56
  end
@@ -71,5 +73,17 @@ class MQ
71
73
  @printer = block if block
72
74
  @printer
73
75
  end
76
+
77
+ def self.disabled?
78
+ !!@disabled
79
+ end
80
+
81
+ def self.enable
82
+ @disabled = false
83
+ end
84
+
85
+ def self.disable
86
+ @disabled = true
87
+ end
74
88
  end
75
89
  end
data/lib/mq/queue.rb CHANGED
@@ -2,8 +2,69 @@ class MQ
2
2
  class Queue
3
3
  include AMQP
4
4
 
5
+ # Queues store and forward messages. Queues can be configured in the server
6
+ # or created at runtime. Queues must be attached to at least one exchange
7
+ # in order to receive messages from publishers.
8
+ #
9
+ # Like an Exchange, queue names starting with 'amq.' are reserved for
10
+ # internal use. Attempts to create queue names in violation of this
11
+ # reservation will raise MQ:Error (ACCESS_REFUSED).
12
+ #
13
+ # When a queue is created without a name, the server will generate a
14
+ # unique name internally (not currently supported in this library).
15
+ #
16
+ # == Options
17
+ # * :passive => true | false (default false)
18
+ # If set, the server will not create the exchange if it does not
19
+ # already exist. The client can use this to check whether an exchange
20
+ # exists without modifying the server state.
21
+ #
22
+ # * :durable => true | false (default false)
23
+ # If set when creating a new queue, the queue will be marked as
24
+ # durable. Durable queues remain active when a server restarts.
25
+ # Non-durable queues (transient queues) are purged if/when a
26
+ # server restarts. Note that durable queues do not necessarily
27
+ # hold persistent messages, although it does not make sense to
28
+ # send persistent messages to a transient queue (though it is
29
+ # allowed).
30
+ #
31
+ # If the queue has already been declared, any redeclaration will
32
+ # ignore this setting. A queue may only be declared durable the
33
+ # first time when it is created.
34
+ #
35
+ # * :exclusive => true | false (default false)
36
+ # Exclusive queues may only be consumed from by the current connection.
37
+ # Setting the 'exclusive' flag always implies 'auto-delete'. Only a
38
+ # single consumer is allowed to remove messages from this queue.
39
+ #
40
+ # The default is a shared queue. Multiple clients may consume messages
41
+ # from this queue.
42
+ #
43
+ # Attempting to redeclare an already-declared queue as :exclusive => true
44
+ # will raise MQ:Error.
45
+ #
46
+ # * :auto_delete = true | false (default false)
47
+ # If set, the queue is deleted when all consumers have finished
48
+ # using it. Last consumer can be cancelled either explicitly or because
49
+ # its channel is closed. If there was no consumer ever on the queue, it
50
+ # won't be deleted.
51
+ #
52
+ # The server waits for a short period of time before
53
+ # determining the queue is unused to give time to the client code
54
+ # to bind an exchange to it.
55
+ #
56
+ # If the queue has been previously declared, this option is ignored
57
+ # on subsequent declarations.
58
+ #
59
+ # * :nowait => true | false (default true)
60
+ # If set, the server will not respond to the method. The client should
61
+ # not wait for a reply method. If the server could not complete the
62
+ # method it will raise a channel or connection exception.
63
+ #
5
64
  def initialize mq, name, opts = {}
6
65
  @mq = mq
66
+ @opts = opts
67
+ @bindings ||= {}
7
68
  @mq.queues[@name = name] ||= self
8
69
  @mq.callback{
9
70
  @mq.send Protocol::Queue::Declare.new({ :queue => name,
@@ -12,26 +73,99 @@ class MQ
12
73
  end
13
74
  attr_reader :name
14
75
 
76
+ # This method binds a queue to an exchange. Until a queue is
77
+ # bound it will not receive any messages. In a classic messaging
78
+ # model, store-and-forward queues are bound to a dest exchange
79
+ # and subscription queues are bound to a dest_wild exchange.
80
+ #
81
+ # A valid exchange name (or reference) must be passed as the first
82
+ # parameter. Both of these are valid:
83
+ # exch = MQ.direct('foo exchange')
84
+ # queue = MQ.queue('bar queue')
85
+ # queue.bind('foo.exchange') # OR
86
+ # queue.bind(exch)
87
+ #
88
+ # It is not valid to call #bind without the +exchange+ parameter.
89
+ #
90
+ # It is unnecessary to call #bind when the exchange name and queue
91
+ # name match exactly (for +direct+ and +fanout+ exchanges only).
92
+ # There is an implicit bind which will deliver the messages from
93
+ # the exchange to the queue.
94
+ #
95
+ # == Options
96
+ # * :key => 'some string'
97
+ # Specifies the routing key for the binding. The routing key is
98
+ # used for routing messages depending on the exchange configuration.
99
+ # Not all exchanges use a routing key - refer to the specific
100
+ # exchange documentation. If the routing key is empty and the queue
101
+ # name is empty, the routing key will be the current queue for the
102
+ # channel, which is the last declared queue.
103
+ #
104
+ # * :nowait => true | false (default true)
105
+ # If set, the server will not respond to the method. The client should
106
+ # not wait for a reply method. If the server could not complete the
107
+ # method it will raise a channel or connection exception.
108
+ #
15
109
  def bind exchange, opts = {}
110
+ exchange = exchange.respond_to?(:name) ? exchange.name : exchange
111
+ @bindings[exchange] = opts
112
+
16
113
  @mq.callback{
17
114
  @mq.send Protocol::Queue::Bind.new({ :queue => name,
18
- :exchange => exchange.respond_to?(:name) ? exchange.name : exchange,
115
+ :exchange => exchange,
19
116
  :routing_key => opts.delete(:key),
20
117
  :nowait => true }.merge(opts))
21
118
  }
22
119
  self
23
120
  end
24
121
 
122
+ # Remove the binding between the queue and exchange. The queue will
123
+ # not receive any more messages until it is bound to another
124
+ # exchange.
125
+ #
126
+ # Due to the asynchronous nature of the protocol, it is possible for
127
+ # "in flight" messages to be received after this call completes.
128
+ # Those messages will be serviced by the last block used in a
129
+ # #subscribe or #pop call.
130
+ #
131
+ # * :nowait => true | false (default true)
132
+ # If set, the server will not respond to the method. The client should
133
+ # not wait for a reply method. If the server could not complete the
134
+ # method it will raise a channel or connection exception.
135
+ #
25
136
  def unbind exchange, opts = {}
137
+ exchange = exchange.respond_to?(:name) ? exchange.name : exchange
138
+ @bindings.delete exchange
139
+
26
140
  @mq.callback{
27
141
  @mq.send Protocol::Queue::Unbind.new({ :queue => name,
28
- :exchange => exchange.respond_to?(:name) ? exchange.name : exchange,
142
+ :exchange => exchange,
29
143
  :routing_key => opts.delete(:key),
30
144
  :nowait => true }.merge(opts))
31
145
  }
32
146
  self
33
147
  end
34
148
 
149
+ # This method deletes a queue. When a queue is deleted any pending
150
+ # messages are sent to a dead-letter queue if this is defined in the
151
+ # server configuration, and all consumers on the queue are cancelled.
152
+ #
153
+ # == Options
154
+ # * :if_unused => true | false (default false)
155
+ # If set, the server will only delete the queue if it has no
156
+ # consumers. If the queue has consumers the server does does not
157
+ # delete it but raises a channel exception instead.
158
+ #
159
+ # * :if_empty => true | false (default false)
160
+ # If set, the server will only delete the queue if it has no
161
+ # messages. If the queue is not empty the server raises a channel
162
+ # exception.
163
+ #
164
+ # * :nowait => true | false (default true)
165
+ # If set, the server will not respond to the method. The client should
166
+ # not wait for a reply method. If the server could not complete the
167
+ # method it will raise a channel or connection exception.
168
+ #
35
169
  def delete opts = {}
36
170
  @mq.callback{
37
171
  @mq.send Protocol::Queue::Delete.new({ :queue => name,
@@ -40,25 +174,247 @@ class MQ
40
174
  @mq.queues.delete @name
41
175
  nil
42
176
  end
43
-
177
+
178
+ # This method provides a direct access to the messages in a queue
179
+ # using a synchronous dialogue that is designed for specific types of
180
+ # application where synchronous functionality is more important than
181
+ # performance.
182
+ #
183
+ # The provided block is passed a single message each time pop is called.
184
+ #
185
+ # EM.run do
186
+ # exchange = MQ.direct("foo queue")
187
+ # EM.add_periodic_timer(1) do
188
+ # exchange.publish("random number #{rand(1000)}")
189
+ # end
190
+ #
191
+ # # note that #bind is never called; it is implicit because
192
+ # # the exchange and queue names match
193
+ # queue = MQ.queue('foo queue')
194
+ # queue.pop { |body| puts "received payload [#{body}]" }
195
+ #
196
+ # EM.add_periodic_timer(1) { queue.pop }
197
+ # end
198
+ #
199
+ # If the block takes 2 parameters, both the +header+ and the +body+ will
200
+ # be passed in for processing. The header object is defined by
201
+ # AMQP::Protocol::Header.
202
+ #
203
+ # EM.run do
204
+ # exchange = MQ.direct("foo queue")
205
+ # EM.add_periodic_timer(1) do
206
+ # exchange.publish("random number #{rand(1000)}")
207
+ # end
208
+ #
209
+ # queue = MQ.queue('foo queue')
210
+ # queue.pop do |header, body|
211
+ # p header
212
+ # puts "received payload [#{body}]"
213
+ # end
214
+ #
215
+ # EM.add_periodic_timer(1) { queue.pop }
216
+ # end
217
+ #
218
+ # == Options
219
+ # * :ack => true | false (default false)
220
+ # If this field is set to false the server does not expect acknowledgments
221
+ # for messages. That is, when a message is delivered to the client
222
+ # the server automatically and silently acknowledges it on behalf
223
+ # of the client. This functionality increases performance but at
224
+ # the cost of reliability. Messages can get lost if a client dies
225
+ # before it can deliver them to the application.
226
+ #
227
+ # * :nowait => true | false (default true)
228
+ # If set, the server will not respond to the method. The client should
229
+ # not wait for a reply method. If the server could not complete the
230
+ # method it will raise a channel or connection exception.
231
+ #
232
+ def pop opts = {}, &blk
233
+ if blk
234
+ @on_pop = blk
235
+ @on_pop_opts = opts
236
+ end
237
+
238
+ @mq.callback{
239
+ @mq.send Protocol::Basic::Get.new({ :queue => name,
240
+ :consumer_tag => name,
241
+ :no_ack => !opts.delete(:ack),
242
+ :nowait => true }.merge(opts))
243
+ @mq.get_queue{ |q|
244
+ q.push(self)
245
+ }
246
+ }
247
+
248
+ self
249
+ end
250
+
251
+ # Subscribes to asynchronous message delivery.
252
+ #
253
+ # The provided block is passed a single message each time the
254
+ # exchange matches a message to this queue.
255
+ #
256
+ # EM.run do
257
+ # exchange = MQ.direct("foo queue")
258
+ # EM.add_periodic_timer(1) do
259
+ # exchange.publish("random number #{rand(1000)}")
260
+ # end
261
+ #
262
+ # queue = MQ.queue('foo queue')
263
+ # queue.subscribe { |body| puts "received payload [#{body}]" }
264
+ # end
265
+ #
266
+ # If the block takes 2 parameters, both the +header+ and the +body+ will
267
+ # be passed in for processing. The header object is defined by
268
+ # AMQP::Protocol::Header.
269
+ #
270
+ # EM.run do
271
+ # exchange = MQ.direct("foo queue")
272
+ # EM.add_periodic_timer(1) do
273
+ # exchange.publish("random number #{rand(1000)}")
274
+ # end
275
+ #
276
+ # # note that #bind is never called; it is implicit because
277
+ # # the exchange and queue names match
278
+ # queue = MQ.queue('foo queue')
279
+ # queue.subscribe do |header, body|
280
+ # p header
281
+ # puts "received payload [#{body}]"
282
+ # end
283
+ # end
284
+ #
285
+ # == Options
286
+ # * :ack => true | false (default false)
287
+ # If this field is set to false the server does not expect acknowledgments
288
+ # for messages. That is, when a message is delivered to the client
289
+ # the server automatically and silently acknowledges it on behalf
290
+ # of the client. This functionality increases performance but at
291
+ # the cost of reliability. Messages can get lost if a client dies
292
+ # before it can deliver them to the application.
293
+ #
294
+ # * :nowait => true | false (default true)
295
+ # If set, the server will not respond to the method. The client should
296
+ # not wait for a reply method. If the server could not complete the
297
+ # method it will raise a channel or connection exception.
298
+ #
44
299
  def subscribe opts = {}, &blk
300
+ @consumer_tag = "#{name}-#{Kernel.rand(999_999_999_999)}"
301
+ @mq.consumers[@consumer_tag] = self
302
+
303
+ raise Error, 'already subscribed to the queue' if subscribed?
304
+
45
305
  @on_msg = blk
306
+ @on_msg_opts = opts
307
+
46
308
  @mq.callback{
47
309
  @mq.send Protocol::Basic::Consume.new({ :queue => name,
48
- :consumer_tag => name,
49
- :no_ack => true,
310
+ :consumer_tag => @consumer_tag,
311
+ :no_ack => !opts.delete(:ack),
50
312
  :nowait => true }.merge(opts))
51
313
  }
52
314
  self
53
315
  end
54
316
 
317
+ # Removes the subscription from the queue and cancels the consumer.
318
+ # New messages will not be received by the queue. This call is similar
319
+ # in result to calling #unbind.
320
+ #
321
+ # Due to the asynchronous nature of the protocol, it is possible for
322
+ # "in flight" messages to be received after this call completes.
323
+ # Those messages will be serviced by the last block used in a
324
+ # #subscribe or #pop call.
325
+ #
326
+ # Additionally, if the queue was created with _autodelete_ set to
327
+ # true, the server will delete the queue after its wait period
328
+ # has expired unless the queue is bound to an active exchange.
329
+ #
330
+ # The method accepts a block which will be executed when the
331
+ # unsubscription request is acknowledged as complete by the server.
332
+ #
333
+ # * :nowait => true | false (default true)
334
+ # If set, the server will not respond to the method. The client should
335
+ # not wait for a reply method. If the server could not complete the
336
+ # method it will raise a channel or connection exception.
337
+ #
338
+ def unsubscribe opts = {}, &blk
339
+ @on_msg = nil
340
+ @on_cancel = blk
341
+ @mq.callback{
342
+ @mq.send Protocol::Basic::Cancel.new({ :consumer_tag => @consumer_tag }.merge(opts))
343
+ }
344
+ self
345
+ end
346
+
55
347
  def publish data, opts = {}
56
348
  exchange.publish(data, opts)
57
349
  end
350
+
351
+ # Boolean check to see if the current queue has already been subscribed
352
+ # to an exchange.
353
+ #
354
+ # Attempts to #subscribe multiple times to any exchange will raise an
355
+ # Exception. Only a single block at a time can be associated with any
356
+ # one queue for processing incoming messages.
357
+ #
358
+ def subscribed?
359
+ !!@on_msg
360
+ end
58
361
 
362
+ # Passes the message to the block passed to pop or subscribe.
363
+ #
364
+ # Performs an arity check on the block's parameters. If arity == 1,
365
+ # pass only the message body. If arity != 1, pass the headers and
366
+ # the body to the block.
367
+ #
368
+ # See AMQP::Protocol::Header for the hash properties available from
369
+ # the headers parameter. See #pop or #subscribe for a code example.
370
+ #
59
371
  def receive headers, body
60
- if @on_msg
61
- @on_msg.call *(@on_msg.arity == 1 ? [body] : [headers, body])
372
+ headers = MQ::Header.new(@mq, headers)
373
+
374
+ if cb = (@on_msg || @on_pop)
375
+ cb.call *(cb.arity == 1 ? [body] : [headers, body])
376
+ end
377
+ end
378
+
379
+ def status opts = {}, &blk
380
+ @on_status = blk
381
+ @mq.callback{
382
+ @mq.send Protocol::Queue::Declare.new({ :queue => name,
383
+ :passive => true }.merge(opts))
384
+ }
385
+ self
386
+ end
387
+
388
+ def recieve_status declare_ok
389
+ if @on_status
390
+ m, c = declare_ok.message_count, declare_ok.consumer_count
391
+ @on_status.call *(@on_status.arity == 1 ? [m] : [m, c])
392
+ @on_status = nil
393
+ end
394
+ end
395
+
396
+ def cancelled
397
+ @on_cancel.call if @on_cancel
398
+ @on_cancel = @on_msg = nil
399
+ @mq.consumers.delete @consumer_tag
400
+ @consumer_tag = nil
401
+ end
402
+
403
+ def reset
404
+ @deferred_status = nil
405
+ initialize @mq, @name, @opts
406
+
407
+ binds = @bindings
408
+ @bindings = {}
409
+ binds.each{|ex,opts| bind(ex, opts) }
410
+
411
+ if blk = @on_msg
412
+ @on_msg = nil
413
+ subscribe @on_msg_opts, &blk
414
+ end
415
+
416
+ if @on_pop
417
+ pop @on_pop_opts, &@on_pop
62
418
  end
63
419
  end
64
420