amq-client 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +8 -0
- data/.gitmodules +9 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/.yardopts +1 -0
- data/CONTRIBUTORS +3 -0
- data/Gemfile +27 -0
- data/LICENSE +20 -0
- data/README.textile +61 -0
- data/amq-client.gemspec +34 -0
- data/bin/jenkins.sh +23 -0
- data/bin/set_test_suite_realms_up.sh +24 -0
- data/examples/coolio_adapter/basic_consume.rb +49 -0
- data/examples/coolio_adapter/basic_consume_with_acknowledgements.rb +43 -0
- data/examples/coolio_adapter/basic_consume_with_rejections.rb +43 -0
- data/examples/coolio_adapter/basic_publish.rb +35 -0
- data/examples/coolio_adapter/channel_close.rb +24 -0
- data/examples/coolio_adapter/example_helper.rb +39 -0
- data/examples/coolio_adapter/exchange_declare.rb +28 -0
- data/examples/coolio_adapter/kitchen_sink1.rb +48 -0
- data/examples/coolio_adapter/queue_bind.rb +32 -0
- data/examples/coolio_adapter/queue_purge.rb +32 -0
- data/examples/coolio_adapter/queue_unbind.rb +37 -0
- data/examples/eventmachine_adapter/authentication/plain_password_with_custom_role_credentials.rb +36 -0
- data/examples/eventmachine_adapter/authentication/plain_password_with_default_role_credentials.rb +27 -0
- data/examples/eventmachine_adapter/authentication/plain_password_with_incorrect_credentials.rb +18 -0
- data/examples/eventmachine_adapter/basic_cancel.rb +49 -0
- data/examples/eventmachine_adapter/basic_consume.rb +51 -0
- data/examples/eventmachine_adapter/basic_consume_with_acknowledgements.rb +45 -0
- data/examples/eventmachine_adapter/basic_consume_with_rejections.rb +45 -0
- data/examples/eventmachine_adapter/basic_get.rb +57 -0
- data/examples/eventmachine_adapter/basic_get_with_empty_queue.rb +53 -0
- data/examples/eventmachine_adapter/basic_publish.rb +38 -0
- data/examples/eventmachine_adapter/basic_qos.rb +29 -0
- data/examples/eventmachine_adapter/basic_recover.rb +29 -0
- data/examples/eventmachine_adapter/basic_return.rb +34 -0
- data/examples/eventmachine_adapter/channel_close.rb +24 -0
- data/examples/eventmachine_adapter/channel_flow.rb +36 -0
- data/examples/eventmachine_adapter/channel_level_exception_handling.rb +44 -0
- data/examples/eventmachine_adapter/example_helper.rb +39 -0
- data/examples/eventmachine_adapter/exchange_declare.rb +54 -0
- data/examples/eventmachine_adapter/extensions/rabbitmq/handling_confirm_select_ok.rb +31 -0
- data/examples/eventmachine_adapter/extensions/rabbitmq/publisher_confirmations_with_transient_messages.rb +56 -0
- data/examples/eventmachine_adapter/extensions/rabbitmq/publisher_confirmations_with_unroutable_message.rb +46 -0
- data/examples/eventmachine_adapter/kitchen_sink1.rb +50 -0
- data/examples/eventmachine_adapter/queue_bind.rb +32 -0
- data/examples/eventmachine_adapter/queue_declare.rb +34 -0
- data/examples/eventmachine_adapter/queue_purge.rb +32 -0
- data/examples/eventmachine_adapter/queue_unbind.rb +37 -0
- data/examples/eventmachine_adapter/tx_commit.rb +29 -0
- data/examples/eventmachine_adapter/tx_rollback.rb +29 -0
- data/examples/eventmachine_adapter/tx_select.rb +27 -0
- data/examples/socket_adapter/basics.rb +19 -0
- data/examples/socket_adapter/connection.rb +53 -0
- data/examples/socket_adapter/multiple_connections.rb +17 -0
- data/irb.rb +66 -0
- data/lib/amq/client.rb +15 -0
- data/lib/amq/client/adapter.rb +356 -0
- data/lib/amq/client/adapters/coolio.rb +221 -0
- data/lib/amq/client/adapters/event_machine.rb +228 -0
- data/lib/amq/client/adapters/socket.rb +89 -0
- data/lib/amq/client/channel.rb +338 -0
- data/lib/amq/client/connection.rb +246 -0
- data/lib/amq/client/entity.rb +117 -0
- data/lib/amq/client/exceptions.rb +86 -0
- data/lib/amq/client/exchange.rb +163 -0
- data/lib/amq/client/extensions/rabbitmq.rb +5 -0
- data/lib/amq/client/extensions/rabbitmq/basic.rb +36 -0
- data/lib/amq/client/extensions/rabbitmq/confirm.rb +254 -0
- data/lib/amq/client/framing/io/frame.rb +32 -0
- data/lib/amq/client/framing/string/frame.rb +62 -0
- data/lib/amq/client/logging.rb +56 -0
- data/lib/amq/client/mixins/anonymous_entity.rb +21 -0
- data/lib/amq/client/mixins/status.rb +62 -0
- data/lib/amq/client/protocol/get_response.rb +55 -0
- data/lib/amq/client/queue.rb +450 -0
- data/lib/amq/client/settings.rb +83 -0
- data/lib/amq/client/version.rb +5 -0
- data/spec/benchmarks/adapters.rb +77 -0
- data/spec/client/framing/io_frame_spec.rb +57 -0
- data/spec/client/framing/string_frame_spec.rb +57 -0
- data/spec/client/protocol/get_response_spec.rb +79 -0
- data/spec/integration/coolio/basic_ack_spec.rb +41 -0
- data/spec/integration/coolio/basic_get_spec.rb +73 -0
- data/spec/integration/coolio/basic_return_spec.rb +33 -0
- data/spec/integration/coolio/channel_close_spec.rb +26 -0
- data/spec/integration/coolio/channel_flow_spec.rb +46 -0
- data/spec/integration/coolio/spec_helper.rb +31 -0
- data/spec/integration/coolio/tx_commit_spec.rb +40 -0
- data/spec/integration/coolio/tx_rollback_spec.rb +44 -0
- data/spec/integration/eventmachine/basic_ack_spec.rb +40 -0
- data/spec/integration/eventmachine/basic_get_spec.rb +73 -0
- data/spec/integration/eventmachine/basic_return_spec.rb +35 -0
- data/spec/integration/eventmachine/channel_close_spec.rb +26 -0
- data/spec/integration/eventmachine/channel_flow_spec.rb +32 -0
- data/spec/integration/eventmachine/spec_helper.rb +22 -0
- data/spec/integration/eventmachine/tx_commit_spec.rb +47 -0
- data/spec/integration/eventmachine/tx_rollback_spec.rb +35 -0
- data/spec/regression/bad_frame_slicing_in_adapters_spec.rb +59 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/unit/client/adapter_spec.rb +49 -0
- data/spec/unit/client/entity_spec.rb +49 -0
- data/spec/unit/client/logging_spec.rb +60 -0
- data/spec/unit/client/mixins/status_spec.rb +72 -0
- data/spec/unit/client/settings_spec.rb +27 -0
- data/spec/unit/client_spec.rb +11 -0
- data/tasks.rb +11 -0
- metadata +202 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: utf-8
|
|
3
|
+
|
|
4
|
+
__dir = File.dirname(File.expand_path(__FILE__))
|
|
5
|
+
require File.join(__dir, "example_helper")
|
|
6
|
+
|
|
7
|
+
amq_client_example "Choose to use acknowledgement transactions on a channel using tx.select" do |client|
|
|
8
|
+
channel = AMQ::Client::Channel.new(client, 1)
|
|
9
|
+
channel.open do
|
|
10
|
+
channel.tx_select do
|
|
11
|
+
puts "Channel #{channel.id} is now using ack transactions"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
show_stopper = Proc.new {
|
|
15
|
+
client.disconnect do
|
|
16
|
+
puts
|
|
17
|
+
puts "AMQP connection is now properly closed"
|
|
18
|
+
EM.stop
|
|
19
|
+
end
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
Signal.trap "INT", show_stopper
|
|
23
|
+
Signal.trap "TERM", show_stopper
|
|
24
|
+
|
|
25
|
+
EM.add_timer(1, show_stopper)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: utf-8
|
|
3
|
+
|
|
4
|
+
require "bundler"
|
|
5
|
+
|
|
6
|
+
Bundler.setup
|
|
7
|
+
Bundler.require(:default)
|
|
8
|
+
|
|
9
|
+
$LOAD_PATH.unshift(File.expand_path("../../../lib", __FILE__))
|
|
10
|
+
|
|
11
|
+
require "amq/client/adapters/socket"
|
|
12
|
+
|
|
13
|
+
AMQ::Client::SocketClient.connect(:host => "localhost") do |client|
|
|
14
|
+
# Socket API is synchronous, so we don't need any callback here:
|
|
15
|
+
tasks = client.queue("tasks", 1)
|
|
16
|
+
tasks.consume do |headers, message| # TODO: this is async, we need to use a loop
|
|
17
|
+
puts ""
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: utf-8
|
|
3
|
+
|
|
4
|
+
require "bundler"
|
|
5
|
+
|
|
6
|
+
Bundler.setup
|
|
7
|
+
Bundler.require(:default)
|
|
8
|
+
|
|
9
|
+
$LOAD_PATH.unshift(File.expand_path("../../../lib", __FILE__))
|
|
10
|
+
|
|
11
|
+
require "amq/client/adapters/socket"
|
|
12
|
+
require "amq/client/amqp/queue"
|
|
13
|
+
require "amq/client/amqp/exchange"
|
|
14
|
+
|
|
15
|
+
AMQ::Client::SocketClient.connect(:port => 5672) do |client|
|
|
16
|
+
begin
|
|
17
|
+
client.handshake
|
|
18
|
+
|
|
19
|
+
# Ruby developers are used to use blocks usually synchronously
|
|
20
|
+
# (so they are called +/- immediately), but this is NOT the case!
|
|
21
|
+
# We always have to wait for the response from the broker, so think
|
|
22
|
+
# about the following blocks are true callbacks as you know them
|
|
23
|
+
# from JavaScript (i. e. window.onload = function () {}).
|
|
24
|
+
|
|
25
|
+
# The only exception is when you use {nowait: true}, then the
|
|
26
|
+
# callback is called immediately.
|
|
27
|
+
channel = AMQ::Client::Channel.new(client, 1)
|
|
28
|
+
channel.open { puts "Channel #{channel.id} opened!" }
|
|
29
|
+
|
|
30
|
+
queue = AMQ::Client::Queue.new(client, "", channel)
|
|
31
|
+
queue.declare { puts "Queue #{queue.name.inspect} declared!" }
|
|
32
|
+
|
|
33
|
+
exchange = AMQ::Client::Exchange.new(client, "tasks", :fanout, channel)
|
|
34
|
+
exchange.declare { puts "Exchange #{exchange.name.inspect} declared!" }
|
|
35
|
+
|
|
36
|
+
until client.connection.closed?
|
|
37
|
+
client.receive_async
|
|
38
|
+
sleep 1
|
|
39
|
+
end
|
|
40
|
+
rescue Interrupt
|
|
41
|
+
warn "Manually interrupted, terminating ..."
|
|
42
|
+
rescue Exception => exception
|
|
43
|
+
STDERR.puts "\n\e[1;31m[#{exception.class}] #{exception.message}\e[0m"
|
|
44
|
+
exception.backtrace.each do |line|
|
|
45
|
+
line = "\e[0;36m#{line}\e[0m" if line.match(Regexp::quote(File.basename(__FILE__)))
|
|
46
|
+
STDERR.puts " - " + line
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# TODO:
|
|
52
|
+
# AMQ::Client.connect(:adapter => :socket)
|
|
53
|
+
# Support for frame_max, heartbeat from Connection.Tune
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: utf-8
|
|
3
|
+
|
|
4
|
+
# Each connection respond to a TCP connection,
|
|
5
|
+
# hence we need to use more client.connect calls.
|
|
6
|
+
|
|
7
|
+
Thread.new do
|
|
8
|
+
AMQ::Client::SocketClient.connect(:port => 5672) do |client|
|
|
9
|
+
# ...
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
Thread.new do
|
|
14
|
+
AMQ::Client::SocketClient.connect(:port => 5672) do |client|
|
|
15
|
+
# ...
|
|
16
|
+
end
|
|
17
|
+
end
|
data/irb.rb
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: utf-8
|
|
3
|
+
|
|
4
|
+
# This file is supposed to make inspecting AMQ client easier.
|
|
5
|
+
|
|
6
|
+
# How does it work:
|
|
7
|
+
# 1) This file is executed.
|
|
8
|
+
# 2) We load irb, redefine where IRB looks for .irbrc and start IRB.
|
|
9
|
+
# 3) IRB loads .irbrc, which we redefined, so it loads this file again.
|
|
10
|
+
# However now the second branch of "if __FILE__ == $0" gets executed,
|
|
11
|
+
# so it runs our custom code which loads the original .irbrc and then
|
|
12
|
+
# it redefines some IRB settings. In this case it add IRB hook which
|
|
13
|
+
# is executed after IRB is started.
|
|
14
|
+
|
|
15
|
+
# Although it looks unnecessarily complicated, I can't see any easier
|
|
16
|
+
# solution to this problem in case that you need to patch original settings.
|
|
17
|
+
# Obviously in case you don't have the need, you'll be happy with simple:
|
|
18
|
+
|
|
19
|
+
# require "irb"
|
|
20
|
+
#
|
|
21
|
+
# require_relative "lib/amq/protocol/client.rb"
|
|
22
|
+
# include AMQ::Protocol
|
|
23
|
+
#
|
|
24
|
+
# IRB.start(__FILE__)
|
|
25
|
+
|
|
26
|
+
require "irb"
|
|
27
|
+
require "bundler"
|
|
28
|
+
|
|
29
|
+
Bundler.setup
|
|
30
|
+
Bundler.require(:default)
|
|
31
|
+
|
|
32
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", __FILE__))
|
|
33
|
+
|
|
34
|
+
if __FILE__ == $0
|
|
35
|
+
puts "~ Using #{__FILE__} as an executable ..."
|
|
36
|
+
|
|
37
|
+
def IRB.rc_file_generators
|
|
38
|
+
yield Proc.new { |_| __FILE__ }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
IRB.start(__FILE__)
|
|
42
|
+
else
|
|
43
|
+
begin
|
|
44
|
+
irbrc = File.join(ENV["HOME"], ".irbrc")
|
|
45
|
+
puts "~ Using #{__FILE__} as a custom .irbrc .."
|
|
46
|
+
|
|
47
|
+
require "amq/client.rb"
|
|
48
|
+
include AMQ::Client
|
|
49
|
+
|
|
50
|
+
require "amq/protocol/client"
|
|
51
|
+
include AMQ
|
|
52
|
+
|
|
53
|
+
require "stringio"
|
|
54
|
+
|
|
55
|
+
def fd(data)
|
|
56
|
+
Frame.decode(StringIO.new(data))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
puts "~ Loading original #{irbrc} ..."
|
|
60
|
+
load irbrc
|
|
61
|
+
|
|
62
|
+
puts "Loading finished."
|
|
63
|
+
rescue Exception => exception # it just discards all the exceptions!
|
|
64
|
+
abort exception.message + "\n - " + exception.backtrace.join("\n - ")
|
|
65
|
+
end
|
|
66
|
+
end
|
data/lib/amq/client.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require "amq/client/version"
|
|
4
|
+
require "amq/client/exceptions"
|
|
5
|
+
require "amq/client/adapter"
|
|
6
|
+
|
|
7
|
+
begin
|
|
8
|
+
require "amq/protocol/client"
|
|
9
|
+
rescue LoadError => exception
|
|
10
|
+
if exception.message.match("amq/protocol")
|
|
11
|
+
raise LoadError.new("You have to install amq-protocol library first!")
|
|
12
|
+
else
|
|
13
|
+
raise exception
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require "amq/client/logging"
|
|
4
|
+
require "amq/client/settings"
|
|
5
|
+
require "amq/client/entity"
|
|
6
|
+
require "amq/client/connection"
|
|
7
|
+
|
|
8
|
+
module AMQ
|
|
9
|
+
# For overview of AMQP client adapters API, see {AMQ::Client::Adapter}
|
|
10
|
+
module Client
|
|
11
|
+
# Base adapter class. Specific implementations (for example, EventMachine-based, Cool.io-based or
|
|
12
|
+
# sockets-based) subclass it and must implement Adapter API methods:
|
|
13
|
+
#
|
|
14
|
+
# * #send_raw(data)
|
|
15
|
+
# * #estabilish_connection(settings)
|
|
16
|
+
# * #close_connection
|
|
17
|
+
#
|
|
18
|
+
# Adapters also must indicate whether they operate in asynchronous or synchronous mode
|
|
19
|
+
# using AMQ::Client::Adapter.sync accessor:
|
|
20
|
+
#
|
|
21
|
+
# @example EventMachine adapter indicates that it is asynchronous
|
|
22
|
+
# module AMQ
|
|
23
|
+
# module Client
|
|
24
|
+
# class EventMachineClient
|
|
25
|
+
#
|
|
26
|
+
# #
|
|
27
|
+
# # Behaviors
|
|
28
|
+
# #
|
|
29
|
+
#
|
|
30
|
+
# include AMQ::Client::Adapter
|
|
31
|
+
# include EventMachine::Deferrable
|
|
32
|
+
#
|
|
33
|
+
# self.sync = false
|
|
34
|
+
#
|
|
35
|
+
# # the rest of implementation code ...
|
|
36
|
+
#
|
|
37
|
+
# end
|
|
38
|
+
# end
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# @abstract
|
|
42
|
+
module Adapter
|
|
43
|
+
|
|
44
|
+
def self.included(host)
|
|
45
|
+
host.extend(ClassMethods)
|
|
46
|
+
|
|
47
|
+
host.class_eval do
|
|
48
|
+
attr_accessor :logger, :settings, :connection
|
|
49
|
+
|
|
50
|
+
# Authentication mechanism
|
|
51
|
+
attr_accessor :mechanism
|
|
52
|
+
|
|
53
|
+
# Security response data
|
|
54
|
+
attr_accessor :response
|
|
55
|
+
|
|
56
|
+
# The locale defines the language in which the server will send reply texts.
|
|
57
|
+
#
|
|
58
|
+
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.4.2.2)
|
|
59
|
+
attr_accessor :locale
|
|
60
|
+
|
|
61
|
+
# @api plugin
|
|
62
|
+
# @see #disconnect
|
|
63
|
+
# @note Adapters must implement this method but it is NOT supposed to be used directly.
|
|
64
|
+
# AMQ protocol defines two-step process of closing connection (send Connection.Close
|
|
65
|
+
# to the peer and wait for Connection.Close-Ok), implemented by {Adapter#disconnect}
|
|
66
|
+
def close_connection
|
|
67
|
+
raise MissingInterfaceMethodError.new("AMQ::Client.close_connection")
|
|
68
|
+
end unless defined?(:close_connection) # since it is a module, this method may already be defined
|
|
69
|
+
end
|
|
70
|
+
end # self.included(host)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
module ClassMethods
|
|
75
|
+
# Settings
|
|
76
|
+
def settings
|
|
77
|
+
@settings ||= AMQ::Client::Settings.default
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def logger
|
|
81
|
+
@logger ||= begin
|
|
82
|
+
require "logger"
|
|
83
|
+
Logger.new(STDERR)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def logger=(logger)
|
|
88
|
+
methods = AMQ::Client::Logging::REQUIRED_METHODS
|
|
89
|
+
unless methods.all? { |method| logger.respond_to?(method) }
|
|
90
|
+
raise AMQ::Client::Logging::IncompatibleLoggerError.new(methods)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
@logger = logger
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# @return [Boolean] Current value of logging flag.
|
|
97
|
+
def logging
|
|
98
|
+
settings[:logging]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Turns loggin on or off.
|
|
102
|
+
def logging=(boolean)
|
|
103
|
+
settings[:logging] = boolean
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# @example Registering Channel implementation
|
|
108
|
+
# Adapter.register_entity(:channel, Channel)
|
|
109
|
+
# # ... so then I can do:
|
|
110
|
+
# channel = client.channel(1)
|
|
111
|
+
# # instead of:
|
|
112
|
+
# channel = Channel.new(client, 1)
|
|
113
|
+
def register_entity(name, klass)
|
|
114
|
+
define_method(name) do |*args, &block|
|
|
115
|
+
klass.new(self, *args, &block)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Establishes connection to AMQ broker and returns it. New connection object is yielded to
|
|
120
|
+
# the block if it is given.
|
|
121
|
+
#
|
|
122
|
+
# @param [Hash] Connection parameters, including :adapter to use.
|
|
123
|
+
# @api public
|
|
124
|
+
def connect(settings = nil, &block)
|
|
125
|
+
if settings && settings[:adapter]
|
|
126
|
+
adapter = load_adapter(settings[:adapter])
|
|
127
|
+
else
|
|
128
|
+
adapter = self
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
@settings = AMQ::Client::Settings.configure(settings)
|
|
132
|
+
instance = adapter.new
|
|
133
|
+
instance.establish_connection(@settings)
|
|
134
|
+
# We don't need anything more, once the server receives the preable, he sends Connection.Start, we just have to reply.
|
|
135
|
+
|
|
136
|
+
if block
|
|
137
|
+
block.call(instance)
|
|
138
|
+
|
|
139
|
+
instance.disconnect
|
|
140
|
+
else
|
|
141
|
+
instance
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# Loads adapter from amq/client/adapters.
|
|
147
|
+
#
|
|
148
|
+
# @raise [InvalidAdapterNameError] When loading attempt failed (LoadError was raised).
|
|
149
|
+
def load_adapter(adapter)
|
|
150
|
+
require "amq/client/adapters/#{adapter}"
|
|
151
|
+
|
|
152
|
+
const_name = adapter.to_s.gsub(/(^|_)(.)/) { $2.upcase! }
|
|
153
|
+
const_get(const_name)
|
|
154
|
+
rescue LoadError
|
|
155
|
+
raise InvalidAdapterNameError.new(adapter)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# @see AMQ::Client::Adapter
|
|
159
|
+
def sync=(boolean)
|
|
160
|
+
@sync = boolean
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Use this method to detect whether adapter is synchronous or asynchronous.
|
|
164
|
+
#
|
|
165
|
+
# @return [Boolean] true if this adapter is synchronous
|
|
166
|
+
# @api plugin
|
|
167
|
+
# @see AMQ::Client::Adapter
|
|
168
|
+
def sync?
|
|
169
|
+
@sync == true
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# @see #sync?
|
|
173
|
+
# @api plugin
|
|
174
|
+
# @see AMQ::Client::Adapter
|
|
175
|
+
def async?
|
|
176
|
+
!sync?
|
|
177
|
+
end
|
|
178
|
+
end # ClassMethods
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
#
|
|
182
|
+
# Behaviors
|
|
183
|
+
#
|
|
184
|
+
|
|
185
|
+
include AMQ::Client::StatusMixin
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
#
|
|
189
|
+
# API
|
|
190
|
+
#
|
|
191
|
+
|
|
192
|
+
def self.load_adapter(adapter)
|
|
193
|
+
ClassMethods.load_adapter(adapter)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def initialize(*args)
|
|
199
|
+
super(*args)
|
|
200
|
+
|
|
201
|
+
self.logger = self.class.logger
|
|
202
|
+
self.settings = self.class.settings
|
|
203
|
+
|
|
204
|
+
@frames = Array.new
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# Establish socket connection to the server.
|
|
210
|
+
#
|
|
211
|
+
# @api plugin
|
|
212
|
+
def establish_connection(settings)
|
|
213
|
+
raise MissingInterfaceMethodError.new("AMQ::Client#establish_connection(settings)")
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def handshake(mechanism = "PLAIN", response = "\0guest\0guest", locale = "en_GB")
|
|
217
|
+
self.send_preamble
|
|
218
|
+
self.connection = AMQ::Client::Connection.new(self, mechanism, response, locale)
|
|
219
|
+
if self.sync?
|
|
220
|
+
self.receive # Start/Start-Ok
|
|
221
|
+
self.receive # Tune/Tune-Ok
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Properly close connection with AMQ broker, as described in
|
|
226
|
+
# section 2.2.4 of the {http://bit.ly/hw2ELX AMQP 0.9.1 specification}.
|
|
227
|
+
#
|
|
228
|
+
# @api plugin
|
|
229
|
+
# @todo This method should await broker's response with Close-Ok. {http://github.com/michaelklishin MK}.
|
|
230
|
+
# @see #close_connection
|
|
231
|
+
def disconnect(reply_code = 200, reply_text = "Goodbye", &block)
|
|
232
|
+
self.on_disconnection(&block)
|
|
233
|
+
closing!
|
|
234
|
+
self.connection.close(reply_code, reply_text)
|
|
235
|
+
end
|
|
236
|
+
alias close disconnect
|
|
237
|
+
|
|
238
|
+
# Sends AMQ protocol header (also known as preamble).
|
|
239
|
+
#
|
|
240
|
+
# @note This must be implemented by all AMQP clients.
|
|
241
|
+
# @api plugin
|
|
242
|
+
# @see http://bit.ly/hw2ELX AMQP 0.9.1 specification (Section 2.2)
|
|
243
|
+
def send_preamble
|
|
244
|
+
self.send_raw(AMQ::Protocol::PREAMBLE)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def send(frame)
|
|
248
|
+
if self.connection.closed?
|
|
249
|
+
raise ConnectionClosedError.new(frame)
|
|
250
|
+
else
|
|
251
|
+
self.send_raw(frame.encode)
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def send_frameset(frames)
|
|
256
|
+
frames.each { |frame| self.send(frame) }
|
|
257
|
+
end # send_frameset(frames)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
# Sends opaque data to AMQ broker over active connection.
|
|
261
|
+
#
|
|
262
|
+
# @note This must be implemented by all AMQP clients.
|
|
263
|
+
# @api plugin
|
|
264
|
+
def send_raw(data)
|
|
265
|
+
raise MissingInterfaceMethodError.new("AMQ::Client#send_raw(data)")
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def receive_frame(frame)
|
|
269
|
+
@frames << frame
|
|
270
|
+
if frameset_complete?(@frames)
|
|
271
|
+
receive_frameset(@frames)
|
|
272
|
+
@frames.clear
|
|
273
|
+
else
|
|
274
|
+
# puts "#{frame.inspect} is NOT final"
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# When the adapter receives all the frames related to
|
|
279
|
+
# given method frame, it's supposed to call this method.
|
|
280
|
+
# It calls handler for method class of the first (method)
|
|
281
|
+
# frame with all the other frames as arguments. Handlers
|
|
282
|
+
# are defined in amq/client/* by the handle(klass, &block)
|
|
283
|
+
# method.
|
|
284
|
+
def receive_frameset(frames)
|
|
285
|
+
frame = frames.first
|
|
286
|
+
|
|
287
|
+
if Protocol::HeartbeatFrame === frame
|
|
288
|
+
@last_server_heartbeat = Time.now
|
|
289
|
+
else
|
|
290
|
+
callable = AMQ::Client::Entity.handlers[frame.method_class]
|
|
291
|
+
if callable
|
|
292
|
+
callable.call(self, frames.first, frames[1..-1])
|
|
293
|
+
else
|
|
294
|
+
raise MissingHandlerError.new(frames.first)
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def send_heartbeat
|
|
300
|
+
if tcp_connection_established?
|
|
301
|
+
if @last_server_heartbeat < (Time.now - (self.heartbeat_interval * 2))
|
|
302
|
+
logger.error "Reconnecting due to missing server heartbeats"
|
|
303
|
+
# TODO: reconnect
|
|
304
|
+
end
|
|
305
|
+
send(Protocol::HeartbeatFrame)
|
|
306
|
+
end
|
|
307
|
+
end # send_heartbeat
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
# @see .sync?
|
|
311
|
+
# @api plugin
|
|
312
|
+
# @see AMQ::Client::Adapter
|
|
313
|
+
def sync?
|
|
314
|
+
self.class.sync?
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# @see .async?
|
|
318
|
+
# @api plugin
|
|
319
|
+
# @see AMQ::Client::Adapter
|
|
320
|
+
def async?
|
|
321
|
+
self.class.async?
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# Returns heartbeat interval this client uses, in seconds.
|
|
325
|
+
# This value may or may not be used depending on broker capabilities.
|
|
326
|
+
#
|
|
327
|
+
# @return [Fixnum] Heartbeat interval this client uses, in seconds.
|
|
328
|
+
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.4.2.6)
|
|
329
|
+
def heartbeat_interval
|
|
330
|
+
@settings[:heartbeat] || @settings[:heartbeat_interval] || 0
|
|
331
|
+
end # heartbeat_interval
|
|
332
|
+
|
|
333
|
+
protected
|
|
334
|
+
|
|
335
|
+
# Utility methods
|
|
336
|
+
|
|
337
|
+
# Determines, whether the received frameset is ready to be further processed
|
|
338
|
+
def frameset_complete?(frames)
|
|
339
|
+
return false if frames.empty?
|
|
340
|
+
first_frame = frames[0]
|
|
341
|
+
first_frame.final? || (first_frame.method_class.has_content? && content_complete?(frames[1..-1]))
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# Determines, whether given frame array contains full content body
|
|
345
|
+
def content_complete?(frames)
|
|
346
|
+
return false if frames.empty?
|
|
347
|
+
header = frames[0]
|
|
348
|
+
raise "Not a content header frame first: #{header.inspect}" unless header.kind_of?(AMQ::Protocol::HeaderFrame)
|
|
349
|
+
header.body_size == frames[1..-1].inject(0) {|sum, frame| sum + frame.payload.size }
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
end # Adapter
|
|
353
|
+
end # Client
|
|
354
|
+
end # AMQ
|
|
355
|
+
|
|
356
|
+
require "amq/client/channel"
|