amqp 1.1.0.pre1 → 1.1.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ChangeLog.md +5 -0
- data/Gemfile +0 -2
- data/README.md +30 -24
- data/amqp.gemspec +0 -1
- data/lib/amq/protocol/get_response.rb +55 -0
- data/lib/amqp.rb +237 -3
- data/lib/amqp/auth_mechanism_adapter.rb +69 -0
- data/lib/amqp/auth_mechanism_adapter/external.rb +27 -0
- data/lib/amqp/auth_mechanism_adapter/plain.rb +24 -0
- data/lib/amqp/callbacks.rb +67 -0
- data/lib/amqp/channel.rb +517 -76
- data/lib/amqp/consumer.rb +200 -32
- data/lib/amqp/consumer_tag_generator.rb +20 -0
- data/lib/amqp/deferrable.rb +5 -0
- data/lib/amqp/entity.rb +71 -0
- data/lib/amqp/exchange.rb +266 -17
- data/lib/amqp/framing/string/frame.rb +36 -0
- data/lib/amqp/handlers_registry.rb +28 -0
- data/lib/amqp/openable.rb +58 -0
- data/lib/amqp/queue.rb +526 -19
- data/lib/amqp/session.rb +943 -70
- data/lib/amqp/settings.rb +157 -0
- data/lib/amqp/version.rb +1 -1
- data/spec/integration/authentication_spec.rb +1 -1
- data/spec/integration/extensions/rabbitmq/publisher_confirmations_spec.rb +0 -15
- data/spec/integration/store_and_forward_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -1
- data/spec/unit/amqp/client_spec.rb +3 -1
- data/spec/unit/amqp/connection_spec.rb +10 -41
- metadata +15 -19
- data/lib/amqp/client.rb +0 -100
- data/lib/amqp/connection.rb +0 -223
data/lib/amqp/session.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "eventmachine"
|
4
|
+
require "amqp/framing/string/frame"
|
5
|
+
require "amqp/auth_mechanism_adapter"
|
4
6
|
require "amqp/broker"
|
5
7
|
|
8
|
+
require "amqp/channel"
|
9
|
+
|
6
10
|
module AMQP
|
7
11
|
# AMQP session represents connection to the broker. Session objects let you define callbacks for
|
8
12
|
# various TCP connection lifecycle events, for instance:
|
@@ -28,23 +32,180 @@ module AMQP
|
|
28
32
|
#
|
29
33
|
#
|
30
34
|
# @api public
|
31
|
-
class Session <
|
35
|
+
class Session < EM::Connection
|
36
|
+
|
37
|
+
|
38
|
+
#
|
39
|
+
# Behaviours
|
40
|
+
#
|
41
|
+
|
42
|
+
include Openable
|
43
|
+
include Callbacks
|
44
|
+
|
45
|
+
extend ProtocolMethodHandlers
|
46
|
+
extend RegisterEntityMixin
|
47
|
+
|
48
|
+
|
49
|
+
register_entity :channel, AMQP::Channel
|
32
50
|
|
33
51
|
#
|
34
52
|
# API
|
35
53
|
#
|
36
54
|
|
55
|
+
attr_accessor :logger
|
56
|
+
attr_accessor :settings
|
57
|
+
|
58
|
+
# @return [Array<#call>]
|
59
|
+
attr_reader :callbacks
|
60
|
+
|
61
|
+
|
62
|
+
# The locale defines the language in which the server will send reply texts.
|
63
|
+
#
|
64
|
+
# @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.2)
|
65
|
+
attr_accessor :locale
|
66
|
+
|
67
|
+
# Client capabilities
|
68
|
+
#
|
69
|
+
# @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.2.1)
|
70
|
+
attr_accessor :client_properties
|
71
|
+
|
72
|
+
# Server properties
|
73
|
+
#
|
74
|
+
# @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.1.3)
|
75
|
+
attr_reader :server_properties
|
76
|
+
|
77
|
+
# Server capabilities
|
78
|
+
#
|
79
|
+
# @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.1.3)
|
80
|
+
attr_reader :server_capabilities
|
81
|
+
|
82
|
+
# Locales server supports
|
83
|
+
#
|
84
|
+
# @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.1.3)
|
85
|
+
attr_reader :server_locales
|
86
|
+
|
87
|
+
# Authentication mechanism used.
|
88
|
+
#
|
89
|
+
# @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.2)
|
90
|
+
attr_reader :mechanism
|
91
|
+
|
92
|
+
# Authentication mechanisms broker supports.
|
93
|
+
#
|
94
|
+
# @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.2)
|
95
|
+
attr_reader :server_authentication_mechanisms
|
96
|
+
|
97
|
+
# Channels within this connection.
|
98
|
+
#
|
99
|
+
# @see http://bit.ly/amqp091spec AMQP 0.9.1 specification (Section 2.2.5)
|
100
|
+
attr_reader :channels
|
101
|
+
|
102
|
+
# Maximum channel number that the server permits this connection to use.
|
103
|
+
# Usable channel numbers are in the range 1..channel_max.
|
104
|
+
# Zero indicates no specified limit.
|
105
|
+
#
|
106
|
+
# @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Sections 1.4.2.5.1 and 1.4.2.6.1)
|
107
|
+
attr_accessor :channel_max
|
108
|
+
|
109
|
+
# Maximum frame size that the server permits this connection to use.
|
110
|
+
#
|
111
|
+
# @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Sections 1.4.2.5.2 and 1.4.2.6.2)
|
112
|
+
attr_accessor :frame_max
|
113
|
+
|
114
|
+
|
115
|
+
attr_reader :known_hosts
|
116
|
+
|
117
|
+
|
118
|
+
class << self
|
119
|
+
# Settings
|
120
|
+
def settings
|
121
|
+
@settings ||= AMQP::Settings.default
|
122
|
+
end
|
123
|
+
|
124
|
+
def logger
|
125
|
+
@logger ||= begin
|
126
|
+
require "logger"
|
127
|
+
Logger.new(STDERR)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def logger=(logger)
|
132
|
+
methods = AMQP::Logging::REQUIRED_METHODS
|
133
|
+
unless methods.all? { |method| logger.respond_to?(method) }
|
134
|
+
raise AMQP::Logging::IncompatibleLoggerError.new(methods)
|
135
|
+
end
|
136
|
+
|
137
|
+
@logger = logger
|
138
|
+
end
|
139
|
+
|
140
|
+
# @return [Boolean] Current value of logging flag.
|
141
|
+
def logging
|
142
|
+
settings[:logging]
|
143
|
+
end
|
144
|
+
|
145
|
+
# Turns loggin on or off.
|
146
|
+
def logging=(boolean)
|
147
|
+
settings[:logging] = boolean
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
# Establishes connection to AMQ broker and returns it. New connection object is yielded to
|
152
|
+
# the block if it is given.
|
153
|
+
#
|
154
|
+
# @example Specifying adapter via the :adapter option
|
155
|
+
# AMQP::Adapter.connect(:adapter => "socket")
|
156
|
+
# @example Specifying using custom adapter class
|
157
|
+
# AMQP::SocketClient.connect
|
158
|
+
# @param [Hash] Connection parameters, including :adapter to use.
|
159
|
+
# @api public
|
160
|
+
def connect(settings = nil, &block)
|
161
|
+
@settings = Settings.configure(settings)
|
162
|
+
|
163
|
+
instance = self.new
|
164
|
+
instance.establish_connection(settings)
|
165
|
+
instance.register_connection_callback(&block)
|
166
|
+
|
167
|
+
instance
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
|
37
172
|
# @group Connecting, reconnecting, disconnecting
|
38
173
|
|
39
174
|
def initialize(*args, &block)
|
40
|
-
super(*args
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
175
|
+
super(*args)
|
176
|
+
|
177
|
+
self.logger = self.class.logger
|
178
|
+
|
179
|
+
# channel => collected frames. MK.
|
180
|
+
@frames = Hash.new { Array.new }
|
181
|
+
@channels = Hash.new
|
182
|
+
@callbacks = Hash.new
|
183
|
+
|
184
|
+
opening!
|
185
|
+
|
186
|
+
# track TCP connection state, used to detect initial TCP connection failures.
|
187
|
+
@tcp_connection_established = false
|
188
|
+
@tcp_connection_failed = false
|
189
|
+
@intentionally_closing_connection = false
|
190
|
+
|
191
|
+
# EventMachine::Connection's and Adapter's constructors arity
|
192
|
+
# make it easier to use *args. MK.
|
193
|
+
@settings = Settings.configure(args.first)
|
194
|
+
@on_tcp_connection_failure = @settings[:on_tcp_connection_failure] || Proc.new { |settings|
|
195
|
+
raise self.class.tcp_connection_failure_exception_class.new(settings)
|
196
|
+
}
|
197
|
+
@on_possible_authentication_failure = @settings[:on_possible_authentication_failure] || Proc.new { |settings|
|
198
|
+
raise self.class.authentication_failure_exception_class.new(settings)
|
199
|
+
}
|
200
|
+
|
201
|
+
@mechanism = @settings.fetch(:auth_mechanism, "PLAIN")
|
202
|
+
@locale = @settings.fetch(:locale, "en_GB")
|
203
|
+
@client_properties = Settings.client_properties.merge(@settings.fetch(:client_properties, Hash.new))
|
204
|
+
|
205
|
+
@auto_recovery = (!!@settings[:auto_recovery])
|
206
|
+
|
207
|
+
self.reset
|
208
|
+
self.set_pending_connect_timeout((@settings[:timeout] || 3).to_f) unless defined?(JRUBY_VERSION)
|
48
209
|
end # initialize(*args, &block)
|
49
210
|
|
50
211
|
# @return [Boolean] true if this AMQP connection is currently open
|
@@ -80,43 +241,27 @@ module AMQP
|
|
80
241
|
alias user username
|
81
242
|
|
82
243
|
|
83
|
-
# Reconnect to the broker using current connection settings.
|
84
|
-
#
|
85
|
-
# @param [Boolean] force Enforce immediate connection
|
86
|
-
# @param [Fixnum] period If given, reconnection will be delayed by this period, in seconds.
|
87
|
-
# @api public
|
88
|
-
def reconnect(force = false, period = 2)
|
89
|
-
# we do this to make sure this method shows up in our documentation
|
90
|
-
# this method is too important to leave out and YARD currently does not
|
91
|
-
# support cross-referencing to dependencies. MK.
|
92
|
-
super(force, period)
|
93
|
-
end # reconnect(force = false)
|
94
|
-
|
95
|
-
# A version of #reconnect that allows connecting to different endpoints (hosts).
|
96
|
-
# @see #reconnect
|
97
|
-
# @api public
|
98
|
-
def reconnect_to(connection_string_or_options = {}, period = 2)
|
99
|
-
opts = case connection_string_or_options
|
100
|
-
when String then
|
101
|
-
AMQP::Client.parse_connection_uri(connection_string_or_options)
|
102
|
-
when Hash then
|
103
|
-
connection_string_or_options
|
104
|
-
else
|
105
|
-
Hash.new
|
106
|
-
end
|
107
|
-
|
108
|
-
super(opts, period)
|
109
|
-
end # reconnect_to(connection_string_or_options = {})
|
110
|
-
|
111
|
-
|
112
244
|
# Properly close connection with AMQ broker, as described in
|
113
245
|
# section 2.2.4 of the {http://files.travis-ci.org/docs/amqp/0.9.1/AMQP091Specification.pdf AMQP 0.9.1 specification}.
|
114
246
|
#
|
115
247
|
# @api plugin
|
116
248
|
# @see #close_connection
|
117
249
|
def disconnect(reply_code = 200, reply_text = "Goodbye", &block)
|
118
|
-
|
119
|
-
|
250
|
+
@intentionally_closing_connection = true
|
251
|
+
self.on_disconnection do
|
252
|
+
@frames.clear
|
253
|
+
block.call if block
|
254
|
+
end
|
255
|
+
|
256
|
+
# ruby-amqp/amqp#66, MK.
|
257
|
+
if self.open?
|
258
|
+
closing!
|
259
|
+
self.send_frame(AMQ::Protocol::Connection::Close.encode(reply_code, reply_text, 0, 0))
|
260
|
+
elsif self.closing?
|
261
|
+
# no-op
|
262
|
+
else
|
263
|
+
self.disconnection_successful
|
264
|
+
end
|
120
265
|
end
|
121
266
|
alias close disconnect
|
122
267
|
|
@@ -160,9 +305,9 @@ module AMQP
|
|
160
305
|
# @see #on_closed
|
161
306
|
# @api public
|
162
307
|
def on_open(&block)
|
163
|
-
|
164
|
-
|
165
|
-
|
308
|
+
@connection_deferrable.callback(&block)
|
309
|
+
end
|
310
|
+
alias on_connection on_open
|
166
311
|
|
167
312
|
|
168
313
|
# @group Error Handling and Recovery
|
@@ -173,17 +318,16 @@ module AMQP
|
|
173
318
|
# @see #on_closed
|
174
319
|
# @api public
|
175
320
|
def on_closed(&block)
|
176
|
-
|
177
|
-
|
178
|
-
|
321
|
+
@disconnection_deferrable.callback(&block)
|
322
|
+
end
|
323
|
+
alias on_disconnection on_closed
|
179
324
|
|
180
325
|
# Defines a callback that will be run when initial TCP connection fails.
|
181
326
|
# You can define only one callback.
|
182
327
|
#
|
183
328
|
# @api public
|
184
329
|
def on_tcp_connection_failure(&block)
|
185
|
-
|
186
|
-
super(&block)
|
330
|
+
@on_tcp_connection_failure = block
|
187
331
|
end
|
188
332
|
|
189
333
|
# Defines a callback that will be run when TCP connection to AMQP broker is lost (interrupted).
|
@@ -191,8 +335,7 @@ module AMQP
|
|
191
335
|
#
|
192
336
|
# @api public
|
193
337
|
def on_tcp_connection_loss(&block)
|
194
|
-
|
195
|
-
super(&block)
|
338
|
+
@on_tcp_connection_loss = block
|
196
339
|
end
|
197
340
|
|
198
341
|
# Defines a callback that will be run when TCP connection is closed before authentication
|
@@ -200,8 +343,7 @@ module AMQP
|
|
200
343
|
#
|
201
344
|
# @api public
|
202
345
|
def on_possible_authentication_failure(&block)
|
203
|
-
|
204
|
-
super(&block)
|
346
|
+
@on_possible_authentication_failure = block
|
205
347
|
end
|
206
348
|
|
207
349
|
# Defines a callback that will be executed after TCP connection is interrupted (typically because of a network failure).
|
@@ -209,25 +351,18 @@ module AMQP
|
|
209
351
|
#
|
210
352
|
# @api public
|
211
353
|
def on_connection_interruption(&block)
|
212
|
-
|
213
|
-
end
|
354
|
+
self.redefine_callback(:after_connection_interruption, &block)
|
355
|
+
end
|
214
356
|
alias after_connection_interruption on_connection_interruption
|
215
357
|
|
216
358
|
|
217
|
-
# @private
|
218
|
-
# @api plugin
|
219
|
-
def handle_connection_interruption
|
220
|
-
super
|
221
|
-
end # handle_connection_interruption
|
222
|
-
|
223
|
-
|
224
359
|
# Defines a callback that will be executed when connection is closed after
|
225
360
|
# connection-level exception. Only one callback can be defined (the one defined last
|
226
361
|
# replaces previously added ones).
|
227
362
|
#
|
228
363
|
# @api public
|
229
364
|
def on_error(&block)
|
230
|
-
|
365
|
+
self.redefine_callback(:error, &block)
|
231
366
|
end
|
232
367
|
|
233
368
|
|
@@ -237,8 +372,8 @@ module AMQP
|
|
237
372
|
#
|
238
373
|
# @api public
|
239
374
|
def before_recovery(&block)
|
240
|
-
|
241
|
-
end
|
375
|
+
self.redefine_callback(:before_recovery, &block)
|
376
|
+
end
|
242
377
|
|
243
378
|
|
244
379
|
# Defines a callback that will be executed after AMQP connection has recovered after a network failure..
|
@@ -246,16 +381,16 @@ module AMQP
|
|
246
381
|
#
|
247
382
|
# @api public
|
248
383
|
def on_recovery(&block)
|
249
|
-
|
250
|
-
end
|
384
|
+
self.redefine_callback(:after_recovery, &block)
|
385
|
+
end
|
251
386
|
alias after_recovery on_recovery
|
252
387
|
|
253
388
|
|
254
389
|
# @return [Boolean] whether connection is in the automatic recovery mode
|
255
390
|
# @api public
|
256
391
|
def auto_recovering?
|
257
|
-
|
258
|
-
end
|
392
|
+
!!@auto_recovery
|
393
|
+
end
|
259
394
|
alias auto_recovery? auto_recovering?
|
260
395
|
|
261
396
|
|
@@ -266,7 +401,7 @@ module AMQP
|
|
266
401
|
# @see Exchange#auto_recover
|
267
402
|
# @api plugin
|
268
403
|
def auto_recover
|
269
|
-
|
404
|
+
@channels.select { |channel_id, ch| ch.auto_recovering? }.each { |n, ch| ch.auto_recover }
|
270
405
|
end # auto_recover
|
271
406
|
|
272
407
|
# @endgroup
|
@@ -297,5 +432,743 @@ module AMQP
|
|
297
432
|
def self.authentication_failure_exception_class
|
298
433
|
@authentication_failure_exception_class ||= AMQP::PossibleAuthenticationFailureError
|
299
434
|
end # self.authentication_failure_exception_class
|
435
|
+
|
436
|
+
# @group Connection operations
|
437
|
+
|
438
|
+
# Initiates connection to AMQP broker. If callback is given, runs it when (and if) AMQP connection
|
439
|
+
# succeeds.
|
440
|
+
#
|
441
|
+
# @option settings [String] :host ("127.0.0.1") Hostname AMQ broker runs on.
|
442
|
+
# @option settings [String] :port (5672) Port AMQ broker listens on.
|
443
|
+
# @option settings [String] :vhost ("/") Virtual host to use.
|
444
|
+
# @option settings [String] :user ("guest") Username to use for authentication.
|
445
|
+
# @option settings [String] :pass ("guest") Password to use for authentication.
|
446
|
+
# @option settings [String] :auth_mechanism ("PLAIN") SASL authentication mechanism to use.
|
447
|
+
# @option settings [String] :ssl (false) Should be use TLS (SSL) for connection?
|
448
|
+
# @option settings [String] :timeout (nil) Connection timeout.
|
449
|
+
# @option settings [Fixnum] :heartbeat (0) Connection heartbeat, in seconds.
|
450
|
+
# @option settings [Fixnum] :frame_max (131072) Maximum frame size to use. If broker cannot support frames this large, broker's maximum value will be used instead.
|
451
|
+
#
|
452
|
+
# @param [Hash] settings
|
453
|
+
def self.connect(settings = {}, &block)
|
454
|
+
@settings = Settings.configure(settings)
|
455
|
+
|
456
|
+
instance = EventMachine.connect(@settings[:host], @settings[:port], self, @settings)
|
457
|
+
instance.register_connection_callback(&block)
|
458
|
+
|
459
|
+
instance
|
460
|
+
end
|
461
|
+
|
462
|
+
# Reconnect after a period of wait.
|
463
|
+
#
|
464
|
+
# @param [Fixnum] period Period of time, in seconds, to wait before reconnection attempt.
|
465
|
+
# @param [Boolean] force If true, enforces immediate reconnection.
|
466
|
+
# @api public
|
467
|
+
def reconnect(force = false, period = 5)
|
468
|
+
if @reconnecting and not force
|
469
|
+
EventMachine::Timer.new(period) {
|
470
|
+
reconnect(true, period)
|
471
|
+
}
|
472
|
+
return
|
473
|
+
end
|
474
|
+
|
475
|
+
if !@reconnecting
|
476
|
+
@reconnecting = true
|
477
|
+
self.reset
|
478
|
+
end
|
479
|
+
|
480
|
+
EventMachine.reconnect(@settings[:host], @settings[:port], self)
|
481
|
+
end
|
482
|
+
|
483
|
+
# Similar to #reconnect, but uses different connection settings
|
484
|
+
# @see #reconnect
|
485
|
+
# @api public
|
486
|
+
def reconnect_to(connection_string_or_options, period = 5)
|
487
|
+
settings = case connection_string_or_options
|
488
|
+
when String then
|
489
|
+
AMQP.parse_connection_uri(connection_string_or_options)
|
490
|
+
when Hash then
|
491
|
+
connection_string_or_options
|
492
|
+
else
|
493
|
+
Hash.new
|
494
|
+
end
|
495
|
+
|
496
|
+
if !@reconnecting
|
497
|
+
@reconnecting = true
|
498
|
+
self.reset
|
499
|
+
end
|
500
|
+
|
501
|
+
@settings = Settings.configure(settings)
|
502
|
+
EventMachine.reconnect(@settings[:host], @settings[:port], self)
|
503
|
+
end
|
504
|
+
|
505
|
+
|
506
|
+
# Periodically try to reconnect.
|
507
|
+
#
|
508
|
+
# @param [Fixnum] period Period of time, in seconds, to wait before reconnection attempt.
|
509
|
+
# @param [Boolean] force If true, enforces immediate reconnection.
|
510
|
+
# @api public
|
511
|
+
def periodically_reconnect(period = 5)
|
512
|
+
@reconnecting = true
|
513
|
+
self.reset
|
514
|
+
|
515
|
+
@periodic_reconnection_timer = EventMachine::PeriodicTimer.new(period) {
|
516
|
+
EventMachine.reconnect(@settings[:host], @settings[:port], self)
|
517
|
+
}
|
518
|
+
end
|
519
|
+
|
520
|
+
# @endgroup
|
521
|
+
|
522
|
+
# @see #on_open
|
523
|
+
# @private
|
524
|
+
def register_connection_callback(&block)
|
525
|
+
unless block.nil?
|
526
|
+
# delay calling block we were given till after we receive
|
527
|
+
# connection.open-ok. Connection will notify us when
|
528
|
+
# that happens.
|
529
|
+
self.on_open do
|
530
|
+
block.call(self)
|
531
|
+
end
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
|
536
|
+
|
537
|
+
# For EventMachine adapter, this is a no-op.
|
538
|
+
# @api public
|
539
|
+
def establish_connection(settings)
|
540
|
+
# Unfortunately there doesn't seem to be any sane way
|
541
|
+
# how to get EventMachine connect to the instance level.
|
542
|
+
end
|
543
|
+
|
544
|
+
alias close disconnect
|
545
|
+
|
546
|
+
|
547
|
+
|
548
|
+
# Whether we are in authentication state (after TCP connection was estabilished
|
549
|
+
# but before broker authenticated us).
|
550
|
+
#
|
551
|
+
# @return [Boolean]
|
552
|
+
# @api public
|
553
|
+
def authenticating?
|
554
|
+
@authenticating
|
555
|
+
end # authenticating?
|
556
|
+
|
557
|
+
# IS TCP connection estabilished and currently active?
|
558
|
+
# @return [Boolean]
|
559
|
+
# @api public
|
560
|
+
def tcp_connection_established?
|
561
|
+
@tcp_connection_established
|
562
|
+
end # tcp_connection_established?
|
563
|
+
|
564
|
+
|
565
|
+
|
566
|
+
|
567
|
+
#
|
568
|
+
# Implementation
|
569
|
+
#
|
570
|
+
|
571
|
+
# Backwards compatibility with 0.7.0.a25. MK.
|
572
|
+
Deferrable = EventMachine::DefaultDeferrable
|
573
|
+
|
574
|
+
|
575
|
+
alias send_raw send_data
|
576
|
+
|
577
|
+
|
578
|
+
# EventMachine reactor callback. Is run when TCP connection is estabilished
|
579
|
+
# but before resumption of the network loop. Note that this includes cases
|
580
|
+
# when TCP connection has failed.
|
581
|
+
# @private
|
582
|
+
def post_init
|
583
|
+
reset
|
584
|
+
|
585
|
+
# note that upgrading to TLS in #connection_completed causes
|
586
|
+
# Erlang SSL app that RabbitMQ relies on to report
|
587
|
+
# error on TCP connection <0.1465.0>:{ssl_upgrade_error,"record overflow"}
|
588
|
+
# and close TCP connection down. Investigation of this issue is likely
|
589
|
+
# to take some time and to not be worth in as long as #post_init
|
590
|
+
# works fine. MK.
|
591
|
+
upgrade_to_tls_if_necessary
|
592
|
+
rescue Exception => error
|
593
|
+
raise error
|
594
|
+
end # post_init
|
595
|
+
|
596
|
+
|
597
|
+
|
598
|
+
# Called by EventMachine reactor once TCP connection is successfully estabilished.
|
599
|
+
# @private
|
600
|
+
def connection_completed
|
601
|
+
# we only can safely set this value here because EventMachine is a lovely piece of
|
602
|
+
# software that calls #post_init before #unbind even when TCP connection
|
603
|
+
# fails. MK.
|
604
|
+
@tcp_connection_established = true
|
605
|
+
@periodic_reconnection_timer.cancel if @periodic_reconnection_timer
|
606
|
+
|
607
|
+
|
608
|
+
# again, this is because #unbind is called in different situations
|
609
|
+
# and there is no easy way to tell initial connection failure
|
610
|
+
# from connection loss. Not in EventMachine 0.12.x, anyway. MK.
|
611
|
+
|
612
|
+
if @had_successfully_connected_before
|
613
|
+
@recovered = true
|
614
|
+
|
615
|
+
|
616
|
+
self.start_automatic_recovery
|
617
|
+
self.upgrade_to_tls_if_necessary
|
618
|
+
end
|
619
|
+
|
620
|
+
# now we can set it. MK.
|
621
|
+
@had_successfully_connected_before = true
|
622
|
+
@reconnecting = false
|
623
|
+
@handling_skipped_hearbeats = false
|
624
|
+
@last_server_heartbeat = Time.now
|
625
|
+
|
626
|
+
self.handshake
|
627
|
+
end
|
628
|
+
|
629
|
+
# @private
|
630
|
+
def close_connection(*args)
|
631
|
+
@intentionally_closing_connection = true
|
632
|
+
|
633
|
+
super(*args)
|
634
|
+
end
|
635
|
+
|
636
|
+
# Called by EventMachine reactor when
|
637
|
+
#
|
638
|
+
# * We close TCP connection down
|
639
|
+
# * Our peer closes TCP connection down
|
640
|
+
# * There is a network connection issue
|
641
|
+
# * Initial TCP connection fails
|
642
|
+
# @private
|
643
|
+
def unbind(exception = nil)
|
644
|
+
if !@tcp_connection_established && !@had_successfully_connected_before && !@intentionally_closing_connection
|
645
|
+
@tcp_connection_failed = true
|
646
|
+
logger.error "[amqp] Detected TCP connection failure"
|
647
|
+
self.tcp_connection_failed
|
648
|
+
end
|
649
|
+
|
650
|
+
closing!
|
651
|
+
@tcp_connection_established = false
|
652
|
+
|
653
|
+
self.handle_connection_interruption if @reconnecting
|
654
|
+
@disconnection_deferrable.succeed
|
655
|
+
|
656
|
+
closed!
|
657
|
+
|
658
|
+
|
659
|
+
self.tcp_connection_lost if !@intentionally_closing_connection && @had_successfully_connected_before
|
660
|
+
|
661
|
+
# since AMQP spec dictates that authentication failure is a protocol exception
|
662
|
+
# and protocol exceptions result in connection closure, check whether we are
|
663
|
+
# in the authentication stage. If so, it is likely to signal an authentication
|
664
|
+
# issue. Java client behaves the same way. MK.
|
665
|
+
if authenticating? && !@intentionally_closing_connection
|
666
|
+
@on_possible_authentication_failure.call(@settings) if @on_possible_authentication_failure
|
667
|
+
end
|
668
|
+
end # unbind
|
669
|
+
|
670
|
+
|
671
|
+
#
|
672
|
+
# EventMachine receives data in chunks, sometimes those chunks are smaller
|
673
|
+
# than the size of AMQP frame. That's why you need to add some kind of buffer.
|
674
|
+
#
|
675
|
+
# @private
|
676
|
+
def receive_data(chunk)
|
677
|
+
@chunk_buffer << chunk
|
678
|
+
while frame = get_next_frame
|
679
|
+
self.receive_frame(AMQP::Framing::String::Frame.decode(frame))
|
680
|
+
end
|
681
|
+
end
|
682
|
+
|
683
|
+
|
684
|
+
# Called by AMQP::Connection after we receive connection.open-ok.
|
685
|
+
# @api public
|
686
|
+
def connection_successful
|
687
|
+
@authenticating = false
|
688
|
+
opened!
|
689
|
+
|
690
|
+
@connection_deferrable.succeed
|
691
|
+
end # connection_successful
|
692
|
+
|
693
|
+
|
694
|
+
# Called by AMQP::Connection after we receive connection.close-ok.
|
695
|
+
#
|
696
|
+
# @api public
|
697
|
+
def disconnection_successful
|
698
|
+
@disconnection_deferrable.succeed
|
699
|
+
|
700
|
+
# true for "after writing buffered data"
|
701
|
+
self.close_connection(true)
|
702
|
+
self.reset
|
703
|
+
closed!
|
704
|
+
end # disconnection_successful
|
705
|
+
|
706
|
+
# Called when time since last server heartbeat received is greater or equal to the
|
707
|
+
# heartbeat interval set via :heartbeat_interval option on connection.
|
708
|
+
#
|
709
|
+
# @api plugin
|
710
|
+
def handle_skipped_hearbeats
|
711
|
+
if !@handling_skipped_hearbeats && @tcp_connection_established && !@intentionally_closing_connection
|
712
|
+
@handling_skipped_hearbeats = true
|
713
|
+
self.cancel_heartbeat_sender
|
714
|
+
|
715
|
+
self.run_skipped_heartbeats_callbacks
|
716
|
+
end
|
717
|
+
end
|
718
|
+
|
719
|
+
# @private
|
720
|
+
def initialize_heartbeat_sender
|
721
|
+
@last_server_heartbeat = Time.now
|
722
|
+
@heartbeats_timer = EventMachine::PeriodicTimer.new(self.heartbeat_interval, &method(:send_heartbeat))
|
723
|
+
end
|
724
|
+
|
725
|
+
# @private
|
726
|
+
def cancel_heartbeat_sender
|
727
|
+
@heartbeats_timer.cancel if @heartbeats_timer
|
728
|
+
end
|
729
|
+
|
730
|
+
|
731
|
+
|
732
|
+
# Sends AMQ protocol header (also known as preamble).
|
733
|
+
#
|
734
|
+
# @note This must be implemented by all AMQP clients.
|
735
|
+
# @api plugin
|
736
|
+
# @see http://bit.ly/amqp091spec AMQP 0.9.1 specification (Section 2.2)
|
737
|
+
def send_preamble
|
738
|
+
self.send_raw(AMQ::Protocol::PREAMBLE)
|
739
|
+
end
|
740
|
+
|
741
|
+
# Sends frame to the peer, checking that connection is open.
|
742
|
+
#
|
743
|
+
# @raise [ConnectionClosedError]
|
744
|
+
def send_frame(frame)
|
745
|
+
if closed?
|
746
|
+
raise ConnectionClosedError.new(frame)
|
747
|
+
else
|
748
|
+
self.send_raw(frame.encode)
|
749
|
+
end
|
750
|
+
end
|
751
|
+
|
752
|
+
# Sends multiple frames, one by one. For thread safety this method takes a channel
|
753
|
+
# object and synchronizes on it.
|
754
|
+
#
|
755
|
+
# @api public
|
756
|
+
def send_frameset(frames, channel)
|
757
|
+
# some (many) developers end up sharing channels between threads and when multiple
|
758
|
+
# threads publish on the same channel aggressively, at some point frames will be
|
759
|
+
# delivered out of order and broker will raise 505 UNEXPECTED_FRAME exception.
|
760
|
+
# If we synchronize on the channel, however, this is both thread safe and pretty fine-grained
|
761
|
+
# locking. Note that "single frame" methods do not need this kind of synchronization. MK.
|
762
|
+
channel.synchronize do
|
763
|
+
frames.each { |frame| self.send_frame(frame) }
|
764
|
+
end
|
765
|
+
end # send_frameset(frames)
|
766
|
+
|
767
|
+
|
768
|
+
|
769
|
+
# Returns heartbeat interval this client uses, in seconds.
|
770
|
+
# This value may or may not be used depending on broker capabilities.
|
771
|
+
# Zero means the server does not want a heartbeat.
|
772
|
+
#
|
773
|
+
# @return [Fixnum] Heartbeat interval this client uses, in seconds.
|
774
|
+
# @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.6)
|
775
|
+
def heartbeat_interval
|
776
|
+
@heartbeat_interval
|
777
|
+
end # heartbeat_interval
|
778
|
+
|
779
|
+
# Returns true if heartbeats are enabled (heartbeat interval is greater than 0)
|
780
|
+
# @return [Boolean]
|
781
|
+
def heartbeats_enabled?
|
782
|
+
@heartbeat_interval && (@heartbeat_interval > 0)
|
783
|
+
end
|
784
|
+
|
785
|
+
|
786
|
+
# vhost this connection uses. Default is "/", a historically estabilished convention
|
787
|
+
# of RabbitMQ and amqp gem.
|
788
|
+
#
|
789
|
+
# @return [String] vhost this connection uses
|
790
|
+
# @api public
|
791
|
+
def vhost
|
792
|
+
@settings.fetch(:vhost, "/")
|
793
|
+
end # vhost
|
794
|
+
|
795
|
+
|
796
|
+
|
797
|
+
# @group Error Handling and Recovery
|
798
|
+
|
799
|
+
# Called when initial TCP connection fails.
|
800
|
+
# @api public
|
801
|
+
def tcp_connection_failed
|
802
|
+
@recovered = false
|
803
|
+
|
804
|
+
@on_tcp_connection_failure.call(@settings) if @on_tcp_connection_failure
|
805
|
+
end
|
806
|
+
|
807
|
+
# Called when previously established TCP connection fails.
|
808
|
+
# @api public
|
809
|
+
def tcp_connection_lost
|
810
|
+
@recovered = false
|
811
|
+
|
812
|
+
@on_tcp_connection_loss.call(self, @settings) if @on_tcp_connection_loss
|
813
|
+
self.handle_connection_interruption
|
814
|
+
end
|
815
|
+
|
816
|
+
# @return [Boolean]
|
817
|
+
def reconnecting?
|
818
|
+
@reconnecting
|
819
|
+
end # reconnecting?
|
820
|
+
|
821
|
+
# @private
|
822
|
+
# @api plugin
|
823
|
+
def handle_connection_interruption
|
824
|
+
self.cancel_heartbeat_sender
|
825
|
+
|
826
|
+
@channels.each { |n, c| c.handle_connection_interruption }
|
827
|
+
self.exec_callback_yielding_self(:after_connection_interruption)
|
828
|
+
end
|
829
|
+
|
830
|
+
|
831
|
+
|
832
|
+
# @private
|
833
|
+
def run_before_recovery_callbacks
|
834
|
+
self.exec_callback_yielding_self(:before_recovery, @settings)
|
835
|
+
|
836
|
+
@channels.each { |n, ch| ch.run_before_recovery_callbacks }
|
837
|
+
end
|
838
|
+
|
839
|
+
|
840
|
+
# @private
|
841
|
+
def run_after_recovery_callbacks
|
842
|
+
self.exec_callback_yielding_self(:after_recovery, @settings)
|
843
|
+
|
844
|
+
@channels.each { |n, ch| ch.run_after_recovery_callbacks }
|
845
|
+
end
|
846
|
+
|
847
|
+
|
848
|
+
# Performs recovery of channels that are in the automatic recovery mode. "before recovery" callbacks
|
849
|
+
# are run immediately, "after recovery" callbacks are run after AMQP connection is re-established and
|
850
|
+
# auto recovery is performed (using #auto_recover).
|
851
|
+
#
|
852
|
+
# Use this method if you want to run automatic recovery process after handling a connection-level exception,
|
853
|
+
# for example, 320 CONNECTION_FORCED (used by RabbitMQ when it is shut down gracefully).
|
854
|
+
#
|
855
|
+
# @see Channel#auto_recover
|
856
|
+
# @see Queue#auto_recover
|
857
|
+
# @see Exchange#auto_recover
|
858
|
+
# @api plugin
|
859
|
+
def start_automatic_recovery
|
860
|
+
self.run_before_recovery_callbacks
|
861
|
+
self.register_connection_callback do
|
862
|
+
# always run automatic recovery, because it is per-channel
|
863
|
+
# and connection has to start it. Channels that did not opt-in for
|
864
|
+
# autorecovery won't be selected. MK.
|
865
|
+
self.auto_recover
|
866
|
+
self.run_after_recovery_callbacks
|
867
|
+
end
|
868
|
+
end # start_automatic_recovery
|
869
|
+
|
870
|
+
|
871
|
+
# Defines a callback that will be executed after time since last broker heartbeat is greater
|
872
|
+
# than or equal to the heartbeat interval (skipped heartbeat is detected).
|
873
|
+
# Only one callback can be defined (the one defined last replaces previously added ones).
|
874
|
+
#
|
875
|
+
# @api public
|
876
|
+
def on_skipped_heartbeats(&block)
|
877
|
+
self.redefine_callback(:skipped_heartbeats, &block)
|
878
|
+
end # on_skipped_heartbeats(&block)
|
879
|
+
|
880
|
+
# @private
|
881
|
+
def run_skipped_heartbeats_callbacks
|
882
|
+
self.exec_callback_yielding_self(:skipped_heartbeats, @settings)
|
883
|
+
end
|
884
|
+
|
885
|
+
# @endgroup
|
886
|
+
|
887
|
+
|
888
|
+
|
889
|
+
|
890
|
+
#
|
891
|
+
# Implementation
|
892
|
+
#
|
893
|
+
|
894
|
+
# Sends connection preamble to the broker.
|
895
|
+
# @api plugin
|
896
|
+
def handshake
|
897
|
+
@authenticating = true
|
898
|
+
self.send_preamble
|
899
|
+
end
|
900
|
+
|
901
|
+
|
902
|
+
# Sends connection.open to the server.
|
903
|
+
#
|
904
|
+
# @api plugin
|
905
|
+
# @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.7)
|
906
|
+
def open(vhost = "/")
|
907
|
+
self.send_frame(AMQ::Protocol::Connection::Open.encode(vhost))
|
908
|
+
end
|
909
|
+
|
910
|
+
# Resets connection state.
|
911
|
+
#
|
912
|
+
# @api plugin
|
913
|
+
def reset_state!
|
914
|
+
# no-op by default
|
915
|
+
end # reset_state!
|
916
|
+
|
917
|
+
# @api plugin
|
918
|
+
# @see http://tools.ietf.org/rfc/rfc2595.txt RFC 2595
|
919
|
+
def encode_credentials(username, password)
|
920
|
+
auth_mechanism_adapter.encode_credentials(username, password)
|
921
|
+
end # encode_credentials(username, password)
|
922
|
+
|
923
|
+
# Retrieves an AuthMechanismAdapter that will encode credentials for
|
924
|
+
# this Adapter.
|
925
|
+
#
|
926
|
+
# @api plugin
|
927
|
+
def auth_mechanism_adapter
|
928
|
+
@auth_mechanism_adapter ||= AuthMechanismAdapter.for_adapter(self)
|
929
|
+
end
|
930
|
+
|
931
|
+
|
932
|
+
# Processes a single frame.
|
933
|
+
#
|
934
|
+
# @param [AMQ::Protocol::Frame] frame
|
935
|
+
# @api plugin
|
936
|
+
def receive_frame(frame)
|
937
|
+
@frames[frame.channel] ||= Array.new
|
938
|
+
@frames[frame.channel] << frame
|
939
|
+
|
940
|
+
if frameset_complete?(@frames[frame.channel])
|
941
|
+
receive_frameset(@frames[frame.channel])
|
942
|
+
# for channel.close, frame.channel will be nil. MK.
|
943
|
+
clear_frames_on(frame.channel) if @frames[frame.channel]
|
944
|
+
end
|
945
|
+
end
|
946
|
+
|
947
|
+
# Processes a frameset by finding and invoking a suitable handler.
|
948
|
+
# Heartbeat frames are treated in a special way: they simply update @last_server_heartbeat
|
949
|
+
# value.
|
950
|
+
#
|
951
|
+
# @param [Array<AMQ::Protocol::Frame>] frames
|
952
|
+
# @api plugin
|
953
|
+
def receive_frameset(frames)
|
954
|
+
if self.heartbeats_enabled?
|
955
|
+
# treat incoming traffic as heartbeats.
|
956
|
+
# this operation is pretty expensive under heavy traffic but heartbeats can be disabled
|
957
|
+
# (and are also disabled by default). MK.
|
958
|
+
@last_server_heartbeat = Time.now
|
959
|
+
end
|
960
|
+
frame = frames.first
|
961
|
+
|
962
|
+
if AMQ::Protocol::HeartbeatFrame === frame
|
963
|
+
# no-op
|
964
|
+
else
|
965
|
+
if callable = AMQP::HandlersRegistry.find(frame.method_class)
|
966
|
+
f = frames.shift
|
967
|
+
callable.call(self, f, frames)
|
968
|
+
else
|
969
|
+
raise MissingHandlerError.new(frames.first)
|
970
|
+
end
|
971
|
+
end
|
972
|
+
end
|
973
|
+
|
974
|
+
# Clears frames that were received but not processed on given channel. Needs to be called
|
975
|
+
# when the channel is closed.
|
976
|
+
# @private
|
977
|
+
def clear_frames_on(channel_id)
|
978
|
+
raise ArgumentError, "channel id cannot be nil!" if channel_id.nil?
|
979
|
+
|
980
|
+
@frames[channel_id].clear
|
981
|
+
end
|
982
|
+
|
983
|
+
# Sends a heartbeat frame if connection is open.
|
984
|
+
# @api plugin
|
985
|
+
def send_heartbeat
|
986
|
+
if tcp_connection_established? && !@handling_skipped_hearbeats && @last_server_heartbeat
|
987
|
+
if @last_server_heartbeat < (Time.now - (self.heartbeat_interval * 2)) && !reconnecting?
|
988
|
+
logger.error "[amqp] Detected missing server heartbeats"
|
989
|
+
self.handle_skipped_hearbeats
|
990
|
+
end
|
991
|
+
send_frame(AMQ::Protocol::HeartbeatFrame)
|
992
|
+
end
|
993
|
+
end # send_heartbeat
|
994
|
+
|
995
|
+
|
996
|
+
|
997
|
+
|
998
|
+
|
999
|
+
|
1000
|
+
# Handles connection.start.
|
1001
|
+
#
|
1002
|
+
# @api plugin
|
1003
|
+
# @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.1.)
|
1004
|
+
def handle_start(connection_start)
|
1005
|
+
@server_properties = connection_start.server_properties
|
1006
|
+
@server_capabilities = @server_properties["capabilities"]
|
1007
|
+
|
1008
|
+
@server_authentication_mechanisms = (connection_start.mechanisms || "").split(" ")
|
1009
|
+
@server_locales = Array(connection_start.locales)
|
1010
|
+
|
1011
|
+
username = @settings[:user] || @settings[:username]
|
1012
|
+
password = @settings[:pass] || @settings[:password]
|
1013
|
+
|
1014
|
+
# It's not clear whether we should transition to :opening state here
|
1015
|
+
# or in #open but in case authentication fails, it would be strange to have
|
1016
|
+
# @status undefined. So lets do this. MK.
|
1017
|
+
opening!
|
1018
|
+
|
1019
|
+
self.send_frame(AMQ::Protocol::Connection::StartOk.encode(@client_properties, mechanism, self.encode_credentials(username, password), @locale))
|
1020
|
+
end
|
1021
|
+
|
1022
|
+
|
1023
|
+
# Handles Connection.Tune-Ok.
|
1024
|
+
#
|
1025
|
+
# @api plugin
|
1026
|
+
# @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.6)
|
1027
|
+
def handle_tune(connection_tune)
|
1028
|
+
@channel_max = connection_tune.channel_max.freeze
|
1029
|
+
@frame_max = connection_tune.frame_max.freeze
|
1030
|
+
|
1031
|
+
client_heartbeat = @settings[:heartbeat] || @settings[:heartbeat_interval] || 0
|
1032
|
+
|
1033
|
+
@heartbeat_interval = negotiate_heartbeat_value(client_heartbeat, connection_tune.heartbeat)
|
1034
|
+
|
1035
|
+
self.send_frame(AMQ::Protocol::Connection::TuneOk.encode(@channel_max, [settings[:frame_max], @frame_max].min, @heartbeat_interval))
|
1036
|
+
self.initialize_heartbeat_sender if heartbeats_enabled?
|
1037
|
+
end # handle_tune(method)
|
1038
|
+
|
1039
|
+
|
1040
|
+
# Handles Connection.Open-Ok.
|
1041
|
+
#
|
1042
|
+
# @api plugin
|
1043
|
+
# @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.8.)
|
1044
|
+
def handle_open_ok(open_ok)
|
1045
|
+
@known_hosts = open_ok.known_hosts.dup.freeze
|
1046
|
+
|
1047
|
+
opened!
|
1048
|
+
self.connection_successful if self.respond_to?(:connection_successful)
|
1049
|
+
end
|
1050
|
+
|
1051
|
+
|
1052
|
+
# Handles connection.close. When broker detects a connection level exception, this method is called.
|
1053
|
+
#
|
1054
|
+
# @api plugin
|
1055
|
+
# @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.5.2.9)
|
1056
|
+
def handle_close(conn_close)
|
1057
|
+
closed!
|
1058
|
+
self.exec_callback_yielding_self(:error, conn_close)
|
1059
|
+
end
|
1060
|
+
|
1061
|
+
|
1062
|
+
# Handles Connection.Close-Ok.
|
1063
|
+
#
|
1064
|
+
# @api plugin
|
1065
|
+
# @see http://bit.ly/amqp091reference AMQP 0.9.1 protocol reference (Section 1.4.2.10)
|
1066
|
+
def handle_close_ok(close_ok)
|
1067
|
+
closed!
|
1068
|
+
self.disconnection_successful
|
1069
|
+
end # handle_close_ok(close_ok)
|
1070
|
+
|
1071
|
+
|
1072
|
+
|
1073
|
+
protected
|
1074
|
+
|
1075
|
+
def negotiate_heartbeat_value(client_value, server_value)
|
1076
|
+
if client_value == 0 || server_value == 0
|
1077
|
+
[client_value, server_value].max
|
1078
|
+
else
|
1079
|
+
[client_value, server_value].min
|
1080
|
+
end
|
1081
|
+
end
|
1082
|
+
|
1083
|
+
# Returns next frame from buffer whenever possible
|
1084
|
+
#
|
1085
|
+
# @api private
|
1086
|
+
def get_next_frame
|
1087
|
+
return nil unless @chunk_buffer.size > 7 # otherwise, cannot read the length
|
1088
|
+
# octet + short
|
1089
|
+
offset = 3 # 1 + 2
|
1090
|
+
# length
|
1091
|
+
payload_length = @chunk_buffer[offset, 4].unpack(AMQ::Protocol::PACK_UINT32).first
|
1092
|
+
# 4 bytes for long payload length, 1 byte final octet
|
1093
|
+
frame_length = offset + payload_length + 5
|
1094
|
+
if frame_length <= @chunk_buffer.size
|
1095
|
+
@chunk_buffer.slice!(0, frame_length)
|
1096
|
+
else
|
1097
|
+
nil
|
1098
|
+
end
|
1099
|
+
end # def get_next_frame
|
1100
|
+
|
1101
|
+
# Utility methods
|
1102
|
+
|
1103
|
+
# Determines, whether the received frameset is ready to be further processed
|
1104
|
+
def frameset_complete?(frames)
|
1105
|
+
return false if frames.empty?
|
1106
|
+
first_frame = frames[0]
|
1107
|
+
first_frame.final? || (first_frame.method_class.has_content? && content_complete?(frames[1..-1]))
|
1108
|
+
end
|
1109
|
+
|
1110
|
+
# Determines, whether given frame array contains full content body
|
1111
|
+
def content_complete?(frames)
|
1112
|
+
return false if frames.empty?
|
1113
|
+
header = frames[0]
|
1114
|
+
raise "Not a content header frame first: #{header.inspect}" unless header.kind_of?(AMQ::Protocol::HeaderFrame)
|
1115
|
+
header.body_size == frames[1..-1].inject(0) {|sum, frame| sum + frame.payload.size }
|
1116
|
+
end
|
1117
|
+
|
1118
|
+
|
1119
|
+
|
1120
|
+
self.handle(AMQ::Protocol::Connection::Start) do |connection, frame|
|
1121
|
+
connection.handle_start(frame.decode_payload)
|
1122
|
+
end
|
1123
|
+
|
1124
|
+
self.handle(AMQ::Protocol::Connection::Tune) do |connection, frame|
|
1125
|
+
connection.handle_tune(frame.decode_payload)
|
1126
|
+
|
1127
|
+
connection.open(connection.vhost)
|
1128
|
+
end
|
1129
|
+
|
1130
|
+
self.handle(AMQ::Protocol::Connection::OpenOk) do |connection, frame|
|
1131
|
+
connection.handle_open_ok(frame.decode_payload)
|
1132
|
+
end
|
1133
|
+
|
1134
|
+
self.handle(AMQ::Protocol::Connection::Close) do |connection, frame|
|
1135
|
+
connection.handle_close(frame.decode_payload)
|
1136
|
+
end
|
1137
|
+
|
1138
|
+
self.handle(AMQ::Protocol::Connection::CloseOk) do |connection, frame|
|
1139
|
+
connection.handle_close_ok(frame.decode_payload)
|
1140
|
+
end
|
1141
|
+
|
1142
|
+
|
1143
|
+
|
1144
|
+
|
1145
|
+
protected
|
1146
|
+
|
1147
|
+
|
1148
|
+
def reset
|
1149
|
+
@size = 0
|
1150
|
+
@payload = ""
|
1151
|
+
@frames = Array.new
|
1152
|
+
|
1153
|
+
@chunk_buffer = ""
|
1154
|
+
@connection_deferrable = EventMachine::DefaultDeferrable.new
|
1155
|
+
@disconnection_deferrable = EventMachine::DefaultDeferrable.new
|
1156
|
+
|
1157
|
+
# used to track down whether authentication succeeded. AMQP 0.9.1 dictates
|
1158
|
+
# that on authentication failure broker must close TCP connection without sending
|
1159
|
+
# any more data. This is why we need to explicitly track whether we are past
|
1160
|
+
# authentication stage to signal possible authentication failures.
|
1161
|
+
@authenticating = false
|
1162
|
+
end
|
1163
|
+
|
1164
|
+
def upgrade_to_tls_if_necessary
|
1165
|
+
tls_options = @settings[:ssl]
|
1166
|
+
|
1167
|
+
if tls_options.is_a?(Hash)
|
1168
|
+
start_tls(tls_options)
|
1169
|
+
elsif tls_options
|
1170
|
+
start_tls
|
1171
|
+
end
|
1172
|
+
end # upgrade_to_tls_if_necessary
|
300
1173
|
end # Session
|
301
1174
|
end # AMQP
|