amqp 0.5.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/README +30 -0
- data/examples/clock.rb +53 -0
- data/examples/hashtable.rb +52 -0
- data/examples/pingpong.rb +53 -0
- data/examples/simple.rb +77 -0
- data/examples/stocks.rb +56 -0
- data/lib/amqp.rb +14 -0
- data/lib/amqp/buffer.rb +387 -0
- data/lib/amqp/client.rb +135 -0
- data/lib/amqp/frame.rb +124 -0
- data/lib/amqp/protocol.rb +181 -0
- data/lib/amqp/spec.rb +813 -0
- data/lib/mq.rb +229 -0
- data/protocol/amqp-0.8.json +606 -0
- data/protocol/amqp-0.8.xml +3908 -0
- data/protocol/codegen.rb +165 -0
- data/protocol/doc.txt +281 -0
- metadata +69 -0
data/README
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
Simple AMQP client for Ruby/EventMachine.
|
2
|
+
|
3
|
+
To use with RabbitMQ, first run the server:
|
4
|
+
|
5
|
+
hg clone http://hg.rabbitmq.com/rabbitmq-codegen
|
6
|
+
hg clone http://hg.rabbitmq.com/rabbitmq-server
|
7
|
+
cd rabbitmq-server
|
8
|
+
make run
|
9
|
+
|
10
|
+
Then run the example client:
|
11
|
+
|
12
|
+
ruby examples/simple.rb
|
13
|
+
|
14
|
+
To run the specs:
|
15
|
+
|
16
|
+
rake spec
|
17
|
+
|
18
|
+
The lib/amqp/spec.rb file is generated automatically based on the AMQP specification. To generate it:
|
19
|
+
|
20
|
+
rake codegen
|
21
|
+
|
22
|
+
This project was inspired by py-amqplib, rabbitmq, qpid and rubbyt.
|
23
|
+
Special thanks to Dmitriy Samovskiy, Ben Hood and Tony Garnock-Jones.
|
24
|
+
|
25
|
+
Other AMQP resources:
|
26
|
+
|
27
|
+
Barry Pederson's py-amqplib: http://barryp.org/software/py-amqplib/
|
28
|
+
Ben Hood's article on writing an AMQP article: http://hopper.squarespace.com/blog/2008/6/21/build-your-own-amqp-client.html
|
29
|
+
Dmitriy Samovskiy's introduction to ruby+rabbitmq: http://somic-org.homelinux.org/blog/2008/06/24/ruby-amqp-rabbitmq-example/
|
30
|
+
Ben Hood's AMQP client in AS3: http://github.com/0x6e6562/as3-amqp
|
data/examples/clock.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__) + '/../lib'
|
2
|
+
require 'mq'
|
3
|
+
|
4
|
+
EM.run{
|
5
|
+
|
6
|
+
def log *args
|
7
|
+
p args
|
8
|
+
end
|
9
|
+
|
10
|
+
# AMQP.logging = true
|
11
|
+
|
12
|
+
EM.add_periodic_timer(1){
|
13
|
+
puts
|
14
|
+
|
15
|
+
log :publishing, time = Time.now
|
16
|
+
MQ.new.fanout('clock').publish(Marshal.dump(time))
|
17
|
+
}
|
18
|
+
|
19
|
+
MQ.new.queue('every second').bind('clock').subscribe{ |time|
|
20
|
+
log 'every second', :received, Marshal.load(time)
|
21
|
+
}
|
22
|
+
|
23
|
+
MQ.new.queue('every 5 seconds').bind('clock').subscribe{ |time|
|
24
|
+
time = Marshal.load(time)
|
25
|
+
log 'every 5 seconds', :received, time if time.strftime('%S').to_i%5 == 0
|
26
|
+
}
|
27
|
+
|
28
|
+
}
|
29
|
+
|
30
|
+
__END__
|
31
|
+
|
32
|
+
[:publishing, Thu Jul 17 20:14:00 -0700 2008]
|
33
|
+
["every 5 seconds", :received, Thu Jul 17 20:14:00 -0700 2008]
|
34
|
+
["every second", :received, Thu Jul 17 20:14:00 -0700 2008]
|
35
|
+
|
36
|
+
[:publishing, Thu Jul 17 20:14:01 -0700 2008]
|
37
|
+
["every second", :received, Thu Jul 17 20:14:01 -0700 2008]
|
38
|
+
|
39
|
+
[:publishing, Thu Jul 17 20:14:02 -0700 2008]
|
40
|
+
["every second", :received, Thu Jul 17 20:14:02 -0700 2008]
|
41
|
+
|
42
|
+
[:publishing, Thu Jul 17 20:14:03 -0700 2008]
|
43
|
+
["every second", :received, Thu Jul 17 20:14:03 -0700 2008]
|
44
|
+
|
45
|
+
[:publishing, Thu Jul 17 20:14:04 -0700 2008]
|
46
|
+
["every second", :received, Thu Jul 17 20:14:04 -0700 2008]
|
47
|
+
|
48
|
+
[:publishing, Thu Jul 17 20:14:05 -0700 2008]
|
49
|
+
["every 5 seconds", :received, Thu Jul 17 20:14:05 -0700 2008]
|
50
|
+
["every second", :received, Thu Jul 17 20:14:05 -0700 2008]
|
51
|
+
|
52
|
+
[:publishing, Thu Jul 17 20:14:06 -0700 2008]
|
53
|
+
["every second", :received, Thu Jul 17 20:14:06 -0700 2008]
|
@@ -0,0 +1,52 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__) + '/../lib'
|
2
|
+
require 'mq'
|
3
|
+
|
4
|
+
EM.run{
|
5
|
+
|
6
|
+
def log *args
|
7
|
+
p args
|
8
|
+
end
|
9
|
+
|
10
|
+
# AMQP.logging = true
|
11
|
+
|
12
|
+
class HashTable < Hash
|
13
|
+
def get key
|
14
|
+
log 'HashTable', :get, key
|
15
|
+
self[key]
|
16
|
+
end
|
17
|
+
|
18
|
+
def set key, value
|
19
|
+
log 'HashTable', :set, key => value
|
20
|
+
self[key] = value
|
21
|
+
end
|
22
|
+
|
23
|
+
def keys
|
24
|
+
log 'HashTable', :keys
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
server = MQ.new.rpc('hash table node', HashTable.new)
|
30
|
+
|
31
|
+
client = MQ.new.rpc('hash table node')
|
32
|
+
client.set(:now, time = Time.now)
|
33
|
+
client.get(:now) do |res|
|
34
|
+
log 'client', :now => res, :eql? => res == time
|
35
|
+
end
|
36
|
+
|
37
|
+
client.set(:one, 1)
|
38
|
+
client.keys do |res|
|
39
|
+
log 'client', :keys => res
|
40
|
+
EM.stop_event_loop
|
41
|
+
end
|
42
|
+
|
43
|
+
}
|
44
|
+
|
45
|
+
__END__
|
46
|
+
|
47
|
+
["HashTable", :set, {:now=>Thu Jul 17 21:04:53 -0700 2008}]
|
48
|
+
["HashTable", :get, :now]
|
49
|
+
["HashTable", :set, {:one=>1}]
|
50
|
+
["HashTable", :keys]
|
51
|
+
["client", {:eql?=>true, :now=>Thu Jul 17 21:04:53 -0700 2008}]
|
52
|
+
["client", {:keys=>[:one, :now]}]
|
@@ -0,0 +1,53 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__) + '/../lib'
|
2
|
+
require 'mq'
|
3
|
+
|
4
|
+
EM.run{
|
5
|
+
|
6
|
+
def log *args
|
7
|
+
p [ Time.now, *args ]
|
8
|
+
end
|
9
|
+
|
10
|
+
# AMQP.logging = true
|
11
|
+
|
12
|
+
amq = MQ.new
|
13
|
+
amq.queue('one').subscribe{ |headers, msg|
|
14
|
+
log 'one', :received, msg, :from => headers.reply_to
|
15
|
+
|
16
|
+
if headers.reply_to
|
17
|
+
msg[1] = 'o'
|
18
|
+
|
19
|
+
log 'one', :sending, msg, :to => headers.reply_to
|
20
|
+
amq.direct.publish(msg, :key => headers.reply_to)
|
21
|
+
else
|
22
|
+
puts
|
23
|
+
end
|
24
|
+
}
|
25
|
+
|
26
|
+
amq = MQ.new
|
27
|
+
amq.queue('two').subscribe{ |msg|
|
28
|
+
log 'two', :received, msg
|
29
|
+
puts
|
30
|
+
}
|
31
|
+
|
32
|
+
amq = MQ.new
|
33
|
+
amq.direct.publish('ding', :key => 'one')
|
34
|
+
EM.add_periodic_timer(1){
|
35
|
+
log :sending, 'ping', :to => 'one', :from => 'two'
|
36
|
+
amq.direct.publish('ping', :key => 'one', :reply_to => 'two')
|
37
|
+
}
|
38
|
+
|
39
|
+
}
|
40
|
+
|
41
|
+
__END__
|
42
|
+
|
43
|
+
[Thu Jul 17 21:23:55 -0700 2008, "one", :received, "ding", {:from=>nil}]
|
44
|
+
|
45
|
+
[Thu Jul 17 21:23:56 -0700 2008, :sending, "ping", {:from=>"two", :to=>"one"}]
|
46
|
+
[Thu Jul 17 21:23:56 -0700 2008, "one", :received, "ping", {:from=>"two"}]
|
47
|
+
[Thu Jul 17 21:23:56 -0700 2008, "one", :sending, "pong", {:to=>"two"}]
|
48
|
+
[Thu Jul 17 21:23:56 -0700 2008, "two", :received, "pong"]
|
49
|
+
|
50
|
+
[Thu Jul 17 21:23:57 -0700 2008, :sending, "ping", {:from=>"two", :to=>"one"}]
|
51
|
+
[Thu Jul 17 21:23:57 -0700 2008, "one", :received, "ping", {:from=>"two"}]
|
52
|
+
[Thu Jul 17 21:23:57 -0700 2008, "one", :sending, "pong", {:to=>"two"}]
|
53
|
+
[Thu Jul 17 21:23:57 -0700 2008, "two", :received, "pong"]
|
data/examples/simple.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__) + '/../lib'
|
2
|
+
require 'amqp'
|
3
|
+
|
4
|
+
module SimpleClient
|
5
|
+
def process_frame frame
|
6
|
+
case frame
|
7
|
+
when Frame::Body
|
8
|
+
EM.stop_event_loop
|
9
|
+
|
10
|
+
when Frame::Method
|
11
|
+
case method = frame.payload
|
12
|
+
when Protocol::Connection::Start
|
13
|
+
send Protocol::Connection::StartOk.new({:platform => 'Ruby/EventMachine',
|
14
|
+
:product => 'AMQP',
|
15
|
+
:information => 'http://github.com/tmm1/amqp',
|
16
|
+
:version => '0.1.0'},
|
17
|
+
'AMQPLAIN',
|
18
|
+
{:LOGIN => 'guest',
|
19
|
+
:PASSWORD => 'guest'},
|
20
|
+
'en_US')
|
21
|
+
|
22
|
+
when Protocol::Connection::Tune
|
23
|
+
send Protocol::Connection::TuneOk.new(:channel_max => 0,
|
24
|
+
:frame_max => 131072,
|
25
|
+
:heartbeat => 0)
|
26
|
+
|
27
|
+
send Protocol::Connection::Open.new(:virtual_host => '/',
|
28
|
+
:capabilities => '',
|
29
|
+
:insist => false)
|
30
|
+
|
31
|
+
when Protocol::Connection::OpenOk
|
32
|
+
send Protocol::Channel::Open.new, :channel => 1
|
33
|
+
|
34
|
+
when Protocol::Channel::OpenOk
|
35
|
+
send Protocol::Access::Request.new(:realm => '/data',
|
36
|
+
:read => true,
|
37
|
+
:write => true,
|
38
|
+
:active => true), :channel => 1
|
39
|
+
|
40
|
+
when Protocol::Access::RequestOk
|
41
|
+
@ticket = method.ticket
|
42
|
+
send Protocol::Queue::Declare.new(:ticket => @ticket,
|
43
|
+
:queue => '',
|
44
|
+
:exclusive => false,
|
45
|
+
:auto_delete => true), :channel => 1
|
46
|
+
|
47
|
+
when Protocol::Queue::DeclareOk
|
48
|
+
@queue = method.queue
|
49
|
+
send Protocol::Queue::Bind.new(:ticket => @ticket,
|
50
|
+
:queue => @queue,
|
51
|
+
:exchange => '',
|
52
|
+
:routing_key => 'test_route'), :channel => 1
|
53
|
+
|
54
|
+
when Protocol::Queue::BindOk
|
55
|
+
send Protocol::Basic::Consume.new(:ticket => @ticket,
|
56
|
+
:queue => @queue,
|
57
|
+
:no_local => false,
|
58
|
+
:no_ack => true), :channel => 1
|
59
|
+
|
60
|
+
when Protocol::Basic::ConsumeOk
|
61
|
+
data = "this is a test!"
|
62
|
+
|
63
|
+
send Protocol::Basic::Publish.new(:ticket => @ticket,
|
64
|
+
:exchange => '',
|
65
|
+
:routing_key => 'test_route'), :channel => 1
|
66
|
+
send Protocol::Header.new(Protocol::Basic, data.length, :content_type => 'application/octet-stream',
|
67
|
+
:delivery_mode => 1,
|
68
|
+
:priority => 0), :channel => 1
|
69
|
+
send Frame::Body.new(data), :channel => 1
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
AMQP.logging = true
|
76
|
+
AMQP.client = SimpleClient
|
77
|
+
AMQP.start
|
data/examples/stocks.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__) + '/../lib'
|
2
|
+
require 'mq'
|
3
|
+
|
4
|
+
EM.run{
|
5
|
+
|
6
|
+
def log *args
|
7
|
+
p [ Time.now, *args ]
|
8
|
+
end
|
9
|
+
|
10
|
+
# AMQP.logging = true
|
11
|
+
|
12
|
+
EM.add_periodic_timer(1){
|
13
|
+
puts
|
14
|
+
|
15
|
+
log :publishing, 'stock.usd.appl', price = 170+rand(1000)/100.0
|
16
|
+
MQ.topic(:key => 'stock.usd.appl').publish(price, :headers => {:symbol => 'appl'})
|
17
|
+
|
18
|
+
log :publishing, 'stock.usd.msft', price = 22+rand(500)/100.0
|
19
|
+
MQ.topic.publish(price, :key => 'stock.usd.msft', :headers => {:symbol => 'msft'})
|
20
|
+
}
|
21
|
+
|
22
|
+
Thread.new{
|
23
|
+
amq = MQ.new
|
24
|
+
amq.queue('apple stock').bind(amq.topic, :key => 'stock.usd.appl').subscribe{ |price|
|
25
|
+
log 'apple stock', price
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
Thread.new{
|
30
|
+
amq = MQ.new
|
31
|
+
amq.queue('us stocks').bind(amq.topic, :key => 'stock.usd.*').subscribe{ |info, price|
|
32
|
+
log 'us stock', info.headers[:symbol], price
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
}
|
37
|
+
|
38
|
+
__END__
|
39
|
+
|
40
|
+
[Thu Jul 17 14:51:07 -0700 2008, :publishing, "stock.usd.appl", 170.84]
|
41
|
+
[Thu Jul 17 14:51:07 -0700 2008, :publishing, "stock.usd.msft", 23.68]
|
42
|
+
[Thu Jul 17 14:51:07 -0700 2008, "apple stock", "170.84"]
|
43
|
+
[Thu Jul 17 14:51:07 -0700 2008, "us stock", "appl", "170.84"]
|
44
|
+
[Thu Jul 17 14:51:07 -0700 2008, "us stock", "msft", "23.68"]
|
45
|
+
|
46
|
+
[Thu Jul 17 14:51:08 -0700 2008, :publishing, "stock.usd.appl", 173.61]
|
47
|
+
[Thu Jul 17 14:51:08 -0700 2008, :publishing, "stock.usd.msft", 25.8]
|
48
|
+
[Thu Jul 17 14:51:08 -0700 2008, "apple stock", "173.61"]
|
49
|
+
[Thu Jul 17 14:51:08 -0700 2008, "us stock", "appl", "173.61"]
|
50
|
+
[Thu Jul 17 14:51:08 -0700 2008, "us stock", "msft", "25.8"]
|
51
|
+
|
52
|
+
[Thu Jul 17 14:51:09 -0700 2008, :publishing, "stock.usd.appl", 173.94]
|
53
|
+
[Thu Jul 17 14:51:09 -0700 2008, :publishing, "stock.usd.msft", 24.88]
|
54
|
+
[Thu Jul 17 14:51:09 -0700 2008, "apple stock", "173.94"]
|
55
|
+
[Thu Jul 17 14:51:09 -0700 2008, "us stock", "appl", "173.94"]
|
56
|
+
[Thu Jul 17 14:51:09 -0700 2008, "us stock", "msft", "24.88"]
|
data/lib/amqp.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module AMQP
|
2
|
+
DIR = File.expand_path(File.dirname(File.expand_path(__FILE__)))
|
3
|
+
|
4
|
+
$:.unshift DIR
|
5
|
+
|
6
|
+
%w[ buffer spec protocol frame client ].each do |file|
|
7
|
+
require "amqp/#{file}"
|
8
|
+
end
|
9
|
+
|
10
|
+
class << self
|
11
|
+
@logging = false
|
12
|
+
attr_accessor :logging
|
13
|
+
end
|
14
|
+
end
|
data/lib/amqp/buffer.rb
ADDED
@@ -0,0 +1,387 @@
|
|
1
|
+
require 'enumerator'
|
2
|
+
|
3
|
+
module AMQP
|
4
|
+
class Buffer
|
5
|
+
class Overflow < Exception; end
|
6
|
+
class InvalidType < Exception; end
|
7
|
+
|
8
|
+
def initialize data = ''
|
9
|
+
@data = data
|
10
|
+
@pos = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :pos
|
14
|
+
|
15
|
+
def data
|
16
|
+
@data.clone
|
17
|
+
end
|
18
|
+
alias :contents :data
|
19
|
+
alias :to_s :data
|
20
|
+
|
21
|
+
def << data
|
22
|
+
@data << data.to_s
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def length
|
27
|
+
@data.length
|
28
|
+
end
|
29
|
+
|
30
|
+
def empty?
|
31
|
+
pos == length
|
32
|
+
end
|
33
|
+
|
34
|
+
def rewind
|
35
|
+
@pos = 0
|
36
|
+
end
|
37
|
+
|
38
|
+
def read_properties *types
|
39
|
+
types.shift if types.first == :properties
|
40
|
+
|
41
|
+
i = 0
|
42
|
+
values = []
|
43
|
+
|
44
|
+
while props = read(:short)
|
45
|
+
(0..14).each do |n|
|
46
|
+
# no more property types
|
47
|
+
break unless types[i]
|
48
|
+
|
49
|
+
# if flag is set
|
50
|
+
if props & (1<<(15-n)) != 0
|
51
|
+
if types[i] == :bit
|
52
|
+
# bit values exist in flags only
|
53
|
+
values << true
|
54
|
+
else
|
55
|
+
# save type name for later reading
|
56
|
+
values << types[i]
|
57
|
+
end
|
58
|
+
else
|
59
|
+
# property not set or is false bit
|
60
|
+
values << (types[i] == :bit ? false : nil)
|
61
|
+
end
|
62
|
+
|
63
|
+
i+=1
|
64
|
+
end
|
65
|
+
|
66
|
+
# bit(0) == 0 means no more property flags
|
67
|
+
break unless props & 1 == 1
|
68
|
+
end
|
69
|
+
|
70
|
+
values.map do |value|
|
71
|
+
value.is_a?(Symbol) ? read(value) : value
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def read *types
|
76
|
+
if types.first == :properties
|
77
|
+
return read_properties(*types)
|
78
|
+
end
|
79
|
+
|
80
|
+
values = types.map do |type|
|
81
|
+
case type
|
82
|
+
when :octet
|
83
|
+
_read(1, 'C')
|
84
|
+
when :short
|
85
|
+
_read(2, 'n')
|
86
|
+
when :long
|
87
|
+
_read(4, 'N')
|
88
|
+
when :longlong
|
89
|
+
upper, lower = _read(8, 'NN')
|
90
|
+
upper << 32 | lower
|
91
|
+
when :shortstr
|
92
|
+
_read read(:octet)
|
93
|
+
when :longstr
|
94
|
+
_read read(:long)
|
95
|
+
when :timestamp
|
96
|
+
Time.at read(:longlong)
|
97
|
+
when :table
|
98
|
+
t = Hash.new
|
99
|
+
|
100
|
+
table = Buffer.new(read(:longstr))
|
101
|
+
until table.empty?
|
102
|
+
key, type = table.read(:shortstr, :octet)
|
103
|
+
key = key.intern
|
104
|
+
t[key] ||= case type
|
105
|
+
when ?S
|
106
|
+
table.read(:longstr)
|
107
|
+
when ?I
|
108
|
+
table.read(:long)
|
109
|
+
when ?D
|
110
|
+
exp = table.read(:octet)
|
111
|
+
num = table.read(:long)
|
112
|
+
num / 10.0**exp
|
113
|
+
when ?T
|
114
|
+
table.read(:timestamp)
|
115
|
+
when ?F
|
116
|
+
table.read(:table)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
t
|
121
|
+
when :bit
|
122
|
+
if (@bits ||= []).empty?
|
123
|
+
val = read(:octet)
|
124
|
+
@bits = (0..7).map{|i| (val & 1<<i) != 0 }
|
125
|
+
end
|
126
|
+
|
127
|
+
@bits.shift
|
128
|
+
else
|
129
|
+
raise InvalidType, "Cannot read data of type #{type}"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
types.size == 1 ? values.first : values
|
134
|
+
end
|
135
|
+
|
136
|
+
def write type, data
|
137
|
+
case type
|
138
|
+
when :octet
|
139
|
+
_write(data, 'C')
|
140
|
+
when :short
|
141
|
+
_write(data, 'n')
|
142
|
+
when :long
|
143
|
+
_write(data, 'N')
|
144
|
+
when :longlong
|
145
|
+
lower = data & 0xffffffff
|
146
|
+
upper = (data & ~0xffffffff) >> 32
|
147
|
+
_write([upper, lower], 'NN')
|
148
|
+
when :shortstr
|
149
|
+
data = (data || '').to_s
|
150
|
+
_write([data.length, data], 'Ca*')
|
151
|
+
when :longstr
|
152
|
+
if data.is_a? Hash
|
153
|
+
write(:table, data)
|
154
|
+
else
|
155
|
+
data = (data || '').to_s
|
156
|
+
_write([data.length, data], 'Na*')
|
157
|
+
end
|
158
|
+
when :timestamp
|
159
|
+
write(:longlong, data.to_i)
|
160
|
+
when :table
|
161
|
+
data ||= {}
|
162
|
+
write :longstr, (data.inject(Buffer.new) do |table, (key, value)|
|
163
|
+
table.write(:shortstr, key.to_s)
|
164
|
+
|
165
|
+
case value
|
166
|
+
when String
|
167
|
+
table.write(:octet, ?S)
|
168
|
+
table.write(:longstr, value.to_s)
|
169
|
+
when Fixnum
|
170
|
+
table.write(:octet, ?I)
|
171
|
+
table.write(:long, value)
|
172
|
+
when Float
|
173
|
+
table.write(:octet, ?D)
|
174
|
+
# XXX there's gotta be a better way to do this..
|
175
|
+
exp = value.to_s.gsub(/^.+\./,'').length
|
176
|
+
num = value * 10**exp
|
177
|
+
table.write(:octet, exp)
|
178
|
+
table.write(:long, num)
|
179
|
+
when Time
|
180
|
+
table.write(:octet, ?T)
|
181
|
+
table.write(:timestamp, value)
|
182
|
+
when Hash
|
183
|
+
table.write(:octet, ?F)
|
184
|
+
table.write(:table, value)
|
185
|
+
end
|
186
|
+
|
187
|
+
table
|
188
|
+
end)
|
189
|
+
when :bit
|
190
|
+
[*data].to_enum(:each_slice, 8).each{|bits|
|
191
|
+
write(:octet, bits.enum_with_index.inject(0){ |byte, (bit, i)|
|
192
|
+
byte |= 1<<i if bit
|
193
|
+
byte
|
194
|
+
})
|
195
|
+
}
|
196
|
+
when :properties
|
197
|
+
values = []
|
198
|
+
data.enum_with_index.inject(0) do |short, ((type, value), i)|
|
199
|
+
n = i % 15
|
200
|
+
last = i+1 == data.size
|
201
|
+
|
202
|
+
if (n == 0 and i != 0) or last
|
203
|
+
if data.size > i+1
|
204
|
+
short |= 1<<0
|
205
|
+
elsif last and value
|
206
|
+
values << [type,value]
|
207
|
+
short |= 1<<(15-n)
|
208
|
+
end
|
209
|
+
|
210
|
+
write(:short, short)
|
211
|
+
short = 0
|
212
|
+
end
|
213
|
+
|
214
|
+
if value and !last
|
215
|
+
values << [type,value]
|
216
|
+
short |= 1<<(15-n)
|
217
|
+
end
|
218
|
+
|
219
|
+
short
|
220
|
+
end
|
221
|
+
|
222
|
+
values.each do |type, value|
|
223
|
+
write(type, value) unless type == :bit
|
224
|
+
end
|
225
|
+
else
|
226
|
+
raise InvalidType, "Cannot write data of type #{type}"
|
227
|
+
end
|
228
|
+
|
229
|
+
self
|
230
|
+
end
|
231
|
+
|
232
|
+
def extract
|
233
|
+
begin
|
234
|
+
cur_data, cur_pos = @data.clone, @pos
|
235
|
+
yield self
|
236
|
+
rescue Overflow
|
237
|
+
@data, @pos = cur_data, cur_pos
|
238
|
+
nil
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def _read size, pack = nil
|
243
|
+
if @pos + size > length
|
244
|
+
raise Overflow
|
245
|
+
else
|
246
|
+
data = @data[@pos,size]
|
247
|
+
@data[@pos,size] = ''
|
248
|
+
if pack
|
249
|
+
data = data.unpack(pack)
|
250
|
+
data = data.pop if data.size == 1
|
251
|
+
end
|
252
|
+
data
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def _write data, pack = nil
|
257
|
+
data = [*data].pack(pack) if pack
|
258
|
+
@data[@pos,0] = data
|
259
|
+
@pos += data.length
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
if $0 =~ /bacon/ or $0 == __FILE__
|
265
|
+
require 'bacon'
|
266
|
+
include AMQP
|
267
|
+
|
268
|
+
describe Buffer do
|
269
|
+
before do
|
270
|
+
@buf = Buffer.new
|
271
|
+
end
|
272
|
+
|
273
|
+
should 'have contents' do
|
274
|
+
@buf.contents.should == ''
|
275
|
+
end
|
276
|
+
|
277
|
+
should 'initialize with data' do
|
278
|
+
@buf = Buffer.new('abc')
|
279
|
+
@buf.contents.should == 'abc'
|
280
|
+
end
|
281
|
+
|
282
|
+
should 'append raw data' do
|
283
|
+
@buf << 'abc'
|
284
|
+
@buf << 'def'
|
285
|
+
@buf.contents.should == 'abcdef'
|
286
|
+
end
|
287
|
+
|
288
|
+
should 'append other buffers' do
|
289
|
+
@buf << Buffer.new('abc')
|
290
|
+
@buf.data.should == 'abc'
|
291
|
+
end
|
292
|
+
|
293
|
+
should 'have a position' do
|
294
|
+
@buf.pos.should == 0
|
295
|
+
end
|
296
|
+
|
297
|
+
should 'have a length' do
|
298
|
+
@buf.length.should == 0
|
299
|
+
@buf << 'abc'
|
300
|
+
@buf.length.should == 3
|
301
|
+
end
|
302
|
+
|
303
|
+
should 'know the end' do
|
304
|
+
@buf.empty?.should == true
|
305
|
+
end
|
306
|
+
|
307
|
+
should 'read and write data' do
|
308
|
+
@buf._write('abc')
|
309
|
+
@buf.rewind
|
310
|
+
@buf._read(2).should == 'ab'
|
311
|
+
@buf._read(1).should == 'c'
|
312
|
+
end
|
313
|
+
|
314
|
+
should 'raise on overflow' do
|
315
|
+
lambda{ @buf._read(1) }.should.raise Buffer::Overflow
|
316
|
+
end
|
317
|
+
|
318
|
+
should 'raise on invalid types' do
|
319
|
+
lambda{ @buf.read(:junk) }.should.raise Buffer::InvalidType
|
320
|
+
lambda{ @buf.write(:junk, 1) }.should.raise Buffer::InvalidType
|
321
|
+
end
|
322
|
+
|
323
|
+
{ :octet => 0b10101010,
|
324
|
+
:short => 100,
|
325
|
+
:long => 100_000_000,
|
326
|
+
:longlong => 666_555_444_333_222_111,
|
327
|
+
:shortstr => 'hello',
|
328
|
+
:longstr => 'bye'*500,
|
329
|
+
:timestamp => time = Time.at(Time.now.to_i),
|
330
|
+
:table => { :this => 'is', :a => 'hash', :with => {:nested => 123, :and => time, :also => 123.456} },
|
331
|
+
:bit => true
|
332
|
+
}.each do |type, value|
|
333
|
+
|
334
|
+
should "read and write a #{type}" do
|
335
|
+
@buf.write(type, value)
|
336
|
+
@buf.rewind
|
337
|
+
@buf.read(type).should == value
|
338
|
+
@buf.should.be.empty
|
339
|
+
end
|
340
|
+
|
341
|
+
end
|
342
|
+
|
343
|
+
should 'read and write multiple bits' do
|
344
|
+
bits = [true, false, false, true, true, false, false, true, true, false]
|
345
|
+
@buf.write(:bit, bits)
|
346
|
+
@buf.write(:octet, 100)
|
347
|
+
|
348
|
+
@buf.rewind
|
349
|
+
|
350
|
+
bits.map do
|
351
|
+
@buf.read(:bit)
|
352
|
+
end.should == bits
|
353
|
+
@buf.read(:octet).should == 100
|
354
|
+
end
|
355
|
+
|
356
|
+
should 'read and write properties' do
|
357
|
+
properties = ([
|
358
|
+
[:octet, 1],
|
359
|
+
[:shortstr, 'abc'],
|
360
|
+
[:bit, true],
|
361
|
+
[:bit, false],
|
362
|
+
[:shortstr, nil],
|
363
|
+
[:timestamp, nil],
|
364
|
+
[:table, { :a => 'hash' }],
|
365
|
+
]*5).sort_by{rand}
|
366
|
+
|
367
|
+
@buf.write(:properties, properties)
|
368
|
+
@buf.rewind
|
369
|
+
@buf.read(:properties, *properties.map{|type,_| type }).should == properties.map{|_,value| value }
|
370
|
+
@buf.should.be.empty
|
371
|
+
end
|
372
|
+
|
373
|
+
should 'do transactional reads with #extract' do
|
374
|
+
@buf.write :octet, 8
|
375
|
+
orig = @buf.to_s
|
376
|
+
|
377
|
+
@buf.rewind
|
378
|
+
@buf.extract do |b|
|
379
|
+
b.read :octet
|
380
|
+
b.read :short
|
381
|
+
end
|
382
|
+
|
383
|
+
@buf.pos.should == 0
|
384
|
+
@buf.data.should == orig
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|