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