amqp 0.7.5 → 0.8.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. data/.gitignore +3 -4
  2. data/.travis.yml +3 -5
  3. data/.yardopts +6 -0
  4. data/CHANGELOG +26 -7
  5. data/Gemfile +15 -7
  6. data/README.textile +216 -0
  7. data/Rakefile +0 -6
  8. data/amqp.gemspec +14 -4
  9. data/bin/jenkins.sh +27 -0
  10. data/bin/set_test_suite_realms_up.sh +16 -2
  11. data/docs/VendorSpecificExtensions.textile +32 -0
  12. data/examples/extensions/rabbitmq/publisher_confirmations_with_transient_messages.rb +53 -0
  13. data/examples/hello_world.rb +29 -0
  14. data/examples/real-world/task-queue/README.textile +3 -0
  15. data/examples/real-world/task-queue/consumer.rb +27 -0
  16. data/examples/real-world/task-queue/producer.rb +22 -0
  17. data/examples/various/ack.rb +70 -0
  18. data/examples/various/automatic_binding_for_default_direct_exchange.rb +53 -0
  19. data/examples/various/basic_get.rb +65 -0
  20. data/examples/various/callbacks.rb +45 -0
  21. data/examples/various/clock.rb +74 -0
  22. data/examples/various/declare_a_queue_without_assignment.rb +46 -0
  23. data/examples/various/declare_an_exchange_without_assignment.rb +46 -0
  24. data/examples/various/hashtable.rb +60 -0
  25. data/examples/{logger.rb → various/logger.rb} +9 -7
  26. data/examples/{multiclock.rb → various/multiclock.rb} +15 -17
  27. data/examples/various/open_channel_without_assignment.rb +34 -0
  28. data/examples/various/pingpong.rb +53 -0
  29. data/examples/various/primes-simple.rb +29 -0
  30. data/examples/various/primes.rb +76 -0
  31. data/examples/various/pubsub.rb +43 -0
  32. data/examples/various/queue_status.rb +58 -0
  33. data/examples/various/stocks.rb +59 -0
  34. data/examples/various/weather_updates.rb +63 -0
  35. data/lib/amqp.rb +11 -2
  36. data/lib/amqp/basic_client.rb +23 -54
  37. data/lib/amqp/channel.rb +577 -805
  38. data/lib/amqp/client.rb +37 -275
  39. data/lib/amqp/connection.rb +165 -93
  40. data/lib/amqp/deprecated/fork.rb +15 -0
  41. data/lib/amqp/deprecated/logger.rb +99 -0
  42. data/lib/amqp/deprecated/mq.rb +20 -0
  43. data/lib/amqp/deprecated/rpc.rb +168 -0
  44. data/lib/amqp/exchange.rb +409 -281
  45. data/lib/amqp/extensions/rabbitmq.rb +1 -0
  46. data/lib/amqp/header.rb +41 -17
  47. data/lib/amqp/logger.rb +10 -84
  48. data/lib/amqp/queue.rb +457 -320
  49. data/lib/amqp/rpc.rb +11 -107
  50. data/lib/amqp/version.rb +1 -1
  51. data/lib/mq.rb +2 -1
  52. data/lib/mq/logger.rb +2 -0
  53. data/lib/mq/rpc.rb +2 -0
  54. data/spec/integration/authentication_spec.rb +36 -40
  55. data/spec/integration/automatic_binding_for_default_direct_exchange_spec.rb +3 -5
  56. data/spec/integration/basic_get_spec.rb +91 -0
  57. data/spec/integration/channel_close_spec.rb +5 -5
  58. data/spec/integration/exchange_declaration_spec.rb +6 -53
  59. data/spec/integration/extensions/basic_return_spec.rb +47 -0
  60. data/spec/integration/queue_declaration_spec.rb +14 -17
  61. data/spec/integration/queue_exclusivity_spec.rb +49 -48
  62. data/spec/integration/reply_queue_communication_spec.rb +6 -4
  63. data/spec/integration/store_and_forward_spec.rb +9 -36
  64. data/spec/integration/topic_subscription_spec.rb +1 -1
  65. data/spec/integration/workload_distribution_spec.rb +1 -0
  66. data/spec/spec_helper.rb +69 -43
  67. data/spec/unit/amqp/connection_spec.rb +27 -23
  68. data/tasks.rb +11 -0
  69. metadata +124 -95
  70. data/README.md +0 -156
  71. data/TODO +0 -30
  72. data/amqp.pre.gemspec +0 -6
  73. data/examples/ack.rb +0 -47
  74. data/examples/automatic_binding_for_default_direct_exchange.rb +0 -65
  75. data/examples/callbacks.rb +0 -40
  76. data/examples/clock.rb +0 -65
  77. data/examples/default_channel.rb +0 -19
  78. data/examples/hashtable.rb +0 -61
  79. data/examples/immediately_bind_a_server_named_queue.rb +0 -38
  80. data/examples/internal.rb +0 -51
  81. data/examples/issues/issue_75.rb +0 -21
  82. data/examples/issues/issue_94.rb +0 -23
  83. data/examples/pingpong.rb +0 -54
  84. data/examples/pop.rb +0 -45
  85. data/examples/primes-simple.rb +0 -21
  86. data/examples/primes.rb +0 -101
  87. data/examples/simple.rb +0 -81
  88. data/examples/stocks.rb +0 -67
  89. data/gemfiles/eventmachine-pre +0 -24
  90. data/lib/amqp/buffer.rb +0 -272
  91. data/lib/amqp/collection.rb +0 -60
  92. data/lib/amqp/frame.rb +0 -68
  93. data/lib/amqp/protocol.rb +0 -163
  94. data/lib/amqp/server.rb +0 -101
  95. data/lib/amqp/spec.rb +0 -832
  96. data/protocol/amqp-0.8.json +0 -617
  97. data/protocol/amqp-0.8.xml +0 -3908
  98. data/protocol/codegen.rb +0 -175
  99. data/protocol/doc.txt +0 -281
  100. data/research/api.rb +0 -52
  101. data/research/primes-forked.rb +0 -65
  102. data/research/primes-processes.rb +0 -137
  103. data/research/primes-threaded.rb +0 -51
  104. data/spec/integration/queue_status_spec.rb +0 -44
  105. data/spec/unit/amqp/buffer_spec.rb +0 -178
  106. data/spec/unit/amqp/client_spec.rb +0 -102
  107. data/spec/unit/amqp/collection_spec.rb +0 -144
  108. data/spec/unit/amqp/frame_spec.rb +0 -60
  109. data/spec/unit/amqp/protocol_spec.rb +0 -51
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require "bundler"
5
+ Bundler.setup
6
+
7
+ $:.unshift(File.expand_path("../../../lib", __FILE__))
8
+
9
+ require "amqp"
10
+
11
+ EventMachine.run do
12
+ AMQP.connect do |connection|
13
+ channel = AMQP::Channel.new(connection)
14
+ exchange = channel.topic("pub/sub")
15
+
16
+ # Subscribers.
17
+ channel.queue("Everything about development").bind(exchange, :routing_key => "technology.dev.#").subscribe do |payload|
18
+ puts "A new dev post: '#{payload}'"
19
+ end
20
+ channel.queue("Everything about rubies").bind(exchange, :routing_key => "#.ruby").subscribe do |headers, payload|
21
+ puts "A new post about rubies: '#{payload}', routing key = #{headers.routing_key}"
22
+ end
23
+
24
+ # Let's publish some test data.
25
+ exchange.publish "Ruby post", :routing_key => "technology.dev.ruby"
26
+ exchange.publish "Erlang post", :routing_key => "technology.dev.erlang"
27
+ exchange.publish "Sinatra post", :routing_key => "technology.web.ruby"
28
+ exchange.publish "Jewelery post", :routing_key => "jewelery.ruby"
29
+
30
+
31
+
32
+ show_stopper = Proc.new {
33
+ connection.close do
34
+ EM.stop
35
+ end
36
+ }
37
+
38
+ Signal.trap "INT", show_stopper
39
+ Signal.trap "TERM", show_stopper
40
+
41
+ EM.add_timer(1, show_stopper)
42
+ end
43
+ end
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require "bundler"
5
+ Bundler.setup
6
+
7
+ $:.unshift(File.expand_path("../../../lib", __FILE__))
8
+
9
+ require 'amqp'
10
+
11
+ if RUBY_VERSION == "1.8.7"
12
+ class Array
13
+ alias sample choice
14
+ end
15
+ end
16
+
17
+
18
+ puts "=> Queue#status example"
19
+ puts
20
+ AMQP.start(:host => 'localhost') do |connection|
21
+ channel = AMQP::Channel.new
22
+
23
+ queue_name = "amqpgem.integration.queue.status.queue"
24
+
25
+ exchange = channel.fanout("amqpgem.integration.queue.status.fanout", :auto_delete => true)
26
+ queue = channel.queue(queue_name, :auto_delete => true)
27
+
28
+ queue.bind(exchange) do
29
+ puts "Bound #{exchange.name} => #{queue.name}"
30
+ end
31
+ 100.times do |i|
32
+ print "."
33
+ exchange.publish(Time.now.to_i.to_s + "_#{i}", :key => queue_name)
34
+ end
35
+ $stdout.flush
36
+
37
+ sleep 1
38
+
39
+ queue.status do |number_of_messages, number_of_consumers|
40
+ puts "# of messages on status = #{number_of_messages}"
41
+ end
42
+
43
+
44
+ show_stopper = Proc.new do
45
+ $stdout.puts "Stopping..."
46
+
47
+ # queue.purge :nowait => true
48
+
49
+ # now change this to just EM.stop and it
50
+ # unbinds instantly
51
+ connection.close {
52
+ EM.stop { exit }
53
+ }
54
+ end
55
+
56
+ Signal.trap "INT", show_stopper
57
+ EM.add_timer(2, show_stopper)
58
+ end
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require "bundler"
5
+ Bundler.setup
6
+
7
+ $:.unshift(File.expand_path("../../../lib", __FILE__))
8
+ require 'amqp'
9
+
10
+ AMQP.start(:host => 'localhost') do |connection|
11
+ def log(*args)
12
+ p [ Time.now, *args ]
13
+ end
14
+
15
+ AMQP::Channel.new(connection) do |ch, open_ok|
16
+ EM.add_periodic_timer(1) do
17
+ puts
18
+
19
+ {
20
+ :appl => 170 + rand(1000) / 100.0,
21
+ :msft => 22 + rand(500) / 100.0
22
+ }.each do |stock, price|
23
+ price = price.to_s
24
+ stock = "usd.#{stock}"
25
+
26
+ log :publishing, stock, price
27
+ ch.topic('stocks').publish(price, :key => stock) if connection.open?
28
+ end # each
29
+ end # add_periodic_timer
30
+ end # Channel.new
31
+
32
+
33
+ AMQP::Channel.new do |ch, open_ok|
34
+ ch.queue('apple stock').bind(ch.topic('stocks'), :key => 'usd.appl').subscribe { |price|
35
+ log 'apple stock', price
36
+ }
37
+ end
38
+
39
+ AMQP::Channel.new do |ch, open_ok|
40
+ ch.queue('us stocks').bind(ch.topic('stocks'), :key => 'usd.*').subscribe { |info, price|
41
+ log 'us stocks', info.routing_key, price
42
+ }
43
+ end
44
+
45
+
46
+
47
+ show_stopper = Proc.new {
48
+ connection.close do
49
+ puts "Connection is now closed properly"
50
+ EM.stop
51
+ end
52
+ }
53
+
54
+ Signal.trap "INT", show_stopper
55
+ Signal.trap "TERM", show_stopper
56
+
57
+ EM.add_timer(3, show_stopper)
58
+
59
+ end
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require "bundler"
5
+ Bundler.setup
6
+
7
+ $:.unshift(File.expand_path("../../../lib", __FILE__))
8
+
9
+ require "amqp"
10
+
11
+ EventMachine.run do
12
+ AMQP.connect do |connection|
13
+ channel = AMQP::Channel.new(connection)
14
+ exchange = channel.topic("pub/sub")
15
+
16
+ # Subscribers.
17
+ channel.queue("", :exclusive => true) do |queue|
18
+ queue.bind(exchange, :routing_key => "americas.north.#").subscribe do |headers, payload|
19
+ puts "An update for North America: #{payload}, routing key is #{headers.routing_key}"
20
+ end
21
+ end
22
+ channel.queue("americas.south").bind(exchange, :routing_key => "americas.south.#").subscribe do |headers, payload|
23
+ puts "An update for South America: #{payload}, routing key is #{headers.routing_key}"
24
+ end
25
+ channel.queue("us.california").bind(exchange, :routing_key => "americas.north.us.ca.*").subscribe do |headers, payload|
26
+ puts "An update for US/California: #{payload}, routing key is #{headers.routing_key}"
27
+ end
28
+ channel.queue("us.tx.austin").bind(exchange, :routing_key => "#.tx.austin").subscribe do |headers, payload|
29
+ puts "An update for Austin, TX: #{payload}, routing key is #{headers.routing_key}"
30
+ end
31
+ channel.queue("it.rome").bind(exchange, :routing_key => "europe.italy.rome").subscribe do |headers, payload|
32
+ puts "An update for Rome, Italy: #{payload}, routing key is #{headers.routing_key}"
33
+ end
34
+ channel.queue("asia.hk").bind(exchange, :routing_key => "asia.southeast.hk.#").subscribe do |headers, payload|
35
+ puts "An update for Hong Kong: #{payload}, routing key is #{headers.routing_key}"
36
+ end
37
+
38
+ EM.add_timer(1) do
39
+ exchange.publish("San Diego update", :routing_key => "americas.north.us.ca.sandiego").
40
+ publish("Berkeley update", :routing_key => "americas.north.us.ca.berkeley").
41
+ publish("San Francisco update", :routing_key => "americas.north.us.ca.sanfrancisco").
42
+ publish("New York update", :routing_key => "americas.north.us.ny.newyork").
43
+ publish("São Paolo update", :routing_key => "americas.south.brazil.saopaolo").
44
+ publish("Hong Kong update", :routing_key => "asia.southeast.hk.hongkong").
45
+ publish("Kyoto update", :routing_key => "asia.southeast.japan.kyoto").
46
+ publish("Shanghai update", :routing_key => "asia.southeast.prc.shanghai").
47
+ publish("Rome update", :routing_key => "europe.italy.roma").
48
+ publish("Paris update", :routing_key => "europe.france.paris")
49
+ end
50
+
51
+
52
+ show_stopper = Proc.new {
53
+ connection.close do
54
+ EM.stop
55
+ end
56
+ }
57
+
58
+ Signal.trap "INT", show_stopper
59
+ Signal.trap "TERM", show_stopper
60
+
61
+ EM.add_timer(3, show_stopper)
62
+ end
63
+ end
@@ -1,10 +1,19 @@
1
1
  # encoding: utf-8
