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