right_amqp 0.2.0
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/LICENSE +20 -0
- data/README.rdoc +216 -0
- data/Rakefile +70 -0
- data/lib/right_amqp.rb +28 -0
- data/lib/right_amqp/amqp.rb +115 -0
- data/lib/right_amqp/amqp/buffer.rb +395 -0
- data/lib/right_amqp/amqp/client.rb +282 -0
- data/lib/right_amqp/amqp/frame.rb +124 -0
- data/lib/right_amqp/amqp/protocol.rb +212 -0
- data/lib/right_amqp/amqp/server.rb +99 -0
- data/lib/right_amqp/amqp/spec.rb +832 -0
- data/lib/right_amqp/amqp/version.rb +3 -0
- data/lib/right_amqp/ext/blankslate.rb +7 -0
- data/lib/right_amqp/ext/em.rb +8 -0
- data/lib/right_amqp/ext/emfork.rb +69 -0
- data/lib/right_amqp/ha_client.rb +25 -0
- data/lib/right_amqp/ha_client/broker_client.rb +690 -0
- data/lib/right_amqp/ha_client/ha_broker_client.rb +1185 -0
- data/lib/right_amqp/mq.rb +866 -0
- data/lib/right_amqp/mq/exchange.rb +304 -0
- data/lib/right_amqp/mq/header.rb +33 -0
- data/lib/right_amqp/mq/logger.rb +89 -0
- data/lib/right_amqp/mq/queue.rb +456 -0
- data/lib/right_amqp/mq/rpc.rb +100 -0
- data/right_amqp.gemspec +57 -0
- data/spec/amqp/client_reconnect_spec.rb +105 -0
- data/spec/ha_client/broker_client_spec.rb +936 -0
- data/spec/ha_client/ha_broker_client_spec.rb +1385 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +56 -0
- metadata +141 -0
@@ -0,0 +1,395 @@
|
|
1
|
+
if [].map.respond_to? :with_index
|
2
|
+
class Array #:nodoc:
|
3
|
+
def enum_with_index
|
4
|
+
each.with_index
|
5
|
+
end
|
6
|
+
end
|
7
|
+
else
|
8
|
+
require 'enumerator'
|
9
|
+
end
|
10
|
+
|
11
|
+
module AMQP
|
12
|
+
class Buffer #:nodoc: all
|
13
|
+
class Overflow < StandardError; end
|
14
|
+
class InvalidType < StandardError; end
|
15
|
+
|
16
|
+
def initialize data = ''
|
17
|
+
@data = data
|
18
|
+
@pos = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :pos
|
22
|
+
|
23
|
+
def data
|
24
|
+
@data.clone
|
25
|
+
end
|
26
|
+
alias :contents :data
|
27
|
+
alias :to_s :data
|
28
|
+
|
29
|
+
def << data
|
30
|
+
@data << data.to_s
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def length
|
35
|
+
@data.bytesize
|
36
|
+
end
|
37
|
+
|
38
|
+
def empty?
|
39
|
+
pos == length
|
40
|
+
end
|
41
|
+
|
42
|
+
def rewind
|
43
|
+
@pos = 0
|
44
|
+
end
|
45
|
+
|
46
|
+
def read_properties *types
|
47
|
+
types.shift if types.first == :properties
|
48
|
+
|
49
|
+
i = 0
|
50
|
+
values = []
|
51
|
+
|
52
|
+
while props = read(:short)
|
53
|
+
(0..14).each do |n|
|
54
|
+
# no more property types
|
55
|
+
break unless types[i]
|
56
|
+
|
57
|
+
# if flag is set
|
58
|
+
if props & (1<<(15-n)) != 0
|
59
|
+
if types[i] == :bit
|
60
|
+
# bit values exist in flags only
|
61
|
+
values << true
|
62
|
+
else
|
63
|
+
# save type name for later reading
|
64
|
+
values << types[i]
|
65
|
+
end
|
66
|
+
else
|
67
|
+
# property not set or is false bit
|
68
|
+
values << (types[i] == :bit ? false : nil)
|
69
|
+
end
|
70
|
+
|
71
|
+
i+=1
|
72
|
+
end
|
73
|
+
|
74
|
+
# bit(0) == 0 means no more property flags
|
75
|
+
break unless props & 1 == 1
|
76
|
+
end
|
77
|
+
|
78
|
+
values.map do |value|
|
79
|
+
value.is_a?(Symbol) ? read(value) : value
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def read *types
|
84
|
+
if types.first == :properties
|
85
|
+
return read_properties(*types)
|
86
|
+
end
|
87
|
+
|
88
|
+
values = types.map do |type|
|
89
|
+
case type
|
90
|
+
when :octet
|
91
|
+
_read(1, 'C')
|
92
|
+
when :short
|
93
|
+
_read(2, 'n')
|
94
|
+
when :long
|
95
|
+
_read(4, 'N')
|
96
|
+
when :longlong
|
97
|
+
upper, lower = _read(8, 'NN')
|
98
|
+
upper << 32 | lower
|
99
|
+
when :shortstr
|
100
|
+
_read read(:octet)
|
101
|
+
when :longstr
|
102
|
+
_read read(:long)
|
103
|
+
when :timestamp
|
104
|
+
Time.at read(:longlong)
|
105
|
+
when :table
|
106
|
+
t = Hash.new
|
107
|
+
|
108
|
+
table = Buffer.new(read(:longstr))
|
109
|
+
until table.empty?
|
110
|
+
key, type = table.read(:shortstr, :octet)
|
111
|
+
key = key.intern
|
112
|
+
t[key] ||= case type
|
113
|
+
when 83 # 'S'
|
114
|
+
table.read(:longstr)
|
115
|
+
when 73 # 'I'
|
116
|
+
table.read(:long)
|
117
|
+
when 68 # 'D'
|
118
|
+
exp = table.read(:octet)
|
119
|
+
num = table.read(:long)
|
120
|
+
num / 10.0**exp
|
121
|
+
when 84 # 'T'
|
122
|
+
table.read(:timestamp)
|
123
|
+
when 70 # 'F'
|
124
|
+
table.read(:table)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
t
|
129
|
+
when :bit
|
130
|
+
if (@bits ||= []).empty?
|
131
|
+
val = read(:octet)
|
132
|
+
@bits = (0..7).map{|i| (val & 1<<i) != 0 }
|
133
|
+
end
|
134
|
+
|
135
|
+
@bits.shift
|
136
|
+
else
|
137
|
+
raise InvalidType, "Cannot read data of type #{type}"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
types.size == 1 ? values.first : values
|
142
|
+
end
|
143
|
+
|
144
|
+
def write type, data
|
145
|
+
case type
|
146
|
+
when :octet
|
147
|
+
_write(data, 'C')
|
148
|
+
when :short
|
149
|
+
_write(data, 'n')
|
150
|
+
when :long
|
151
|
+
_write(data, 'N')
|
152
|
+
when :longlong
|
153
|
+
lower = data & 0xffffffff
|
154
|
+
upper = (data & ~0xffffffff) >> 32
|
155
|
+
_write([upper, lower], 'NN')
|
156
|
+
when :shortstr
|
157
|
+
data = (data || '').to_s
|
158
|
+
_write([data.bytesize, data], 'Ca*')
|
159
|
+
when :longstr
|
160
|
+
if data.is_a? Hash
|
161
|
+
write(:table, data)
|
162
|
+
else
|
163
|
+
data = (data || '').to_s
|
164
|
+
_write([data.bytesize, data], 'Na*')
|
165
|
+
end
|
166
|
+
when :timestamp
|
167
|
+
write(:longlong, data.to_i)
|
168
|
+
when :table
|
169
|
+
data ||= {}
|
170
|
+
write :longstr, (data.inject(Buffer.new) do |table, (key, value)|
|
171
|
+
table.write(:shortstr, key.to_s)
|
172
|
+
|
173
|
+
case value
|
174
|
+
when String
|
175
|
+
table.write(:octet, 83) # 'S'
|
176
|
+
table.write(:longstr, value.to_s)
|
177
|
+
when Fixnum
|
178
|
+
table.write(:octet, 73) # 'I'
|
179
|
+
table.write(:long, value)
|
180
|
+
when Float
|
181
|
+
table.write(:octet, 68) # 'D'
|
182
|
+
# XXX there's gotta be a better way to do this..
|
183
|
+
exp = value.to_s.split('.').last.bytesize
|
184
|
+
num = value * 10**exp
|
185
|
+
table.write(:octet, exp)
|
186
|
+
table.write(:long, num)
|
187
|
+
when Time
|
188
|
+
table.write(:octet, 84) # 'T'
|
189
|
+
table.write(:timestamp, value)
|
190
|
+
when Hash
|
191
|
+
table.write(:octet, 70) # 'F'
|
192
|
+
table.write(:table, value)
|
193
|
+
end
|
194
|
+
|
195
|
+
table
|
196
|
+
end)
|
197
|
+
when :bit
|
198
|
+
[*data].to_enum(:each_slice, 8).each{|bits|
|
199
|
+
write(:octet, bits.enum_with_index.inject(0){ |byte, (bit, i)|
|
200
|
+
byte |= 1<<i if bit
|
201
|
+
byte
|
202
|
+
})
|
203
|
+
}
|
204
|
+
when :properties
|
205
|
+
values = []
|
206
|
+
data.enum_with_index.inject(0) do |short, ((type, value), i)|
|
207
|
+
n = i % 15
|
208
|
+
last = i+1 == data.size
|
209
|
+
|
210
|
+
if (n == 0 and i != 0) or last
|
211
|
+
if data.size > i+1
|
212
|
+
short |= 1<<0
|
213
|
+
elsif last and value
|
214
|
+
values << [type,value]
|
215
|
+
short |= 1<<(15-n)
|
216
|
+
end
|
217
|
+
|
218
|
+
write(:short, short)
|
219
|
+
short = 0
|
220
|
+
end
|
221
|
+
|
222
|
+
if value and !last
|
223
|
+
values << [type,value]
|
224
|
+
short |= 1<<(15-n)
|
225
|
+
end
|
226
|
+
|
227
|
+
short
|
228
|
+
end
|
229
|
+
|
230
|
+
values.each do |type, value|
|
231
|
+
write(type, value) unless type == :bit
|
232
|
+
end
|
233
|
+
else
|
234
|
+
raise InvalidType, "Cannot write data of type #{type}"
|
235
|
+
end
|
236
|
+
|
237
|
+
self
|
238
|
+
end
|
239
|
+
|
240
|
+
def extract
|
241
|
+
begin
|
242
|
+
cur_data, cur_pos = @data.clone, @pos
|
243
|
+
yield self
|
244
|
+
rescue Overflow
|
245
|
+
@data, @pos = cur_data, cur_pos
|
246
|
+
nil
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def _read size, pack = nil
|
251
|
+
if @pos + size > length
|
252
|
+
raise Overflow
|
253
|
+
else
|
254
|
+
data = @data[@pos,size]
|
255
|
+
@data[@pos,size] = ''
|
256
|
+
if pack
|
257
|
+
data = data.unpack(pack)
|
258
|
+
data = data.pop if data.size == 1
|
259
|
+
end
|
260
|
+
data
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def _write data, pack = nil
|
265
|
+
data = [*data].pack(pack) if pack
|
266
|
+
@data[@pos,0] = data
|
267
|
+
@pos += data.bytesize
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
if $0 =~ /bacon/ or $0 == __FILE__
|
273
|
+
require 'bacon'
|
274
|
+
include AMQP
|
275
|
+
|
276
|
+
describe Buffer do
|
277
|
+
before do
|
278
|
+
@buf = Buffer.new
|
279
|
+
end
|
280
|
+
|
281
|
+
should 'have contents' do
|
282
|
+
@buf.contents.should == ''
|
283
|
+
end
|
284
|
+
|
285
|
+
should 'initialize with data' do
|
286
|
+
@buf = Buffer.new('abc')
|
287
|
+
@buf.contents.should == 'abc'
|
288
|
+
end
|
289
|
+
|
290
|
+
should 'append raw data' do
|
291
|
+
@buf << 'abc'
|
292
|
+
@buf << 'def'
|
293
|
+
@buf.contents.should == 'abcdef'
|
294
|
+
end
|
295
|
+
|
296
|
+
should 'append other buffers' do
|
297
|
+
@buf << Buffer.new('abc')
|
298
|
+
@buf.data.should == 'abc'
|
299
|
+
end
|
300
|
+
|
301
|
+
should 'have a position' do
|
302
|
+
@buf.pos.should == 0
|
303
|
+
end
|
304
|
+
|
305
|
+
should 'have a length' do
|
306
|
+
@buf.length.should == 0
|
307
|
+
@buf << 'abc'
|
308
|
+
@buf.length.should == 3
|
309
|
+
end
|
310
|
+
|
311
|
+
should 'know the end' do
|
312
|
+
@buf.empty?.should == true
|
313
|
+
end
|
314
|
+
|
315
|
+
should 'read and write data' do
|
316
|
+
@buf._write('abc')
|
317
|
+
@buf.rewind
|
318
|
+
@buf._read(2).should == 'ab'
|
319
|
+
@buf._read(1).should == 'c'
|
320
|
+
end
|
321
|
+
|
322
|
+
should 'raise on overflow' do
|
323
|
+
lambda{ @buf._read(1) }.should.raise Buffer::Overflow
|
324
|
+
end
|
325
|
+
|
326
|
+
should 'raise on invalid types' do
|
327
|
+
lambda{ @buf.read(:junk) }.should.raise Buffer::InvalidType
|
328
|
+
lambda{ @buf.write(:junk, 1) }.should.raise Buffer::InvalidType
|
329
|
+
end
|
330
|
+
|
331
|
+
{ :octet => 0b10101010,
|
332
|
+
:short => 100,
|
333
|
+
:long => 100_000_000,
|
334
|
+
:longlong => 666_555_444_333_222_111,
|
335
|
+
:shortstr => 'hello',
|
336
|
+
:longstr => 'bye'*500,
|
337
|
+
:timestamp => time = Time.at(Time.now.to_i),
|
338
|
+
:table => { :this => 'is', :a => 'hash', :with => {:nested => 123, :and => time, :also => 123.456} },
|
339
|
+
:bit => true
|
340
|
+
}.each do |type, value|
|
341
|
+
|
342
|
+
should "read and write a #{type}" do
|
343
|
+
@buf.write(type, value)
|
344
|
+
@buf.rewind
|
345
|
+
@buf.read(type).should == value
|
346
|
+
@buf.should.be.empty
|
347
|
+
end
|
348
|
+
|
349
|
+
end
|
350
|
+
|
351
|
+
should 'read and write multiple bits' do
|
352
|
+
bits = [true, false, false, true, true, false, false, true, true, false]
|
353
|
+
@buf.write(:bit, bits)
|
354
|
+
@buf.write(:octet, 100)
|
355
|
+
|
356
|
+
@buf.rewind
|
357
|
+
|
358
|
+
bits.map do
|
359
|
+
@buf.read(:bit)
|
360
|
+
end.should == bits
|
361
|
+
@buf.read(:octet).should == 100
|
362
|
+
end
|
363
|
+
|
364
|
+
should 'read and write properties' do
|
365
|
+
properties = ([
|
366
|
+
[:octet, 1],
|
367
|
+
[:shortstr, 'abc'],
|
368
|
+
[:bit, true],
|
369
|
+
[:bit, false],
|
370
|
+
[:shortstr, nil],
|
371
|
+
[:timestamp, nil],
|
372
|
+
[:table, { :a => 'hash' }],
|
373
|
+
]*5).sort_by{rand}
|
374
|
+
|
375
|
+
@buf.write(:properties, properties)
|
376
|
+
@buf.rewind
|
377
|
+
@buf.read(:properties, *properties.map{|type,_| type }).should == properties.map{|_,value| value }
|
378
|
+
@buf.should.be.empty
|
379
|
+
end
|
380
|
+
|
381
|
+
should 'do transactional reads with #extract' do
|
382
|
+
@buf.write :octet, 8
|
383
|
+
orig = @buf.to_s
|
384
|
+
|
385
|
+
@buf.rewind
|
386
|
+
@buf.extract do |b|
|
387
|
+
b.read :octet
|
388
|
+
b.read :short
|
389
|
+
end
|
390
|
+
|
391
|
+
@buf.pos.should == 0
|
392
|
+
@buf.data.should == orig
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
@@ -0,0 +1,282 @@
|
|
1
|
+
require 'right_support'
|
2
|
+
require File.expand_path('../frame', __FILE__)
|
3
|
+
|
4
|
+
module AMQP
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
module BasicClient
|
8
|
+
def process_frame frame
|
9
|
+
if mq = channels[frame.channel]
|
10
|
+
mq.process_frame(frame)
|
11
|
+
return
|
12
|
+
end
|
13
|
+
|
14
|
+
case frame
|
15
|
+
when Frame::Method
|
16
|
+
case method = frame.payload
|
17
|
+
when Protocol::Connection::Start
|
18
|
+
send Protocol::Connection::StartOk.new({:platform => 'Ruby/EventMachine',
|
19
|
+
:product => 'AMQP',
|
20
|
+
:information => 'http://github.com/tmm1/amqp',
|
21
|
+
:version => VERSION},
|
22
|
+
'AMQPLAIN',
|
23
|
+
{:LOGIN => @settings[:user],
|
24
|
+
:PASSWORD => @settings[:pass]},
|
25
|
+
'en_US')
|
26
|
+
|
27
|
+
when Protocol::Connection::Tune
|
28
|
+
send Protocol::Connection::TuneOk.new(:channel_max => 0,
|
29
|
+
:frame_max => 131072,
|
30
|
+
:heartbeat => @settings[:heartbeat] || 0)
|
31
|
+
|
32
|
+
send Protocol::Connection::Open.new(:virtual_host => @settings[:vhost],
|
33
|
+
:capabilities => '',
|
34
|
+
:insist => @settings[:insist])
|
35
|
+
|
36
|
+
when Protocol::Connection::OpenOk
|
37
|
+
logger.debug("[amqp] Received open completion from broker #{@settings[:identity]}")
|
38
|
+
succeed(self)
|
39
|
+
|
40
|
+
when Protocol::Connection::Close
|
41
|
+
# raise Error, "#{method.reply_text} in #{Protocol.classes[method.class_id].methods[method.method_id]}"
|
42
|
+
STDERR.puts "#{method.reply_text} in #{Protocol.classes[method.class_id].methods[method.method_id]}"
|
43
|
+
|
44
|
+
when Protocol::Connection::CloseOk
|
45
|
+
logger.debug("[amqp] Received close completion from broker #{@settings[:identity]}")
|
46
|
+
@on_disconnect.call if @on_disconnect
|
47
|
+
end
|
48
|
+
|
49
|
+
when Frame::Heartbeat
|
50
|
+
logger.debug("[amqp] Received heartbeat from broker #{@settings[:identity]}")
|
51
|
+
@last_server_heartbeat = Time.now
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
# Make callback now that handshake with the broker has completed
|
56
|
+
# The 'connected' status callback happens before the handshake is done and if it results in
|
57
|
+
# a lot of activity it might prevent EM from being able to call the code handling the
|
58
|
+
# incoming handshake packet in a timely fashion causing the broker to close the connection
|
59
|
+
@connection_status.call(:ready) if @connection_status && frame.payload.is_a?(AMQP::Protocol::Connection::Start)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.client
|
64
|
+
@client ||= BasicClient
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.client= mod
|
68
|
+
mod.__send__ :include, AMQP
|
69
|
+
@client = mod
|
70
|
+
end
|
71
|
+
|
72
|
+
module Client
|
73
|
+
include EM::Deferrable
|
74
|
+
include RightSupport::Log::Mixin
|
75
|
+
|
76
|
+
def self.included(base)
|
77
|
+
base.extend(RightSupport::Log::Mixin::ClassMethods)
|
78
|
+
end
|
79
|
+
|
80
|
+
def initialize opts = {}
|
81
|
+
@settings = opts
|
82
|
+
extend AMQP.client
|
83
|
+
|
84
|
+
@on_disconnect ||= proc{ @connection_status.call(:failed) if @connection_status }
|
85
|
+
|
86
|
+
timeout @settings[:timeout] if @settings[:timeout]
|
87
|
+
errback{ @on_disconnect.call } unless @reconnecting
|
88
|
+
|
89
|
+
@connected = false
|
90
|
+
end
|
91
|
+
|
92
|
+
def connection_completed
|
93
|
+
start_tls if @settings[:ssl]
|
94
|
+
log 'connected'
|
95
|
+
# @on_disconnect = proc{ raise Error, 'Disconnected from server' }
|
96
|
+
unless @closing
|
97
|
+
@on_disconnect = method(:disconnected)
|
98
|
+
@reconnecting = false
|
99
|
+
end
|
100
|
+
|
101
|
+
@connected = true
|
102
|
+
@connection_status.call(:connected) if @connection_status
|
103
|
+
|
104
|
+
@buf = Buffer.new
|
105
|
+
send_data HEADER
|
106
|
+
send_data [1, 1, VERSION_MAJOR, VERSION_MINOR].pack('C4')
|
107
|
+
|
108
|
+
if heartbeat = @settings[:heartbeat]
|
109
|
+
init_heartbeat if (@settings[:heartbeat] = heartbeat.to_i) > 0
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def init_heartbeat
|
114
|
+
logger.debug("[amqp] Initializing heartbeat for broker #{@settings[:identity]} to #{@settings[:heartbeat]}")
|
115
|
+
@last_server_heartbeat = Time.now
|
116
|
+
|
117
|
+
@timer.cancel if @timer
|
118
|
+
@timer = EM::PeriodicTimer.new(@settings[:heartbeat]) do
|
119
|
+
if connected?
|
120
|
+
if @last_server_heartbeat < (Time.now - (@settings[:heartbeat] * 2))
|
121
|
+
log "Reconnecting due to missing server heartbeats"
|
122
|
+
logger.info("[amqp] Reconnecting to broker #{@settings[:identity]} due to missing server heartbeats")
|
123
|
+
reconnect(true)
|
124
|
+
else
|
125
|
+
logger.debug("[amqp] Sending heartbeat to broker #{@settings[:identity]}")
|
126
|
+
@last_server_heartbeat = Time.now
|
127
|
+
send AMQP::Frame::Heartbeat.new, :channel => 0
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def connected?
|
134
|
+
@connected
|
135
|
+
end
|
136
|
+
|
137
|
+
def unbind
|
138
|
+
log 'disconnected'
|
139
|
+
@connected = false
|
140
|
+
EM.next_tick{ @on_disconnect.call }
|
141
|
+
end
|
142
|
+
|
143
|
+
def add_channel mq
|
144
|
+
(@_channel_mutex ||= Mutex.new).synchronize do
|
145
|
+
channels[ key = (channels.keys.max || 0) + 1 ] = mq
|
146
|
+
key
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def channels
|
151
|
+
@channels ||= {}
|
152
|
+
end
|
153
|
+
|
154
|
+
# Catch exceptions that would otherwise cause EM to stop or be in a bad
|
155
|
+
# state if a top level EM error handler was setup. Instead close the connection and leave EM
|
156
|
+
# alone.
|
157
|
+
# Don't log an error if the environment variable IGNORE_AMQP_FAILURES is set (used in the
|
158
|
+
# enroll script)
|
159
|
+
def receive_data data
|
160
|
+
begin
|
161
|
+
# log 'receive_data', data
|
162
|
+
@buf << data
|
163
|
+
|
164
|
+
while frame = Frame.parse(@buf)
|
165
|
+
log 'receive', frame
|
166
|
+
process_frame frame
|
167
|
+
end
|
168
|
+
rescue Exception => e
|
169
|
+
logger.exception("[amqp] Failed processing frame, closing connection", e, :trace) unless ENV['IGNORE_AMQP_FAILURES']
|
170
|
+
failed
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def process_frame frame
|
175
|
+
# this is a stub meant to be
|
176
|
+
# replaced by the module passed into initialize
|
177
|
+
end
|
178
|
+
|
179
|
+
def send data, opts = {}
|
180
|
+
channel = opts[:channel] ||= 0
|
181
|
+
data = data.to_frame(channel) unless data.is_a? Frame
|
182
|
+
data.channel = channel
|
183
|
+
|
184
|
+
log 'send', data
|
185
|
+
send_data data.to_s
|
186
|
+
end
|
187
|
+
|
188
|
+
#:stopdoc:
|
189
|
+
# def send_data data
|
190
|
+
# log 'send_data', data
|
191
|
+
# super
|
192
|
+
# end
|
193
|
+
#:startdoc:
|
194
|
+
|
195
|
+
def close &on_disconnect
|
196
|
+
if on_disconnect
|
197
|
+
@closing = true
|
198
|
+
@on_disconnect = proc{
|
199
|
+
on_disconnect.call
|
200
|
+
@closing = false
|
201
|
+
}
|
202
|
+
end
|
203
|
+
|
204
|
+
callback{ |c|
|
205
|
+
if c.channels.any?
|
206
|
+
c.channels.each do |ch, mq|
|
207
|
+
mq.close
|
208
|
+
end
|
209
|
+
else
|
210
|
+
send Protocol::Connection::Close.new(:reply_code => 200,
|
211
|
+
:reply_text => 'Goodbye',
|
212
|
+
:class_id => 0,
|
213
|
+
:method_id => 0)
|
214
|
+
end
|
215
|
+
}
|
216
|
+
end
|
217
|
+
|
218
|
+
def reconnect force = false
|
219
|
+
if @reconnecting and not force
|
220
|
+
# Wait after first reconnect attempt and in between each subsequent attempt
|
221
|
+
EM.add_timer(@settings[:reconnect_interval] || 5) { reconnect(true) }
|
222
|
+
return
|
223
|
+
end
|
224
|
+
|
225
|
+
unless @reconnecting
|
226
|
+
@deferred_status = nil
|
227
|
+
initialize(@settings)
|
228
|
+
|
229
|
+
mqs = @channels
|
230
|
+
@channels = {}
|
231
|
+
mqs.each{ |_,mq| mq.reset } if mqs
|
232
|
+
|
233
|
+
@reconnecting = true
|
234
|
+
|
235
|
+
again = @settings[:reconnect_delay]
|
236
|
+
again = again.call if again.is_a?(Proc)
|
237
|
+
if again.is_a?(Numeric)
|
238
|
+
# Wait before making initial reconnect attempt
|
239
|
+
EM.add_timer(again) { reconnect(true) }
|
240
|
+
return
|
241
|
+
elsif ![nil, true].include?(again)
|
242
|
+
raise ::AMQP::Error, "Could not interpret :reconnect_delay => #{again.inspect}; expected nil, true, or Numeric"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
log 'reconnecting'
|
247
|
+
logger.info("[amqp] Attempting to reconnect to #{@settings[:identity]}")
|
248
|
+
EM.reconnect(@settings[:host], @settings[:port], self)
|
249
|
+
end
|
250
|
+
|
251
|
+
def self.connect opts = {}
|
252
|
+
opts = AMQP.settings.merge(opts)
|
253
|
+
EM.connect opts[:host], opts[:port], self, opts
|
254
|
+
end
|
255
|
+
|
256
|
+
def connection_status &blk
|
257
|
+
@connection_status = blk
|
258
|
+
end
|
259
|
+
|
260
|
+
def failed
|
261
|
+
@connection_status.call(:failed) if @connection_status
|
262
|
+
@failed = true
|
263
|
+
close_connection
|
264
|
+
end
|
265
|
+
|
266
|
+
private
|
267
|
+
|
268
|
+
def disconnected
|
269
|
+
unless @failed
|
270
|
+
@connection_status.call(:disconnected) if @connection_status
|
271
|
+
reconnect
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def log *args
|
276
|
+
return unless @settings[:logging] or AMQP.logging
|
277
|
+
require 'pp'
|
278
|
+
pp args
|
279
|
+
puts
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|