amqp 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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