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,866 @@
1
+ #:main: README
2
+ #
3
+
4
+ $:.unshift File.expand_path(File.dirname(File.expand_path(__FILE__)))
5
+ require 'amqp'
6
+
7
+ class MQ
8
+ %w[ exchange queue rpc header ].each do |file|
9
+ require "mq/#{file}"
10
+ end
11
+
12
+ class << self
13
+ @logging = false
14
+ attr_accessor :logging
15
+ end
16
+
17
+ # Raised whenever an illegal operation is attempted.
18
+ class Error < StandardError; end
19
+ end
20
+
21
+ # The top-level class for building AMQP clients. This class contains several
22
+ # convenience methods for working with queues and exchanges. Many calls
23
+ # delegate/forward to subclasses, but this is the preferred API. The subclass
24
+ # API is subject to change while this high-level API will likely remain
25
+ # unchanged as the library evolves. All code examples will be written using
26
+ # the MQ API.
27
+ #
28
+ # Below is a somewhat complex example that demonstrates several capabilities
29
+ # of the library. The example starts a clock using a +fanout+ exchange which
30
+ # is used for 1 to many communications. Each consumer generates a queue to
31
+ # receive messages and do some operation (in this case, print the time).
32
+ # One consumer prints messages every second while the second consumer prints
33
+ # messages every 2 seconds. After 5 seconds has elapsed, the 1 second
34
+ # consumer is deleted.
35
+ #
36
+ # Of interest is the relationship of EventMachine to the process. All MQ
37
+ # operations must occur within the context of an EM.run block. We start
38
+ # EventMachine in its own thread with an empty block; all subsequent calls
39
+ # to the MQ API add their blocks to the EM.run block. This demonstrates how
40
+ # the library could be used to build up and tear down communications outside
41
+ # the context of an EventMachine block and/or integrate the library with
42
+ # other synchronous operations. See the EventMachine documentation for
43
+ # more information.
44
+ #
45
+ # require 'rubygems'
46
+ # require 'mq'
47
+ #
48
+ # thr = Thread.new { EM.run }
49
+ #
50
+ # # turns on extreme logging
51
+ # #AMQP.logging = true
52
+ #
53
+ # def log *args
54
+ # p args
55
+ # end
56
+ #
57
+ # def publisher
58
+ # clock = MQ.fanout('clock')
59
+ # EM.add_periodic_timer(1) do
60
+ # puts
61
+ #
62
+ # log :publishing, time = Time.now
63
+ # clock.publish(Marshal.dump(time))
64
+ # end
65
+ # end
66
+ #
67
+ # def one_second_consumer
68
+ # MQ.queue('every second').bind(MQ.fanout('clock')).subscribe do |time|
69
+ # log 'every second', :received, Marshal.load(time)
70
+ # end
71
+ # end
72
+ #
73
+ # def two_second_consumer
74
+ # MQ.queue('every 2 seconds').bind('clock').subscribe do |time|
75
+ # time = Marshal.load(time)
76
+ # log 'every 2 seconds', :received, time if time.sec % 2 == 0
77
+ # end
78
+ # end
79
+ #
80
+ # def delete_one_second
81
+ # EM.add_timer(5) do
82
+ # # delete the 'every second' queue
83
+ # log 'Deleting [every second] queue'
84
+ # MQ.queue('every second').delete
85
+ # end
86
+ # end
87
+ #
88
+ # publisher
89
+ # one_second_consumer
90
+ # two_second_consumer
91
+ # delete_one_second
92
+ # thr.join
93
+ #
94
+ #
95
+ # [:publishing, Tue Jan 06 22:46:14 -0600 2009]
96
+ # ["every second", :received, Tue Jan 06 22:46:14 -0600 2009]
97
+ # ["every 2 seconds", :received, Tue Jan 06 22:46:14 -0600 2009]
98
+ #
99
+ # [:publishing, Tue Jan 06 22:46:16 -0600 2009]
100
+ # ["every second", :received, Tue Jan 06 22:46:16 -0600 2009]
101
+ # ["every 2 seconds", :received, Tue Jan 06 22:46:16 -0600 2009]
102
+ #
103
+ # [:publishing, Tue Jan 06 22:46:17 -0600 2009]
104
+ # ["every second", :received, Tue Jan 06 22:46:17 -0600 2009]
105
+ #
106
+ # [:publishing, Tue Jan 06 22:46:18 -0600 2009]
107
+ # ["every second", :received, Tue Jan 06 22:46:18 -0600 2009]
108
+ # ["every 2 seconds", :received, Tue Jan 06 22:46:18 -0600 2009]
109
+ # ["Deleting [every second] queue"]
110
+ #
111
+ # [:publishing, Tue Jan 06 22:46:19 -0600 2009]
112
+ #
113
+ # [:publishing, Tue Jan 06 22:46:20 -0600 2009]
114
+ # ["every 2 seconds", :received, Tue Jan 06 22:46:20 -0600 2009]
115
+ #
116
+ class MQ
117
+ include AMQP
118
+ include EM::Deferrable
119
+ include RightSupport::Log::Mixin
120
+
121
+ # Returns a new channel. A channel is a bidirectional virtual
122
+ # connection between the client and the AMQP server. Elsewhere in the
123
+ # library the channel is referred to in parameter lists as +mq+.
124
+ #
125
+ # Optionally takes the result from calling AMQP::connect.
126
+ #
127
+ # Rarely called directly by client code. This is implicitly called
128
+ # by most instance methods. See #method_missing.
129
+ #
130
+ # EM.run do
131
+ # channel = MQ.new
132
+ # end
133
+ #
134
+ # EM.run do
135
+ # channel = MQ.new AMQP::connect
136
+ # end
137
+ #
138
+ def initialize connection = nil
139
+ raise 'MQ can only be used from within EM.run{}' unless EM.reactor_running?
140
+
141
+ @connection = connection || AMQP.start
142
+
143
+ conn.callback{ |c|
144
+ @channel = c.add_channel(self)
145
+ send Protocol::Channel::Open.new
146
+ }
147
+ end
148
+ attr_reader :channel, :connection
149
+
150
+ # May raise a MQ::Error exception when the frame payload contains a
151
+ # Protocol::Channel::Close object.
152
+ #
153
+ # This usually occurs when a client attempts to perform an illegal
154
+ # operation. A short, and incomplete, list of potential illegal operations
155
+ # follows:
156
+ # * publish a message to a deleted exchange (NOT_FOUND)
157
+ # * declare an exchange using the reserved 'amq.' naming structure (ACCESS_REFUSED)
158
+ #
159
+ def process_frame frame
160
+ log :received, frame
161
+
162
+ case frame
163
+ when Frame::Header
164
+ @header = frame.payload
165
+ @body = ''
166
+
167
+ when Frame::Body
168
+ @body << frame.payload
169
+ if @body.length >= @header.size
170
+ if @method.is_a? Protocol::Basic::Return
171
+ @on_return_message.call @method, @body if @on_return_message
172
+ else
173
+ @header.properties.update(@method.arguments)
174
+ @consumer.receive @header, @body if @consumer
175
+ end
176
+ @body = @header = @consumer = @method = nil
177
+ end
178
+
179
+ when Frame::Method
180
+ case method = frame.payload
181
+ when Protocol::Channel::OpenOk
182
+ send Protocol::Access::Request.new(:realm => '/data',
183
+ :read => true,
184
+ :write => true,
185
+ :active => true,
186
+ :passive => true)
187
+
188
+ when Protocol::Access::RequestOk
189
+ @ticket = method.ticket
190
+ callback{
191
+ send Protocol::Channel::Close.new(:reply_code => 200,
192
+ :reply_text => 'bye',
193
+ :method_id => 0,
194
+ :class_id => 0)
195
+ } if @closing
196
+ succeed
197
+
198
+ when Protocol::Basic::CancelOk
199
+ if @consumer = consumers[ method.consumer_tag ]
200
+ @consumer.cancelled
201
+ else
202
+ MQ.error "Basic.CancelOk for invalid consumer tag: #{method.consumer_tag}"
203
+ end
204
+
205
+ when Protocol::Queue::DeclareOk
206
+ queues[ method.queue ].receive_status method
207
+
208
+ when Protocol::Basic::Deliver, Protocol::Basic::GetOk
209
+ @method = method
210
+ @header = nil
211
+ @body = ''
212
+
213
+ if method.is_a? Protocol::Basic::GetOk
214
+ @consumer = get_queue{|q| q.shift }
215
+ MQ.error "No pending Basic.GetOk requests" unless @consumer
216
+ else
217
+ @consumer = consumers[ method.consumer_tag ]
218
+ MQ.error "Basic.Deliver for invalid consumer tag: #{method.consumer_tag}" unless @consumer
219
+ end
220
+
221
+ when Protocol::Basic::GetEmpty
222
+ if @consumer = get_queue{|q| q.shift }
223
+ @consumer.receive nil, nil
224
+ else
225
+ MQ.error "Basic.GetEmpty for invalid consumer"
226
+ end
227
+
228
+ when Protocol::Basic::Return
229
+ @method = method
230
+ @header = nil
231
+ @body = ''
232
+
233
+ when Protocol::Channel::Close
234
+ raise Error, "#{method.reply_text} in #{Protocol.classes[method.class_id].methods[method.method_id]} on #{@channel}"
235
+
236
+ when Protocol::Channel::CloseOk
237
+ @closing = false
238
+ conn.callback{ |c|
239
+ c.channels.delete @channel
240
+ c.close if c.channels.empty?
241
+ }
242
+
243
+ when Protocol::Basic::ConsumeOk
244
+ if @consumer = consumers[ method.consumer_tag ]
245
+ @consumer.confirm_subscribe
246
+ else
247
+ MQ.error "Basic.ConsumeOk for invalid consumer tag: #{method.consumer_tag}"
248
+ end
249
+ end
250
+ end
251
+ end
252
+
253
+ # Provide callback to be activated when a message is returned
254
+ def return_message &blk
255
+ @on_return_message = blk
256
+ end
257
+
258
+ def send *args
259
+ conn.callback{ |c|
260
+ (@_send_mutex ||= Mutex.new).synchronize do
261
+ if @channel.nil? || c.channels.empty?
262
+ logger.error("[amqp] Failed to send data, no open channels, closing connection")
263
+ conn.failed
264
+ else
265
+ args.each do |data|
266
+ data.ticket = @ticket if @ticket and data.respond_to? :ticket=
267
+ log :sending, data
268
+ c.send data, :channel => @channel
269
+ end
270
+ end
271
+ end
272
+ }
273
+ end
274
+
275
+ # Defines, intializes and returns an Exchange to act as an ingress
276
+ # point for all published messages.
277
+ #
278
+ # == Direct
279
+ # A direct exchange is useful for 1:1 communication between a publisher and
280
+ # subscriber. Messages are routed to the queue with a binding that shares
281
+ # the same name as the exchange. Alternately, the messages are routed to
282
+ # the bound queue that shares the same name as the routing key used for
283
+ # defining the exchange. This exchange type does not honor the +:key+ option
284
+ # when defining a new instance with a name. It _will_ honor the +:key+ option
285
+ # if the exchange name is the empty string.
286
+ # Allocating this exchange without a name _or_ with the empty string
287
+ # will use the internal 'amq.direct' exchange.
288
+ #
289
+ # Any published message, regardless of its persistence setting, is thrown
290
+ # away by the exchange when there are no queues bound to it.
291
+ #
292
+ # # exchange is named 'foo'
293
+ # exchange = MQ.direct('foo')
294
+ #
295
+ # # or, the exchange can use the default name (amq.direct) and perform
296
+ # # routing comparisons using the :key
297
+ # exchange = MQ.direct("", :key => 'foo')
298
+ # exchange.publish('some data') # will be delivered to queue bound to 'foo'
299
+ #
300
+ # queue = MQ.queue('foo')
301
+ # # can receive data since the queue name and the exchange key match exactly
302
+ # queue.pop { |data| puts "received data [#{data}]" }
303
+ #
304
+ # == Options
305
+ # * :passive => true | false (default false)
306
+ # If set, the server will not create the exchange if it does not
307
+ # already exist. The client can use this to check whether an exchange
308
+ # exists without modifying the server state.
309
+ #
310
+ # * :durable => true | false (default false)
311
+ # If set when creating a new exchange, the exchange will be marked as
312
+ # durable. Durable exchanges remain active when a server restarts.
313
+ # Non-durable exchanges (transient exchanges) are purged if/when a
314
+ # server restarts.
315
+ #
316
+ # A transient exchange (the default) is stored in memory-only. The
317
+ # exchange and all bindings will be lost on a server restart.
318
+ # It makes no sense to publish a persistent message to a transient
319
+ # exchange.
320
+ #
321
+ # Durable exchanges and their bindings are recreated upon a server
322
+ # restart. Any published messages not routed to a bound queue are lost.
323
+ #
324
+ # * :auto_delete => true | false (default false)
325
+ # If set, the exchange is deleted when all queues have finished
326
+ # using it. The server waits for a short period of time before
327
+ # determining the exchange is unused to give time to the client code
328
+ # to bind a queue to it.
329
+ #
330
+ # If the exchange has been previously declared, this option is ignored
331
+ # on subsequent declarations.
332
+ #
333
+ # * :internal => true | false (default false)
334
+ # If set, the exchange may not be used directly by publishers, but
335
+ # only when bound to other exchanges. Internal exchanges are used to
336
+ # construct wiring that is not visible to applications.
337
+ #
338
+ # * :nowait => true | false (default true)
339
+ # If set, the server will not respond to the method. The client should
340
+ # not wait for a reply method. If the server could not complete the
341
+ # method it will raise a channel or connection exception.
342
+ #
343
+ # == Exceptions
344
+ # Doing any of these activities are illegal and will raise MQ:Error.
345
+ # * redeclare an already-declared exchange to a different type
346
+ # * :passive => true and the exchange does not exist (NOT_FOUND)
347
+ #
348
+ def direct name = 'amq.direct', opts = {}
349
+ exchanges[name] ||= Exchange.new(self, :direct, name, opts)
350
+ end
351
+
352
+ # Defines, intializes and returns an Exchange to act as an ingress
353
+ # point for all published messages.
354
+ #
355
+ # == Fanout
356
+ # A fanout exchange is useful for 1:N communication where one publisher
357
+ # feeds multiple subscribers. Like direct exchanges, messages published
358
+ # to a fanout exchange are delivered to queues whose name matches the
359
+ # exchange name (or are bound to that exchange name). Each queue gets
360
+ # its own copy of the message.
361
+ #
362
+ # Any published message, regardless of its persistence setting, is thrown
363
+ # away by the exchange when there are no queues bound to it.
364
+ #
365
+ # Like the direct exchange type, this exchange type does not honor the
366
+ # +:key+ option when defining a new instance with a name. It _will_ honor
367
+ # the +:key+ option if the exchange name is the empty string.
368
+ # Allocating this exchange without a name _or_ with the empty string
369
+ # will use the internal 'amq.fanout' exchange.
370
+ #
371
+ # EM.run do
372
+ # clock = MQ.fanout('clock')
373
+ # EM.add_periodic_timer(1) do
374
+ # puts "\npublishing #{time = Time.now}"
375
+ # clock.publish(Marshal.dump(time))
376
+ # end
377
+ #
378
+ # amq = MQ.queue('every second')
379
+ # amq.bind(MQ.fanout('clock')).subscribe do |time|
380
+ # puts "every second received #{Marshal.load(time)}"
381
+ # end
382
+ #
383
+ # # note the string passed to #bind
384
+ # MQ.queue('every 5 seconds').bind('clock').subscribe do |time|
385
+ # time = Marshal.load(time)
386
+ # puts "every 5 seconds received #{time}" if time.strftime('%S').to_i%5 == 0
387
+ # end
388
+ # end
389
+ #
390
+ # == Options
391
+ # * :passive => true | false (default false)
392
+ # If set, the server will not create the exchange if it does not
393
+ # already exist. The client can use this to check whether an exchange
394
+ # exists without modifying the server state.
395
+ #
396
+ # * :durable => true | false (default false)
397
+ # If set when creating a new exchange, the exchange will be marked as
398
+ # durable. Durable exchanges remain active when a server restarts.
399
+ # Non-durable exchanges (transient exchanges) are purged if/when a
400
+ # server restarts.
401
+ #
402
+ # A transient exchange (the default) is stored in memory-only. The
403
+ # exchange and all bindings will be lost on a server restart.
404
+ # It makes no sense to publish a persistent message to a transient
405
+ # exchange.
406
+ #
407
+ # Durable exchanges and their bindings are recreated upon a server
408
+ # restart. Any published messages not routed to a bound queue are lost.
409
+ #
410
+ # * :auto_delete => true | false (default false)
411
+ # If set, the exchange is deleted when all queues have finished
412
+ # using it. The server waits for a short period of time before
413
+ # determining the exchange is unused to give time to the client code
414
+ # to bind a queue to it.
415
+ #
416
+ # If the exchange has been previously declared, this option is ignored
417
+ # on subsequent declarations.
418
+ #
419
+ # * :internal => true | false (default false)
420
+ # If set, the exchange may not be used directly by publishers, but
421
+ # only when bound to other exchanges. Internal exchanges are used to
422
+ # construct wiring that is not visible to applications.
423
+ #
424
+ # * :nowait => true | false (default true)
425
+ # If set, the server will not respond to the method. The client should
426
+ # not wait for a reply method. If the server could not complete the
427
+ # method it will raise a channel or connection exception.
428
+ #
429
+ # == Exceptions
430
+ # Doing any of these activities are illegal and will raise MQ:Error.
431
+ # * redeclare an already-declared exchange to a different type
432
+ # * :passive => true and the exchange does not exist (NOT_FOUND)
433
+ #
434
+ def fanout name = 'amq.fanout', opts = {}
435
+ exchanges[name] ||= Exchange.new(self, :fanout, name, opts)
436
+ end
437
+
438
+ # Defines, intializes and returns an Exchange to act as an ingress
439
+ # point for all published messages.
440
+ #
441
+ # == Topic
442
+ # A topic exchange allows for messages to be published to an exchange
443
+ # tagged with a specific routing key. The Exchange uses the routing key
444
+ # to determine which queues to deliver the message. Wildcard matching
445
+ # is allowed. The topic must be declared using dot notation to separate
446
+ # each subtopic.
447
+ #
448
+ # This is the only exchange type to honor the +key+ hash key for all
449
+ # cases.
450
+ #
451
+ # Any published message, regardless of its persistence setting, is thrown
452
+ # away by the exchange when there are no queues bound to it.
453
+ #
454
+ # As part of the AMQP standard, each server _should_ predeclare a topic
455
+ # exchange called 'amq.topic' (this is not required by the standard).
456
+ # Allocating this exchange without a name _or_ with the empty string
457
+ # will use the internal 'amq.topic' exchange.
458
+ #
459
+ # The classic example is delivering market data. When publishing market
460
+ # data for stocks, we may subdivide the stream based on 2
461
+ # characteristics: nation code and trading symbol. The topic tree for
462
+ # Apple Computer would look like:
463
+ # 'stock.us.aapl'
464
+ # For a foreign stock, it may look like:
465
+ # 'stock.de.dax'
466
+ #
467
+ # When publishing data to the exchange, bound queues subscribing to the
468
+ # exchange indicate which data interests them by passing a routing key
469
+ # for matching against the published routing key.
470
+ #
471
+ # EM.run do
472
+ # exch = MQ.topic("stocks")
473
+ # keys = ['stock.us.aapl', 'stock.de.dax']
474
+ #
475
+ # EM.add_periodic_timer(1) do # every second
476
+ # puts
477
+ # exch.publish(10+rand(10), :routing_key => keys[rand(2)])
478
+ # end
479
+ #
480
+ # # match against one dot-separated item
481
+ # MQ.queue('us stocks').bind(exch, :key => 'stock.us.*').subscribe do |price|
482
+ # puts "us stock price [#{price}]"
483
+ # end
484
+ #
485
+ # # match against multiple dot-separated items
486
+ # MQ.queue('all stocks').bind(exch, :key => 'stock.#').subscribe do |price|
487
+ # puts "all stocks: price [#{price}]"
488
+ # end
489
+ #
490
+ # # require exact match
491
+ # MQ.queue('only dax').bind(exch, :key => 'stock.de.dax').subscribe do |price|
492
+ # puts "dax price [#{price}]"
493
+ # end
494
+ # end
495
+ #
496
+ # For matching, the '*' (asterisk) wildcard matches against one
497
+ # dot-separated item only. The '#' wildcard (hash or pound symbol)
498
+ # matches against 0 or more dot-separated items. If none of these
499
+ # symbols are used, the exchange performs a comparison looking for an
500
+ # exact match.
501
+ #
502
+ # == Options
503
+ # * :passive => true | false (default false)
504
+ # If set, the server will not create the exchange if it does not
505
+ # already exist. The client can use this to check whether an exchange
506
+ # exists without modifying the server state.
507
+ #
508
+ # * :durable => true | false (default false)
509
+ # If set when creating a new exchange, the exchange will be marked as
510
+ # durable. Durable exchanges remain active when a server restarts.
511
+ # Non-durable exchanges (transient exchanges) are purged if/when a
512
+ # server restarts.
513
+ #
514
+ # A transient exchange (the default) is stored in memory-only. The
515
+ # exchange and all bindings will be lost on a server restart.
516
+ # It makes no sense to publish a persistent message to a transient
517
+ # exchange.
518
+ #
519
+ # Durable exchanges and their bindings are recreated upon a server
520
+ # restart. Any published messages not routed to a bound queue are lost.
521
+ #
522
+ # * :auto_delete => true | false (default false)
523
+ # If set, the exchange is deleted when all queues have finished
524
+ # using it. The server waits for a short period of time before
525
+ # determining the exchange is unused to give time to the client code
526
+ # to bind a queue to it.
527
+ #
528
+ # If the exchange has been previously declared, this option is ignored
529
+ # on subsequent declarations.
530
+ #
531
+ # * :internal => true | false (default false)
532
+ # If set, the exchange may not be used directly by publishers, but
533
+ # only when bound to other exchanges. Internal exchanges are used to
534
+ # construct wiring that is not visible to applications.
535
+ #
536
+ # * :nowait => true | false (default true)
537
+ # If set, the server will not respond to the method. The client should
538
+ # not wait for a reply method. If the server could not complete the
539
+ # method it will raise a channel or connection exception.
540
+ #
541
+ # == Exceptions
542
+ # Doing any of these activities are illegal and will raise MQ:Error.
543
+ # * redeclare an already-declared exchange to a different type
544
+ # * :passive => true and the exchange does not exist (NOT_FOUND)
545
+ #
546
+ def topic name = 'amq.topic', opts = {}
547
+ exchanges[name] ||= Exchange.new(self, :topic, name, opts)
548
+ end
549
+
550
+ # Defines, intializes and returns an Exchange to act as an ingress
551
+ # point for all published messages.
552
+ #
553
+ # == Headers
554
+ # A headers exchange allows for messages to be published to an exchange
555
+ #
556
+ # Any published message, regardless of its persistence setting, is thrown
557
+ # away by the exchange when there are no queues bound to it.
558
+ #
559
+ # As part of the AMQP standard, each server _should_ predeclare a headers
560
+ # exchange called 'amq.match' (this is not required by the standard).
561
+ # Allocating this exchange without a name _or_ with the empty string
562
+ # will use the internal 'amq.match' exchange.
563
+ #
564
+ # TODO: The classic example is ...
565
+ #
566
+ # When publishing data to the exchange, bound queues subscribing to the
567
+ # exchange indicate which data interests them by passing arguments
568
+ # for matching against the headers in published messages. The
569
+ # form of the matching can be controlled by the 'x-match' argument, which
570
+ # may be 'any' or 'all'. If unspecified (in RabbitMQ at least), it defaults
571
+ # to "all".
572
+ #
573
+ # A value of 'all' for 'x-match' implies that all values must match (i.e.
574
+ # it does an AND of the headers ), while a value of 'any' implies that
575
+ # at least one should match (ie. it does an OR).
576
+ #
577
+ # TODO: document behavior when either the binding or the message is missing
578
+ # a header present in the other
579
+ #
580
+ # TODO: insert example
581
+ #
582
+ # == Options
583
+ # * :passive => true | false (default false)
584
+ # If set, the server will not create the exchange if it does not
585
+ # already exist. The client can use this to check whether an exchange
586
+ # exists without modifying the server state.
587
+ #
588
+ # * :durable => true | false (default false)
589
+ # If set when creating a new exchange, the exchange will be marked as
590
+ # durable. Durable exchanges remain active when a server restarts.
591
+ # Non-durable exchanges (transient exchanges) are purged if/when a
592
+ # server restarts.
593
+ #
594
+ # A transient exchange (the default) is stored in memory-only. The
595
+ # exchange and all bindings will be lost on a server restart.
596
+ # It makes no sense to publish a persistent message to a transient
597
+ # exchange.
598
+ #
599
+ # Durable exchanges and their bindings are recreated upon a server
600
+ # restart. Any published messages not routed to a bound queue are lost.
601
+ #
602
+ # * :auto_delete => true | false (default false)
603
+ # If set, the exchange is deleted when all queues have finished
604
+ # using it. The server waits for a short period of time before
605
+ # determining the exchange is unused to give time to the client code
606
+ # to bind a queue to it.
607
+ #
608
+ # If the exchange has been previously declared, this option is ignored
609
+ # on subsequent declarations.
610
+ #
611
+ # * :internal => true | false (default false)
612
+ # If set, the exchange may not be used directly by publishers, but
613
+ # only when bound to other exchanges. Internal exchanges are used to
614
+ # construct wiring that is not visible to applications.
615
+ #
616
+ # * :nowait => true | false (default true)
617
+ # If set, the server will not respond to the method. The client should
618
+ # not wait for a reply method. If the server could not complete the
619
+ # method it will raise a channel or connection exception.
620
+ #
621
+ # == Exceptions
622
+ # Doing any of these activities are illegal and will raise MQ:Error.
623
+ # * redeclare an already-declared exchange to a different type
624
+ # * :passive => true and the exchange does not exist (NOT_FOUND)
625
+ # * using a value other than "any" or "all" for "x-match"
626
+ def headers name = 'amq.match', opts = {}
627
+ exchanges[name] ||= Exchange.new(self, :headers, name, opts)
628
+ end
629
+
630
+ # Queues store and forward messages. Queues can be configured in the server
631
+ # or created at runtime. Queues must be attached to at least one exchange
632
+ # in order to receive messages from publishers.
633
+ #
634
+ # Like an Exchange, queue names starting with 'amq.' are reserved for
635
+ # internal use. Attempts to create queue names in violation of this
636
+ # reservation will raise MQ:Error (ACCESS_REFUSED).
637
+ #
638
+ # It is not supported to create a queue without a name; some string
639
+ # (even the empty string) must be passed in the +name+ parameter.
640
+ #
641
+ # == Options
642
+ # * :passive => true | false (default false)
643
+ # If set, the server will not create the exchange if it does not
644
+ # already exist. The client can use this to check whether an exchange
645
+ # exists without modifying the server state.
646
+ #
647
+ # * :durable => true | false (default false)
648
+ # If set when creating a new queue, the queue will be marked as
649
+ # durable. Durable queues remain active when a server restarts.
650
+ # Non-durable queues (transient queues) are purged if/when a
651
+ # server restarts. Note that durable queues do not necessarily
652
+ # hold persistent messages, although it does not make sense to
653
+ # send persistent messages to a transient queue (though it is
654
+ # allowed).
655
+ #
656
+ # Again, note the durability property on a queue has no influence on
657
+ # the persistence of published messages. A durable queue containing
658
+ # transient messages will flush those messages on a restart.
659
+ #
660
+ # If the queue has already been declared, any redeclaration will
661
+ # ignore this setting. A queue may only be declared durable the
662
+ # first time when it is created.
663
+ #
664
+ # * :exclusive => true | false (default false)
665
+ # Exclusive queues may only be consumed from by the current connection.
666
+ # Setting the 'exclusive' flag always implies 'auto-delete'. Only a
667
+ # single consumer is allowed to remove messages from this queue.
668
+ #
669
+ # The default is a shared queue. Multiple clients may consume messages
670
+ # from this queue.
671
+ #
672
+ # Attempting to redeclare an already-declared queue as :exclusive => true
673
+ # will raise MQ:Error.
674
+ #
675
+ # * :auto_delete = true | false (default false)
676
+ # If set, the queue is deleted when all consumers have finished
677
+ # using it. Last consumer can be cancelled either explicitly or because
678
+ # its channel is closed. If there was no consumer ever on the queue, it
679
+ # won't be deleted.
680
+ #
681
+ # The server waits for a short period of time before
682
+ # determining the queue is unused to give time to the client code
683
+ # to bind an exchange to it.
684
+ #
685
+ # If the queue has been previously declared, this option is ignored
686
+ # on subsequent declarations.
687
+ #
688
+ # Any remaining messages in the queue will be purged when the queue
689
+ # is deleted regardless of the message's persistence setting.
690
+ #
691
+ # * :nowait => true | false (default true)
692
+ # If set, the server will not respond to the method. The client should
693
+ # not wait for a reply method. If the server could not complete the
694
+ # method it will raise a channel or connection exception.
695
+ #
696
+ def queue name, opts = {}
697
+ queues[name] ||= Queue.new(self, name, opts)
698
+ end
699
+
700
+ # Takes a channel, queue and optional object.
701
+ #
702
+ # The optional object may be a class name, module name or object
703
+ # instance. When given a class or module name, the object is instantiated
704
+ # during this setup. The passed queue is automatically subscribed to so
705
+ # it passes all messages (and their arguments) to the object.
706
+ #
707
+ # Marshalling and unmarshalling the objects is handled internally. This
708
+ # marshalling is subject to the same restrictions as defined in the
709
+ # Marshal[http://ruby-doc.org/core/classes/Marshal.html] standard
710
+ # library. See that documentation for further reference.
711
+ #
712
+ # When the optional object is not passed, the returned rpc reference is
713
+ # used to send messages and arguments to the queue. See #method_missing
714
+ # which does all of the heavy lifting with the proxy. Some client
715
+ # elsewhere must call this method *with* the optional block so that
716
+ # there is a valid destination. Failure to do so will just enqueue
717
+ # marshalled messages that are never consumed.
718
+ #
719
+ # EM.run do
720
+ # server = MQ.rpc('hash table node', Hash)
721
+ #
722
+ # client = MQ.rpc('hash table node')
723
+ # client[:now] = Time.now
724
+ # client[:one] = 1
725
+ #
726
+ # client.values do |res|
727
+ # p 'client', :values => res
728
+ # end
729
+ #
730
+ # client.keys do |res|
731
+ # p 'client', :keys => res
732
+ # EM.stop_event_loop
733
+ # end
734
+ # end
735
+ #
736
+ def rpc name, obj = nil
737
+ rpcs[name] ||= RPC.new(self, name, obj)
738
+ end
739
+
740
+ def close
741
+ if @deferred_status == :succeeded
742
+ send Protocol::Channel::Close.new(:reply_code => 200,
743
+ :reply_text => 'bye',
744
+ :method_id => 0,
745
+ :class_id => 0)
746
+ else
747
+ @closing = true
748
+ end
749
+ end
750
+
751
+ # Define a message and callback block to be executed on all
752
+ # errors.
753
+ def self.error msg = nil, &blk
754
+ if blk
755
+ @error_callback = blk
756
+ else
757
+ @error_callback.call(msg) if @error_callback and msg
758
+ end
759
+ end
760
+
761
+ def prefetch(size)
762
+ @prefetch_size = size
763
+ send Protocol::Basic::Qos.new(:prefetch_size => 0, :prefetch_count => size, :global => false)
764
+ self
765
+ end
766
+
767
+ # Asks the broker to redeliver all unacknowledged messages on this
768
+ # channel.
769
+ #
770
+ # * requeue (default false)
771
+ # If this parameter is false, the message will be redelivered to the original recipient.
772
+ # If this flag is true, the server will attempt to requeue the message, potentially then
773
+ # delivering it to an alternative subscriber.
774
+ #
775
+ def recover requeue = false
776
+ send Protocol::Basic::Recover.new(:requeue => requeue)
777
+ self
778
+ end
779
+
780
+ # Returns a hash of all the exchange proxy objects.
781
+ #
782
+ # Not typically called by client code.
783
+ def exchanges
784
+ @exchanges ||= {}
785
+ end
786
+
787
+ # Returns a hash of all the queue proxy objects.
788
+ #
789
+ # Not typically called by client code.
790
+ def queues
791
+ @queues ||= {}
792
+ end
793
+
794
+ def get_queue
795
+ if block_given?
796
+ (@get_queue_mutex ||= Mutex.new).synchronize{
797
+ yield( @get_queue ||= [] )
798
+ }
799
+ end
800
+ end
801
+
802
+ # Returns a hash of all rpc proxy objects.
803
+ #
804
+ # Not typically called by client code.
805
+ def rpcs
806
+ @rcps ||= {}
807
+ end
808
+
809
+ # Queue objects keyed on their consumer tags.
810
+ #
811
+ # Not typically called by client code.
812
+ def consumers
813
+ @consumers ||= {}
814
+ end
815
+
816
+ def reset
817
+ @deferred_status = nil
818
+ @channel = nil
819
+ initialize @connection
820
+
821
+ @consumers = {}
822
+
823
+ exs = @exchanges
824
+ @exchanges = {}
825
+ exs.each{ |_,e| e.reset } if exs
826
+
827
+ qus = @queues
828
+ @queues = {}
829
+ qus.each{ |_,q| q.reset } if qus
830
+
831
+ prefetch(@prefetch_size) if @prefetch_size
832
+ end
833
+
834
+ private
835
+
836
+ def log *args
837
+ return unless MQ.logging
838
+ pp args
839
+ puts
840
+ end
841
+
842
+ attr_reader :connection
843
+ alias :conn :connection
844
+ end
845
+
846
+ #-- convenience wrapper (read: HACK) for thread-local MQ object
847
+
848
+ class MQ
849
+ def MQ.default
850
+ #-- XXX clear this when connection is closed
851
+ Thread.current[:mq] ||= MQ.new
852
+ end
853
+
854
+ # Allows for calls to all MQ instance methods. This implicitly calls
855
+ # MQ.new so that a new channel is allocated for subsequent operations.
856
+ def MQ.method_missing meth, *args, &blk
857
+ MQ.default.__send__(meth, *args, &blk)
858
+ end
859
+ end
860
+
861
+ class MQ
862
+ # unique identifier
863
+ def MQ.id
864
+ Thread.current[:mq_id] ||= "#{`hostname`.strip}-#{Process.pid}-#{Thread.current.object_id}"
865
+ end
866
+ end