secure_carrot 0.1.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/.gitignore +4 -0
- data/LICENSE +20 -0
- data/README.markdown +34 -0
- data/Rakefile +42 -0
- data/VERSION.yml +5 -0
- data/lib/amqp/buffer.rb +401 -0
- data/lib/amqp/exchange.rb +51 -0
- data/lib/amqp/frame.rb +121 -0
- data/lib/amqp/header.rb +27 -0
- data/lib/amqp/protocol.rb +209 -0
- data/lib/amqp/queue.rb +144 -0
- data/lib/amqp/server.rb +187 -0
- data/lib/amqp/spec.rb +820 -0
- data/lib/carrot.rb +92 -0
- data/lib/examples/simple_pop.rb +13 -0
- data/protocol/amqp-0.8.json +617 -0
- data/protocol/amqp-0.8.xml +3908 -0
- data/protocol/codegen.rb +173 -0
- data/protocol/doc.txt +281 -0
- data/test/carrot_test.rb +25 -0
- data/test/test_helper.rb +18 -0
- metadata +102 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Amos Elliston
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# Carrot
|
2
|
+
|
3
|
+
A synchronous amqp client. Based on Aman's amqp client:
|
4
|
+
|
5
|
+
[http://github.com/tmm1/amqp/tree/master] (http://github.com/tmm1/amqp/tree/master)
|
6
|
+
|
7
|
+
## Motivation
|
8
|
+
|
9
|
+
This client does not use eventmachine so no background thread necessary. As a result, it is much easier to use from script/console and Passenger. It also solves the problem of buffering messages and ack responses. For more details see [this thread] (http://groups.google.com/group/ruby-amqp/browse_thread/thread/fdae324a0ebb1961/fa185fdce1841b68).
|
10
|
+
|
11
|
+
There is currently no way to prevent buffering using eventmachine. Support for prefetch is still unreliable.
|
12
|
+
|
13
|
+
|
14
|
+
## Example
|
15
|
+
|
16
|
+
require 'carrot'
|
17
|
+
|
18
|
+
q = Carrot.queue('name')
|
19
|
+
10.times do |num|
|
20
|
+
q.publish(num.to_s)
|
21
|
+
end
|
22
|
+
|
23
|
+
puts "Queued #{q.message_count} messages"
|
24
|
+
puts
|
25
|
+
|
26
|
+
while msg = q.pop(:ack => true)
|
27
|
+
puts "Popping: #{msg}"
|
28
|
+
q.ack
|
29
|
+
end
|
30
|
+
Carrot.stop
|
31
|
+
|
32
|
+
# LICENSE
|
33
|
+
|
34
|
+
Copyright (c) 2009 Amos Elliston, Geni.com; Published under The MIT License, see License
|
data/Rakefile
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'rcov/rcovtask'
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'jeweler'
|
8
|
+
Jeweler::Tasks.new do |s|
|
9
|
+
s.name = "secure_carrot"
|
10
|
+
s.email = "sriram.varahan@gmail.com"
|
11
|
+
s.homepage = "http://github.com/sriram/carrot"
|
12
|
+
s.description = "A fork of carrot with added features for encrypting and decrypting messages."
|
13
|
+
s.summary = "A synchronous version of the ruby amqp client with added security features"
|
14
|
+
s.authors = ["Sriram Varahan"]
|
15
|
+
s.add_runtime_dependency 'encrypted_strings'
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
20
|
+
end
|
21
|
+
|
22
|
+
Rake::TestTask.new do |t|
|
23
|
+
t.libs << 'lib'
|
24
|
+
t.pattern = 'test/**/*_test.rb'
|
25
|
+
t.verbose = false
|
26
|
+
end
|
27
|
+
|
28
|
+
Rake::RDocTask.new do |rdoc|
|
29
|
+
rdoc.rdoc_dir = 'rdoc'
|
30
|
+
rdoc.title = 'carrot'
|
31
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
32
|
+
rdoc.rdoc_files.include('README*')
|
33
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
34
|
+
end
|
35
|
+
|
36
|
+
Rcov::RcovTask.new do |t|
|
37
|
+
t.libs << 'test'
|
38
|
+
t.test_files = FileList['test/**/*_test.rb']
|
39
|
+
t.verbose = true
|
40
|
+
end
|
41
|
+
|
42
|
+
task :default => :test
|
data/VERSION.yml
ADDED
data/lib/amqp/buffer.rb
ADDED
@@ -0,0 +1,401 @@
|
|
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 Carrot::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 @data.is_a?(Server)
|
252
|
+
raw = @data.read(size)
|
253
|
+
return raw if raw.nil? or pack.nil?
|
254
|
+
return raw.unpack(pack).first
|
255
|
+
end
|
256
|
+
|
257
|
+
if @pos + size > length
|
258
|
+
raise Overflow
|
259
|
+
else
|
260
|
+
data = @data[@pos,size]
|
261
|
+
@data[@pos,size] = ''
|
262
|
+
if pack
|
263
|
+
data = data.unpack(pack)
|
264
|
+
data = data.pop if data.size == 1
|
265
|
+
end
|
266
|
+
data
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def _write data, pack = nil
|
271
|
+
data = [*data].pack(pack) if pack
|
272
|
+
@data[@pos,0] = data
|
273
|
+
@pos += data.length
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
if $0 =~ /bacon/ or $0 == __FILE__
|
279
|
+
require 'bacon'
|
280
|
+
include AMQP
|
281
|
+
|
282
|
+
describe Buffer do
|
283
|
+
before do
|
284
|
+
@buf = Buffer.new
|
285
|
+
end
|
286
|
+
|
287
|
+
should 'have contents' do
|
288
|
+
@buf.contents.should == ''
|
289
|
+
end
|
290
|
+
|
291
|
+
should 'initialize with data' do
|
292
|
+
@buf = Buffer.new('abc')
|
293
|
+
@buf.contents.should == 'abc'
|
294
|
+
end
|
295
|
+
|
296
|
+
should 'append raw data' do
|
297
|
+
@buf << 'abc'
|
298
|
+
@buf << 'def'
|
299
|
+
@buf.contents.should == 'abcdef'
|
300
|
+
end
|
301
|
+
|
302
|
+
should 'append other buffers' do
|
303
|
+
@buf << Buffer.new('abc')
|
304
|
+
@buf.data.should == 'abc'
|
305
|
+
end
|
306
|
+
|
307
|
+
should 'have a position' do
|
308
|
+
@buf.pos.should == 0
|
309
|
+
end
|
310
|
+
|
311
|
+
should 'have a length' do
|
312
|
+
@buf.length.should == 0
|
313
|
+
@buf << 'abc'
|
314
|
+
@buf.length.should == 3
|
315
|
+
end
|
316
|
+
|
317
|
+
should 'know the end' do
|
318
|
+
@buf.empty?.should == true
|
319
|
+
end
|
320
|
+
|
321
|
+
should 'read and write data' do
|
322
|
+
@buf._write('abc')
|
323
|
+
@buf.rewind
|
324
|
+
@buf._read(2).should == 'ab'
|
325
|
+
@buf._read(1).should == 'c'
|
326
|
+
end
|
327
|
+
|
328
|
+
should 'raise on overflow' do
|
329
|
+
lambda{ @buf._read(1) }.should.raise Buffer::Overflow
|
330
|
+
end
|
331
|
+
|
332
|
+
should 'raise on invalid types' do
|
333
|
+
lambda{ @buf.read(:junk) }.should.raise Buffer::InvalidType
|
334
|
+
lambda{ @buf.write(:junk, 1) }.should.raise Buffer::InvalidType
|
335
|
+
end
|
336
|
+
|
337
|
+
{ :octet => 0b10101010,
|
338
|
+
:short => 100,
|
339
|
+
:long => 100_000_000,
|
340
|
+
:longlong => 666_555_444_333_222_111,
|
341
|
+
:shortstr => 'hello',
|
342
|
+
:longstr => 'bye'*500,
|
343
|
+
:timestamp => time = Time.at(Time.now.to_i),
|
344
|
+
:table => { :this => 'is', :a => 'hash', :with => {:nested => 123, :and => time, :also => 123.456} },
|
345
|
+
:bit => true
|
346
|
+
}.each do |type, value|
|
347
|
+
|
348
|
+
should "read and write a #{type}" do
|
349
|
+
@buf.write(type, value)
|
350
|
+
@buf.rewind
|
351
|
+
@buf.read(type).should == value
|
352
|
+
@buf.should.be.empty
|
353
|
+
end
|
354
|
+
|
355
|
+
end
|
356
|
+
|
357
|
+
should 'read and write multiple bits' do
|
358
|
+
bits = [true, false, false, true, true, false, false, true, true, false]
|
359
|
+
@buf.write(:bit, bits)
|
360
|
+
@buf.write(:octet, 100)
|
361
|
+
|
362
|
+
@buf.rewind
|
363
|
+
|
364
|
+
bits.map do
|
365
|
+
@buf.read(:bit)
|
366
|
+
end.should == bits
|
367
|
+
@buf.read(:octet).should == 100
|
368
|
+
end
|
369
|
+
|
370
|
+
should 'read and write properties' do
|
371
|
+
properties = ([
|
372
|
+
[:octet, 1],
|
373
|
+
[:shortstr, 'abc'],
|
374
|
+
[:bit, true],
|
375
|
+
[:bit, false],
|
376
|
+
[:shortstr, nil],
|
377
|
+
[:timestamp, nil],
|
378
|
+
[:table, { :a => 'hash' }],
|
379
|
+
]*5).sort_by{rand}
|
380
|
+
|
381
|
+
@buf.write(:properties, properties)
|
382
|
+
@buf.rewind
|
383
|
+
@buf.read(:properties, *properties.map{|type,_| type }).should == properties.map{|_,value| value }
|
384
|
+
@buf.should.be.empty
|
385
|
+
end
|
386
|
+
|
387
|
+
should 'do transactional reads with #extract' do
|
388
|
+
@buf.write :octet, 8
|
389
|
+
orig = @buf.to_s
|
390
|
+
|
391
|
+
@buf.rewind
|
392
|
+
@buf.extract do |b|
|
393
|
+
b.read :octet
|
394
|
+
b.read :short
|
395
|
+
end
|
396
|
+
|
397
|
+
@buf.pos.should == 0
|
398
|
+
@buf.data.should == orig
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Carrot::AMQP
|
2
|
+
class Exchange
|
3
|
+
attr_reader :server, :type, :name, :opts, :key, :carrot
|
4
|
+
|
5
|
+
def initialize(carrot, type, name, opts = {})
|
6
|
+
@server, @type, @name, @opts = carrot.server, type, name, opts
|
7
|
+
@key = opts[:key]
|
8
|
+
@carrot = carrot
|
9
|
+
|
10
|
+
unless name == "amq.#{type}" or name == ''
|
11
|
+
server.send_frame(
|
12
|
+
Protocol::Exchange::Declare.new(
|
13
|
+
{ :exchange => name, :type => type, :nowait => true }.merge(opts)
|
14
|
+
)
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
attr_reader :name, :type, :key
|
19
|
+
|
20
|
+
def publish(data, opts = {})
|
21
|
+
out = []
|
22
|
+
|
23
|
+
out << Protocol::Basic::Publish.new(
|
24
|
+
{ :exchange => name, :routing_key => opts.delete(:key) || key }.merge(opts)
|
25
|
+
)
|
26
|
+
data = data.to_s
|
27
|
+
out << Protocol::Header.new(
|
28
|
+
Protocol::Basic,
|
29
|
+
data.length, {
|
30
|
+
:content_type => 'application/octet-stream',
|
31
|
+
:delivery_mode => (opts.delete(:persistent) ? 2 : 1),
|
32
|
+
:priority => 0
|
33
|
+
}.merge(opts)
|
34
|
+
)
|
35
|
+
out << Frame::Body.new(data)
|
36
|
+
|
37
|
+
server.send_frame(*out)
|
38
|
+
end
|
39
|
+
|
40
|
+
def delete(opts = {})
|
41
|
+
server.send_frame(
|
42
|
+
Protocol::Exchange::Delete.new({ :exchange => name, :nowait => true }.merge(opts))
|
43
|
+
)
|
44
|
+
carrot.exchanges.delete(name)
|
45
|
+
end
|
46
|
+
|
47
|
+
def reset
|
48
|
+
initialize(server, type, name, opts)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/amqp/frame.rb
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
module Carrot::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
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
if $0 =~ /bacon/ or $0 == __FILE__
|
65
|
+
require 'rubygems'
|
66
|
+
require 'bacon'
|
67
|
+
include AMQP
|
68
|
+
|
69
|
+
describe Frame do
|
70
|
+
should 'handle basic frame types' do
|
71
|
+
Frame::Method.new.id.should == 1
|
72
|
+
Frame::Header.new.id.should == 2
|
73
|
+
Frame::Body.new.id.should == 3
|
74
|
+
end
|
75
|
+
|
76
|
+
should 'convert method frames to binary' do
|
77
|
+
meth = Protocol::Connection::Secure.new :challenge => 'secret'
|
78
|
+
|
79
|
+
frame = Frame::Method.new(meth)
|
80
|
+
frame.to_binary.should.be.kind_of? Buffer
|
81
|
+
frame.to_s.should == [ 1, 0, meth.to_s.length, meth.to_s, 206 ].pack('CnNa*C')
|
82
|
+
end
|
83
|
+
|
84
|
+
should 'convert binary to method frames' do
|
85
|
+
orig = Frame::Method.new Protocol::Connection::Secure.new(:challenge => 'secret')
|
86
|
+
|
87
|
+
copy = Frame.parse(orig.to_binary)
|
88
|
+
copy.should == orig
|
89
|
+
end
|
90
|
+
|
91
|
+
should 'ignore partial frames until ready' do
|
92
|
+
frame = Frame::Method.new Protocol::Connection::Secure.new(:challenge => 'secret')
|
93
|
+
data = frame.to_s
|
94
|
+
|
95
|
+
buf = Buffer.new
|
96
|
+
Frame.parse(buf).should == nil
|
97
|
+
|
98
|
+
buf << data[0..5]
|
99
|
+
Frame.parse(buf).should == nil
|
100
|
+
|
101
|
+
buf << data[6..-1]
|
102
|
+
Frame.parse(buf).should == frame
|
103
|
+
|
104
|
+
Frame.parse(buf).should == nil
|
105
|
+
end
|
106
|
+
|
107
|
+
should 'convert header frames to binary' do
|
108
|
+
head = Protocol::Header.new(Protocol::Basic, :priority => 1)
|
109
|
+
|
110
|
+
frame = Frame::Header.new(head)
|
111
|
+
frame.to_s.should == [ 2, 0, head.to_s.length, head.to_s, 206 ].pack('CnNa*C')
|
112
|
+
end
|
113
|
+
|
114
|
+
should 'convert binary to header frame' do
|
115
|
+
orig = Frame::Header.new Protocol::Header.new(Protocol::Basic, :priority => 1)
|
116
|
+
|
117
|
+
copy = Frame.parse(orig.to_binary)
|
118
|
+
copy.should == orig
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
data/lib/amqp/header.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Carrot::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
|