amqp 0.7.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. data/.gitignore +3 -2
  2. data/CHANGELOG +25 -0
  3. data/Gemfile +4 -2
  4. data/README.md +2 -0
  5. data/{amqp.todo → TODO} +1 -3
  6. data/amqp.gemspec +3 -3
  7. data/bin/irb +2 -2
  8. data/bin/jenkins.sh +25 -0
  9. data/bin/set_test_suite_realms_up.sh +21 -0
  10. data/doc/EXAMPLE_01_PINGPONG +1 -1
  11. data/doc/EXAMPLE_02_CLOCK +1 -1
  12. data/doc/EXAMPLE_03_STOCKS +1 -1
  13. data/doc/EXAMPLE_04_MULTICLOCK +1 -1
  14. data/doc/EXAMPLE_05_ACK +1 -1
  15. data/doc/EXAMPLE_05_POP +1 -1
  16. data/doc/EXAMPLE_06_HASHTABLE +1 -1
  17. data/examples/{mq/ack.rb → ack.rb} +6 -6
  18. data/examples/{mq/automatic_binding_for_default_direct_exchange.rb → automatic_binding_for_default_direct_exchange.rb} +4 -4
  19. data/examples/{mq/callbacks.rb → callbacks.rb} +2 -2
  20. data/examples/{mq/clock.rb → clock.rb} +5 -5
  21. data/examples/{mq/hashtable.rb → hashtable.rb} +4 -4
  22. data/examples/{mq/internal.rb → internal.rb} +5 -5
  23. data/examples/{mq/logger.rb → logger.rb} +5 -5
  24. data/examples/{mq/multiclock.rb → multiclock.rb} +4 -4
  25. data/examples/{mq/pingpong.rb → pingpong.rb} +5 -5
  26. data/examples/{mq/pop.rb → pop.rb} +3 -3
  27. data/examples/{mq/primes-simple.rb → primes-simple.rb} +0 -0
  28. data/examples/{mq/primes.rb → primes.rb} +6 -6
  29. data/examples/{amqp/simple.rb → simple.rb} +1 -1
  30. data/examples/{mq/stocks.rb → stocks.rb} +5 -5
  31. data/lib/amqp.rb +8 -112
  32. data/lib/amqp/basic_client.rb +58 -0
  33. data/lib/amqp/channel.rb +937 -0
  34. data/lib/amqp/client.rb +72 -79
  35. data/lib/{mq → amqp}/collection.rb +12 -2
  36. data/lib/amqp/connection.rb +115 -0
  37. data/lib/amqp/exceptions.rb +18 -0
  38. data/lib/{mq → amqp}/exchange.rb +32 -34
  39. data/lib/{ext → amqp/ext}/em.rb +1 -1
  40. data/lib/{ext → amqp/ext}/emfork.rb +0 -0
  41. data/lib/amqp/frame.rb +3 -3
  42. data/lib/{mq → amqp}/header.rb +5 -11
  43. data/lib/{mq → amqp}/logger.rb +2 -2
  44. data/lib/amqp/protocol.rb +2 -2
  45. data/lib/{mq → amqp}/queue.rb +20 -17
  46. data/lib/{mq → amqp}/rpc.rb +20 -8
  47. data/lib/amqp/server.rb +1 -1
  48. data/lib/amqp/version.rb +1 -1
  49. data/lib/mq.rb +20 -964
  50. data/protocol/codegen.rb +1 -1
  51. data/research/api.rb +3 -3
  52. data/research/primes-forked.rb +5 -5
  53. data/research/primes-processes.rb +5 -5
  54. data/research/primes-threaded.rb +5 -5
  55. data/spec/integration/authentication_spec.rb +114 -0
  56. data/spec/integration/automatic_binding_for_default_direct_exchange_spec.rb +13 -12
  57. data/spec/{unit/mq → integration}/channel_close_spec.rb +2 -2
  58. data/spec/{unit/mq → integration}/exchange_declaration_spec.rb +26 -14
  59. data/spec/{unit/mq → integration}/queue_declaration_spec.rb +4 -4
  60. data/spec/integration/queue_exclusivity_spec.rb +95 -0
  61. data/spec/integration/reply_queue_communication_spec.rb +63 -0
  62. data/spec/integration/store_and_forward_spec.rb +121 -0
  63. data/spec/integration/topic_subscription_spec.rb +193 -0
  64. data/spec/integration/workload_distribution_spec.rb +245 -0
  65. data/spec/spec_helper.rb +16 -32
  66. data/spec/unit/{mq/mq_basic_spec.rb → amqp/basic_spec.rb} +4 -4
  67. data/spec/unit/{mq → amqp}/collection_spec.rb +22 -7
  68. data/spec/unit/amqp/connection_spec.rb +116 -0
  69. data/spec/unit/amqp/frame_spec.rb +18 -18
  70. data/spec/unit/amqp/protocol_spec.rb +9 -11
  71. metadata +54 -49
  72. data/lib/ext/blankslate.rb +0 -9
  73. data/spec/mq_helper.rb +0 -70
  74. data/spec/unit/amqp/client_spec.rb +0 -472
  75. data/spec/unit/amqp/misc_spec.rb +0 -123
  76. data/spec/unit/mq/misc_spec.rb +0 -228
  77. data/spec/unit/mq/queue_spec.rb +0 -71
