amq-client 0.7.0.alpha34 → 0.7.0.alpha35
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/.travis.yml +4 -0
- data/Gemfile +1 -1
- data/README.textile +1 -1
- data/bin/ci/before_build.sh +24 -0
- data/examples/eventmachine_adapter/extensions/rabbitmq/handling_confirm_select_ok.rb +2 -2
- data/examples/eventmachine_adapter/extensions/rabbitmq/publisher_confirmations_with_transient_messages.rb +1 -1
- data/examples/eventmachine_adapter/extensions/rabbitmq/publisher_confirmations_with_unroutable_message.rb +1 -1
- data/lib/amq/client.rb +29 -17
- data/lib/amq/client/adapter.rb +8 -504
- data/lib/amq/client/adapters/coolio.rb +4 -282
- data/lib/amq/client/adapters/event_machine.rb +4 -382
- data/lib/amq/client/async/adapter.rb +517 -0
- data/lib/amq/client/async/adapters/coolio.rb +291 -0
- data/lib/amq/client/async/adapters/event_machine.rb +392 -0
- data/lib/amq/client/async/adapters/eventmachine.rb +1 -0
- data/lib/amq/client/async/callbacks.rb +71 -0
- data/lib/amq/client/async/channel.rb +385 -0
- data/lib/amq/client/async/entity.rb +66 -0
- data/lib/amq/client/async/exchange.rb +157 -0
- data/lib/amq/client/async/extensions/rabbitmq/basic.rb +38 -0
- data/lib/amq/client/async/extensions/rabbitmq/confirm.rb +248 -0
- data/lib/amq/client/async/queue.rb +455 -0
- data/lib/amq/client/callbacks.rb +6 -65
- data/lib/amq/client/channel.rb +4 -376
- data/lib/amq/client/entity.rb +6 -57
- data/lib/amq/client/exchange.rb +4 -148
- data/lib/amq/client/extensions/rabbitmq/basic.rb +4 -28
- data/lib/amq/client/extensions/rabbitmq/confirm.rb +5 -240
- data/lib/amq/client/queue.rb +5 -450
- data/lib/amq/client/version.rb +1 -1
- data/spec/unit/client_spec.rb +10 -30
- metadata +16 -22
data/.travis.yml
CHANGED
@@ -1,10 +1,14 @@
|
|
1
|
+
bundler_args: --without development
|
1
2
|
script: "bundle exec rspec spec"
|
3
|
+
before_script: ./bin/ci/before_build.sh
|
2
4
|
rvm:
|
3
5
|
- 1.8.7
|
6
|
+
- 1.8.7-p174
|
4
7
|
- ree
|
5
8
|
- rbx
|
6
9
|
- 1.9.2
|
7
10
|
- jruby
|
11
|
+
- ruby-head
|
8
12
|
gemfile:
|
9
13
|
- Gemfile
|
10
14
|
- gemfiles/eventmachine-pre
|
data/Gemfile
CHANGED
@@ -22,7 +22,7 @@ custom_gem "amq-protocol", :git => "git://github.com/ruby-amqp/amq-protocol.git"
|
|
22
22
|
group :development do
|
23
23
|
gem "yard"
|
24
24
|
# yard tags this buddy along
|
25
|
-
gem "RedCloth"
|
25
|
+
gem "RedCloth", :platform => :mri
|
26
26
|
|
27
27
|
gem "nake", :platform => :ruby_19
|
28
28
|
gem "contributors", :platform => :ruby_19
|
data/README.textile
CHANGED
@@ -71,7 +71,7 @@ h2. See also
|
|
71
71
|
|
72
72
|
* "API documentation":http://rdoc.info/github/ruby-amqp/amq-client/master/frames
|
73
73
|
* "Examples":https://github.com/ruby-amqp/amq-client/tree/master/examples/
|
74
|
-
*
|
74
|
+
* Stop by #rabbitmq on irc.freenode.net. You can use "Web IRC client":http://webchat.freenode.net?channels=rabbitmq if you don't have IRC client installed.
|
75
75
|
* "Ruby AMQP mailing list":http://groups.google.com/group/ruby-amqp
|
76
76
|
* "Issue tracker":http://github.com/ruby-amqp/amq-client/issues
|
77
77
|
* "Continous integration server":http://travis-ci.org/#!/ruby-amqp/amq-client
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
# guest:guest has full access to /
|
4
|
+
|
5
|
+
sudo rabbitmqctl add_vhost /
|
6
|
+
sudo rabbitmqctl add_user guest guest
|
7
|
+
sudo rabbitmqctl set_permissions -p / guest ".*" ".*" ".*"
|
8
|
+
|
9
|
+
|
10
|
+
# guest:guest has full access to amq_client_testbed
|
11
|
+
# amq_client_gem:amq_client_gem has full access to /amq_client_testbed
|
12
|
+
|
13
|
+
sudo rabbitmqctl delete_vhost "amq_client_testbed"
|
14
|
+
sudo rabbitmqctl add_vhost "amq_client_testbed"
|
15
|
+
sudo rabbitmqctl delete_user amq_client_gem
|
16
|
+
sudo rabbitmqctl add_user amq_client_gem amq_client_gem_password
|
17
|
+
sudo rabbitmqctl set_permissions -p amq_client_testbed guest ".*" ".*" ".*"
|
18
|
+
sudo rabbitmqctl set_permissions -p amq_client_testbed amq_client_gem ".*" ".*" ".*"
|
19
|
+
|
20
|
+
|
21
|
+
# amqp_gem_reader:reader_password has read access to amq_client_testbed
|
22
|
+
|
23
|
+
sudo rabbitmqctl add_user amq_client_gem_reader reader_password
|
24
|
+
sudo rabbitmqctl set_permissions -p amq_client_testbed amq_client_gem_reader "^---$" "^---$" ".*"
|
@@ -11,7 +11,7 @@ amq_client_example "confirm.select (a RabbitMQ extension)" do |client|
|
|
11
11
|
channel.open do
|
12
12
|
puts "Channel #{channel.id} is now open"
|
13
13
|
|
14
|
-
channel.
|
14
|
+
channel.confirm_select do |select_ok|
|
15
15
|
puts "Broker replied with confirm.select_ok"
|
16
16
|
end
|
17
17
|
|
@@ -28,4 +28,4 @@ amq_client_example "confirm.select (a RabbitMQ extension)" do |client|
|
|
28
28
|
|
29
29
|
EM.add_timer(1, show_stopper)
|
30
30
|
end
|
31
|
-
end
|
31
|
+
end
|
@@ -24,7 +24,7 @@ amq_client_example "Publisher confirmations using RabbitMQ extension: routable m
|
|
24
24
|
x = AMQ::Client::Exchange.new(client, channel, "amq.fanout", :fanout)
|
25
25
|
|
26
26
|
q = AMQ::Client::Queue.new(client, channel, AMQ::Protocol::EMPTY_STRING)
|
27
|
-
q.declare(false, false, true, true) do |_|
|
27
|
+
q.declare(false, false, true, true) do |_, declare_ok|
|
28
28
|
puts "Defined a new server-named queue: #{q.name}"
|
29
29
|
|
30
30
|
q.bind("amq.fanout").consume(false, true, true) { |consume_ok|
|
@@ -22,7 +22,7 @@ amq_client_example "Publisher confirmations using RabbitMQ extension: unroutable
|
|
22
22
|
end
|
23
23
|
|
24
24
|
x = AMQ::Client::Exchange.new(client, channel, AMQ::Protocol::EMPTY_STRING, :direct)
|
25
|
-
x.on_return { |basic_return|
|
25
|
+
x.on_return { |basic_return, metadata, payload|
|
26
26
|
puts "Received basic.return: reply_text = #{basic_return.reply_text}, reply_code = #{basic_return.reply_code}"
|
27
27
|
}
|
28
28
|
|
data/lib/amq/client.rb
CHANGED
@@ -12,7 +12,7 @@ begin
|
|
12
12
|
require "amq/protocol/client"
|
13
13
|
rescue LoadError => exception
|
14
14
|
if exception.message.match("amq/protocol")
|
15
|
-
raise LoadError.new("
|
15
|
+
raise LoadError.new("amq-client could not load amq-protocol.")
|
16
16
|
else
|
17
17
|
raise exception
|
18
18
|
end
|
@@ -20,26 +20,38 @@ end
|
|
20
20
|
|
21
21
|
module AMQ
|
22
22
|
module Client
|
23
|
-
# List
|
23
|
+
# List available adapters as a hash of { :adapter_name => metadata },
|
24
24
|
# where metadata are hash with :path and :const_name keys.
|
25
25
|
#
|
26
|
-
# @example
|
27
|
-
# AMQ::Client.adapters[:event_machine] # => {path: "...", const_name: "EventMachineClient"}}
|
28
|
-
#
|
29
26
|
# @return [Hash]
|
30
|
-
# @api
|
27
|
+
# @api plugin
|
31
28
|
def self.adapters
|
32
|
-
@adapters ||=
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
29
|
+
@adapters ||= (self.async_adapters)
|
30
|
+
end
|
31
|
+
|
32
|
+
# List available asynchronous adapters.
|
33
|
+
#
|
34
|
+
# @return [Hash]
|
35
|
+
# @api plugin
|
36
|
+
# @see AMQ::Client.adapters
|
37
|
+
def self.async_adapters
|
38
|
+
@async_adapters ||= {
|
39
|
+
:eventmachine => {
|
40
|
+
:path => "amq/client/async/adapters/eventmachine",
|
41
|
+
:const_name => "Async::EventMachineClient"
|
42
|
+
},
|
43
|
+
:event_machine => {
|
44
|
+
:path => "amq/client/async/adapters/eventmachine",
|
45
|
+
:const_name => "Async::EventMachineClient"
|
46
|
+
},
|
47
|
+
:coolio => {
|
48
|
+
:path => "amq/client/async/adapters/coolio",
|
49
|
+
:const_name => "Async::CoolioClient"
|
50
|
+
}
|
51
|
+
}
|
41
52
|
end
|
42
53
|
|
54
|
+
|
43
55
|
# Establishes connection to AMQ broker using given adapter
|
44
56
|
# (defaults to the socket adapter) and returns it. The new
|
45
57
|
# connection object is yielded to the block if it is given.
|
@@ -56,14 +68,14 @@ module AMQ
|
|
56
68
|
adapter.connect(settings, &block)
|
57
69
|
end
|
58
70
|
|
59
|
-
# Loads adapter
|
71
|
+
# Loads adapter given its name as a Symbol.
|
60
72
|
#
|
61
73
|
# @raise [InvalidAdapterNameError] When loading attempt failed (LoadError was raised).
|
62
74
|
def self.load_adapter(adapter)
|
63
75
|
meta = self.adapters[adapter.to_sym]
|
64
76
|
|
65
77
|
require meta[:path]
|
66
|
-
|
78
|
+
eval(meta[:const_name])
|
67
79
|
rescue LoadError
|
68
80
|
raise InvalidAdapterNameError.new(adapter)
|
69
81
|
end
|
data/lib/amq/client/adapter.rb
CHANGED
@@ -2,514 +2,18 @@
|
|
2
2
|
|
3
3
|
require "amq/client/logging"
|
4
4
|
require "amq/client/settings"
|
5
|
-
|
6
|
-
require "amq/client/
|
5
|
+
|
6
|
+
require "amq/client/async/queue"
|
7
|
+
require "amq/client/async/exchange"
|
8
|
+
require "amq/client/async/channel"
|
9
|
+
require "amq/client/async/adapter"
|
7
10
|
|
8
11
|
module AMQ
|
9
12
|
# For overview of AMQP client adapters API, see {AMQ::Client::Adapter}
|
10
13
|
module Client
|
11
14
|
|
12
|
-
|
13
|
-
#
|
14
|
-
|
15
|
-
#
|
16
|
-
# * #send_raw(data)
|
17
|
-
# * #estabilish_connection(settings)
|
18
|
-
# * #close_connection
|
19
|
-
#
|
20
|
-
# @abstract
|
21
|
-
module Adapter
|
22
|
-
|
23
|
-
def self.included(host)
|
24
|
-
host.extend ClassMethods
|
25
|
-
host.extend ProtocolMethodHandlers
|
26
|
-
|
27
|
-
host.class_eval do
|
28
|
-
|
29
|
-
#
|
30
|
-
# API
|
31
|
-
#
|
32
|
-
|
33
|
-
attr_accessor :logger
|
34
|
-
attr_accessor :settings
|
35
|
-
|
36
|
-
# @return [Array<#call>]
|
37
|
-
attr_reader :callbacks
|
38
|
-
|
39
|
-
|
40
|
-
# The locale defines the language in which the server will send reply texts.
|
41
|
-
#
|
42
|
-
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.4.2.2)
|
43
|
-
attr_accessor :locale
|
44
|
-
|
45
|
-
# Client capabilities
|
46
|
-
#
|
47
|
-
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.4.2.2.1)
|
48
|
-
attr_accessor :client_properties
|
49
|
-
|
50
|
-
# Server properties
|
51
|
-
#
|
52
|
-
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.4.2.1.3)
|
53
|
-
attr_reader :server_properties
|
54
|
-
|
55
|
-
# Server capabilities
|
56
|
-
#
|
57
|
-
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.4.2.1.3)
|
58
|
-
attr_reader :server_capabilities
|
59
|
-
|
60
|
-
# Locales server supports
|
61
|
-
#
|
62
|
-
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.4.2.1.3)
|
63
|
-
attr_reader :server_locales
|
64
|
-
|
65
|
-
# Authentication mechanism used.
|
66
|
-
#
|
67
|
-
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.4.2.2)
|
68
|
-
attr_reader :mechanism
|
69
|
-
|
70
|
-
# Authentication mechanisms broker supports.
|
71
|
-
#
|
72
|
-
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.4.2.2)
|
73
|
-
attr_reader :server_authentication_mechanisms
|
74
|
-
|
75
|
-
# Channels within this connection.
|
76
|
-
#
|
77
|
-
# @see http://bit.ly/hw2ELX AMQP 0.9.1 specification (Section 2.2.5)
|
78
|
-
attr_reader :channels
|
79
|
-
|
80
|
-
# Maximum channel number that the server permits this connection to use.
|
81
|
-
# Usable channel numbers are in the range 1..channel_max.
|
82
|
-
# Zero indicates no specified limit.
|
83
|
-
#
|
84
|
-
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Sections 1.4.2.5.1 and 1.4.2.6.1)
|
85
|
-
attr_accessor :channel_max
|
86
|
-
|
87
|
-
# Maximum frame size that the server permits this connection to use.
|
88
|
-
#
|
89
|
-
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Sections 1.4.2.5.2 and 1.4.2.6.2)
|
90
|
-
attr_accessor :frame_max
|
91
|
-
|
92
|
-
|
93
|
-
attr_reader :known_hosts
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
# @api plugin
|
98
|
-
# @see #disconnect
|
99
|
-
# @note Adapters must implement this method but it is NOT supposed to be used directly.
|
100
|
-
# AMQ protocol defines two-step process of closing connection (send Connection.Close
|
101
|
-
# to the peer and wait for Connection.Close-Ok), implemented by {Adapter#disconnect}
|
102
|
-
def close_connection
|
103
|
-
raise MissingInterfaceMethodError.new("AMQ::Client.close_connection")
|
104
|
-
end unless defined?(:close_connection) # since it is a module, this method may already be defined
|
105
|
-
end
|
106
|
-
end # self.included(host)
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
module ClassMethods
|
111
|
-
# Settings
|
112
|
-
def settings
|
113
|
-
@settings ||= AMQ::Client::Settings.default
|
114
|
-
end
|
115
|
-
|
116
|
-
def logger
|
117
|
-
@logger ||= begin
|
118
|
-
require "logger"
|
119
|
-
Logger.new(STDERR)
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
def logger=(logger)
|
124
|
-
methods = AMQ::Client::Logging::REQUIRED_METHODS
|
125
|
-
unless methods.all? { |method| logger.respond_to?(method) }
|
126
|
-
raise AMQ::Client::Logging::IncompatibleLoggerError.new(methods)
|
127
|
-
end
|
128
|
-
|
129
|
-
@logger = logger
|
130
|
-
end
|
131
|
-
|
132
|
-
# @return [Boolean] Current value of logging flag.
|
133
|
-
def logging
|
134
|
-
settings[:logging]
|
135
|
-
end
|
136
|
-
|
137
|
-
# Turns loggin on or off.
|
138
|
-
def logging=(boolean)
|
139
|
-
settings[:logging] = boolean
|
140
|
-
end
|
141
|
-
|
142
|
-
|
143
|
-
# Establishes connection to AMQ broker and returns it. New connection object is yielded to
|
144
|
-
# the block if it is given.
|
145
|
-
#
|
146
|
-
# @example Specifying adapter via the :adapter option
|
147
|
-
# AMQ::Client::Adapter.connect(:adapter => "socket")
|
148
|
-
# @example Specifying using custom adapter class
|
149
|
-
# AMQ::Client::SocketClient.connect
|
150
|
-
# @param [Hash] Connection parameters, including :adapter to use.
|
151
|
-
# @api public
|
152
|
-
def connect(settings = nil, &block)
|
153
|
-
@settings = Settings.configure(settings)
|
154
|
-
|
155
|
-
instance = self.new
|
156
|
-
instance.establish_connection(settings)
|
157
|
-
instance.register_connection_callback(&block)
|
158
|
-
|
159
|
-
instance
|
160
|
-
end
|
161
|
-
|
162
|
-
|
163
|
-
# Can be overriden by higher-level libraries like amqp gem or bunny.
|
164
|
-
# Defaults to AMQ::Client::TCPConnectionFailed.
|
165
|
-
#
|
166
|
-
# @return [Class]
|
167
|
-
def tcp_connection_failure_exception_class
|
168
|
-
@tcp_connection_failure_exception_class ||= AMQ::Client::TCPConnectionFailed
|
169
|
-
end # tcp_connection_failure_exception_class
|
170
|
-
|
171
|
-
# Can be overriden by higher-level libraries like amqp gem or bunny.
|
172
|
-
# Defaults to AMQ::Client::PossibleAuthenticationFailure.
|
173
|
-
#
|
174
|
-
# @return [Class]
|
175
|
-
def authentication_failure_exception_class
|
176
|
-
@authentication_failure_exception_class ||= AMQ::Client::PossibleAuthenticationFailureError
|
177
|
-
end # authentication_failure_exception_class
|
178
|
-
end # ClassMethods
|
179
|
-
|
180
|
-
|
181
|
-
#
|
182
|
-
# Behaviors
|
183
|
-
#
|
184
|
-
|
185
|
-
include Openable
|
186
|
-
include Callbacks
|
187
|
-
|
188
|
-
|
189
|
-
extend RegisterEntityMixin
|
190
|
-
|
191
|
-
register_entity :channel, AMQ::Client::Channel
|
192
|
-
|
193
|
-
|
194
|
-
#
|
195
|
-
# API
|
196
|
-
#
|
197
|
-
|
198
|
-
|
199
|
-
# Establish socket connection to the server.
|
200
|
-
#
|
201
|
-
# @api plugin
|
202
|
-
def establish_connection(settings)
|
203
|
-
raise MissingInterfaceMethodError.new("AMQ::Client#establish_connection(settings)")
|
204
|
-
end
|
205
|
-
|
206
|
-
# Properly close connection with AMQ broker, as described in
|
207
|
-
# section 2.2.4 of the {http://bit.ly/hw2ELX AMQP 0.9.1 specification}.
|
208
|
-
#
|
209
|
-
# @api plugin
|
210
|
-
# @see #close_connection
|
211
|
-
def disconnect(reply_code = 200, reply_text = "Goodbye", class_id = 0, method_id = 0, &block)
|
212
|
-
@intentionally_closing_connection = true
|
213
|
-
self.on_disconnection(&block)
|
214
|
-
|
215
|
-
# ruby-amqp/amqp#66, MK.
|
216
|
-
if self.open?
|
217
|
-
closing!
|
218
|
-
self.send_frame(Protocol::Connection::Close.encode(reply_code, reply_text, class_id, method_id))
|
219
|
-
elsif self.closing?
|
220
|
-
# no-op
|
221
|
-
else
|
222
|
-
self.disconnection_successful
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
|
227
|
-
# Sends AMQ protocol header (also known as preamble).
|
228
|
-
#
|
229
|
-
# @note This must be implemented by all AMQP clients.
|
230
|
-
# @api plugin
|
231
|
-
# @see http://bit.ly/hw2ELX AMQP 0.9.1 specification (Section 2.2)
|
232
|
-
def send_preamble
|
233
|
-
self.send_raw(AMQ::Protocol::PREAMBLE)
|
234
|
-
end
|
235
|
-
|
236
|
-
# Sends frame to the peer, checking that connection is open.
|
237
|
-
#
|
238
|
-
# @raise [ConnectionClosedError]
|
239
|
-
def send_frame(frame)
|
240
|
-
if closed?
|
241
|
-
raise ConnectionClosedError.new(frame)
|
242
|
-
else
|
243
|
-
self.send_raw(frame.encode)
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
|
-
# Sends multiple frames, one by one.
|
248
|
-
#
|
249
|
-
# @api public
|
250
|
-
def send_frameset(frames)
|
251
|
-
frames.each { |frame| self.send_frame(frame) }
|
252
|
-
end # send_frameset(frames)
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
# Returns heartbeat interval this client uses, in seconds.
|
257
|
-
# This value may or may not be used depending on broker capabilities.
|
258
|
-
# Zero means the server does not want a heartbeat.
|
259
|
-
#
|
260
|
-
# @return [Fixnum] Heartbeat interval this client uses, in seconds.
|
261
|
-
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.4.2.6)
|
262
|
-
def heartbeat_interval
|
263
|
-
@settings[:heartbeat] || @settings[:heartbeat_interval] || 0
|
264
|
-
end # heartbeat_interval
|
265
|
-
|
266
|
-
|
267
|
-
# vhost this connection uses. Default is "/", a historically estabilished convention
|
268
|
-
# of RabbitMQ and amqp gem.
|
269
|
-
#
|
270
|
-
# @return [String] vhost this connection uses
|
271
|
-
# @api public
|
272
|
-
def vhost
|
273
|
-
@settings.fetch(:vhost, "/")
|
274
|
-
end # vhost
|
275
|
-
|
276
|
-
|
277
|
-
# Called when previously established TCP connection fails.
|
278
|
-
# @api public
|
279
|
-
def tcp_connection_lost
|
280
|
-
@on_tcp_connection_loss.call(self, @settings) if @on_tcp_connection_loss
|
281
|
-
end
|
282
|
-
|
283
|
-
# Called when initial TCP connection fails.
|
284
|
-
# @api public
|
285
|
-
def tcp_connection_failed
|
286
|
-
@on_tcp_connection_failure.call(@settings) if @on_tcp_connection_failure
|
287
|
-
end
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
#
|
292
|
-
# Implementation
|
293
|
-
#
|
294
|
-
|
295
|
-
|
296
|
-
# Sends opaque data to AMQ broker over active connection.
|
297
|
-
#
|
298
|
-
# @note This must be implemented by all AMQP clients.
|
299
|
-
# @api plugin
|
300
|
-
def send_raw(data)
|
301
|
-
raise MissingInterfaceMethodError.new("AMQ::Client#send_raw(data)")
|
302
|
-
end
|
303
|
-
|
304
|
-
# Sends connection preamble to the broker.
|
305
|
-
# @api plugin
|
306
|
-
def handshake
|
307
|
-
@authenticating = true
|
308
|
-
self.send_preamble
|
309
|
-
end
|
310
|
-
|
311
|
-
|
312
|
-
# Sends connection.open to the server.
|
313
|
-
#
|
314
|
-
# @api plugin
|
315
|
-
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.4.2.7)
|
316
|
-
def open(vhost = "/")
|
317
|
-
self.send_frame(Protocol::Connection::Open.encode(vhost))
|
318
|
-
end
|
319
|
-
|
320
|
-
# Resets connection state.
|
321
|
-
#
|
322
|
-
# @api plugin
|
323
|
-
def reset_state!
|
324
|
-
# no-op by default
|
325
|
-
end # reset_state!
|
326
|
-
|
327
|
-
# @api plugin
|
328
|
-
# @see http://tools.ietf.org/rfc/rfc2595.txt RFC 2595
|
329
|
-
def encode_credentials(username, password)
|
330
|
-
"\0#{username}\0#{password}"
|
331
|
-
end # encode_credentials(username, password)
|
332
|
-
|
333
|
-
|
334
|
-
# Processes a single frame.
|
335
|
-
#
|
336
|
-
# @param [AMQ::Protocol::Frame] frame
|
337
|
-
# @api plugin
|
338
|
-
def receive_frame(frame)
|
339
|
-
@frames << frame
|
340
|
-
if frameset_complete?(@frames)
|
341
|
-
receive_frameset(@frames)
|
342
|
-
@frames.clear
|
343
|
-
else
|
344
|
-
# puts "#{frame.inspect} is NOT final"
|
345
|
-
end
|
346
|
-
end
|
347
|
-
|
348
|
-
# Processes a frameset by finding and invoking a suitable handler.
|
349
|
-
# Heartbeat frames are treated in a special way: they simply update @last_server_heartbeat
|
350
|
-
# value.
|
351
|
-
#
|
352
|
-
# @param [Array<AMQ::Protocol::Frame>] frames
|
353
|
-
# @api plugin
|
354
|
-
def receive_frameset(frames)
|
355
|
-
frame = frames.first
|
356
|
-
|
357
|
-
if Protocol::HeartbeatFrame === frame
|
358
|
-
@last_server_heartbeat = Time.now
|
359
|
-
else
|
360
|
-
if callable = AMQ::Client::HandlersRegistry.find(frame.method_class)
|
361
|
-
callable.call(self, frames.first, frames[1..-1])
|
362
|
-
else
|
363
|
-
raise MissingHandlerError.new(frames.first)
|
364
|
-
end
|
365
|
-
end
|
366
|
-
end
|
367
|
-
|
368
|
-
# Sends a heartbeat frame if connection is open.
|
369
|
-
# @api plugin
|
370
|
-
def send_heartbeat
|
371
|
-
if tcp_connection_established?
|
372
|
-
if @last_server_heartbeat < (Time.now - (self.heartbeat_interval * 2))
|
373
|
-
logger.error "Reconnecting due to missing server heartbeats"
|
374
|
-
# TODO: reconnect
|
375
|
-
end
|
376
|
-
send_frame(Protocol::HeartbeatFrame)
|
377
|
-
end
|
378
|
-
end # send_heartbeat
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
# @group Error handling
|
384
|
-
|
385
|
-
# Defines a callback that will be executed when channel is closed after
|
386
|
-
# channel-level exception. Only one callback can be added (the one added last
|
387
|
-
# replaces previous added ones).
|
388
|
-
#
|
389
|
-
# @api public
|
390
|
-
def on_error(&block)
|
391
|
-
self.redefine_callback(:error, &block)
|
392
|
-
end
|
393
|
-
|
394
|
-
# @endgroup
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
# Handles connection.start.
|
400
|
-
#
|
401
|
-
# @api plugin
|
402
|
-
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.4.2.1.)
|
403
|
-
def handle_start(connection_start)
|
404
|
-
@server_properties = connection_start.server_properties
|
405
|
-
@server_capabilities = @server_properties["capabilities"]
|
406
|
-
|
407
|
-
@server_authentication_mechanisms = (connection_start.mechanisms || "").split(" ")
|
408
|
-
@server_locales = Array(connection_start.locales)
|
409
|
-
|
410
|
-
username = @settings[:user] || @settings[:username]
|
411
|
-
password = @settings[:pass] || @settings[:password]
|
412
|
-
|
413
|
-
# It's not clear whether we should transition to :opening state here
|
414
|
-
# or in #open but in case authentication fails, it would be strange to have
|
415
|
-
# @status undefined. So lets do this. MK.
|
416
|
-
opening!
|
417
|
-
|
418
|
-
self.send_frame(Protocol::Connection::StartOk.encode(@client_properties, @mechanism, self.encode_credentials(username, password), @locale))
|
419
|
-
end
|
420
|
-
|
421
|
-
|
422
|
-
# Handles Connection.Tune-Ok.
|
423
|
-
#
|
424
|
-
# @api plugin
|
425
|
-
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.4.2.6)
|
426
|
-
def handle_tune(tune_ok)
|
427
|
-
@channel_max = tune_ok.channel_max.freeze
|
428
|
-
@frame_max = tune_ok.frame_max.freeze
|
429
|
-
@heartbeat_interval = self.heartbeat_interval || tune_ok.heartbeat
|
430
|
-
|
431
|
-
self.send_frame(Protocol::Connection::TuneOk.encode(@channel_max, [settings[:frame_max], @frame_max].min, @heartbeat_interval))
|
432
|
-
end # handle_tune(method)
|
433
|
-
|
434
|
-
|
435
|
-
# Handles Connection.Open-Ok.
|
436
|
-
#
|
437
|
-
# @api plugin
|
438
|
-
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.4.2.8.)
|
439
|
-
def handle_open_ok(open_ok)
|
440
|
-
@known_hosts = open_ok.known_hosts.dup.freeze
|
441
|
-
|
442
|
-
opened!
|
443
|
-
self.connection_successful if self.respond_to?(:connection_successful)
|
444
|
-
end
|
445
|
-
|
446
|
-
|
447
|
-
# Handles connection.close. When broker detects a connection level exception, this method is called.
|
448
|
-
#
|
449
|
-
# @api plugin
|
450
|
-
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.5.2.9)
|
451
|
-
def handle_close(conn_close)
|
452
|
-
self.handle_connection_interruption
|
453
|
-
|
454
|
-
closed!
|
455
|
-
# TODO: use proper exception class, provide protocol class (we know conn_close.class_id and conn_close.method_id) as well!
|
456
|
-
self.exec_callback_yielding_self(:error, conn_close)
|
457
|
-
end
|
458
|
-
|
459
|
-
|
460
|
-
# Handles Connection.Close-Ok.
|
461
|
-
#
|
462
|
-
# @api plugin
|
463
|
-
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.4.2.10)
|
464
|
-
def handle_close_ok(close_ok)
|
465
|
-
closed!
|
466
|
-
self.disconnection_successful
|
467
|
-
end # handle_close_ok(close_ok)
|
468
|
-
|
469
|
-
# @api plugin
|
470
|
-
def handle_connection_interruption
|
471
|
-
@channels.each { |n, c| c.handle_connection_interruption }
|
472
|
-
end # handle_connection_interruption
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
protected
|
477
|
-
|
478
|
-
# Returns next frame from buffer whenever possible
|
479
|
-
#
|
480
|
-
# @api private
|
481
|
-
def get_next_frame
|
482
|
-
return nil unless @chunk_buffer.size > 7 # otherwise, cannot read the length
|
483
|
-
# octet + short
|
484
|
-
offset = 3 # 1 + 2
|
485
|
-
# length
|
486
|
-
payload_length = @chunk_buffer[offset, 4].unpack(AMQ::Protocol::PACK_UINT32).first
|
487
|
-
# 4 bytes for long payload length, 1 byte final octet
|
488
|
-
frame_length = offset + payload_length + 5
|
489
|
-
if frame_length <= @chunk_buffer.size
|
490
|
-
@chunk_buffer.slice!(0, frame_length)
|
491
|
-
else
|
492
|
-
nil
|
493
|
-
end
|
494
|
-
end # def get_next_frame
|
495
|
-
|
496
|
-
# Utility methods
|
497
|
-
|
498
|
-
# Determines, whether the received frameset is ready to be further processed
|
499
|
-
def frameset_complete?(frames)
|
500
|
-
return false if frames.empty?
|
501
|
-
first_frame = frames[0]
|
502
|
-
first_frame.final? || (first_frame.method_class.has_content? && content_complete?(frames[1..-1]))
|
503
|
-
end
|
504
|
-
|
505
|
-
# Determines, whether given frame array contains full content body
|
506
|
-
def content_complete?(frames)
|
507
|
-
return false if frames.empty?
|
508
|
-
header = frames[0]
|
509
|
-
raise "Not a content header frame first: #{header.inspect}" unless header.kind_of?(AMQ::Protocol::HeaderFrame)
|
510
|
-
header.body_size == frames[1..-1].inject(0) {|sum, frame| sum + frame.payload.size }
|
511
|
-
end
|
512
|
-
|
513
|
-
end # Adapter
|
15
|
+
# backwards compatibility
|
16
|
+
# @private
|
17
|
+
Adapter = Async::Adapter
|
514
18
|
end # Client
|
515
19
|
end # AMQ
|