adamh-amqp 0.6.3.1

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.
Files changed (54) hide show
  1. data/README +128 -0
  2. data/Rakefile +15 -0
  3. data/amqp.gemspec +83 -0
  4. data/amqp.todo +32 -0
  5. data/doc/EXAMPLE_01_PINGPONG +2 -0
  6. data/doc/EXAMPLE_02_CLOCK +2 -0
  7. data/doc/EXAMPLE_03_STOCKS +2 -0
  8. data/doc/EXAMPLE_04_MULTICLOCK +2 -0
  9. data/doc/EXAMPLE_05_ACK +2 -0
  10. data/doc/EXAMPLE_05_POP +2 -0
  11. data/doc/EXAMPLE_06_HASHTABLE +2 -0
  12. data/examples/amqp/simple.rb +79 -0
  13. data/examples/mq/ack.rb +45 -0
  14. data/examples/mq/clock.rb +56 -0
  15. data/examples/mq/hashtable.rb +52 -0
  16. data/examples/mq/internal.rb +49 -0
  17. data/examples/mq/logger.rb +88 -0
  18. data/examples/mq/multiclock.rb +49 -0
  19. data/examples/mq/pingpong.rb +45 -0
  20. data/examples/mq/pop.rb +43 -0
  21. data/examples/mq/primes-simple.rb +19 -0
  22. data/examples/mq/primes.rb +99 -0
  23. data/examples/mq/stocks.rb +58 -0
  24. data/lib/amqp.rb +115 -0
  25. data/lib/amqp/buffer.rb +395 -0
  26. data/lib/amqp/client.rb +210 -0
  27. data/lib/amqp/frame.rb +124 -0
  28. data/lib/amqp/protocol.rb +212 -0
  29. data/lib/amqp/server.rb +99 -0
  30. data/lib/amqp/spec.rb +832 -0
  31. data/lib/ext/blankslate.rb +7 -0
  32. data/lib/ext/em.rb +51 -0
  33. data/lib/ext/emfork.rb +69 -0
  34. data/lib/mq.rb +823 -0
  35. data/lib/mq/exchange.rb +302 -0
  36. data/lib/mq/header.rb +33 -0
  37. data/lib/mq/logger.rb +89 -0
  38. data/lib/mq/queue.rb +433 -0
  39. data/lib/mq/rpc.rb +100 -0
  40. data/old/README +30 -0
  41. data/old/Rakefile +12 -0
  42. data/old/amqp-0.8.json +606 -0
  43. data/old/amqp_spec.rb +796 -0
  44. data/old/amqpc.rb +695 -0
  45. data/old/codegen.rb +148 -0
  46. data/protocol/amqp-0.8.json +617 -0
  47. data/protocol/amqp-0.8.xml +3908 -0
  48. data/protocol/codegen.rb +173 -0
  49. data/protocol/doc.txt +281 -0
  50. data/research/api.rb +88 -0
  51. data/research/primes-forked.rb +63 -0
  52. data/research/primes-processes.rb +135 -0
  53. data/research/primes-threaded.rb +49 -0
  54. metadata +121 -0