@@ -7,4 +7,4 @@ rescue LoadError
7
7
  require 'eventmachine'
8
8
  end
9
9
 
10
- require 'ext/emfork'
10
+ require 'amqp/ext/emfork'
File without changes
@@ -1,8 +1,8 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.expand_path('../spec', __FILE__)
4
- require File.expand_path('../buffer', __FILE__)
5
- require File.expand_path('../protocol', __FILE__)
3
+ require "amqp/spec"
4
+ require "amqp/buffer"
5
+ require "amqp/protocol"
6
6
 
7
7
  module AMQP
8
8
  class Frame #:nodoc: all
@@ -1,9 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
- class MQ
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 (XXX currently unimplemented in rabbitmq)
17
+ # Reject this message.
20
18
  # * :requeue => true | false (default false)
21
19
  def reject(opts = {})
22
- if @mq.broker.server_properties[:product] == "RabbitMQ"
23
- raise NotImplementedError.new("RabbitMQ doesn't implement the Basic.Reject method\nSee http://lists.rabbitmq.com/pipermail/rabbitmq-discuss/2009-February/002853.html")
24
- else
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)
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- class MQ
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
- MQ.fanout('logging', :durable => true).publish Marshal.dump(opts)
54
+ AMQP::Channel.fanout('logging', :durable => true).publish Marshal.dump(opts)
55
55
  end
56
56
 
57
57
  opts
@@ -1,7 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.expand_path('../spec', __FILE__)
4
- require File.expand_path('../buffer', __FILE__)
3
+ require "amqp/spec"
4
+ require "amqp/buffer"
5
5
 
6
6
  module AMQP
7
7
  module Protocol
@@ -1,9 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
- class MQ
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 MQ:Error (ACCESS_REFUSED).
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 MQ:Error.
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 = MQ.direct('foo exchange')
94
- # queue = MQ.queue('bar 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 = MQ.direct("foo queue")
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 = MQ.queue('foo 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 = MQ.direct("foo queue")
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 = MQ.queue('foo 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 = MQ.direct("foo queue")
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 = MQ.queue('foo 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 = MQ.direct("foo queue")
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 = MQ.queue('foo 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 = MQ::Header.new(@mq, headers) unless headers.nil?
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
- # MQ.queue('name').status { |num_messages, num_consumers|
413
+ # AMQP::Channel.queue('name').status { |num_messages, num_consumers|
411
414
  # puts num_messages
412
415
  # }
413
416
  #
@@ -1,14 +1,26 @@
1
1
  # encoding: utf-8
2
2
 
3
- class MQ
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 = MQ.rpc('hash table node', Hash)
21
+ # server = AMQP::Channel.new.rpc('hash table node', Hash)
10
22
  #
11
- # client = MQ.rpc('hash table node')
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 MQ.rpc(*args) returns a proxy object without any methods beyond
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 = MQ.rpc('hash table node', Hash)
87
- # client = MQ.rpc('hash table node')
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
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require File.expand_path('../frame', __FILE__)
3
+ require "amqp/frame"
4
4
 
5
5
  module AMQP
6
6
  module Server
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module AMQP
4
- VERSION = '0.7.0'
4
+ VERSION = '0.7.1'
5
5
  end
data/lib/mq.rb CHANGED
@@ -1,975 +1,31 @@
1
- # encoding: utf-8
1
+ $stdout.puts <<-MESSAGE
2
+ -------------------------------------------------------------------------------------
3
+ DEPRECATION WARNING!
2
4
 
3
- #:main: README
4
- #
5
+ Use of mq.rb is deprecated. Instead of
5
6
 
6
- $:.unshift File.expand_path(File.dirname(File.expand_path(__FILE__)))
7
- require 'amqp'
8
- require 'mq/collection'
7
+ require "mq"
9
8
 
10
- class MQ
11
- %w[ exchange queue rpc header ].each do |file|
12
- require "mq/#{file}"
13
- end
9
+ please use
14
10
 
15
- class << self
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
- class IncompatibleOptionsError < Error
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
- class ChannelClosedError < Error
30
- def initialize(instance)
31
- super("The channel #{instance.channel} was closed, you can't use it anymore!")
32
- end
33
- end
34
- end
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
- # The top-level class for building AMQP clients. This class contains several
37
- # convenience methods for working with queues and exchanges. Many calls
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
- include AMQP
139
- include EM::Deferrable
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"