amq-client 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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"
|