2
2
 
3
+ require "amq/client"
4
+ require "amq/client/adapters/event_machine"
5
+
3
6
  require "amqp/version"
4
7
  require "amqp/exceptions"
5
8
  require "amqp/connection"
6
- require "amqp/channel"
7
9
  require "amqp/exchange"
8
10
  require "amqp/queue"
9
- require "amqp/rpc"
11
+ require "amqp/channel"
10
12
  require "amqp/header"
13
+
14
+
15
+ # Will be removed before 1.0.
16
+
17
+ require "amqp/deprecated/mq"
18
+ require "amqp/deprecated/rpc"
19
+ require "amqp/deprecated/fork"
@@ -1,58 +1,27 @@
1
1
  # encoding: utf-8
2
2
 
3
- require "amqp/frame"
4
- require "amqp/protocol"
3
+ require "amq/client/adapters/event_machine"
5
4
 
6
5
  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
6
+ # AMQP client implementation based on amq-client library. Left here for API compatibility
7
+ # with 0.7.x series.
8
+ #
9
+ # @note This class is not part of the public API and may be removed in the future without any warning.
10
+ class BasicClient < AMQ::Client::EventMachineClient
11
+
12
+ #
13
+ # API
14
+ #
15
+
16
+ # @api plugin
17
+ def connected?
18
+ self.opened?
19
+ end
20
+
21
+ # @api plugin
22
+ def reconnect(force = false)
23
+ # TODO
24
+ raise NotImplementedError.new
25
+ end # reconnect(force = false)
26
+ end
27
+ end
@@ -1,248 +1,221 @@
1
1
  # encoding: utf-8
2
2
 
3
- require "amqp/collection"
3
+ require "amqp/exchange"
4
+ require "amqp/queue"
4
5
 
5
6
  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.
7
+ # To quote {AMQP 0.9.1 specification http://bit.ly/hw2ELX}:
12
8
  #
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.
9
+ # AMQP is a multi-channelled protocol. Channels provide a way to multiplex
10
+ # a heavyweight TCP/IP connection into several light weight connections.
11
+ # This makes the protocol more “firewall friendly” since port usage is predictable.
12
+ # It also means that traffic shaping and other network QoS features can be easily employed.
13
+ # Channels are independent of each other and can perform different functions simultaneously
14
+ # with other channels, the available bandwidth being shared between the concurrent activities.
20
15
  #
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
16
  #
30
- # require 'rubygems'
31
- # require 'mq'
17
+ # h2. RabbitMQ extensions.
32
18
  #
33
- # thr = Thread.new { EM.run }
19
+ # AMQP gem supports several RabbitMQ extensions taht extend Channel functionality.
20
+ # Learn more in {file:docs/VendorSpecificExtensions.textile}
34
21
  #
35
- # # turns on extreme logging
36
- # #AMQP.logging = true
37
22
  #
38
- # def log *args
39
- # p args
40
- # end
23
+ # h2. Key methods
41
24
  #
42
- # def publisher
43
- # clock = AMQP::Channel.fanout('clock')
44
- # EM.add_periodic_timer(1) do
45
- # puts
25
+ # Key methods of Channel class are
46
26
  #
47
- # log :publishing, time = Time.now
48
- # clock.publish(Marshal.dump(time))
49
- # end
50
- # end
27
+ # * {Channel#queue}
28
+ # * {Channel#default_exchange}
29
+ # * {Channel#direct}
30
+ # * {Channel#fanout}
31
+ # * {Channel#topic}
32
+ # * {Channel#close}
51
33
  #
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
34
+ # Channel provides a number of convenience methods that instantiate queues and exchanges
35
+ # of various types associated with this channel:
57
36
  #
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
37
+ # * {Channel#queue}
38
+ # * {Channel#default_exchange}
39
+ # * {Channel#direct}
40
+ # * {Channel#fanout}
41
+ # * {Channel#topic}
64
42
  #
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
43
+ # Channels are opened when objects is instantiated and closed using {#close} method when application no longer
44
+ # needs it.
72
45
  #
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
46
+ # @see http://bit.ly/hw2ELX AMQP 0.9.1 specification (Section 2.2.5)
47
+ class Channel < AMQ::Client::Channel
103
48
 
104
49
  #
105
- # Behaviors
50
+ # API
106
51
  #
107
52
 
108
- include EM::Deferrable
53
+ # AMQP connection this channel is part of
54
+ # @return [Connection]
55
+ attr_reader :connection
56
+ alias :conn :connection
109
57
 
58
+ # Status of this channel (one of: :opening, :closing, :open, :closed)
59
+ # @return [Symbol]
60
+ attr_reader :status
110
61
 
111
62
 
63
+ # @note We encourage you to not rely on default AMQP connection and pass connection parameter
64
+ # explicitly.
112
65
  #
