right_amqp 0.2.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.
@@ -0,0 +1,304 @@
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
+ #
22
+ class Exchange
23
+ include AMQP
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
+ #
191
+ def initialize mq, type, name, opts = {}
192
+ @mq = mq
193
+ @type, @name, @opts = type, name, opts
194
+ @mq.exchanges[@name = name] ||= self
195
+ @key = opts[:key]
196
+
197
+ unless name == "amq.#{type}" or name == '' or opts[:no_declare]
198
+ @mq.callback{
199
+ @mq.send Protocol::Exchange::Declare.new({ :exchange => name,
200
+ :type => type,
201
+ :nowait => true }.merge(opts))
202
+ }
203
+ end
204
+ end
205
+ attr_reader :name, :type, :key
206
+
207
+ # This method publishes a staged file message to a specific exchange.
208
+ # The file message will be routed to queues as defined by the exchange
209
+ # configuration and distributed to any active consumers when the
210
+ # transaction, if any, is committed.
211
+ #
212
+ # exchange = MQ.direct('name', :key => 'foo.bar')
213
+ # exchange.publish("some data")
214
+ #
215
+ # The method takes several hash key options which modify the behavior or
216
+ # lifecycle of the message.
217
+ #
218
+ # * :routing_key => 'string'
219
+ #
220
+ # Specifies the routing key for the message. The routing key is
221
+ # used for routing messages depending on the exchange configuration.
222
+ #
223
+ # * :mandatory => true | false (default false)
224
+ #
225
+ # This flag tells the server how to react if the message cannot be
226
+ # routed to a queue. If this flag is set, the server will return an
227
+ # unroutable message with a Return method. If this flag is zero, the
228
+ # server silently drops the message.
229
+ #
230
+ # * :immediate => true | false (default false)
231
+ #
232
+ # This flag tells the server how to react if the message cannot be
233
+ # routed to a queue consumer immediately. If this flag is set, the
234
+ # server will return an undeliverable message with a Return method.
235
+ # If this flag is zero, the server will queue the message, but with
236
+ # no guarantee that it will ever be consumed.
237
+ #
238
+ # * :persistent
239
+ # True or False. When true, this message will remain in the queue until
240
+ # it is consumed (if the queue is durable). When false, the message is
241
+ # lost if the server restarts and the queue is recreated.
242
+ #
243
+ # For high-performance and low-latency, set :persistent => false so the
244
+ # message stays in memory and is never persisted to non-volatile (slow)
245
+ # storage.
246
+ #
247
+ def publish data, opts = {}
248
+ @mq.callback{
249
+ out = []
250
+
251
+ out << Protocol::Basic::Publish.new({ :exchange => name,
252
+ :routing_key => opts[:key] || @key }.merge(opts))
253
+
254
+ data = data.to_s
255
+
256
+ out << Protocol::Header.new(Protocol::Basic,
257
+ data.bytesize, { :content_type => 'application/octet-stream',
258
+ :delivery_mode => (opts[:persistent] ? 2 : 1),
259
+ :priority => 0 }.merge(opts))
260
+
261
+ out << Frame::Body.new(data)
262
+
263
+ @mq.send *out
264
+ }
265
+ self
266
+ end
267
+
268
+ # This method deletes an exchange. When an exchange is deleted all queue
269
+ # bindings on the exchange are cancelled.
270
+ #
271
+ # Further attempts to publish messages to a deleted exchange will raise
272
+ # an MQ::Error due to a channel close exception.
273
+ #
274
+ # exchange = MQ.direct('name', :key => 'foo.bar')
275
+ # exchange.delete
276
+ #
277
+ # == Options
278
+ # * :nowait => true | false (default true)
279
+ # If set, the server will not respond to the method. The client should
280
+ # not wait for a reply method. If the server could not complete the
281
+ # method it will raise a channel or connection exception.
282
+ #
283
+ # exchange.delete(:nowait => false)
284
+ #
285
+ # * :if_unused => true | false (default false)
286
+ # If set, the server will only delete the exchange if it has no queue
287
+ # bindings. If the exchange has queue bindings the server does not
288
+ # delete it but raises a channel exception instead (MQ:Error).
289
+ #
290
+ def delete opts = {}
291
+ @mq.callback{
292
+ @mq.send Protocol::Exchange::Delete.new({ :exchange => name,
293
+ :nowait => true }.merge(opts))
294
+ @mq.exchanges.delete name
295
+ }
296
+ nil
297
+ end
298
+
299
+ def reset
300
+ @deferred_status = nil
301
+ initialize @mq, @type, @name, @opts
302
+ end
303
+ end
304
+ end
@@ -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
@@ -0,0 +1,89 @@
1
+ class MQ
2
+ class Logger
3
+ def initialize *args, &block
4
+ opts = args.pop if args.last.is_a? Hash
5
+ opts ||= {}
6
+
7
+ printer(block) if block
8
+
9
+ @prop = opts
10
+ @tags = ([:timestamp] + args).uniq
11
+ end
12
+
13
+ attr_reader :prop
14
+ alias :base :prop
15
+
16
+ def log severity, *args
17
+ opts = args.pop if args.last.is_a? Hash and args.size != 1
18
+ opts ||= {}
19
+ opts = @prop.clone.update(opts)
20
+
21
+ data = args.shift
22
+
23
+ data = {:type => :exception,
24
+ :name => data.class.to_s.intern,
25
+ :backtrace => data.backtrace,
26
+ :message => data.message} if data.is_a? Exception
27
+
28
+ (@tags + args).each do |tag|
29
+ tag = tag.to_sym
30
+ case tag
31
+ when :timestamp
32
+ opts.update :timestamp => Time.now
33
+ when :hostname
34
+ @hostname ||= { :hostname => `hostname`.strip }
35
+ opts.update @hostname
36
+ when :process
37
+ @process_id ||= { :process_id => Process.pid,
38
+ :process_name => $0,
39
+ :process_parent_id => Process.ppid,
40
+ :thread_id => Thread.current.object_id }
41
+ opts.update :process => @process_id
42
+ else
43
+ (opts[:tags] ||= []) << tag
44
+ end
45
+ end
46
+
47
+ opts.update(:severity => severity,
48
+ :msg => data)
49
+
50
+ print(opts)
51
+ unless Logger.disabled?
52
+ MQ.fanout('logging', :durable => true).publish Marshal.dump(opts)
53
+ end
54
+
55
+ opts
56
+ end
57
+ alias :method_missing :log
58
+
59
+ def print data = nil, &block
60
+ if block
61
+ @printer = block
62
+ elsif data.is_a? Proc
63
+ @printer = data
64
+ elsif data
65
+ (pr = @printer || self.class.printer) and pr.call(data)
66
+ else
67
+ @printer
68
+ end
69
+ end
70
+ alias :printer :print
71
+
72
+ def self.printer &block
73
+ @printer = block if block
74
+ @printer
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
88
+ end
89
+ end
@@ -0,0 +1,456 @@
1
+ class MQ
2
+ class Queue
3
+ include AMQP
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
+ #
64
+ def initialize mq, name, opts = {}
65
+ @mq = mq
66
+ @opts = opts
67
+ @bindings ||= {}
68
+ @mq.queues[@name = name] ||= self
69
+ unless opts[:no_declare]
70
+ @mq.callback{
71
+ @mq.send Protocol::Queue::Declare.new({ :queue => name,
72
+ :nowait => true }.merge(opts))
73
+ }
74
+ end
75
+ end
76
+ attr_reader :name
77
+
78
+ # This method binds a queue to an exchange. Until a queue is
79
+ # bound it will not receive any messages. In a classic messaging
80
+ # model, store-and-forward queues are bound to a dest exchange
81
+ # and subscription queues are bound to a dest_wild exchange.
82
+ #
83
+ # A valid exchange name (or reference) must be passed as the first
84
+ # parameter. Both of these are valid:
85
+ # exch = MQ.direct('foo exchange')
86
+ # queue = MQ.queue('bar queue')
87
+ # queue.bind('foo.exchange') # OR
88
+ # queue.bind(exch)
89
+ #
90
+ # It is not valid to call #bind without the +exchange+ parameter.
91
+ #
92
+ # It is unnecessary to call #bind when the exchange name and queue
93
+ # name match exactly (for +direct+ and +fanout+ exchanges only).
94
+ # There is an implicit bind which will deliver the messages from
95
+ # the exchange to the queue.
96
+ #
97
+ # == Options
98
+ # * :key => 'some string'
99
+ # Specifies the routing key for the binding. The routing key is
100
+ # used for routing messages depending on the exchange configuration.
101
+ # Not all exchanges use a routing key - refer to the specific
102
+ # exchange documentation. If the routing key is empty and the queue
103
+ # name is empty, the routing key will be the current queue for the
104
+ # channel, which is the last declared queue.
105
+ #
106
+ # * :nowait => true | false (default true)
107
+ # If set, the server will not respond to the method. The client should
108
+ # not wait for a reply method. If the server could not complete the
109
+ # method it will raise a channel or connection exception.
110
+ #
111
+ def bind exchange, opts = {}
112
+ exchange = exchange.respond_to?(:name) ? exchange.name : exchange
113
+ @bindings[exchange] = opts
114
+
115
+ @mq.callback{
116
+ @mq.send Protocol::Queue::Bind.new({ :queue => name,
117
+ :exchange => exchange,
118
+ :routing_key => opts[:key],
119
+ :nowait => true }.merge(opts))
120
+ }
121
+ self
122
+ end
123
+
124
+ # Remove the binding between the queue and exchange. The queue will
125
+ # not receive any more messages until it is bound to another
126
+ # exchange.
127
+ #
128
+ # Due to the asynchronous nature of the protocol, it is possible for
129
+ # "in flight" messages to be received after this call completes.
130
+ # Those messages will be serviced by the last block used in a
131
+ # #subscribe or #pop call.
132
+ #
133
+ # * :nowait => true | false (default true)
134
+ # If set, the server will not respond to the method. The client should
135
+ # not wait for a reply method. If the server could not complete the
136
+ # method it will raise a channel or connection exception.
137
+ #
138
+ def unbind exchange, opts = {}
139
+ exchange = exchange.respond_to?(:name) ? exchange.name : exchange
140
+ @bindings.delete exchange
141
+
142
+ @mq.callback{
143
+ @mq.send Protocol::Queue::Unbind.new({ :queue => name,
144
+ :exchange => exchange,
145
+ :routing_key => opts[:key],
146
+ :nowait => true }.merge(opts))
147
+ }
148
+ self
149
+ end
150
+
151
+ # This method deletes a queue. When a queue is deleted any pending
152
+ # messages are sent to a dead-letter queue if this is defined in the
153
+ # server configuration, and all consumers on the queue are cancelled.
154
+ #
155
+ # == Options
156
+ # * :if_unused => true | false (default false)
157
+ # If set, the server will only delete the queue if it has no
158
+ # consumers. If the queue has consumers the server does does not
159
+ # delete it but raises a channel exception instead.
160
+ #
161
+ # * :if_empty => true | false (default false)
162
+ # If set, the server will only delete the queue if it has no
163
+ # messages. If the queue is not empty the server raises a channel
164
+ # exception.
165
+ #
166
+ # * :nowait => true | false (default true)
167
+ # If set, the server will not respond to the method. The client should
168
+ # not wait for a reply method. If the server could not complete the
169
+ # method it will raise a channel or connection exception.
170
+ #
171
+ def delete opts = {}
172
+ @mq.callback{
173
+ @mq.send Protocol::Queue::Delete.new({ :queue => name,
174
+ :nowait => true }.merge(opts))
175
+ }
176
+ @mq.queues.delete @name
177
+ nil
178
+ end
179
+
180
+ # Purge all messages from the queue.
181
+ #
182
+ def purge opts = {}
183
+ @mq.callback{
184
+ @mq.send Protocol::Queue::Purge.new({ :queue => name,
185
+ :nowait => true }.merge(opts))
186
+ }
187
+ nil
188
+ end
189
+
190
+ # This method provides a direct access to the messages in a queue
191
+ # using a synchronous dialogue that is designed for specific types of
192
+ # application where synchronous functionality is more important than
193
+ # performance.
194
+ #
195
+ # The provided block is passed a single message each time pop is called.
196
+ #
197
+ # EM.run do
198
+ # exchange = MQ.direct("foo queue")
199
+ # EM.add_periodic_timer(1) do
200
+ # exchange.publish("random number #{rand(1000)}")
201
+ # end
202
+ #
203
+ # # note that #bind is never called; it is implicit because
204
+ # # the exchange and queue names match
205
+ # queue = MQ.queue('foo queue')
206
+ # queue.pop { |body| puts "received payload [#{body}]" }
207
+ #
208
+ # EM.add_periodic_timer(1) { queue.pop }
209
+ # end
210
+ #
211
+ # If the block takes 2 parameters, both the +header+ and the +body+ will
212
+ # be passed in for processing. The header object is defined by
213
+ # AMQP::Protocol::Header.
214
+ #
215
+ # EM.run do
216
+ # exchange = MQ.direct("foo queue")
217
+ # EM.add_periodic_timer(1) do
218
+ # exchange.publish("random number #{rand(1000)}")
219
+ # end
220
+ #
221
+ # queue = MQ.queue('foo queue')
222
+ # queue.pop do |header, body|
223
+ # p header
224
+ # puts "received payload [#{body}]"
225
+ # end
226
+ #
227
+ # EM.add_periodic_timer(1) { queue.pop }
228
+ # end
229
+ #
230
+ # == Options
231
+ # * :ack => true | false (default false)
232
+ # If this field is set to false the server does not expect acknowledgments
233
+ # for messages. That is, when a message is delivered to the client
234
+ # the server automatically and silently acknowledges it on behalf
235
+ # of the client. This functionality increases performance but at
236
+ # the cost of reliability. Messages can get lost if a client dies
237
+ # before it can deliver them to the application.
238
+ #
239
+ # * :nowait => true | false (default true)
240
+ # If set, the server will not respond to the method. The client should
241
+ # not wait for a reply method. If the server could not complete the
242
+ # method it will raise a channel or connection exception.
243
+ #
244
+ def pop opts = {}, &blk
245
+ if blk
246
+ @on_pop = blk
247
+ @on_pop_opts = opts
248
+ end
249
+
250
+ @mq.callback{
251
+ @mq.get_queue{ |q|
252
+ q.push(self)
253
+ @mq.send Protocol::Basic::Get.new({ :queue => name,
254
+ :consumer_tag => name,
255
+ :no_ack => !opts[:ack],
256
+ :nowait => true }.merge(opts))
257
+ }
258
+ }
259
+
260
+ self
261
+ end
262
+
263
+ # Subscribes to asynchronous message delivery.
264
+ #
265
+ # The provided block is passed a single message each time the
266
+ # exchange matches a message to this queue.
267
+ #
268
+ # EM.run do
269
+ # exchange = MQ.direct("foo queue")
270
+ # EM.add_periodic_timer(1) do
271
+ # exchange.publish("random number #{rand(1000)}")
272
+ # end
273
+ #
274
+ # queue = MQ.queue('foo queue')
275
+ # queue.subscribe { |body| puts "received payload [#{body}]" }
276
+ # end
277
+ #
278
+ # If the block takes 2 parameters, both the +header+ and the +body+ will
279
+ # be passed in for processing. The header object is defined by
280
+ # AMQP::Protocol::Header.
281
+ #
282
+ # EM.run do
283
+ # exchange = MQ.direct("foo queue")
284
+ # EM.add_periodic_timer(1) do
285
+ # exchange.publish("random number #{rand(1000)}")
286
+ # end
287
+ #
288
+ # # note that #bind is never called; it is implicit because
289
+ # # the exchange and queue names match
290
+ # queue = MQ.queue('foo queue')
291
+ # queue.subscribe do |header, body|
292
+ # p header
293
+ # puts "received payload [#{body}]"
294
+ # end
295
+ # end
296
+ #
297
+ # == Options
298
+ # * :ack => true | false (default false)
299
+ # If this field is set to false the server does not expect acknowledgments
300
+ # for messages. That is, when a message is delivered to the client
301
+ # the server automatically and silently acknowledges it on behalf
302
+ # of the client. This functionality increases performance but at
303
+ # the cost of reliability. Messages can get lost if a client dies
304
+ # before it can deliver them to the application.
305
+ #
306
+ # * :nowait => true | false (default true)
307
+ # If set, the server will not respond to the method. The client should
308
+ # not wait for a reply method. If the server could not complete the
309
+ # method it will raise a channel or connection exception.
310
+ #
311
+ # * :confirm => proc (default nil)
312
+ # If set, this proc will be called when the server confirms subscription
313
+ # to the queue with a ConsumeOk message. Setting this option will
314
+ # automatically set :nowait => false. This is required for the server
315
+ # to send a confirmation.
316
+ #
317
+ def subscribe opts = {}, &blk
318
+ @consumer_tag = "#{name}-#{Kernel.rand(999_999_999_999)}"
319
+ @mq.consumers[@consumer_tag] = self
320
+
321
+ raise Error, 'already subscribed to the queue' if subscribed?
322
+
323
+ @on_msg = blk
324
+ @on_msg_opts = opts
325
+ opts[:nowait] = false if (@on_confirm_subscribe = opts[:confirm])
326
+
327
+ @mq.callback{
328
+ @mq.send Protocol::Basic::Consume.new({ :queue => name,
329
+ :consumer_tag => @consumer_tag,
330
+ :no_ack => !opts[:ack],
331
+ :nowait => true }.merge(opts))
332
+ }
333
+ self
334
+ end
335
+
336
+ # Removes the subscription from the queue and cancels the consumer.
337
+ # New messages will not be received by the queue. This call is similar
338
+ # in result to calling #unbind.
339
+ #
340
+ # Due to the asynchronous nature of the protocol, it is possible for
341
+ # "in flight" messages to be received after this call completes.
342
+ # Those messages will be serviced by the last block used in a
343
+ # #subscribe or #pop call.
344
+ #
345
+ # Additionally, if the queue was created with _autodelete_ set to
346
+ # true, the server will delete the queue after its wait period
347
+ # has expired unless the queue is bound to an active exchange.
348
+ #
349
+ # The method accepts a block which will be executed when the
350
+ # unsubscription request is acknowledged as complete by the server.
351
+ #
352
+ # * :nowait => true | false (default true)
353
+ # If set, the server will not respond to the method. The client should
354
+ # not wait for a reply method. If the server could not complete the
355
+ # method it will raise a channel or connection exception.
356
+ #
357
+ def unsubscribe opts = {}, &blk
358
+ @on_cancel = blk
359
+ @mq.callback{
360
+ @mq.send Protocol::Basic::Cancel.new({ :consumer_tag => @consumer_tag }.merge(opts))
361
+ }
362
+ self
363
+ end
364
+
365
+ def publish data, opts = {}
366
+ exchange.publish(data, opts)
367
+ end
368
+
369
+ # Boolean check to see if the current queue has already been subscribed
370
+ # to an exchange.
371
+ #
372
+ # Attempts to #subscribe multiple times to any exchange will raise an
373
+ # Exception. Only a single block at a time can be associated with any
374
+ # one queue for processing incoming messages.
375
+ #
376
+ def subscribed?
377
+ !!@on_msg
378
+ end
379
+
380
+ # Passes the message to the block passed to pop or subscribe.
381
+ #
382
+ # Performs an arity check on the block's parameters. If arity == 1,
383
+ # pass only the message body. If arity != 1, pass the headers and
384
+ # the body to the block.
385
+ #
386
+ # See AMQP::Protocol::Header for the hash properties available from
387
+ # the headers parameter. See #pop or #subscribe for a code example.
388
+ #
389
+ def receive headers, body
390
+ headers = MQ::Header.new(@mq, headers)
391
+
392
+ if cb = (@on_msg || @on_pop)
393
+ cb.call *(cb.arity == 1 ? [body] : [headers, body])
394
+ end
395
+ end
396
+
397
+ # Get the number of messages and consumers on a queue.
398
+ #
399
+ # MQ.queue('name').status{ |num_messages, num_consumers|
400
+ # puts num_messages
401
+ # }
402
+ #
403
+ def status opts = {}, &blk
404
+ @on_status = blk
405
+ @mq.callback{
406
+ @mq.send Protocol::Queue::Declare.new({ :queue => name,
407
+ :passive => true }.merge(opts))
408
+ }
409
+ self
410
+ end
411
+
412
+ def receive_status declare_ok
413
+ if @on_status
414
+ m, c = declare_ok.message_count, declare_ok.consumer_count
415
+ @on_status.call *(@on_status.arity == 1 ? [m] : [m, c])
416
+ @on_status = nil
417
+ end
418
+ end
419
+
420
+ def confirm_subscribe
421
+ @on_confirm_subscribe.call if @on_confirm_subscribe
422
+ @on_confirm_subscribe = nil
423
+ end
424
+
425
+ def cancelled
426
+ @on_cancel.call if @on_cancel
427
+ @on_cancel = @on_msg = nil
428
+ @mq.consumers.delete @consumer_tag
429
+ @consumer_tag = nil
430
+ end
431
+
432
+ def reset
433
+ @deferred_status = nil
434
+ initialize @mq, @name, @opts
435
+
436
+ binds = @bindings
437
+ @bindings = {}
438
+ binds.each{|ex,opts| bind(ex, opts) }
439
+
440
+ if blk = @on_msg
441
+ @on_msg = nil
442
+ subscribe @on_msg_opts, &blk
443
+ end
444
+
445
+ if @on_pop
446
+ pop @on_pop_opts, &@on_pop
447
+ end
448
+ end
449
+
450
+ private
451
+
452
+ def exchange
453
+ @exchange ||= Exchange.new(@mq, :direct, '', :key => name)
454
+ end
455
+ end
456
+ end