amqp 0.6.0 → 0.6.4

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/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>]