amqp 1.1.0.pre1 → 1.1.0.pre2
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.
- 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
|