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
@@ -0,0 +1,517 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "amq/client/logging"
|
4
|
+
require "amq/client/settings"
|
5
|
+
require "amq/client/async/entity"
|
6
|
+
require "amq/client/async/channel"
|
7
|
+
|
8
|
+
module AMQ
|
9
|
+
# For overview of AMQP client adapters API, see {AMQ::Client::Adapter}
|
10
|
+
module Client
|
11
|
+
module Async
|
12
|
+
|
13
|
+
# Base adapter class. Specific implementations (for example, EventMachine-based, Cool.io-based or
|
14
|
+
# sockets-based) subclass it and must implement Adapter API methods:
|
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::Async::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 defined (the one defined last
|
387
|
+
# replaces previously 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
|
514
|
+
|
515
|
+
end # Async
|
516
|
+
end # Client
|
517
|
+
end # AMQ
|