famoseagle-carrot 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown ADDED
@@ -0,0 +1,24 @@
1
+ # Carrot
2
+
3
+ A synchronous amqp client. Based on Aman's amqp client:
4
+
5
+ http://github.com/tmm1/amqp/tree/master
6
+
7
+ ## Example
8
+
9
+ q = Carrot.queue('name', :durable => true, :host => 'q1.rabbitmq.com')
10
+ 100.times do
11
+ q.publish('foo')
12
+ end
13
+
14
+ pp :count, q.message_count
15
+
16
+ while msg = q.pop(:ack => true)
17
+ puts msg
18
+ q.ack
19
+ end
20
+ Carrot.stop
21
+
22
+ # LICENSE
23
+
24
+ Copyright (c) 2009 Amos Elliston, Geni.com; Published under The MIT License, see License
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 0
3
+ :major: 0
4
+ :minor: 1
@@ -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.length
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.length, 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.length, 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.length
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.length
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,47 @@
1
+ module AMQP
2
+ class Exchange
3
+ attr_accessor :server, :type, :name, :opts, :key
4
+
5
+ def initialize(server, type, name, opts = {})
6
+ @server, @type, @name, @opts = server, type, name, opts
7
+ @key = opts[:key]
8
+
9
+ unless name == "amq.#{type}" or name == ''
10
+ server.send_frame(
11
+ Protocol::Exchange::Declare.new(
12
+ { :exchange => name, :type => type, :nowait => true }.merge(opts)
13
+ )
14
+ )
15
+ end
16
+ end
17
+ attr_reader :name, :type, :key
18
+
19
+ def publish(data, opts = {})
20
+ out = []
21
+
22
+ out << Protocol::Basic::Publish.new(
23
+ { :exchange => name, :routing_key => opts.delete(:key) || key }.merge(opts)
24
+ )
25
+ data = data.to_s
26
+ out << Protocol::Header.new(
27
+ Protocol::Basic,
28
+ data.length, {
29
+ :content_type => 'application/octet-stream',
30
+ :delivery_mode => (opts.delete(:persistent) ? 2 : 1),
31
+ :priority => 0
32
+ }.merge(opts)
33
+ )
34
+ out << Frame::Body.new(data)
35
+
36
+ server.send_frame(*out)
37
+ end
38
+
39
+ def delete(opts = {})
40
+ server.send_frame(Protocol::Exchange::Delete.new({ :exchange => name, :nowait => true }.merge(opts)))
41
+ end
42
+
43
+ def reset
44
+ initialize(server, type, name, opts)
45
+ end
46
+ end
47
+ end
data/lib/amqp/frame.rb ADDED
@@ -0,0 +1,130 @@
1
+ module AMQP
2
+ class Frame #:nodoc: all
3
+ def initialize payload = nil, channel = 0
4
+ @channel, @payload = channel, payload
5
+ end
6
+ attr_accessor :channel, :payload
7
+
8
+ def id
9
+ self.class::ID
10
+ end
11
+
12
+ def to_binary
13
+ buf = Buffer.new
14
+ buf.write :octet, id
15
+ buf.write :short, channel
16
+ buf.write :longstr, payload
17
+ buf.write :octet, FOOTER
18
+ buf.rewind
19
+ buf
20
+ end
21
+
22
+ def to_s
23
+ to_binary.to_s
24
+ end
25
+
26
+ def == frame
27
+ [ :id, :channel, :payload ].inject(true) do |eql, field|
28
+ eql and __send__(field) == frame.__send__(field)
29
+ end
30
+ end
31
+
32
+ class Invalid < StandardError; end
33
+
34
+ class Method
35
+ def initialize payload = nil, channel = 0
36
+ super
37
+ unless @payload.is_a? Protocol::Class::Method or @payload.nil?
38
+ @payload = Protocol.parse(@payload)
39
+ end
40
+ end
41
+ end
42
+
43
+ class Header
44
+ def initialize payload = nil, channel = 0
45
+ super
46
+ unless @payload.is_a? Protocol::Header or @payload.nil?
47
+ @payload = Protocol::Header.new(@payload)
48
+ end
49
+ end
50
+ end
51
+
52
+ class Body; end
53
+
54
+ def self.parse buf
55
+ buf = Buffer.new(buf) unless buf.is_a? Buffer
56
+ buf.extract do
57
+ id, channel, payload, footer = buf.read(:octet, :short, :longstr, :octet)
58
+ Frame.types[id].new(payload, channel) if footer == FOOTER
59
+ end
60
+ end
61
+
62
+ def self.get(server)
63
+ id = server.read(1).unpack('C').first
64
+ channel = server.read(2).unpack('n').first
65
+ size = server.read(4).unpack('N').first
66
+ data = server.read(size)
67
+ footer = server.read(1).unpack('C').first
68
+ Frame.types[id].new(data, channel) if footer == FOOTER
69
+ end
70
+ end
71
+ end
72
+
73
+ if $0 =~ /bacon/ or $0 == __FILE__
74
+ require 'rubygems'
75
+ require 'bacon'
76
+ include AMQP
77
+
78
+ describe Frame do
79
+ should 'handle basic frame types' do
80
+ Frame::Method.new.id.should == 1
81
+ Frame::Header.new.id.should == 2
82
+ Frame::Body.new.id.should == 3
83
+ end
84
+
85
+ should 'convert method frames to binary' do
86
+ meth = Protocol::Connection::Secure.new :challenge => 'secret'
87
+
88
+ frame = Frame::Method.new(meth)
89
+ frame.to_binary.should.be.kind_of? Buffer
90
+ frame.to_s.should == [ 1, 0, meth.to_s.length, meth.to_s, 206 ].pack('CnNa*C')
91
+ end
92
+
93
+ should 'convert binary to method frames' do
94
+ orig = Frame::Method.new Protocol::Connection::Secure.new(:challenge => 'secret')
95
+
96
+ copy = Frame.parse(orig.to_binary)
97
+ copy.should == orig
98
+ end
99
+
100
+ should 'ignore partial frames until ready' do
101
+ frame = Frame::Method.new Protocol::Connection::Secure.new(:challenge => 'secret')
102
+ data = frame.to_s
103
+
104
+ buf = Buffer.new
105
+ Frame.parse(buf).should == nil
106
+
107
+ buf << data[0..5]
108
+ Frame.parse(buf).should == nil
109
+
110
+ buf << data[6..-1]
111
+ Frame.parse(buf).should == frame
112
+
113
+ Frame.parse(buf).should == nil
114
+ end
115
+
116
+ should 'convert header frames to binary' do
117
+ head = Protocol::Header.new(Protocol::Basic, :priority => 1)
118
+
119
+ frame = Frame::Header.new(head)
120
+ frame.to_s.should == [ 2, 0, head.to_s.length, head.to_s, 206 ].pack('CnNa*C')
121
+ end
122
+
123
+ should 'convert binary to header frame' do
124
+ orig = Frame::Header.new Protocol::Header.new(Protocol::Basic, :priority => 1)
125
+
126
+ copy = Frame.parse(orig.to_binary)
127
+ copy.should == orig
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,27 @@
1
+ module AMQP
2
+ class Header
3
+ def initialize(server, header_obj)
4
+ @server = server
5
+ @header = header_obj
6
+ end
7
+
8
+ # Acknowledges the receipt of this message with the server.
9
+ def ack
10
+ @server.send(Protocol::Basic::Ack.new(:delivery_tag => properties[:delivery_tag]))
11
+ end
12
+
13
+ # Reject this message (XXX currently unimplemented in rabbitmq)
14
+ # * :requeue => true | false (default false)
15
+ def reject(opts = {})
16
+ @server.send(Protocol::Basic::Reject.new(opts.merge(:delivery_tag => properties[:delivery_tag])))
17
+ end
18
+
19
+ def method_missing(meth, *args, &blk)
20
+ @header.send(meth, *args, &blk)
21
+ end
22
+
23
+ def inspect
24
+ @header.inspect
25
+ end
26
+ end
27
+ end