arvicco-amqp 0.6.8 → 0.6.9

Sign up to get free protection for your applications and to get access to all the features.
data/old/amqpc.rb ADDED
@@ -0,0 +1,695 @@
1
+ require 'enumerator'
2
+
3
+ require 'rubygems'
4
+ require 'eventmachine'
5
+
6
+ require 'amqp_spec'
7
+
8
+ module AMQP
9
+ class BufferOverflow < StandardError; end
10
+ class InvalidFrame < StandardError; end
11
+
12
+ module Protocol
13
+ class Class::Method
14
+ def initialize *args
15
+ opts = args.pop if args.size == 1 and args.last.is_a? Hash
16
+ opts ||= {}
17
+
18
+ # XXX hack, p(obj) == '' if no instance vars are set
19
+ @debug = 1
20
+
21
+ if args.size == 1 and args.first.is_a? Buffer
22
+ buf = args.first
23
+ else
24
+ buf = nil
25
+ end
26
+
27
+ self.class.arguments.each do |type, name|
28
+ if buf
29
+ val = buf.parse(type)
30
+ else
31
+ val = args.shift || opts[name] || opts[name.to_s]
32
+ end
33
+
34
+ instance_variable_set("@#{name}", val)
35
+ end
36
+ end
37
+
38
+ def convert_bits
39
+ if @bits.size > 0
40
+ data = @bits.to_enum(:each_slice, 8).inject(''){|octet, list|
41
+ octet + pack(:octet,
42
+ list.enum_with_index.inject(0){ |byte, (bit, i)|
43
+ byte |= 1<<i if bit
44
+ byte
45
+ }
46
+ )
47
+ }
48
+ @bits = []
49
+ end
50
+
51
+ data || ''
52
+ end
53
+
54
+ def to_binary
55
+ @bits = []
56
+
57
+ pack(:short, self.class.parent.id) +
58
+ pack(:short, self.class.id) +
59
+ self.class.arguments.inject(''){ |data, (type, name)|
60
+ if type == :bit
61
+ @bits << (instance_variable_get("@#{name}") || false)
62
+ data
63
+ else
64
+ data + convert_bits + pack(type, instance_variable_get("@#{name}"))
65
+ end
66
+ } + convert_bits
67
+ end
68
+ alias :to_s :to_binary
69
+
70
+ def to_frame channel = 0
71
+ Frame.new(:METHOD, channel, self)
72
+ end
73
+
74
+ private
75
+
76
+ def pack type, data
77
+ # p ['pack', type, data]
78
+
79
+ case type
80
+ when :octet
81
+ [data].pack('C')
82
+ when :short
83
+ [data].pack('n')
84
+ when :long
85
+ [data].pack('N')
86
+ when :shortstr
87
+ data ||= ''
88
+ len = data.length
89
+ [len, data].pack("Ca#{len}")
90
+ when :longstr
91
+ if data.is_a? Hash
92
+ pack(:table, data)
93
+ else
94
+ len = data.length
95
+ [len, data].pack("Na#{len}")
96
+ end
97
+ when :table
98
+ data ||= {}
99
+ pack :longstr, (data.inject('') do |str, (key, value)|
100
+ str +
101
+ pack(:shortstr, key.to_s) +
102
+ pack(:octet, ?S) +
103
+ pack(:longstr, value.to_s) # XXX support other field types here
104
+ end)
105
+ when :longlong
106
+ lower = l & 0xffffffff
107
+ upper = (l & ~0xffffffff) >> 32
108
+ pack(:long, upper) + pack(:long, lower)
109
+ when :bit
110
+ data = if data.nil? or data == false or data == 0
111
+ 0
112
+ else
113
+ 1
114
+ end
115
+ pack(:octet, data)
116
+ end
117
+ end
118
+ end
119
+
120
+ def self.parse payload
121
+ buf = Buffer.new(payload)
122
+ class_id, method_id = buf.parse(:short, :short)
123
+ classes[class_id].methods[method_id].new(buf)
124
+ end
125
+ end
126
+
127
+ class Frame
128
+ def initialize type, channel, payload
129
+ @channel = channel
130
+ @type = (1..8).include?(type) ? TYPES[type] :
131
+ TYPES.include?(type) ? type : raise(InvalidFrame)
132
+
133
+ if @type == :METHOD and payload.is_a? String
134
+ @payload = Protocol.parse(payload)
135
+ else
136
+ @payload = payload
137
+ end
138
+ end
139
+ attr_reader :type, :channel, :payload
140
+
141
+ def to_binary
142
+ data = payload.to_s
143
+ size = data.length
144
+ # XXX the spec falsely claims there is another empty 'cycle' octet in this header
145
+ [TYPES.index(type), channel, size, data, FOOTER].pack("CnNa*C")
146
+ end
147
+ alias :to_s :to_binary
148
+
149
+ def == b
150
+ [ :type, :channel, :payload ].inject(true){|eql, field| eql and __send__(field) == b.__send__(field) }
151
+ end
152
+
153
+ def self.extract data
154
+ (@buffer ||= Buffer.new).extract(data.to_s)
155
+ end
156
+ end
157
+
158
+ class Buffer
159
+ def initialize data = ''
160
+ @data = data
161
+ @pos = 0
162
+ end
163
+ attr_reader :pos
164
+
165
+ def extract data = nil
166
+ @data << data if data
167
+
168
+ processed = 0
169
+ frames = []
170
+
171
+ while true
172
+ type, channel, size = parse(:octet, :short, :long)
173
+ payload = read(size)
174
+ if read(1) == Frame::FOOTER.chr
175
+ frames << Frame.new(type, channel, payload)
176
+ else
177
+ raise InvalidFrame
178
+ end
179
+ processed = @pos
180
+ end
181
+ rescue BufferOverflow
182
+ # log 'buffer overflow', @pos, processed
183
+ @data[0..processed] = '' if processed > 0
184
+ @pos = 0
185
+ frames
186
+ end
187
+
188
+ def parse *syms
189
+ res = syms.map do |sym|
190
+ # log 'parsing', sym
191
+ case sym
192
+ when :octet
193
+ read(1, 'C')
194
+ when :short
195
+ read(2, 'n')
196
+ when :long
197
+ read(4, 'N')
198
+ when :longlong
199
+ # FIXME
200
+ when :shortstr
201
+ len = parse(:octet)
202
+ read(len)
203
+ when :longstr
204
+ len = parse(:long)
205
+ read(len)
206
+ when :timestamp
207
+ parse(:longlong)
208
+ when :bit
209
+ # FIXME
210
+ when :table
211
+ t = Hash.new
212
+
213
+ table = Buffer.new(parse(:longstr))
214
+ until table.eof?
215
+ key, type = table.parse(:shortstr, :octet)
216
+ t[key] = case type
217
+ when ?S
218
+ table.parse(:longstr)
219
+ when ?I
220
+ table.parse(:long)
221
+ when ?D
222
+ d = table.parse(:octet)
223
+ table.parse(:long) / (10**d)
224
+ when ?T
225
+ table.parse(:timestamp)
226
+ when ?F
227
+ table.parse(:table)
228
+ end
229
+ end
230
+
231
+ t
232
+ else
233
+ # FIXME remove
234
+ end
235
+ end
236
+
237
+ syms.length == 1 ? res[0] : res
238
+ end
239
+
240
+ def read len, type = nil
241
+ # log 'reading', len, type, :pos => @pos
242
+ raise BufferOverflow if @pos+len > @data.length
243
+
244
+ d = @data.slice(@pos, len)
245
+ @pos += len
246
+ d = d.unpack(type).pop if type
247
+ # log 'read', d
248
+ d
249
+ end
250
+
251
+ def eof?
252
+ @pos == @data.length
253
+ end
254
+
255
+ private
256
+
257
+ def log *args
258
+ p args
259
+ end
260
+ end
261
+
262
+ module Connection
263
+ def initialize blk
264
+ @blk = blk
265
+ end
266
+
267
+ def connection_completed
268
+ log 'connected'
269
+ send_data HEADER
270
+ send_data [1, 1, VERSION_MAJOR, VERSION_MINOR].pack('CCCC')
271
+ end
272
+
273
+ def receive_data data
274
+ # log 'receive_data', data
275
+
276
+ Frame.extract(data).each do |frame|
277
+ log 'receive', frame
278
+
279
+ case method = frame.payload
280
+ when Protocol::Connection::Start
281
+ send Protocol::Connection::StartOk.new({:platform => 'Ruby/EventMachine',
282
+ :product => 'AMQP',
283
+ :information => 'http://github.com/tmm1/amqp',
284
+ :version => '0.0.1'},
285
+ 'AMQPLAIN',
286
+ {:LOGIN => 'guest',
287
+ :PASSWORD => 'guest'},
288
+ 'en_US')
289
+
290
+ when Protocol::Connection::Tune
291
+ send Protocol::Connection::TuneOk.new :channel_max => 0,
292
+ :frame_max => 131072,
293
+ :heartbeat => 0
294
+
295
+ send Protocol::Connection::Open.new :virtual_host => '/',
296
+ :capabilities => '',
297
+ :insist => false
298
+
299
+ when Protocol::Connection::OpenOk
300
+ send Protocol::Channel::Open.new, :channel => 1
301
+
302
+ when Protocol::Channel::OpenOk
303
+ send Protocol::Access::Request.new(:realm => '/data',
304
+ :read => true,
305
+ :write => true,
306
+ :active => true), :channel => 1
307
+
308
+ when Protocol::Access::RequestOk
309
+ @ticket = method.ticket
310
+ send Protocol::Queue::Declare.new(:ticket => @ticket,
311
+ :queue => '',
312
+ :exclusive => false,
313
+ :auto_delete => true), :channel => 1
314
+
315
+ when Protocol::Queue::DeclareOk
316
+ @queue = method.queue
317
+ send Protocol::Queue::Bind.new(:ticket => @ticket,
318
+ :queue => @queue,
319
+ :exchange => '',
320
+ :routing_key => 'test_route'), :channel => 1
321
+
322
+ when Protocol::Queue::BindOk
323
+ send Protocol::Basic::Consume.new(:ticket => @ticket,
324
+ :queue => @queue,
325
+ :no_local => false,
326
+ :no_ack => true), :channel => 1
327
+
328
+ when Protocol::Basic::ConsumeOk
329
+ send Protocol::Basic::Publish.new(:ticket => @ticket,
330
+ :exchange => '',
331
+ :routing_key => 'test_route'), :channel => 1, :content => 'this is a test!'
332
+ end
333
+ end
334
+ end
335
+
336
+ def send data, opts = {}
337
+ channel = opts[:channel] ||= 0
338
+ data = data.to_frame(channel) unless data.is_a? Frame
339
+ log 'send', data
340
+ send_data data.to_binary
341
+
342
+ if content = opts[:content]
343
+ size = content.length
344
+
345
+ lower = size & 0xffffffff
346
+ upper = (size & ~0xffffffff) >> 32
347
+ # XXX rabbitmq only supports weight == 0
348
+ data = [ 60, weight = 0, upper, lower, 0b1001_1000_0000_0000 ].pack("nnNNn")
349
+ data += [ len = 'application/octet-stream'.length, 'application/octet-stream', 1, 1 ].pack("Ca*CC")
350
+ send_data Frame.new(:HEADER, channel, data).to_binary
351
+
352
+ # XXX spec says add FRAME_END, but rabbitmq doesn't support it
353
+ # data = [ content, Frame::FOOTER ].pack('a*C')
354
+ data = [ content ].pack("a*")
355
+ send_data Frame.new(:BODY, channel, data).to_binary
356
+ end
357
+ end
358
+
359
+ def send_data data
360
+ log 'send_data', data
361
+ super
362
+ end
363
+
364
+ def unbind
365
+ log 'disconnected'
366
+ end
367
+
368
+ def self.start host = 'localhost', port = PORT, &blk
369
+ EM.run{
370
+ EM.connect host, port, self, blk
371
+ }
372
+ end
373
+
374
+ private
375
+
376
+ def log *args
377
+ pp args
378
+ puts
379
+ end
380
+ end
381
+
382
+ def self.start *args, &blk
383
+ Connection.start *args, &blk
384
+ end
385
+ end
386
+
387
+ require 'pp'
388
+
389
+ if $0 == __FILE__
390
+ EM.run{
391
+ AMQP.start do |amqp|
392
+ amqp.test.integer { |meth|
393
+ meth
394
+ }.send(0)
395
+
396
+ amqp(1).on(:method) {
397
+
398
+ }
399
+
400
+ amqp(1).test.integer
401
+ end
402
+ }
403
+ elsif $0 =~ /bacon/
404
+ include AMQP
405
+
406
+ describe Frame do
407
+ should 'convert to binary' do
408
+ Frame.new(2, 0, 'abc').to_binary.should == "\002\000\000\000\000\000\003abc\316"
409
+ end
410
+
411
+ should 'return type as symbol' do
412
+ Frame.new(3, 0, 'abc').type.should == :BODY
413
+ Frame.new(:BODY, 0, 'abc').type.should == :BODY
414
+ end
415
+
416
+ should 'wrap Buffer#extract' do
417
+ Frame.extract(frame = Frame.new(2,0,'abc')).first.should == frame
418
+ end
419
+ end
420
+
421
+ describe Buffer do
422
+ @frame = Frame.new(2, 0, 'abc')
423
+
424
+ should 'parse complete frames' do
425
+ frame = Buffer.new(@frame.to_binary).extract.first
426
+
427
+ frame.should.be.kind_of? Frame
428
+ frame.should.be == @frame
429
+ end
430
+
431
+ should 'not return incomplete frames until complete' do
432
+ buffer = Buffer.new(@frame.to_binary[0..5])
433
+ buffer.extract.should == []
434
+ buffer.extract(@frame.to_binary[6..-1]).should == [@frame]
435
+ buffer.extract.should == []
436
+ end
437
+ end
438
+
439
+ describe Protocol do
440
+ @start = Protocol::Connection::Start.new(:locales => 'en_US',
441
+ :mechanisms => 'PLAIN AMQPLAIN',
442
+ :version_major => 8,
443
+ :version_minor => 0,
444
+ :server_properties => {'product' => 'RabbitMQ'})
445
+
446
+ @startok = Protocol::Connection::StartOk.new({:platform => 'Ruby/EventMachine',
447
+ :product => 'AMQP',
448
+ :information => 'http://github.com/tmm1/amqp',
449
+ :version => '0.0.1'},
450
+ 'PLAIN',
451
+ {:LOGIN => 'guest',
452
+ :PASSWORD => 'guest'},
453
+ 'en_US')
454
+
455
+ should 'generate method packets' do
456
+ meth = Protocol::Connection::StartOk.new :locale => 'en_US',
457
+ :mechanism => 'PLAIN'
458
+ meth.locale.should == @startok.locale
459
+ end
460
+
461
+ should 'generate method frames' do
462
+ @startok.to_frame.should == Frame.new(:METHOD, 0, @startok)
463
+ end
464
+
465
+ should 'convert to and from binary' do
466
+ Protocol.parse(@start.to_binary).should == @start
467
+ end
468
+
469
+ should 'convert to and from frames' do
470
+ # XXX make this Frame.parse (refactor Buffer#extract)
471
+ Buffer.new(@start.to_frame.to_binary).extract.first.payload.should == @start
472
+ end
473
+ end
474
+ end
475
+
476
+ __END__
477
+
478
+ ["connected"]
479
+
480
+ ["receive",
481
+ #<AMQP::Frame:0x1063834
482
+ @channel=0,
483
+ @payload=
484
+ #<AMQP::Protocol::Connection::Start:0x1063500
485
+ @debug=1,
486
+ @locales="en_US",
487
+ @mechanisms="PLAIN AMQPLAIN",
488
+ @server_properties=
489
+ {"platform"=>"Erlang/OTP",
490
+ "product"=>"RabbitMQ",
491
+ "information"=>"Licensed under the MPL. See http://www.rabbitmq.com/",
492
+ "version"=>"%%VERSION%%",
493
+ "copyright"=>
494
+ "Copyright (C) 2007-2008 LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd."},
495
+ @version_major=8,
496
+ @version_minor=0>,
497
+ @type=:METHOD>]
498
+
499
+ ["send",
500
+ #<AMQP::Frame:0x104c5bc
501
+ @channel=0,
502
+ @payload=
503
+ #<AMQP::Protocol::Connection::StartOk:0x104c7d8
504
+ @client_properties=
505
+ {:product=>"AMQP",
506
+ :information=>"http://github.com/tmm1/amqp",
507
+ :platform=>"Ruby/EventMachine",
508
+ :version=>"0.0.1"},
509
+ @debug=1,
510
+ @locale="en_US",
511
+ @mechanism="AMQPLAIN",
512
+ @response={:PASSWORD=>"guest", :LOGIN=>"guest"}>,
513
+ @type=:METHOD>]
514
+
515
+ ["receive",
516
+ #<AMQP::Frame:0x1035b78
517
+ @channel=0,
518
+ @payload=
519
+ #<AMQP::Protocol::Connection::Tune:0x1035844
520
+ @channel_max=0,
521
+ @debug=1,
522
+ @frame_max=131072,
523
+ @heartbeat=0>,
524
+ @type=:METHOD>]
525
+
526
+ ["send",
527
+ #<AMQP::Frame:0x102ad18
528
+ @channel=0,
529
+ @payload=
530
+ #<AMQP::Protocol::Connection::TuneOk:0x102af34
531
+ @channel_max=0,
532
+ @debug=1,
533
+ @frame_max=131072,
534
+ @heartbeat=0>,
535
+ @type=:METHOD>]
536
+
537
+ ["send",
538
+ #<AMQP::Frame:0x1020034
539
+ @channel=0,
540
+ @payload=
541
+ #<AMQP::Protocol::Connection::Open:0x1020278
542
+ @capabilities="",
543
+ @debug=1,
544
+ @insist=nil,
545
+ @virtual_host="/">,
546
+ @type=:METHOD>]
547
+
548
+ ["receive",
549
+ #<AMQP::Frame:0x1014e8c
550
+ @channel=0,
551
+ @payload=
552
+ #<AMQP::Protocol::Connection::OpenOk:0x1014b58
553
+ @debug=1,
554
+ @known_hosts="julie.local:5672">,
555
+ @type=:METHOD>]
556
+
557
+ ["send",
558
+ #<AMQP::Frame:0x100c138
559
+ @channel=1,
560
+ @payload=
561
+ #<AMQP::Protocol::Channel::Open:0x100c37c @debug=1, @out_of_band=nil>,
562
+ @type=:METHOD>]
563
+
564
+ ["receive",
565
+ #<AMQP::Frame:0x10036a0
566
+ @channel=1,
567
+ @payload=#<AMQP::Protocol::Channel::OpenOk:0x100336c @debug=1>,
568
+ @type=:METHOD>]
569
+
570
+ ["send",
571
+ #<AMQP::Frame:0x60e7e4
572
+ @channel=1,
573
+ @payload=
574
+ #<AMQP::Protocol::Access::Request:0x60ef14
575
+ @active=true,
576
+ @debug=1,
577
+ @exclusive=nil,
578
+ @passive=nil,
579
+ @read=true,
580
+ @realm="/data",
581
+ @write=true>,
582
+ @type=:METHOD>]
583
+
584
+ ["receive",
585
+ #<AMQP::Frame:0x5dd4b4
586
+ @channel=1,
587
+ @payload=#<AMQP::Protocol::Access::RequestOk:0x5dcc30 @debug=1, @ticket=101>,
588
+ @type=:METHOD>]
589
+
590
+ ["send",
591
+ #<AMQP::Frame:0x5c02d8
592
+ @channel=1,
593
+ @payload=
594
+ #<AMQP::Protocol::Queue::Declare:0x5c08dc
595
+ @arguments=nil,
596
+ @auto_delete=true,
597
+ @debug=1,
598
+ @durable=nil,
599
+ @exclusive=nil,
600
+ @nowait=nil,
601
+ @passive=nil,
602
+ @queue="",
603
+ @ticket=101>,
604
+ @type=:METHOD>]
605
+
606
+ ["receive",
607
+ #<AMQP::Frame:0x58d1bc
608
+ @channel=1,
609
+ @payload=
610
+ #<AMQP::Protocol::Queue::DeclareOk:0x58cbb8
611
+ @consumer_count=0,
612
+ @debug=1,
613
+ @message_count=0,
614
+ @queue="amq.gen-FiDSuLv/KvSgdcIWrNOdYg==">,
615
+ @type=:METHOD>]
616
+
617
+ ["send",
618
+ #<AMQP::Frame:0x570f44
619
+ @channel=1,
620
+ @payload=
621
+ #<AMQP::Protocol::Queue::Bind:0x571624
622
+ @arguments=nil,
623
+ @debug=1,
624
+ @exchange="",
625
+ @nowait=nil,
626
+ @queue="amq.gen-FiDSuLv/KvSgdcIWrNOdYg==",
627
+ @routing_key="test_route",
628
+ @ticket=101>,
629
+ @type=:METHOD>]
630
+
631
+ ["receive",
632
+ #<AMQP::Frame:0x549124
633
+ @channel=1,
634
+ @payload=#<AMQP::Protocol::Queue::BindOk:0x54897c @debug=1>,
635
+ @type=:METHOD>]
636
+
637
+ ["send",
638
+ #<AMQP::Frame:0x5339f0
639
+ @channel=1,
640
+ @payload=
641
+ #<AMQP::Protocol::Basic::Consume:0x534120
642
+ @consumer_tag=nil,
643
+ @debug=1,
644
+ @exclusive=nil,
645
+ @no_ack=true,
646
+ @no_local=nil,
647
+ @nowait=nil,
648
+ @queue="amq.gen-FiDSuLv/KvSgdcIWrNOdYg==",
649
+ @ticket=101>,
650
+ @type=:METHOD>]
651
+
652
+ ["receive",
653
+ #<AMQP::Frame:0x503d04
654
+ @channel=1,
655
+ @payload=
656
+ #<AMQP::Protocol::Basic::ConsumeOk:0x5035d4
657
+ @consumer_tag="amq.ctag-r6XkTE2+kN1qqbMG7FXraQ==",
658
+ @debug=1>,
659
+ @type=:METHOD>]
660
+
661
+ ["send",
662
+ #<AMQP::Frame:0x365498
663
+ @channel=1,
664
+ @payload=
665
+ #<AMQP::Protocol::Basic::Publish:0x366cbc
666
+ @debug=1,
667
+ @exchange="",
668
+ @immediate=nil,
669
+ @mandatory=nil,
670
+ @routing_key="test_route",
671
+ @ticket=101>,
672
+ @type=:METHOD>]
673
+
674
+ ["receive",
675
+ #<AMQP::Frame:0x11fd000
676
+ @channel=1,
677
+ @payload=
678
+ #<AMQP::Protocol::Basic::Deliver:0x11fc4fc
679
+ @consumer_tag="amq.ctag-r6XkTE2+kN1qqbMG7FXraQ==",
680
+ @debug=1,
681
+ @delivery_tag=nil,
682
+ @exchange="",
683
+ @redelivered=nil,
684
+ @routing_key="">,
685
+ @type=:METHOD>]
686
+
687
+ ["receive",
688
+ #<AMQP::Frame:0x11fb534
689
+ @channel=1,
690
+ @payload=
691
+ "\000<\000\000\000\000\000\000\000\000\000\017\230\000\030application/octet-stream\001\001",
692
+ @type=:HEADER>]
693
+
694
+ ["receive",
695
+ #<AMQP::Frame:0x11faef4 @channel=1, @payload="this is a test!", @type=:BODY>]