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.
- data/.gitignore +3 -4
- data/.travis.yml +3 -5
- data/.yardopts +6 -0
- data/CHANGELOG +26 -7
- data/Gemfile +15 -7
- data/README.textile +216 -0
- data/Rakefile +0 -6
- data/amqp.gemspec +14 -4
- data/bin/jenkins.sh +27 -0
- data/bin/set_test_suite_realms_up.sh +16 -2
- data/docs/VendorSpecificExtensions.textile +32 -0
- data/examples/extensions/rabbitmq/publisher_confirmations_with_transient_messages.rb +53 -0
- data/examples/hello_world.rb +29 -0
- data/examples/real-world/task-queue/README.textile +3 -0
- data/examples/real-world/task-queue/consumer.rb +27 -0
- data/examples/real-world/task-queue/producer.rb +22 -0
- data/examples/various/ack.rb +70 -0
- data/examples/various/automatic_binding_for_default_direct_exchange.rb +53 -0
- data/examples/various/basic_get.rb +65 -0
- data/examples/various/callbacks.rb +45 -0
- data/examples/various/clock.rb +74 -0
- data/examples/various/declare_a_queue_without_assignment.rb +46 -0
- data/examples/various/declare_an_exchange_without_assignment.rb +46 -0
- data/examples/various/hashtable.rb +60 -0
- data/examples/{logger.rb → various/logger.rb} +9 -7
- data/examples/{multiclock.rb → various/multiclock.rb} +15 -17
- data/examples/various/open_channel_without_assignment.rb +34 -0
- data/examples/various/pingpong.rb +53 -0
- data/examples/various/primes-simple.rb +29 -0
- data/examples/various/primes.rb +76 -0
- data/examples/various/pubsub.rb +43 -0
- data/examples/various/queue_status.rb +58 -0
- data/examples/various/stocks.rb +59 -0
- data/examples/various/weather_updates.rb +63 -0
- data/lib/amqp.rb +11 -2
- data/lib/amqp/basic_client.rb +23 -54
- data/lib/amqp/channel.rb +577 -805
- data/lib/amqp/client.rb +37 -275
- data/lib/amqp/connection.rb +165 -93
- data/lib/amqp/deprecated/fork.rb +15 -0
- data/lib/amqp/deprecated/logger.rb +99 -0
- data/lib/amqp/deprecated/mq.rb +20 -0
- data/lib/amqp/deprecated/rpc.rb +168 -0
- data/lib/amqp/exchange.rb +409 -281
- data/lib/amqp/extensions/rabbitmq.rb +1 -0
- data/lib/amqp/header.rb +41 -17
- data/lib/amqp/logger.rb +10 -84
- data/lib/amqp/queue.rb +457 -320
- data/lib/amqp/rpc.rb +11 -107
- data/lib/amqp/version.rb +1 -1
- data/lib/mq.rb +2 -1
- data/lib/mq/logger.rb +2 -0
- data/lib/mq/rpc.rb +2 -0
- data/spec/integration/authentication_spec.rb +36 -40
- data/spec/integration/automatic_binding_for_default_direct_exchange_spec.rb +3 -5
- data/spec/integration/basic_get_spec.rb +91 -0
- data/spec/integration/channel_close_spec.rb +5 -5
- data/spec/integration/exchange_declaration_spec.rb +6 -53
- data/spec/integration/extensions/basic_return_spec.rb +47 -0
- data/spec/integration/queue_declaration_spec.rb +14 -17
- data/spec/integration/queue_exclusivity_spec.rb +49 -48
- data/spec/integration/reply_queue_communication_spec.rb +6 -4
- data/spec/integration/store_and_forward_spec.rb +9 -36
- data/spec/integration/topic_subscription_spec.rb +1 -1
- data/spec/integration/workload_distribution_spec.rb +1 -0
- data/spec/spec_helper.rb +69 -43
- data/spec/unit/amqp/connection_spec.rb +27 -23
- data/tasks.rb +11 -0
- metadata +124 -95
- data/README.md +0 -156
- data/TODO +0 -30
- data/amqp.pre.gemspec +0 -6
- data/examples/ack.rb +0 -47
- data/examples/automatic_binding_for_default_direct_exchange.rb +0 -65
- data/examples/callbacks.rb +0 -40
- data/examples/clock.rb +0 -65
- data/examples/default_channel.rb +0 -19
- data/examples/hashtable.rb +0 -61
- data/examples/immediately_bind_a_server_named_queue.rb +0 -38
- data/examples/internal.rb +0 -51
- data/examples/issues/issue_75.rb +0 -21
- data/examples/issues/issue_94.rb +0 -23
- data/examples/pingpong.rb +0 -54
- data/examples/pop.rb +0 -45
- data/examples/primes-simple.rb +0 -21
- data/examples/primes.rb +0 -101
- data/examples/simple.rb +0 -81
- data/examples/stocks.rb +0 -67
- data/gemfiles/eventmachine-pre +0 -24
- data/lib/amqp/buffer.rb +0 -272
- data/lib/amqp/collection.rb +0 -60
- data/lib/amqp/frame.rb +0 -68
- data/lib/amqp/protocol.rb +0 -163
- data/lib/amqp/server.rb +0 -101
- data/lib/amqp/spec.rb +0 -832
- data/protocol/amqp-0.8.json +0 -617
- data/protocol/amqp-0.8.xml +0 -3908
- data/protocol/codegen.rb +0 -175
- data/protocol/doc.txt +0 -281
- data/research/api.rb +0 -52
- data/research/primes-forked.rb +0 -65
- data/research/primes-processes.rb +0 -137
- data/research/primes-threaded.rb +0 -51
- data/spec/integration/queue_status_spec.rb +0 -44
- data/spec/unit/amqp/buffer_spec.rb +0 -178
- data/spec/unit/amqp/client_spec.rb +0 -102
- data/spec/unit/amqp/collection_spec.rb +0 -144
- data/spec/unit/amqp/frame_spec.rb +0 -60
- 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
|
data/lib/amqp.rb
CHANGED
@@ -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/
|
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"
|
data/lib/amqp/basic_client.rb
CHANGED
@@ -1,58 +1,27 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require "
|
4
|
-
require "amqp/protocol"
|
3
|
+
require "amq/client/adapters/event_machine"
|
5
4
|
|
6
5
|
module AMQP
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
data/lib/amqp/channel.rb
CHANGED
@@ -1,248 +1,221 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require "amqp/
|
3
|
+
require "amqp/exchange"
|
4
|
+
require "amqp/queue"
|
4
5
|
|
5
6
|
module AMQP
|
6
|
-
#
|
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
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
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
|
-
#
|
31
|
-
# require 'mq'
|
17
|
+
# h2. RabbitMQ extensions.
|
32
18
|
#
|
33
|
-
#
|
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
|
-
#
|
39
|
-
# p args
|
40
|
-
# end
|
23
|
+
# h2. Key methods
|
41
24
|
#
|
42
|
-
#
|
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
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
27
|
+
# * {Channel#queue}
|
28
|
+
# * {Channel#default_exchange}
|
29
|
+
# * {Channel#direct}
|
30
|
+
# * {Channel#fanout}
|
31
|
+
# * {Channel#topic}
|
32
|
+
# * {Channel#close}
|
51
33
|
#
|
52
|
-
#
|
53
|
-
#
|
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
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
# end
|
37
|
+
# * {Channel#queue}
|
38
|
+
# * {Channel#default_exchange}
|
39
|
+
# * {Channel#direct}
|
40
|
+
# * {Channel#fanout}
|
41
|
+
# * {Channel#topic}
|
64
42
|
#
|
65
|
-
#
|
66
|
-
#
|
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
|
-
#
|
74
|
-
|
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
|
-
#
|
50
|
+
# API
|
106
51
|
#
|
107
52
|
|
108
|
-
|
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
|
-
#
|
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
|
-
#
|
117
|
-
#
|
118
|
-
#
|
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
|
-
#
|
81
|
+
# @example Instantiating a channel for explicitly given connection
|
121
82
|
#
|
122
|
-
#
|
123
|
-
#
|
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
|
-
#
|
130
|
-
#
|
131
|
-
#
|
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
|
-
|
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
|
-
@
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
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
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
#
|
165
|
-
#
|
166
|
-
#
|
167
|
-
#
|
168
|
-
#
|
169
|
-
#
|
170
|
-
#
|
171
|
-
#
|
172
|
-
#
|
173
|
-
#
|
174
|
-
#
|
175
|
-
#
|
176
|
-
#
|
177
|
-
#
|
178
|
-
#
|
179
|
-
#
|
180
|
-
#
|
181
|
-
#
|
182
|
-
#
|
183
|
-
#
|
184
|
-
#
|
185
|
-
#
|
186
|
-
#
|
187
|
-
#
|
188
|
-
#
|
189
|
-
#
|
190
|
-
#
|
191
|
-
#
|
192
|
-
#
|
193
|
-
#
|
194
|
-
#
|
195
|
-
#
|
196
|
-
#
|
197
|
-
#
|
198
|
-
#
|
199
|
-
#
|
200
|
-
#
|
201
|
-
#
|
202
|
-
#
|
203
|
-
#
|
204
|
-
#
|
205
|
-
#
|
206
|
-
#
|
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 =
|
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
|
-
|
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
|
-
#
|
262
|
-
#
|
263
|
-
#
|
264
|
-
#
|
265
|
-
#
|
266
|
-
#
|
267
|
-
#
|
268
|
-
#
|
269
|
-
#
|
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 =
|
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
|
-
|
325
|
+
register_exchange(Exchange.new(self, :fanout, name, opts, &block))
|
352
326
|
end
|
353
327
|
end
|
354
328
|
|
355
|
-
|
356
|
-
#
|
357
|
-
#
|
358
|
-
#
|
359
|
-
#
|
360
|
-
#
|
361
|
-
#
|
362
|
-
#
|
363
|
-
#
|
364
|
-
#
|
365
|
-
#
|
366
|
-
#
|
367
|
-
#
|
368
|
-
#
|
369
|
-
#
|
370
|
-
#
|
371
|
-
#
|
372
|
-
#
|
373
|
-
#
|
374
|
-
#
|
375
|
-
#
|
376
|
-
#
|
377
|
-
#
|
378
|
-
#
|
379
|
-
#
|
380
|
-
#
|
381
|
-
#
|
382
|
-
#
|
383
|
-
#
|
384
|
-
#
|
385
|
-
#
|
386
|
-
#
|
387
|
-
#
|
388
|
-
#
|
389
|
-
#
|
390
|
-
#
|
391
|
-
#
|
392
|
-
#
|
393
|
-
#
|
394
|
-
#
|
395
|
-
#
|
396
|
-
#
|
397
|
-
#
|
398
|
-
#
|
399
|
-
#
|
400
|
-
#
|
401
|
-
#
|
402
|
-
#
|
403
|
-
#
|
404
|
-
#
|
405
|
-
#
|
406
|
-
#
|
407
|
-
#
|
408
|
-
#
|
409
|
-
#
|
410
|
-
#
|
411
|
-
#
|
412
|
-
#
|
413
|
-
#
|
414
|
-
#
|
415
|
-
#
|
416
|
-
#
|
417
|
-
#
|
418
|
-
#
|
419
|
-
#
|
420
|
-
#
|
421
|
-
#
|
422
|
-
#
|
423
|
-
#
|
424
|
-
#
|
425
|
-
#
|
426
|
-
#
|
427
|
-
#
|
428
|
-
#
|
429
|
-
#
|
430
|
-
#
|
431
|
-
#
|
432
|
-
#
|
433
|
-
#
|
434
|
-
#
|
435
|
-
#
|
436
|
-
#
|
437
|
-
#
|
438
|
-
#
|
439
|
-
#
|
440
|
-
#
|
441
|
-
#
|
442
|
-
#
|
443
|
-
#
|
444
|
-
#
|
445
|
-
#
|
446
|
-
#
|
447
|
-
#
|
448
|
-
#
|
449
|
-
#
|
450
|
-
#
|
451
|
-
#
|
452
|
-
#
|
453
|
-
#
|
454
|
-
#
|
455
|
-
#
|
456
|
-
#
|
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 =
|
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
|
-
|
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
|
-
#
|
564
|
-
#
|
565
|
-
#
|
445
|
+
# Defines, intializes and returns a headers Exchange instance.
|
446
|
+
#
|
447
|
+
# Learn more about headers exchanges in {Exchange Exchange class documentation}.
|
566
448
|
#
|
567
|
-
#
|
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
|
-
#
|
572
|
-
#
|
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
|
-
#
|
575
|
-
#
|
576
|
-
#
|
577
|
-
#
|
578
|
-
#
|
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
|
-
#
|
590
|
-
#
|
591
|
-
#
|
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
|
594
|
-
#
|
595
|
-
#
|
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
|
-
#
|
598
|
-
#
|
599
|
-
#
|
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
|
-
#
|
606
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
625
|
-
#
|
626
|
-
#
|
627
|
-
#
|
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
|
-
|
630
|
-
|
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
|
-
|
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
|
-
|
640
|
-
|
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
|
-
|
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
|
-
|
623
|
+
# TODO
|
624
|
+
raise NotImplementedError.new
|
648
625
|
end
|
649
626
|
|
650
|
-
|
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
|
-
#
|
670
|
-
# server = AMQP::Channel.new.rpc('hash table node', Hash)
|
647
|
+
# @example Use of RPC
|
671
648
|
#
|
672
|
-
#
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
|
729
|
-
|
730
|
-
|
664
|
+
# @api public
|
665
|
+
def self.error(msg = nil, &block)
|
666
|
+
# TODO
|
667
|
+
raise NotImplementedError.new
|
731
668
|
end
|
732
669
|
|
733
|
-
#
|
670
|
+
# @param [Fixnum] size
|
671
|
+
# @param [Boolean] global (false)
|
734
672
|
#
|
735
|
-
#
|
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
|
-
#
|
743
|
-
def
|
744
|
-
|
745
|
-
|
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
|
-
|
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
|
-
#
|
687
|
+
# Most of the time, this method is not
|
688
|
+
# called by application code.
|
689
|
+
# @api plugin
|
758
690
|
def rpcs
|
759
|
-
@
|
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
|
-
|
796
|
-
#
|
700
|
+
|
701
|
+
# Resets channel state (for example, list of registered queue objects and so on).
|
797
702
|
#
|
798
|
-
#
|
799
|
-
#
|
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
|
-
|
805
|
-
|
706
|
+
# @private
|
707
|
+
# @api plugin
|
708
|
+
def reset
|
709
|
+
# TODO
|
710
|
+
raise NotImplementedError.new
|
711
|
+
end
|
806
712
|
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
713
|
+
# @private
|
714
|
+
# @api private
|
715
|
+
def self.channel_id_mutex
|
716
|
+
@channel_id_mutex ||= Mutex.new
|
717
|
+
end
|
812
718
|
|
813
|
-
|
814
|
-
|
815
|
-
|
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
|
-
|
818
|
-
handle_method(frame)
|
727
|
+
@last_channel_id
|
819
728
|
end
|
820
|
-
end
|
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
|
-
|
824
|
-
|
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
|