right_amqp 0.2.0

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