113
- # API
66
+ # @param [AMQ::Client::EventMachineAdapter] Connection to open this channel on. If not given, default AMQP
67
+ # connection (accessible via {AMQP.connection}) will be used.
68
+ # @param [Integer] Channel id. Must not be greater than max channel id client and broker
69
+ # negotiated on during connection setup. Almost always the right thing to do
70
+ # is to let AMQP gem pick channel identifier for you.
114
71
  #
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+.
72
+ # @example Instantiating a channel for default connection (accessible as AMQP.connection)
73
+ #
74
+ # AMQP.connect do |connection|
75
+ # AMQP::Channel.new(connection) do |channel|
76
+ # # channel is ready: set up your messaging flow by creating exchanges,
77
+ # # queues, binding them together and so on.
78
+ # end
79
+ # end
119
80
  #
120
- # Optionally takes the result from calling AMQP::connect.
81
+ # @example Instantiating a channel for explicitly given connection
121
82
  #
122
- # Rarely called directly by client code. This is implicitly called
123
- # by most instance methods. See #method_missing.
83
+ # AMQP.connect do |connection|
84
+ # AMQP::Channel.new(connection) do |channel|
85
+ # # channel is ready: set up your messaging flow by creating exchanges,
86
+ # # queues, binding them together and so on.
87
+ # end
88
+ # end
124
89
  #
125
- # EM.run do
126
- # channel = AMQP::Channel.new
127
- # end
128
90
  #
129
- # EM.run do
130
- # channel = AMQP::Channel.new AMQP::connect
131
- # end
91
+ # @yield [channel, open_ok] Yields open channel instance and AMQP method (channel.open-ok) instance. The latter is optional.
92
+ # @yieldparam [Channel] channel Channel that is successfully open
93
+ # @yieldparam [AMQP::Protocol::Channel::OpenOk] open_ok AMQP channel.open-ok) instance
132
94
  #
133
- def initialize(connection = nil)
95
+ #
96
+ # @api public
97
+ def initialize(connection = nil, id = self.class.next_channel_id, &block)
134
98
  raise 'AMQP can only be used from within EM.run {}' unless EM.reactor_running?
135
99
 
136
- @_send_mutex = Mutex.new
137
- @get_queue_mutex = Mutex.new
138
-
139
100
  @connection = connection || AMQP.start
140
101
 
141
- @queues_awaiting_declare_ok = Array.new
142
-
143
- conn.callback { |c|
144
- @channel = c.add_channel(self)
145
- send Protocol::Channel::Open.new
146
- }
102
+ super(@connection, id)
103
+
104
+ @rpcs = Hash.new
105
+ # we need this deferrable to mimic what AMQP gem 0.7 does to enable
106
+ # the following (HIGHLY discouraged) style of programming some people use in their
107
+ # existing codebases:
108
+ #
109
+ # connection = AMQP.connect
110
+ # channel = AMQP::Channel.new(connection)
111
+ # queue = AMQP::Queue.new(channel)
112
+ #
113
+ # ...
114
+ #
115
+ # Read more about EM::Deferrable#callback behavior in EventMachine documentation. MK.
116
+ @channel_is_open_deferrable = AMQ::Client::EventMachineClient::Deferrable.new
117
+
118
+ # only send channel.open when connection is actually open. Makes it possible to
119
+ # do c = AMQP.connect; AMQP::Channel.new(c) that is what some people do. MK.
120
+ @connection.on_open do
121
+ self.open do |*args|
122
+ @channel_is_open_deferrable.succeed
123
+
124
+ block.call(*args) if block
125
+ end
126
+ end
147
127
  end
148
128
 
149
- attr_reader :channel, :connection, :status
150
- alias :conn :connection
151
-
152
- attr_reader :queues_awaiting_declare_ok
153
129
 
130
+ def once_open(&block)
131
+ @channel_is_open_deferrable.callback(&block)
132
+ end # once_open(&block)
154
133
 
155
134
 
156
- def closed?
157
- @status.eql?(:closed)
158
- end
159
-
160
- def open?
161
- !self.closed?
162
- end # open?
163
-
164
- # Defines, intializes and returns an Exchange to act as an ingress
165
- # point for all published messages.
166
- #
167
- # == Direct
168
- # A direct exchange is useful for 1:1 communication between a publisher and
169
- # subscriber. Messages are routed to the queue with a binding that shares
170
- # the same name as the exchange. Alternately, the messages are routed to
171
- # the bound queue that shares the same name as the routing key used for
172
- # defining the exchange. This exchange type does not honor the +:key+ option
173
- # when defining a new instance with a name. It _will_ honor the +:key+ option
174
- # if the exchange name is the empty string.
175
- # Allocating this exchange without a name _or_ with the empty string
176
- # will use the internal 'amq.direct' exchange.
177
- #
178
- # Any published message, regardless of its persistence setting, is thrown
179
- # away by the exchange when there are no queues bound to it.
180
- #
181
- # # exchange is named 'foo'
182
- # exchange = AMQP::Channel.direct('foo')
183
- #
184
- # # or, the exchange can use the default name (amq.direct) and perform
185
- # # routing comparisons using the :key
186
- # exchange = AMQP::Channel.direct("", :key => 'foo')
187
- # exchange.publish('some data') # will be delivered to queue bound to 'foo'
188
- #
189
- # queue = AMQP::Channel.queue('foo')
190
- # # can receive data since the queue name and the exchange key match exactly
191
- # queue.pop { |data| puts "received data [#{data}]" }
192
- #
193
- # == Options
194
- # * :passive => true | false (default false)
195
- # If set, the server will not create the exchange if it does not
196
- # already exist. The client can use this to check whether an exchange
197
- # exists without modifying the server state.
198
- #
199
- # * :durable => true | false (default false)
200
- # If set when creating a new exchange, the exchange will be marked as
201
- # durable. Durable exchanges remain active when a server restarts.
202
- # Non-durable exchanges (transient exchanges) are purged if/when a
203
- # server restarts.
204
- #
205
- # A transient exchange (the default) is stored in memory-only. The
206
- # exchange and all bindings will be lost on a server restart.
207
- # It makes no sense to publish a persistent message to a transient
208
- # exchange.
209
- #
210
- # Durable exchanges and their bindings are recreated upon a server
211
- # restart. Any published messages not routed to a bound queue are lost.
212
- #
213
- # * :auto_delete => true | false (default false)
214
- # If set, the exchange is deleted when all queues have finished
215
- # using it. The server waits for a short period of time before
216
- # determining the exchange is unused to give time to the client code
217
- # to bind a queue to it.
218
- #
219
- # If the exchange has been previously declared, this option is ignored
220
- # on subsequent declarations.
221
- #
222
- # * :internal => true | false (default false)
223
- # If set, the exchange may not be used directly by publishers, but
224
- # only when bound to other exchanges. Internal exchanges are used to
225
- # construct wiring that is not visible to applications.
226
- #
227
- # * :nowait => true | false (default true)
228
- # If set, the server will not respond to the method. The client should
229
- # not wait for a reply method. If the server could not complete the
230
- # method it will raise a channel or connection exception.
231
- #
232
- # == Exceptions
233
- # Doing any of these activities are illegal and will raise AMQP::Error.
234
- # * redeclare an already-declared exchange to a different type
235
- # * :passive => true and the exchange does not exist (NOT_FOUND)
135
+ # Defines, intializes and returns a direct Exchange instance.
136
+ #
137
+ # Learn more about direct exchanges in {Exchange Exchange class documentation}.
138
+ #
139
+ #
140
+ # @param [String] name (amq.direct) Exchange name.
141
+ #
142
+ # @option opts [Boolean] :passive (false) If set, the server will not create the exchange if it does not
143
+ # already exist. The client can use this to check whether an exchange
144
+ # exists without modifying the server state.
145
+ #
146
+ # @option opts [Boolean] :durable (false) If set when creating a new exchange, the exchange will be marked as
147
+ # durable. Durable exchanges and their bindings are recreated upon a server
148
+ # restart (information about them is persisted). Non-durable (transient) exchanges
149
+ # do not survive if/when a server restarts (information about them is stored exclusively
150
+ # in RAM).
151
+ #
152
+ #
153
+ # @option opts [Boolean] :auto_delete (false) If set, the exchange is deleted when all queues have finished
154
+ # using it. The server waits for a short period of time before
155
+ # determining the exchange is unused to give time to the client code
156
+ # to bind a queue to it.
157
+ #
158
+ # @option opts [Boolean] :internal (default false) If set, the exchange may not be used directly by publishers, but
159
+ # only when bound to other exchanges. Internal exchanges are used to
160
+ # construct wiring that is not visible to applications. This is a RabbitMQ-specific
161
+ # extension.
162
+ #
163
+ # @option opts [Boolean] :nowait (true) If set, the server will not respond to the method. The client should
164
+ # not wait for a reply method. If the server could not complete the
165
+ # method it will raise a channel or connection exception.
166
+ #
167
+ #
168
+ # @raise [AMQP::Error] Raised when exchange is redeclared with parameters different from original declaration.
169
+ # @raise [AMQP::Error] Raised when exchange is declared with :passive => true and the exchange does not exist.
170
+ #
171
+ #
172
+ # @example Using default pre-declared direct exchange and no callbacks (pseudo-synchronous style)
173
+ #
174
+ # # an exchange application A will be using to publish updates
175
+ # # to some search index
176
+ # exchange = channel.direct("index.updates")
177
+ #
178
+ # # In the same (or different) process declare a queue that broker will
179
+ # # generate name for, bind it to aforementioned exchange using method chaining
180
+ # queue = channel.queue("").
181
+ # # queue will be receiving messages that were published with
182
+ # # :routing_key attribute value of "search.index.updates"
183
+ # bind(exchange, :routing_key => "search.index.updates").
184
+ # # register a callback that will be run when messages arrive
185
+ # subscribe { |header, message| puts("Received #{message}") }
236
186
  #