@@ -0,0 +1,58 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../../lib'
2
+ require 'mq'
3
+
4
+ AMQP.start(:host => 'localhost') do
5
+
6
+ def log *args
7
+ p [ Time.now, *args ]
8
+ end
9
+
10
+ def publish_stock_prices
11
+ mq = MQ.new
12
+ EM.add_periodic_timer(1){
13
+ puts
14
+
15
+ { :appl => 170+rand(1000)/100.0,
16
+ :msft => 22+rand(500)/100.0
17
+ }.each do |stock, price|
18
+ stock = "usd.#{stock}"
19
+
20
+ log :publishing, stock, price
21
+ mq.topic('stocks').publish(price, :key => stock)
22
+ end
23
+ }
24
+ end
25
+
26
+ def watch_appl_stock
27
+ mq = MQ.new
28
+ mq.queue('apple stock').bind(mq.topic('stocks'), :key => 'usd.appl').subscribe{ |price|
29
+ log 'apple stock', price
30
+ }
31
+ end
32
+
33
+ def watch_us_stocks
34
+ mq = MQ.new
35
+ mq.queue('us stocks').bind(mq.topic('stocks'), :key => 'usd.*').subscribe{ |info, price|
36
+ log 'us stock', info.routing_key, price
37
+ }
38
+ end
39
+
40
+ publish_stock_prices
41
+ watch_appl_stock
42
+ watch_us_stocks
43
+
44
+ end
45
+
46
+ __END__
47
+
48
+ [Fri Aug 15 01:39:00 -0700 2008, :publishing, "usd.appl", 173.45]
49
+ [Fri Aug 15 01:39:00 -0700 2008, :publishing, "usd.msft", 26.98]
50
+ [Fri Aug 15 01:39:00 -0700 2008, "apple stock", "173.45"]
51
+ [Fri Aug 15 01:39:00 -0700 2008, "us stock", "usd.appl", "173.45"]
52
+ [Fri Aug 15 01:39:00 -0700 2008, "us stock", "usd.msft", "26.98"]
53
+
54
+ [Fri Aug 15 01:39:01 -0700 2008, :publishing, "usd.appl", 179.72]
55
+ [Fri Aug 15 01:39:01 -0700 2008, :publishing, "usd.msft", 26.56]
56
+ [Fri Aug 15 01:39:01 -0700 2008, "apple stock", "179.72"]
57
+ [Fri Aug 15 01:39:01 -0700 2008, "us stock", "usd.appl", "179.72"]
58
+ [Fri Aug 15 01:39:01 -0700 2008, "us stock", "usd.msft", "26.56"]
@@ -0,0 +1,115 @@
1
+ module AMQP
2
+ VERSION = '0.5.9'
3
+
4
+ DIR = File.expand_path(File.dirname(File.expand_path(__FILE__)))
5
+ $:.unshift DIR
6
+
7
+ require 'ext/em'
8
+ require 'ext/blankslate'
9
+
10
+ %w[ buffer spec protocol frame client ].each do |file|
11
+ require "amqp/#{file}"
12
+ end
13
+
14
+ class << self
15
+ @logging = false
16
+ attr_accessor :logging
17
+ attr_reader :conn, :closing
18
+ alias :closing? :closing
19
+ alias :connection :conn
20
+ end
21
+
22
+ def self.connect *args
23
+ Client.connect *args
24
+ end
25
+
26
+ def self.settings
27
+ @settings ||= {
28
+ # server address
29
+ :host => '127.0.0.1',
30
+ :port => PORT,
31
+
32
+ # login details
33
+ :user => 'guest',
34
+ :pass => 'guest',
35
+ :vhost => '/',
36
+
37
+ # connection timeout
38
+ :timeout => nil,
39
+
40
+ # logging
41
+ :logging => false,
42
+
43
+ # ssl
44
+ :ssl => false
45
+ }
46
+ end
47
+
48
+ # Must be called to startup the connection to the AMQP server.
49
+ #
50
+ # The method takes several arguments and an optional block.
51
+ #
52
+ # This takes any option that is also accepted by EventMachine::connect.
53
+ # Additionally, there are several AMQP-specific options.
54
+ #
55
+ # * :user => String (default 'guest')
56
+ # The username as defined by the AMQP server.
57
+ # * :pass => String (default 'guest')
58
+ # The password for the associated :user as defined by the AMQP server.
59
+ # * :vhost => String (default '/')
60
+ # The virtual host as defined by the AMQP server.
61
+ # * :timeout => Numeric (default nil)
62
+ # Measured in seconds.
63
+ # * :logging => true | false (default false)
64
+ # Toggle the extremely verbose logging of all protocol communications
65
+ # between the client and the server. Extremely useful for debugging.
66
+ #
67
+ # AMQP.start do
68
+ # # default is to connect to localhost:5672
69
+ #
70
+ # # define queues, exchanges and bindings here.
71
+ # # also define all subscriptions and/or publishers
72
+ # # here.
73
+ #
74
+ # # this block never exits unless EM.stop_event_loop
75
+ # # is called.
76
+ # end
77
+ #
78
+ # Most code will use the MQ api. Any calls to MQ.direct / MQ.fanout /
79
+ # MQ.topic / MQ.queue will implicitly call #start. In those cases,
80
+ # it is sufficient to put your code inside of an EventMachine.run
81
+ # block. See the code examples in MQ for details.
82
+ #
83
+ def self.start *args, &blk
84
+ EM.run{
85
+ @conn ||= connect *args
86
+ @conn.callback(&blk) if blk
87
+ @conn
88
+ }
89
+ end
90
+
91
+ class << self
92
+ alias :run :start
93
+ end
94
+
95
+ def self.stop
96
+ if @conn and not @closing
97
+ @closing = true
98
+ @conn.close{
99
+ yield if block_given?
100
+ @conn = nil
101
+ @closing = false
102
+ }
103
+ end
104
+ end
105
+
106
+ def self.fork workers
107
+ EM.fork(workers) do
108
+ # clean up globals in the fork
109
+ Thread.current[:mq] = nil
110
+ AMQP.instance_variable_set('@conn', nil)
111
+
112
+ yield
113
+ end
114
+ end
115
+ end
@@ -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