187
+ # # now publish a new document contents for indexing,
188
+ # # message will be delivered to the queue we declared and bound on the line above
189
+ # exchange.publish(document.content, :routing_key => "search.index.updates")
190
+ #
191
+ #
192
+ # @example Instantiating a direct exchange using {Channel#direct} with a callback
193
+ #
194
+ # AMQP.connect do |connection|
195
+ # AMQP::Channel.new(connection) do |channel|
196
+ # channel.direct("email.replies_listener") do |exchange, declare_ok|
197
+ # # by now exchange is ready and waiting
198
+ # end
199
+ # end
200
+ # end
201
+ #
202
+ #
203
+ # @see Channel#default_exchange
204
+ # @see Exchange
205
+ # @see Exchange#initialize
206
+ # @see http://bit.ly/hw2ELX AMQP 0.9.1 specification (Section 3.1.3.1)
207
+ #
208
+ # @return [Exchange]
209
+ # @api public
237
210
  def direct(name = 'amq.direct', opts = {}, &block)
238
- if exchange = self.exchanges.find { |exchange| exchange.name == name }
211
+ if exchange = find_exchange(name)
239
212
  extended_opts = Exchange.add_default_options(:direct, name, opts, block)
240
213
 
241
214
  validate_parameters_match!(exchange, extended_opts)
242
215
 
243
216
  exchange
244
217
  else
245
- self.exchanges << Exchange.new(self, :direct, name, opts, &block)
218
+ register_exchange(Exchange.new(self, :direct, name, opts, &block))
246
219
  end
247
220
  end
248
221
 
@@ -253,401 +226,406 @@ module AMQP
253
226
  #
254
227
  # *Use default exchange when you want to route messages directly to specific queues*
255
228
  # (queue names are known, you don't mind this kind of coupling between applications).
229
+ #
230
+ #
231
+ # @example Using default exchange to publish messages to queues with known names
232
+ # AMQP.start(:host => 'localhost') do |connection|
233
+ # ch = AMQP::Channel.new(connection)
234
+ #
235
+ # queue1 = ch.queue("queue1").subscribe do |payload|
236
+ # puts "[#{queue1.name}] => #{payload}"
237
+ # end
238
+ # queue2 = ch.queue("queue2").subscribe do |payload|
239
+ # puts "[#{queue2.name}] => #{payload}"
240
+ # end
241
+ # queue3 = ch.queue("queue3").subscribe do |payload|
242
+ # puts "[#{queue3.name}] => #{payload}"
243
+ # end
244
+ # queues = [queue1, queue2, queue3]
245
+ #
246
+ # # Rely on default direct exchange binding, see section 2.1.2.4 Automatic Mode in AMQP 0.9.1 spec.
247
+ # exchange = AMQP::Exchange.default
248
+ # EM.add_periodic_timer(1) do
249
+ # q = queues.sample
250
+ #
251
+ # exchange.publish "Some payload from #{Time.now.to_i}", :routing_key => q.name
252
+ # end
253
+ # end
254
+ #
255
+ #
256
+ #
257
+ # @see Exchange
258
+ # @see http://bit.ly/hw2ELX AMQP 0.9.1 specification (Section 2.1.2.4)
259
+ #
260
+ # @return [Exchange]
261
+ # @api public
256
262
  def default_exchange
257
263
  Exchange.default(self)
258
264
  end
259
265
 
260
-
261
- # Defines, intializes and returns an Exchange to act as an ingress
262
- # point for all published messages.
263
- #
264
- # == Fanout
265
- # A fanout exchange is useful for 1:N communication where one publisher
266
- # feeds multiple subscribers. Like direct exchanges, messages published
267
- # to a fanout exchange are delivered to queues whose name matches the
268
- # exchange name (or are bound to that exchange name). Each queue gets
269
- # its own copy of the message.
270
- #
271
- # Any published message, regardless of its persistence setting, is thrown
272
- # away by the exchange when there are no queues bound to it.
273
- #
274
- # Like the direct exchange type, this exchange type does not honor the
275
- # +:key+ option when defining a new instance with a name. It _will_ honor
276
- # the +:key+ option if the exchange name is the empty string.
277
- # Allocating this exchange without a name _or_ with the empty string
278
- # will use the internal 'amq.fanout' exchange.
279
- #
280
- # EM.run do
281
- # clock = AMQP::Channel.fanout('clock')
282
- # EM.add_periodic_timer(1) do
283
- # puts "\npublishing #{time = Time.now}"
284
- # clock.publish(Marshal.dump(time))
285
- # end
286
- #
287
- # amq = AMQP::Channel.queue('every second')
288
- # amq.bind(AMQP::Channel.fanout('clock')).subscribe do |time|
289
- # puts "every second received #{Marshal.load(time)}"
290
- # end
291
- #
292
- # # note the string passed to #bind
293
- # AMQP::Channel.queue('every 5 seconds').bind('clock').subscribe do |time|
294
- # time = Marshal.load(time)
295
- # puts "every 5 seconds received #{time}" if time.strftime('%S').to_i%5 == 0
296
- # end
297
- # end
298
- #
299
- # == Options
300
- # * :passive => true | false (default false)
301
- # If set, the server will not create the exchange if it does not
302
- # already exist. The client can use this to check whether an exchange
303
- # exists without modifying the server state.
304
- #
305
- # * :durable => true | false (default false)
306
- # If set when creating a new exchange, the exchange will be marked as
307
- # durable. Durable exchanges remain active when a server restarts.
308
- # Non-durable exchanges (transient exchanges) are purged if/when a
309
- # server restarts.
310
- #
311
- # A transient exchange (the default) is stored in memory-only. The
312
- # exchange and all bindings will be lost on a server restart.
313
- # It makes no sense to publish a persistent message to a transient
314
- # exchange.
315
- #
316
- # Durable exchanges and their bindings are recreated upon a server
317
- # restart. Any published messages not routed to a bound queue are lost.
318
- #
319
- # * :auto_delete => true | false (default false)
320
- # If set, the exchange is deleted when all queues have finished
321
- # using it. The server waits for a short period of time before
322
- # determining the exchange is unused to give time to the client code
323
- # to bind a queue to it.
324
- #
325
- # If the exchange has been previously declared, this option is ignored
326
- # on subsequent declarations.
327
- #
328
- # * :internal => true | false (default false)
329
- # If set, the exchange may not be used directly by publishers, but
330
- # only when bound to other exchanges. Internal exchanges are used to
331
- # construct wiring that is not visible to applications.
332
- #
333
- # * :nowait => true | false (default true)
334
- # If set, the server will not respond to the method. The client should
335
- # not wait for a reply method. If the server could not complete the
336
- # method it will raise a channel or connection exception.
337
- #
338
- # == Exceptions
339
- # Doing any of these activities are illegal and will raise AMQP::Error.
340
- # * redeclare an already-declared exchange to a different type
341
- # * :passive => true and the exchange does not exist (NOT_FOUND)
266
+ # Defines, intializes and returns a fanout Exchange instance.
267
+ #
268
+ # Learn more about fanout exchanges in {Exchange Exchange class documentation}.
269
+ #
270
+ #
271
+ # @param [String] name (amq.fanout) Exchange name.
272
+ #
273
+ # @option opts [Boolean] :passive (false) If set, the server will not create the exchange if it does not
274
+ # already exist. The client can use this to check whether an exchange
275
+ # exists without modifying the server state.
342
276
  #
277
+ # @option opts [Boolean] :durable (false) If set when creating a new exchange, the exchange will be marked as
278
+ # durable. Durable exchanges and their bindings are recreated upon a server
279
+ # restart (information about them is persisted). Non-durable (transient) exchanges
280
+ # do not survive if/when a server restarts (information about them is stored exclusively
281
+ # in RAM).
282
+ #
283
+ #
284
+ # @option opts [Boolean] :auto_delete (false) If set, the exchange is deleted when all queues have finished
285
+ # using it. The server waits for a short period of time before
286
+ # determining the exchange is unused to give time to the client code
287
+ # to bind a queue to it.
288
+ #
289
+ # @option opts [Boolean] :internal (default false) If set, the exchange may not be used directly by publishers, but
290
+ # only when bound to other exchanges. Internal exchanges are used to
291
+ # construct wiring that is not visible to applications. This is a RabbitMQ-specific
292
+ # extension.
293
+ #
294
+ # @option opts [Boolean] :nowait (true) If set, the server will not respond to the method. The client should
295
+ # not wait for a reply method. If the server could not complete the
296
+ # method it will raise a channel or connection exception.
297
+ #
298
+ #
299
+ # @raise [AMQP::Error] Raised when exchange is redeclared with parameters different from original declaration.
300
+ # @raise [AMQP::Error] Raised when exchange is declared with :passive => true and the exchange does not exist.
301
+ #
302
+ #
303
+ # @example Using fanout exchange to deliver messages to multiple consumers
304
+ #
305
+ # # open up a channel
306
+ # # declare a fanout exchange
307
+ # # declare 3 queues, binds them
308
+ # # publish a message
309
+ #
310
+ # @see Exchange
311
+ # @see Exchange#initialize
312
+ # @see Channel#default_exchange
313
+ # @see http://bit.ly/hw2ELX AMQP 0.9.1 specification (Section 3.1.3.2)
314
+ #
315
+ # @return [Exchange]
316
+ # @api public
343
317
  def fanout(name = 'amq.fanout', opts = {}, &block)
344
- if exchange = self.exchanges.find { |exchange| exchange.name == name }
318
+ if exchange = find_exchange(name)
345
319
  extended_opts = Exchange.add_default_options(:fanout, name, opts, block)
346
320
 
347
321
  validate_parameters_match!(exchange, extended_opts)
348
322
 
349
323
  exchange
350
324
  else
351
- self.exchanges << Exchange.new(self, :fanout, name, opts, &block)
325
+ register_exchange(Exchange.new(self, :fanout, name, opts, &block))
352
326
  end
353
327
  end
354
328
 
355
- # Defines, intializes and returns an Exchange to act as an ingress
356
- # point for all published messages.
357
- #
358
- # == Topic
359
- # A topic exchange allows for messages to be published to an exchange
360
- # tagged with a specific routing key. The Exchange uses the routing key
361
- # to determine which queues to deliver the message. Wildcard matching
362
- # is allowed. The topic must be declared using dot notation to separate
363
- # each subtopic.
364
- #
365
- # This is the only exchange type to honor the +key+ hash key for all
366
- # cases.
367
- #
368
- # Any published message, regardless of its persistence setting, is thrown
369
- # away by the exchange when there are no queues bound to it.
370
- #
371
- # As part of the AMQP standard, each server _should_ predeclare a topic
372
- # exchange called 'amq.topic' (this is not required by the standard).
373
- # Allocating this exchange without a name _or_ with the empty string
374
- # will use the internal 'amq.topic' exchange.
375
- #
376
- # The classic example is delivering market data. When publishing market
377
- # data for stocks, we may subdivide the stream based on 2
378
- # characteristics: nation code and trading symbol. The topic tree for
379
- # Apple Computer would look like:
380
- # 'stock.us.aapl'
381
- # For a foreign stock, it may look like:
382
- # 'stock.de.dax'
383
- #
384
- # When publishing data to the exchange, bound queues subscribing to the
385
- # exchange indicate which data interests them by passing a routing key
386
- # for matching against the published routing key.
387
- #
388
- # EM.run do
389
- # exch = AMQP::Channel.topic("stocks")
390
- # keys = ['stock.us.aapl', 'stock.de.dax']
391
- #
392
- # EM.add_periodic_timer(1) do # every second
393
- # puts
394
- # exch.publish(10+rand(10), :routing_key => keys[rand(2)])
395
- # end
396
- #
397
- # # match against one dot-separated item
398
- # AMQP::Channel.queue('us stocks').bind(exch, :key => 'stock.us.*').subscribe do |price|
399
- # puts "us stock price [#{price}]"
400
- # end
401
- #
402
- # # match against multiple dot-separated items
403
- # AMQP::Channel.queue('all stocks').bind(exch, :key => 'stock.#').subscribe do |price|
404
- # puts "all stocks: price [#{price}]"
405
- # end
406
- #
407
- # # require exact match
408
- # AMQP::Channel.queue('only dax').bind(exch, :key => 'stock.de.dax').subscribe do |price|
409
- # puts "dax price [#{price}]"
410
- # end
411
- # end
412
- #
413
- # For matching, the '*' (asterisk) wildcard matches against one
414
- # dot-separated item only. The '#' wildcard (hash or pound symbol)
415
- # matches against 0 or more dot-separated items. If none of these
416
- # symbols are used, the exchange performs a comparison looking for an
417
- # exact match.
418
- #
419
- # == Options
420
- # * :passive => true | false (default false)
421
- # If set, the server will not create the exchange if it does not
422
- # already exist. The client can use this to check whether an exchange
423
- # exists without modifying the server state.
424
- #
425
- # * :durable => true | false (default false)
426
- # If set when creating a new exchange, the exchange will be marked as
427
- # durable. Durable exchanges remain active when a server restarts.
428
- # Non-durable exchanges (transient exchanges) are purged if/when a
429
- # server restarts.
430
- #
431
- # A transient exchange (the default) is stored in memory-only. The
432
- # exchange and all bindings will be lost on a server restart.
433
- # It makes no sense to publish a persistent message to a transient
434
- # exchange.
435
- #
436
- # Durable exchanges and their bindings are recreated upon a server
437
- # restart. Any published messages not routed to a bound queue are lost.
438
- #
439
- # * :auto_delete => true | false (default false)
440
- # If set, the exchange is deleted when all queues have finished
441
- # using it. The server waits for a short period of time before
442
- # determining the exchange is unused to give time to the client code
443
- # to bind a queue to it.
444
- #
445
- # If the exchange has been previously declared, this option is ignored
446
- # on subsequent declarations.
447
- #
448
- # * :internal => true | false (default false)
449
- # If set, the exchange may not be used directly by publishers, but
450
- # only when bound to other exchanges. Internal exchanges are used to
451
- # construct wiring that is not visible to applications.
452
- #
453
- # * :nowait => true | false (default true)
454
- # If set, the server will not respond to the method. The client should
455
- # not wait for a reply method. If the server could not complete the
456
- # method it will raise a channel or connection exception.
457
- #
458
- # == Exceptions
459
- # Doing any of these activities are illegal and will raise AMQP::Error.
460
- # * redeclare an already-declared exchange to a different type
461
- # * :passive => true and the exchange does not exist (NOT_FOUND)
462
- #
329
+
330
+ # Defines, intializes and returns a topic Exchange instance.
331
+ #
332
+ # Learn more about topic exchanges in {Exchange Exchange class documentation}.
333
+ #
334
+ # @param [String] name (amq.topic) Exchange name.
335
+ #
336
+ #
337
+ # @option opts [Boolean] :passive (false) If set, the server will not create the exchange if it does not
338
+ # already exist. The client can use this to check whether an exchange
339
+ # exists without modifying the server state.
340
+ #
341
+ # @option opts [Boolean] :durable (false) If set when creating a new exchange, the exchange will be marked as
342
+ # durable. Durable exchanges and their bindings are recreated upon a server
343
+ # restart (information about them is persisted). Non-durable (transient) exchanges
344
+ # do not survive if/when a server restarts (information about them is stored exclusively
345
+ # in RAM).
346
+ #
347
+ #
348
+ # @option opts [Boolean] :auto_delete (false) If set, the exchange is deleted when all queues have finished
349
+ # using it. The server waits for a short period of time before
350
+ # determining the exchange is unused to give time to the client code
351
+ # to bind a queue to it.
352
+ #
353
+ # @option opts [Boolean] :internal (default false) If set, the exchange may not be used directly by publishers, but
354
+ # only when bound to other exchanges. Internal exchanges are used to
355
+ # construct wiring that is not visible to applications. This is a RabbitMQ-specific
356
+ # extension.
357
+ #
358
+ # @option opts [Boolean] :nowait (true) If set, the server will not respond to the method. The client should
359
+ # not wait for a reply method. If the server could not complete the
360
+ # method it will raise a channel or connection exception.
361
+ #
362
+ #
363
+ # @raise [AMQP::Error] Raised when exchange is redeclared with parameters different from original declaration.
364
+ # @raise [AMQP::Error] Raised when exchange is declared with :passive => true and the exchange does not exist.
365
+ #
366
+ #
367
+ # @example Using topic exchange to deliver relevant news updates
368
+ # AMQP.connect do |connection|
369
+ # channel = AMQP::Channel.new(connection)
370
+ # exchange = channel.topic("pub/sub")
371
+ #
372
+ # # Subscribers.
373
+ # channel.queue("development").bind(exchange, :key => "technology.dev.#").subscribe do |payload|
374
+ # puts "A new dev post: '#{payload}'"
375
+ # end
376
+ # channel.queue("ruby").bind(exchange, :key => "technology.#.ruby").subscribe do |payload|
377
+ # puts "A new post about Ruby: '#{payload}'"
378
+ # end
379
+ #
380
+ # # Let's publish some data.
381
+ # exchange.publish "Ruby post", :routing_key => "technology.dev.ruby"
382
+ # exchange.publish "Erlang post", :routing_key => "technology.dev.erlang"
383
+ # exchange.publish "Sinatra post", :routing_key => "technology.web.ruby"
384
+ # exchange.publish "Jewelery post", :routing_key => "jewelery.ruby"
385
+ # end
386
+ #
387
+ #
388
+ # @example Using topic exchange to deliver geographically-relevant data
389
+ # AMQP.connect do |connection|
390
+ # channel = AMQP::Channel.new(connection)
391
+ # exchange = channel.topic("pub/sub")
392
+ #
393
+ # # Subscribers.
394
+ # channel.queue("americas.north").bind(exchange, :routing_key => "americas.north.#").subscribe do |headers, payload|
395
+ # puts "An update for North America: #{payload}, routing key is #{headers.routing_key}"
396
+ # end
397
+ # channel.queue("americas.south").bind(exchange, :routing_key => "americas.south.#").subscribe do |headers, payload|
398
+ # puts "An update for South America: #{payload}, routing key is #{headers.routing_key}"
399
+ # end
400
+ # channel.queue("us.california").bind(exchange, :routing_key => "americas.north.us.ca.*").subscribe do |headers, payload|
401
+ # puts "An update for US/California: #{payload}, routing key is #{headers.routing_key}"
402
+ # end
403
+ # channel.queue("us.tx.austin").bind(exchange, :routing_key => "#.tx.austin").subscribe do |headers, payload|
404
+ # puts "An update for Austin, TX: #{payload}, routing key is #{headers.routing_key}"
405
+ # end
406
+ # channel.queue("it.rome").bind(exchange, :routing_key => "europe.italy.rome").subscribe do |headers, payload|
407
+ # puts "An update for Rome, Italy: #{payload}, routing key is #{headers.routing_key}"
408
+ # end
409
+ # channel.queue("asia.hk").bind(exchange, :routing_key => "asia.southeast.hk.#").subscribe do |headers, payload|
410
+ # puts "An update for Hong Kong: #{payload}, routing key is #{headers.routing_key}"
411
+ # end
412
+ #
413
+ # exchange.publish("San Diego update", :routing_key => "americas.north.us.ca.sandiego").
414
+ # publish("Berkeley update", :routing_key => "americas.north.us.ca.berkeley").
415
+ # publish("San Francisco update", :routing_key => "americas.north.us.ca.sanfrancisco").
416
+ # publish("New York update", :routing_key => "americas.north.us.ny.newyork").
417
+ # publish("São Paolo update", :routing_key => "americas.south.brazil.saopaolo").
418
+ # publish("Hong Kong update", :routing_key => "asia.southeast.hk.hongkong").
419
+ # publish("Kyoto update", :routing_key => "asia.southeast.japan.kyoto").
420
+ # publish("Shanghai update", :routing_key => "asia.southeast.prc.shanghai").
421
+ # publish("Rome update", :routing_key => "europe.italy.roma").
422
+ # publish("Paris update", :routing_key => "europe.france.paris")
423
+ # end
424
+ #
425
+ # @see Exchange
426
+ # @see Exchange#initialize
427
+ # @see http://www.rabbitmq.com/faq.html#Binding-and-Routing RabbitMQ FAQ on routing & wildcards
428
+ # @see http://bit.ly/hw2ELX AMQP 0.9.1 specification (Section 3.1.3.3)
429
+ #
430
+ # @return [Exchange]
431
+ # @api public
463
432
  def topic(name = 'amq.topic', opts = {}, &block)
464
- if exchange = self.exchanges.find { |exchange| exchange.name == name }
433
+ if exchange = find_exchange(name)
465
434
  extended_opts = Exchange.add_default_options(:topic, name, opts, block)
466
435
 
467
436
  validate_parameters_match!(exchange, extended_opts)
468
437
 
469
438
  exchange
470
439
  else
471
- self.exchanges << Exchange.new(self, :topic, name, opts, &block)
440
+ register_exchange(Exchange.new(self, :topic, name, opts, &block))
472
441
  end
473
442
  end
474
443
 
475
- # Defines, intializes and returns an Exchange to act as an ingress
476
- # point for all published messages.
477
- #
478
- # == Headers
479
- # A headers exchange allows for messages to be published to an exchange
480
- #
481
- # Any published message, regardless of its persistence setting, is thrown
482
- # away by the exchange when there are no queues bound to it.
483
- #
484
- # As part of the AMQP standard, each server _should_ predeclare a headers
485
- # exchange called 'amq.match' (this is not required by the standard).
486
- # Allocating this exchange without a name _or_ with the empty string
487
- # will use the internal 'amq.match' exchange.
488
- #
489
- # TODO: The classic example is ...
490
- #
491
- # When publishing data to the exchange, bound queues subscribing to the
492
- # exchange indicate which data interests them by passing arguments
493
- # for matching against the headers in published messages. The
494
- # form of the matching can be controlled by the 'x-match' argument, which
495
- # may be 'any' or 'all'. If unspecified (in RabbitMQ at least), it defaults
496
- # to "all".
497
- #
498
- # A value of 'all' for 'x-match' implies that all values must match (i.e.
499
- # it does an AND of the headers ), while a value of 'any' implies that
500
- # at least one should match (ie. it does an OR).
501
- #
502
- # TODO: document behavior when either the binding or the message is missing
503
- # a header present in the other
504
- #
505
- # TODO: insert example
506
- #
507
- # == Options
508
- # * :passive => true | false (default false)
509
- # If set, the server will not create the exchange if it does not
510
- # already exist. The client can use this to check whether an exchange
511
- # exists without modifying the server state.
512
- #
513
- # * :durable => true | false (default false)
514
- # If set when creating a new exchange, the exchange will be marked as
515
- # durable. Durable exchanges remain active when a server restarts.
516
- # Non-durable exchanges (transient exchanges) are purged if/when a
517
- # server restarts.
518
- #
519
- # A transient exchange (the default) is stored in memory-only. The
520
- # exchange and all bindings will be lost on a server restart.
521
- # It makes no sense to publish a persistent message to a transient
522
- # exchange.
523
- #
524
- # Durable exchanges and their bindings are recreated upon a server
525
- # restart. Any published messages not routed to a bound queue are lost.
526
- #
527
- # * :auto_delete => true | false (default false)
528
- # If set, the exchange is deleted when all queues have finished
529
- # using it. The server waits for a short period of time before
530
- # determining the exchange is unused to give time to the client code
531
- # to bind a queue to it.
532
- #
533
- # If the exchange has been previously declared, this option is ignored
534
- # on subsequent declarations.
535
- #
536
- # * :internal => true | false (default false)
537
- # If set, the exchange may not be used directly by publishers, but
538
- # only when bound to other exchanges. Internal exchanges are used to
539
- # construct wiring that is not visible to applications.
540
- #
541
- # * :nowait => true | false (default true)
542
- # If set, the server will not respond to the method. The client should
543
- # not wait for a reply method. If the server could not complete the
544
- # method it will raise a channel or connection exception.
545
- #
546
- # == Exceptions
547
- # Doing any of these activities are illegal and will raise AMQP::Error.
548
- # * redeclare an already-declared exchange to a different type
549
- # * :passive => true and the exchange does not exist (NOT_FOUND)
550
- # * using a value other than "any" or "all" for "x-match"
551
- def headers(name = 'amq.match', opts = {}, &block)
552
- if exchange = self.exchanges.find { |exchange| exchange.name == name }
553
- extended_opts = Exchange.add_default_options(:headers, name, opts, block)
554
-
555
- validate_parameters_match!(exchange, extended_opts)
556
-
557
- exchange
558
- else
559
- self.exchanges << Exchange.new(self, :headers, name, opts, &block)
560
- end
561
- end
562
444
 
563
- # Queues store and forward messages. Queues can be configured in the server
564
- # or created at runtime. Queues must be attached to at least one exchange
565
- # in order to receive messages from publishers.
445
+ # Defines, intializes and returns a headers Exchange instance.
446
+ #
447
+ # Learn more about headers exchanges in {Exchange Exchange class documentation}.
566
448
  #
567
- # Like an Exchange, queue names starting with 'amq.' are reserved for
568
- # internal use. Attempts to create queue names in violation of this
569
- # reservation will raise AMQP::Error (ACCESS_REFUSED).
449
+ # @param [String] name (amq.match) Exchange name.
570
450
  #
571
- # It is not supported to create a queue without a name; some string
572
- # (even the empty string) must be passed in the +name+ parameter.
451
+ # @option opts [Boolean] :passive (false) If set, the server will not create the exchange if it does not
452
+ # already exist. The client can use this to check whether an exchange
453
+ # exists without modifying the server state.
573
454
  #
574
- # == Options
575
- # * :passive => true | false (default false)
576
- # If set, the server will not create the queue if it does not
577
- # already exist. The client can use this to check whether the queue
578
- # exists without modifying the server state.
455
+ # @option opts [Boolean] :durable (false) If set when creating a new exchange, the exchange will be marked as
456
+ # durable. Durable exchanges and their bindings are recreated upon a server
457
+ # restart (information about them is persisted). Non-durable (transient) exchanges
458
+ # do not survive if/when a server restarts (information about them is stored exclusively
459
+ # in RAM).
579
460
  #
580
- # * :durable => true | false (default false)
581
- # If set when creating a new queue, the queue will be marked as
582
- # durable. Durable queues remain active when a server restarts.
583
- # Non-durable queues (transient queues) are purged if/when a
584
- # server restarts. Note that durable queues do not necessarily
585
- # hold persistent messages, although it does not make sense to
586
- # send persistent messages to a transient queue (though it is
587
- # allowed).
588
461
  #
589
- # Again, note the durability property on a queue has no influence on
590
- # the persistence of published messages. A durable queue containing
591
- # transient messages will flush those messages on a restart.
462
+ # @option opts [Boolean] :auto_delete (false) If set, the exchange is deleted when all queues have finished
463
+ # using it. The server waits for a short period of time before
464
+ # determining the exchange is unused to give time to the client code
465
+ # to bind a queue to it.
592
466
  #
593
- # If the queue has already been declared, any redeclaration will
594
- # ignore this setting. A queue may only be declared durable the
595
- # first time when it is created.
467
+ # @option opts [Boolean] :internal (default false) If set, the exchange may not be used directly by publishers, but
468
+ # only when bound to other exchanges. Internal exchanges are used to
469
+ # construct wiring that is not visible to applications. This is a RabbitMQ-specific
470
+ # extension.
596
471
  #
597
- # * :exclusive => true | false (default false)
598
- # Exclusive queues may only be consumed from by the current connection.
599
- # Setting the 'exclusive' flag always implies 'auto-delete'. Only a
600
- # single consumer is allowed to remove messages from this queue.
472
+ # @option opts [Boolean] :nowait (true) If set, the server will not respond to the method. The client should
473
+ # not wait for a reply method. If the server could not complete the
474
+ # method it will raise a channel or connection exception.
601
475
  #
602
- # The default is a shared queue. Multiple clients may consume messages
603
- # from this queue.
604
476
  #
605
- # Attempting to redeclare an already-declared queue as :exclusive => true
606
- # will raise AMQP::Error.
477
+ # @raise [AMQP::Error] Raised when exchange is redeclared with parameters different from original declaration.
478
+ # @raise [AMQP::Error] Raised when exchange is declared with :passive => true and the exchange does not exist.
607
479
  #
608
- # * :auto_delete = true | false (default false)
609
- # If set, the queue is deleted when all consumers have finished
610
- # using it. Last consumer can be cancelled either explicitly or because
611
- # its channel is closed. If there was no consumer ever on the queue, it
612
- # won't be deleted.
613
480
  #
614
- # The server waits for a short period of time before
615
- # determining the queue is unused to give time to the client code
616
- # to bind a queue to it.
481
+ # @example Using fanout exchange to deliver messages to multiple consumers
617
482
  #
618
- # If the queue has been previously declared, this option is ignored
619
- # on subsequent declarations.
483
+ # # TODO
620
484
  #
621
- # Any remaining messages in the queue will be purged when the queue
622
- # is deleted regardless of the message's persistence setting.
623
485
  #
624
- # * :nowait => true | false (default true)
625
- # If set, the server will not respond to the method. The client should
626
- # not wait for a reply method. If the server could not complete the
627
- # method it will raise a channel or connection exception.
486
+ # @see Exchange
487
+ # @see Exchange#initialize
488
+ # @see Channel#default_exchange
489
+ # @see http://bit.ly/hw2ELX AMQP 0.9.1 specification (Section 3.1.3.3)
628
490
  #
629
- def queue(name, opts = {}, &block)
630
- raise ArgumentError, "queue name must not be nil. Use '' (empty string) for server-named queues." if name.nil?
491
+ # @return [Exchange]
492
+ # @api public
493
+ def headers(name = 'amq.match', opts = {}, &block)
494
+ if exchange = find_exchange(name)
495
+ extended_opts = Exchange.add_default_options(:headers, name, opts, block)
496
+
497
+ validate_parameters_match!(exchange, extended_opts)
498
+
499
+ exchange
500
+ else
501
+ register_exchange(Exchange.new(self, :headers, name, opts, &block))
502
+ end
503
+ end
504
+
631
505
 
632
- if name && !name.empty? && (queue = self.queues.find { |queue| queue.name == name })
506
+ # Declares and returns a Queue instance associated with this channel. See {Queue Queue class documentation} for
507
+ # more information about queues.
508
+ #
509
+ # To make broker generate queue name for you (a classic example is exclusive
510
+ # queues that are only used for a short period of time), pass empty string
511
+ # as name value. Then queue will get it's name as soon as broker's response
512
+ # (queue.declare-ok) arrives. Note that in this case, block is required.
513
+ #
514
+ #
515
+ # Like for exchanges, queue names starting with 'amq.' cannot be modified and
516
+ # should not be used by applications.
517
+ #
518
+ # @example Declaring a queue in a mail delivery app using Channel#queue without a block
519
+ # AMQP.connect do |connection|
520
+ # AMQP::Channel.new(connection) do |ch|
521
+ # # message producers will be able to send messages to this queue
522
+ # # using direct exchange and routing key = "mail.delivery"
523
+ # queue = ch.queue("mail.delivery", :durable => true)
524
+ # queue.subscribe do |headers, payload|
525
+ # # ...
526
+ # end
527
+ # end
528
+ # end
529
+ #
530
+ # @example Declaring a server-named exclusive queue that receives all messages related to events, using a block.
531
+ # AMQP.connect do |connection|
532
+ # AMQP::Channel.new(connection) do |ch|
533
+ # # message producers will be able to send messages to this queue
534
+ # # using amq.topic exchange with routing keys that begin with "events"
535
+ # ch.queue("", :exclusive => true) do |queue|
536
+ # queue.bind(ch.exchange("amq.topic"), :routing_key => "events.#").subscribe do |headers, payload|
537
+ # # ...
538
+ # end
539
+ # end
540
+ # end
541
+ # end
542
+ #
543
+ # @param [String] name Queue name. If you want a server-named queue, you can omit the name (note that in this case, using block is mandatory).
544
+ # See {Queue Queue class documentation} for discussion of queue lifecycles and when use of server-named queues
545
+ # is optimal.
546
+ #
547
+ # @option opts [Boolean] :passive (false) If set, the server will not create the exchange if it does not
548
+ # already exist. The client can use this to check whether an exchange
549
+ # exists without modifying the server state.
550
+ #
551
+ # @option opts [Boolean] :durable (false) If set when creating a new exchange, the exchange will be marked as
552
+ # durable. Durable exchanges and their bindings are recreated upon a server
553
+ # restart (information about them is persisted). Non-durable (transient) exchanges
554
+ # do not survive if/when a server restarts (information about them is stored exclusively
555
+ # in RAM). Any remaining messages in the queue will be purged when the queue
556
+ # is deleted regardless of the message's persistence setting.
557
+ #
558
+ #
559
+ # @option opts [Boolean] :auto_delete (false) If set, the exchange is deleted when all queues have finished
560
+ # using it. The server waits for a short period of time before
561
+ # determining the exchange is unused to give time to the client code
562
+ # to bind a queue to it.
563
+ #
564
+ # @option opts [Boolean] :exclusive (false) Exclusive queues may only be used by a single connection.
565
+ # Exclusivity also implies that queue is automatically deleted when connection
566
+ # is closed. Only one consumer is allowed to remove messages from exclusive queue.
567
+ #
568
+ # @option opts [Boolean] :nowait (true) If set, the server will not respond to the method. The client should
569
+ # not wait for a reply method. If the server could not complete the
570
+ # method it will raise a channel or connection exception.
571
+ #
572
+ #
573
+ # @raise [AMQP::Error] Raised when queue is redeclared with parameters different from original declaration.
574
+ # @raise [AMQP::Error] Raised when queue is declared with :passive => true and the queue does not exist.
575
+ # @raise [AMQP::Error] Raised when queue is declared with :exclusive => true and queue with that name already exist.
576
+ #
577
+ #
578
+ # @yield [queue, declare_ok] Yields successfully declared queue instance and AMQP method (queue.declare-ok) instance. The latter is optional.
579
+ # @yieldparam [Queue] queue Queue that is successfully declared and is ready to be used.
580
+ # @yieldparam [AMQP::Protocol::Queue::DeclareOk] declare_ok AMQP queue.declare-ok) instance.
581
+ #
582
+ # @see Queue
583
+ # @see Queue#initialize
584
+ # @see http://bit.ly/hw2ELX AMQP 0.9.1 specification (Section 2.1.4)
585
+ #
586
+ # @return [Queue]
587
+ # @api public
588
+ def queue(name = AMQ::Protocol::EMPTY_STRING, opts = {}, &block)
589
+ if name && !name.empty? && (queue = find_queue(name))
633
590
  extended_opts = Queue.add_default_options(name, opts, block)
634
591
 
635
592
  validate_parameters_match!(queue, extended_opts)
636
593
 
637
594
  queue
638
595
  else
639
- q = Queue.new(self, name, opts, &block)
640
- self.queues << q
596
+ queue = if block.nil?
597
+ Queue.new(self, name, opts)
598
+ else
599
+ shim = Proc.new { |q, method|
600
+ queue = find_queue(method.queue)
601
+ if block.arity == 1
602
+ block.call(queue)
603
+ else
604
+ block.call(queue, method.consumer_count, method.message_count)
605
+ end
606
+ }
607
+ Queue.new(self, name, opts, &shim)
608
+ end
641
609
 
642
- q
610
+ register_queue(queue)
643
611
  end
644
612
  end
645
613
 
614
+ # Returns true if channel is not closed.
615
+ # @return [Boolean]
616
+ # @api public
617
+ def open?
618
+ self.status == :opened || self.status == :opening
619
+ end # open?
620
+
621
+
646
622
  def queue!(name, opts = {}, &block)
647
- self.queues.add! Queue.new(self, name, opts, &block)
623
+ # TODO
624
+ raise NotImplementedError.new
648
625
  end
649
626
 
650
- # Takes a channel, queue and optional object.
627
+
628
+ # Instantiates and returns an RPC instance associated with this channel.
651
629
  #
652
630
  # The optional object may be a class name, module name or object
653
631
  # instance. When given a class or module name, the object is instantiated
@@ -666,288 +644,107 @@ module AMQP
666
644
  # there is a valid destination. Failure to do so will just enqueue
667
645
  # marshalled messages that are never consumed.
668
646
  #
669
- # EM.run do
670
- # server = AMQP::Channel.new.rpc('hash table node', Hash)
647
+ # @example Use of RPC
671
648
  #
672
- # client = AMQP::Channel.new.rpc('hash table node')
673
- # client[:now] = Time.now
674
- # client[:one] = 1
649
+ # # TODO
675
650
  #
676
- # client.values do |res|
677
- # p 'client', :values => res
678
- # end
679
- #
680
- # client.keys do |res|
681
- # p 'client', :keys => res
682
- # EM.stop_event_loop
683
- # end
684
- # end
685
651
  #
652
+ # @param [String, Queue] Queue to be used by RPC server.
653
+ # @return [RPC]
654
+ # @api public
686
655
  def rpc(name, obj = nil)
687
- rpcs[name] ||= RPC.new(self, name, obj)
688
- end
689
-
690
- def close(&block)
691
- @on_close = block
692
- if @deferred_status == :succeeded
693
- send Protocol::Channel::Close.new(:reply_code => 200,
694
- :reply_text => 'bye',
695
- :method_id => 0,
696
- :class_id => 0)
697
- else
698
- @closing = true
699
- end
656
+ RPC.new(self, name, obj)
700
657
  end
701
658
 
702
- # Define a message and callback block to be executed on all
703
- # errors.
704
- def self.error msg = nil, &blk
705
- if blk
706
- @error_callback = blk
707
- else
708
- @error_callback.call(msg) if @error_callback and msg
709
- end
710
- end
711
-
712
- def prefetch(size)
713
- @prefetch_size = size
714
-
715
- send Protocol::Basic::Qos.new(:prefetch_size => 0, :prefetch_count => size, :global => false)
716
659
 
717
- self
718
- end
719
-
720
- # Asks the broker to redeliver all unacknowledged messages on this
721
- # channel.
660
+ # Define a callback to be run on channel-level exception.
722
661
  #
723
- # * requeue (default false)
724
- # If this parameter is false, the message will be redelivered to the original recipient.
725
- # If this flag is true, the server will attempt to requeue the message, potentially then
726
- # delivering it to an alternative subscriber.
662
+ # @param [String] msg Error message
727
663
  #
728
- def recover(requeue = false)
729
- send Protocol::Basic::Recover.new(:requeue => requeue)
730
- self
664
+ # @api public
665
+ def self.error(msg = nil, &block)
666
+ # TODO
667
+ raise NotImplementedError.new
731
668
  end
732
669
 
733
- # Returns a hash of all the exchange proxy objects.
670
+ # @param [Fixnum] size
671
+ # @param [Boolean] global (false)
734
672
  #
735
- # Not typically called by client code.
736
- def exchanges
737
- @exchanges ||= AMQP::Collection.new
738
- end
739
-
740
- # Returns a hash of all the queue proxy objects.
673
+ # @return [Channel] self
741
674
  #
742
- # Not typically called by client code.
743
- def queues
744
- @queues ||= AMQP::Collection.new
745
- end
675
+ # @api public
676
+ def prefetch(size, global = false, &block)
677
+ # RabbitMQ as of 2.3.1 does not support prefetch_size.
678
+ self.qos(0, size, global, &block)
746
679
 
747
- def get_queue
748
- if block_given?
749
- @get_queue_mutex.synchronize {
750
- yield( @get_queue ||= [] )
751
- }
752
- end
680
+ self
753
681
  end
754
682
 
683
+
684
+
755
685
  # Returns a hash of all rpc proxy objects.
756
686
  #
757
- # Not typically called by client code.
687
+ # Most of the time, this method is not
688
+ # called by application code.
689
+ # @api plugin
758
690
  def rpcs
759
- @rcps ||= {}
691
+ @rpcs.values
760
692
  end
761
693
 
762
- # Queue objects keyed on their consumer tags.
763
- #
764
- # Not typically called by client code.
765
- def consumers
766
- @consumers ||= {}
767
- end
768
-
769
- def reset
770
- @deferred_status = nil
771
- @channel = nil
772
-
773
- @queues_awaiting_declare_ok = Array.new
774
-
775
- initialize @connection
776
-
777
- @consumers = {}
778
-
779
- exs = @exchanges
780
- @exchanges = AMQP::Collection.new
781
- exs.each { |e| e.reset } if exs
782
-
783
- qus = @queues
784
- @queues = AMQP::Collection.new
785
- qus.each { |q| q.reset } if qus
786
-
787
- prefetch(@prefetch_size) if @prefetch_size
788
- end
789
694
 
790
695
 
791
696
  #
792
697
  # Implementation
793
698
  #
794
699
 
795
- # May raise a AMQP::Channel::Error exception when the frame payload contains a
796
- # Protocol::Channel::Close object.
700
+
701
+ # Resets channel state (for example, list of registered queue objects and so on).
797
702
  #
798
- # This usually occurs when a client attempts to perform an illegal
799
- # operation. A short, and incomplete, list of potential illegal operations
800
- # follows:
801
- # * publish a message to a deleted exchange (NOT_FOUND)
802
- # * declare an exchange using the reserved 'amq.' naming structure (ACCESS_REFUSED)
703
+ # Most of the time, this method is not
704
+ # called by application code.
803
705
  #
804
- def process_frame(frame)
805
- log :received, frame
706
+ # @private
707
+ # @api plugin
708
+ def reset
709
+ # TODO
710
+ raise NotImplementedError.new
711
+ end
806
712
 
807
- case frame
808
- when Frame::Header
809
- @header = frame.payload
810
- @body = ''
811
- check_content_completion
713
+ # @private
714
+ # @api private
715
+ def self.channel_id_mutex
716
+ @channel_id_mutex ||= Mutex.new
717
+ end
812
718
 
813
- when Frame::Body
814
- @body << frame.payload
815
- check_content_completion
719
+ # @return [Fixnum]
720
+ # @private
721
+ # @api private
722
+ def self.next_channel_id
723
+ channel_id_mutex.synchronize do
724
+ @last_channel_id ||= 0
725
+ @last_channel_id += 1
816
726
 
817
- when Frame::Method
818
- handle_method(frame)
727
+ @last_channel_id
819
728
  end
820
- end # process_frame
729
+ end
821
730
 
731
+ # @private
732
+ # @api plugin
733
+ def register_rpc(rpc)
734
+ raise ArgumentError, "argument is nil!" unless rpc
822
735
 
823
- def send(*args)
824
- conn.callback { |c|
825
- @_send_mutex.synchronize do
826
- args.each do |data|
827
- unless self.closed?
828
- data.ticket = @ticket if @ticket and data.respond_to? :ticket=
829
- log :sending, data
830
- c.send data, :channel => @channel
831
- else
832
- unless data.class == AMQP::Protocol::Channel::CloseOk
833
- raise ChannelClosedError.new(self)
834
- end
835
- end
836
- end
837
- end
838
- }
839
- end # send
736
+ @rpcs[rpc.name] = rpc
737
+ end # register_rpc(rpc)
840
738
 
739
+ # @private
740
+ # @api plugin
741
+ def find_rpc(name)
742
+ @rpcs[name]
743
+ end
841
744
 
842
- def check_content_completion
843
- if @body.length >= @header.size
844
- @header.properties.update(@method.arguments)
845
- @consumer.receive @header, @body if @consumer
846
- @body = @header = @consumer = @method = nil
847
- end
848
- end # check_content_completion
849
745
 
850
746
  protected
851
747
 
852
- def handle_method(frame)
853
- case method = frame.payload
854
- when Protocol::Channel::OpenOk
855
- send Protocol::Access::Request.new(:realm => '/data',
856
- :read => true,
857
- :write => true,
858
- :active => true,
859
- :passive => true)
860
-
861
- when Protocol::Access::RequestOk
862
- @ticket = method.ticket
863
- callback {
864
- send Protocol::Channel::Close.new(:reply_code => 200,
865
- :reply_text => 'bye',
866
- :method_id => 0,
867
- :class_id => 0)
868
- } if @closing
869
- succeed
870
-
871
- when Protocol::Basic::CancelOk
872
- if @consumer = consumers[ method.consumer_tag ]
873
- @consumer.cancelled
874
- else
875
- AMQP::Channel.error "Basic.CancelOk for invalid consumer tag: #{method.consumer_tag}"
876
- end
877
-
878
- when Protocol::Exchange::DeclareOk
879
- # We can't use exchanges[method.exchange] because if the name would
880
- # be an empty string, then AMQP broker generated a random one.
881
- exchanges = self.exchanges.select { |exchange| exchange.opts[:nowait].eql?(false) }
882
- exchange = exchanges.reverse.find { |exchange| exchange.status.eql?(:unfinished) }
883
- exchange.receive_response method
884
-
885
- when Protocol::Queue::DeclareOk
886
- queue = @queues_awaiting_declare_ok.shift
887
-
888
- queue.receive_status method
889
- when Protocol::Queue::BindOk
890
- # We can't use queues[method.queue] because if the name would
891
- # be an empty string, then AMQP broker generated a random one.
892
- queues = self.queues.select { |queue| queue.sync_bind }
893
- queue = queues.reverse.find { |queue| queue.status.eql?(:unbound) }
894
- queue.after_bind method
895
-
896
- when Protocol::Basic::Deliver, Protocol::Basic::GetOk
897
- @method = method
898
- @header = nil
899
- @body = ''
900
-
901
- if method.is_a? Protocol::Basic::GetOk
902
- @consumer = get_queue { |q| q.shift }
903
- AMQP::Channel.error "No pending Basic.GetOk requests" unless @consumer
904
- else
905
- @consumer = consumers[ method.consumer_tag ]
906
- AMQP::Channel.error "Basic.Deliver for invalid consumer tag: #{method.consumer_tag}" unless @consumer
907
- end
908
-
909
- when Protocol::Basic::GetEmpty
910
- if @consumer = get_queue { |q| q.shift }
911
- @consumer.receive nil, nil
912
- else
913
- AMQP::Channel.error "Basic.GetEmpty for invalid consumer"
914
- end
915
-
916
- when Protocol::Channel::Close
917
- @status = :closed
918
- AMQP::Channel.error "#{method.reply_text} in #{Protocol.classes[method.class_id].methods[method.method_id]} on #{@channel}"
919
-
920
- when Protocol::Channel::CloseOk
921
- @status = :closed
922
- @on_close && @on_close.call(self)
923
-
924
- @closing = false
925
- conn.callback { |c|
926
- c.channels.delete @channel
927
- c.close if c.channels.empty?
928
- }
929
-
930
- when Protocol::Basic::ConsumeOk
931
- if @consumer = consumers[ method.consumer_tag ]
932
- @consumer.confirm_subscribe
933
- else
934
- AMQP::Channel.error "Basic.ConsumeOk for invalid consumer tag: #{method.consumer_tag}"
935
- end
936
- when Protocol::Basic::Return
937
- @method = method
938
- end # case
939
- end # handle_method(frame)
940
-
941
-
942
-
943
- private
944
-
945
- def log(*args)
946
- return unless AMQP::logging
947
- pp args
948
- puts
949
- end # log
950
-
951
748
  def validate_parameters_match!(entity, parameters)
952
749
  unless entity.opts == parameters || parameters[:passive]
953
750
  raise AMQP::IncompatibleOptionsError.new(entity.name, entity.opts, parameters)
@@ -955,28 +752,3 @@ module AMQP
955
752
  end # validate_parameters_match!(entity, parameters)
956
753
  end # Channel
957
754
  end # AMQP
958
-
959
-
960
- MQ = AMQP::Channel
961
-
962
- #
963
- # Backwards compatibility with 0.6.x
964
- #
965
-
966
- class MQ
967
- # unique identifier
968
- def MQ.id
969
- Thread.current[:mq_id] ||= "#{`hostname`.strip}-#{Process.pid}-#{Thread.current.object_id}"
970
- end
971
-
972
- def MQ.default
973
- # TODO: clear this when connection is closed
974
- Thread.current[:mq] ||= MQ.new
975
- end
976
-
977
- # Allows for calls to all MQ instance methods. This implicitly calls
978
- # MQ.new so that a new channel is allocated for subsequent operations.
979
- def MQ.method_missing(meth, *args, &blk)
980
- MQ.default.__send__(meth, *args, &blk)
981
- end
982
- end