fotonauts-amqp 0.6.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 +84 -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 +190 -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 +831 -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 +438 -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,190 @@
1
+ require 'amqp/frame'
2
+
3
+ module AMQP
4
+ class Error < StandardError; end
5
+
6
+ module BasicClient
7
+ def process_frame frame
8
+ if mq = channels[frame.channel]
9
+ mq.process_frame(frame)
10
+ return
11
+ end
12
+
13
+ case frame
14
+ when Frame::Method
15
+ case method = frame.payload
16
+ when Protocol::Connection::Start
17
+ send Protocol::Connection::StartOk.new({:platform => 'Ruby/EventMachine',
18
+ :product => 'AMQP',
19
+ :information => 'http://github.com/tmm1/amqp',
20
+ :version => VERSION},
21
+ 'AMQPLAIN',
22
+ {:LOGIN => @settings[:user],
23
+ :PASSWORD => @settings[:pass]},
24
+ 'en_US')
25
+
26
+ when Protocol::Connection::Tune
27
+ send Protocol::Connection::TuneOk.new(:channel_max => 0,
28
+ :frame_max => 131072,
29
+ :heartbeat => 0)
30
+
31
+ send Protocol::Connection::Open.new(:virtual_host => @settings[:vhost],
32
+ :capabilities => '',
33
+ :insist => @settings[:insist])
34
+
35
+ when Protocol::Connection::OpenOk
36
+ succeed(self)
37
+
38
+ when Protocol::Connection::Close
39
+ # raise Error, "#{method.reply_text} in #{Protocol.classes[method.class_id].methods[method.method_id]}"
40
+ STDERR.puts "#{method.reply_text} in #{Protocol.classes[method.class_id].methods[method.method_id]}"
41
+
42
+ when Protocol::Connection::CloseOk
43
+ @on_disconnect.call if @on_disconnect
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ def self.client
50
+ @client ||= BasicClient
51
+ end
52
+
53
+ def self.client= mod
54
+ mod.__send__ :include, AMQP
55
+ @client = mod
56
+ end
57
+
58
+ module Client
59
+ include EM::Deferrable
60
+
61
+ def initialize opts = {}
62
+ @settings = opts
63
+ extend AMQP.client
64
+
65
+ @on_disconnect ||= proc{ raise Error, "Could not connect to server #{opts[:host]}:#{opts[:port]}" }
66
+
67
+ timeout @settings[:timeout] if @settings[:timeout]
68
+ errback{ @on_disconnect.call } unless @reconnecting
69
+ end
70
+
71
+ def connection_completed
72
+ start_tls if @settings[:ssl]
73
+ log 'connected'
74
+ # @on_disconnect = proc{ raise Error, 'Disconnected from server' }
75
+ unless @closing
76
+ @on_disconnect = method(:reconnect)
77
+ @reconnecting = false
78
+ end
79
+ @buf = Buffer.new
80
+ send_data HEADER
81
+ send_data [1, 1, VERSION_MAJOR, VERSION_MINOR].pack('C4')
82
+ end
83
+
84
+ def unbind
85
+ log 'disconnected'
86
+ EM.next_tick{ @on_disconnect.call }
87
+ end
88
+
89
+ def add_channel mq
90
+ (@_channel_mutex ||= Mutex.new).synchronize do
91
+ channels[ key = (channels.keys.max || 0) + 1 ] = mq
92
+ key
93
+ end
94
+ end
95
+
96
+ def channels
97
+ @channels ||= {}
98
+ end
99
+
100
+ def receive_data data
101
+ # log 'receive_data', data
102
+ @buf << data
103
+
104
+ while frame = Frame.parse(@buf)
105
+ log 'receive', frame
106
+ process_frame frame
107
+ end
108
+ end
109
+
110
+ def process_frame frame
111
+ # this is a stub meant to be
112
+ # replaced by the module passed into initialize
113
+ end
114
+
115
+ def send data, opts = {}
116
+ channel = opts[:channel] ||= 0
117
+ data = data.to_frame(channel) unless data.is_a? Frame
118
+ data.channel = channel
119
+
120
+ log 'send', data
121
+ send_data data.to_s
122
+ end
123
+
124
+ #:stopdoc:
125
+ # def send_data data
126
+ # log 'send_data', data
127
+ # super
128
+ # end
129
+ #:startdoc:
130
+
131
+ def close &on_disconnect
132
+ if on_disconnect
133
+ @closing = true
134
+ @on_disconnect = proc{
135
+ on_disconnect.call
136
+ @closing = false
137
+ }
138
+ end
139
+
140
+ callback{ |c|
141
+ if c.channels.any?
142
+ c.channels.each do |ch, mq|
143
+ mq.close
144
+ end
145
+ else
146
+ send Protocol::Connection::Close.new(:reply_code => 200,
147
+ :reply_text => 'Goodbye',
148
+ :class_id => 0,
149
+ :method_id => 0)
150
+ end
151
+ }
152
+ end
153
+
154
+ def reconnect force = false
155
+ if @reconnecting and not force
156
+ # wait 1 second after first reconnect attempt, in between each subsequent attempt
157
+ EM.add_timer(1){ reconnect(true) }
158
+ return
159
+ end
160
+
161
+ unless @reconnecting
162
+ @reconnecting = true
163
+
164
+ @deferred_status = nil
165
+ initialize(@settings)
166
+
167
+ mqs = @channels
168
+ @channels = {}
169
+ mqs.each{ |_,mq| mq.reset } if mqs
170
+ end
171
+
172
+ log 'reconnecting'
173
+ EM.reconnect @settings[:host], @settings[:port], self
174
+ end
175
+
176
+ def self.connect opts = {}
177
+ opts = AMQP.settings.merge(opts)
178
+ EM.connect opts[:host], opts[:port], self, opts
179
+ end
180
+
181
+ private
182
+
183
+ def log *args
184
+ return unless @settings[:logging] or AMQP.logging
185
+ require 'pp'
186
+ pp args
187
+ puts
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,124 @@
1
+ require 'amqp/spec'
2
+ require 'amqp/buffer'
3
+ require 'amqp/protocol'
4
+
5
+ module AMQP
6
+ class Frame #:nodoc: all
7
+ def initialize payload = nil, channel = 0
8
+ @channel, @payload = channel, payload
9
+ end
10
+ attr_accessor :channel, :payload
11
+
12
+ def id
13
+ self.class::ID
14
+ end
15
+
16
+ def to_binary
17
+ buf = Buffer.new
18
+ buf.write :octet, id
19
+ buf.write :short, channel
20
+ buf.write :longstr, payload
21
+ buf.write :octet, FOOTER
22
+ buf.rewind
23
+ buf
24
+ end
25
+
26
+ def to_s
27
+ to_binary.to_s
28
+ end
29
+
30
+ def == frame
31
+ [ :id, :channel, :payload ].inject(true) do |eql, field|
32
+ eql and __send__(field) == frame.__send__(field)
33
+ end
34
+ end
35
+
36
+ class Invalid < StandardError; end
37
+
38
+ class Method
39
+ def initialize payload = nil, channel = 0
40
+ super
41
+ unless @payload.is_a? Protocol::Class::Method or @payload.nil?
42
+ @payload = Protocol.parse(@payload)
43
+ end
44
+ end
45
+ end
46
+
47
+ class Header
48
+ def initialize payload = nil, channel = 0
49
+ super
50
+ unless @payload.is_a? Protocol::Header or @payload.nil?
51
+ @payload = Protocol::Header.new(@payload)
52
+ end
53
+ end
54
+ end
55
+
56
+ class Body; end
57
+
58
+ def self.parse buf
59
+ buf = Buffer.new(buf) unless buf.is_a? Buffer
60
+ buf.extract do
61
+ id, channel, payload, footer = buf.read(:octet, :short, :longstr, :octet)
62
+ Frame.types[id].new(payload, channel) if footer == FOOTER
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ if $0 =~ /bacon/ or $0 == __FILE__
69
+ require 'bacon'
70
+ include AMQP
71
+
72
+ describe Frame do
73
+ should 'handle basic frame types' do
74
+ Frame::Method.new.id.should == 1
75
+ Frame::Header.new.id.should == 2
76
+ Frame::Body.new.id.should == 3
77
+ end
78
+
79
+ should 'convert method frames to binary' do
80
+ meth = Protocol::Connection::Secure.new :challenge => 'secret'
81
+
82
+ frame = Frame::Method.new(meth)
83
+ frame.to_binary.should.be.kind_of? Buffer
84
+ frame.to_s.should == [ 1, 0, meth.to_s.length, meth.to_s, 206 ].pack('CnNa*C')
85
+ end
86
+
87
+ should 'convert binary to method frames' do
88
+ orig = Frame::Method.new Protocol::Connection::Secure.new(:challenge => 'secret')
89
+
90
+ copy = Frame.parse(orig.to_binary)
91
+ copy.should == orig
92
+ end
93
+
94
+ should 'ignore partial frames until ready' do
95
+ frame = Frame::Method.new Protocol::Connection::Secure.new(:challenge => 'secret')
96
+ data = frame.to_s
97
+
98
+ buf = Buffer.new
99
+ Frame.parse(buf).should == nil
100
+
101
+ buf << data[0..5]
102
+ Frame.parse(buf).should == nil
103
+
104
+ buf << data[6..-1]
105
+ Frame.parse(buf).should == frame
106
+
107
+ Frame.parse(buf).should == nil
108
+ end
109
+
110
+ should 'convert header frames to binary' do
111
+ head = Protocol::Header.new(Protocol::Basic, :priority => 1)
112
+
113
+ frame = Frame::Header.new(head)
114
+ frame.to_s.should == [ 2, 0, head.to_s.length, head.to_s, 206 ].pack('CnNa*C')
115
+ end
116
+
117
+ should 'convert binary to header frame' do
118
+ orig = Frame::Header.new Protocol::Header.new(Protocol::Basic, :priority => 1)
119
+
120
+ copy = Frame.parse(orig.to_binary)
121
+ copy.should == orig
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,212 @@
1
+ require 'amqp/spec'
2
+ require 'amqp/buffer'
3
+
4
+ module AMQP
5
+ module Protocol
6
+ #:stopdoc:
7
+ class Class::Method
8
+ def initialize *args
9
+ opts = args.pop if args.last.is_a? Hash
10
+ opts ||= {}
11
+
12
+ @debug = 1 # XXX hack, p(obj) == '' if no instance vars are set
13
+
14
+ if args.size == 1 and args.first.is_a? Buffer
15
+ buf = args.shift
16
+ else
17
+ buf = nil
18
+ end
19
+
20
+ self.class.arguments.each do |type, name|
21
+ val = buf ? buf.read(type) :
22
+ args.shift || opts[name] || opts[name.to_s]
23
+ instance_variable_set("@#{name}", val)
24
+ end
25
+ end
26
+
27
+ def arguments
28
+ self.class.arguments.inject({}) do |hash, (type, name)|
29
+ hash.update name => instance_variable_get("@#{name}")
30
+ end
31
+ end
32
+
33
+ def to_binary
34
+ buf = Buffer.new
35
+ buf.write :short, self.class.parent.id
36
+ buf.write :short, self.class.id
37
+
38
+ bits = []
39
+
40
+ self.class.arguments.each do |type, name|
41
+ val = instance_variable_get("@#{name}")
42
+ if type == :bit
43
+ bits << (val || false)
44
+ else
45
+ unless bits.empty?
46
+ buf.write :bit, bits
47
+ bits = []
48
+ end
49
+ buf.write type, val
50
+ end
51
+ end
52
+
53
+ buf.write :bit, bits unless bits.empty?
54
+ buf.rewind
55
+
56
+ buf
57
+ end
58
+
59
+ def to_s
60
+ to_binary.to_s
61
+ end
62
+
63
+ def to_frame channel = 0
64
+ Frame::Method.new(self, channel)
65
+ end
66
+ end
67
+
68
+ #:startdoc:
69
+ #
70
+ # Contains a properties hash that holds some potentially interesting
71
+ # information.
72
+ # * :delivery_mode
73
+ # 1 equals transient.
74
+ # 2 equals persistent. Unconsumed persistent messages will survive
75
+ # a server restart when they are stored in a durable queue.
76
+ # * :redelivered
77
+ # True or False
78
+ # * :routing_key
79
+ # The routing string used for matching this message to this queue.
80
+ # * :priority
81
+ # An integer in the range of 0 to 9 inclusive.
82
+ # * :content_type
83
+ # Always "application/octet-stream" (byte stream)
84
+ # * :exchange
85
+ # The source exchange which published this message.
86
+ # * :message_count
87
+ # The number of unconsumed messages contained in the queue.
88
+ # * :delivery_tag
89
+ # A monotonically increasing integer. This number should not be trusted
90
+ # as a sequence number. There is no guarantee it won't get reset.
91
+ class Header
92
+ def initialize *args
93
+ opts = args.pop if args.last.is_a? Hash
94
+ opts ||= {}
95
+
96
+ first = args.shift
97
+
98
+ if first.is_a? ::Class and first.ancestors.include? Protocol::Class
99
+ @klass = first
100
+ @size = args.shift || 0
101
+ @weight = args.shift || 0
102
+ @properties = opts
103
+
104
+ elsif first.is_a? Buffer or first.is_a? String
105
+ buf = first
106
+ buf = Buffer.new(buf) unless buf.is_a? Buffer
107
+
108
+ @klass = Protocol.classes[buf.read(:short)]
109
+ @weight = buf.read(:short)
110
+ @size = buf.read(:longlong)
111
+
112
+ props = buf.read(:properties, *klass.properties.map{|type,_| type })
113
+ @properties = Hash[*klass.properties.map{|_,name| name }.zip(props).reject{|k,v| v.nil? }.flatten]
114
+
115
+ else
116
+ raise ArgumentError, 'Invalid argument'
117
+ end
118
+
119
+ end
120
+ attr_accessor :klass, :size, :weight, :properties
121
+
122
+ def to_binary
123
+ buf = Buffer.new
124
+ buf.write :short, klass.id
125
+ buf.write :short, weight # XXX rabbitmq only supports weight == 0
126
+ buf.write :longlong, size
127
+ buf.write :properties, (klass.properties.map do |type, name|
128
+ [ type, properties[name] || properties[name.to_s] ]
129
+ end)
130
+ buf.rewind
131
+ buf
132
+ end
133
+
134
+ def to_s
135
+ to_binary.to_s
136
+ end
137
+
138
+ def to_frame channel = 0
139
+ Frame::Header.new(self, channel)
140
+ end
141
+
142
+ def == header
143
+ [ :klass, :size, :weight, :properties ].inject(true) do |eql, field|
144
+ eql and __send__(field) == header.__send__(field)
145
+ end
146
+ end
147
+
148
+ def method_missing meth, *args, &blk
149
+ @properties.has_key?(meth) || @klass.properties.find{|_,name| name == meth } ? @properties[meth] :
150
+ super
151
+ end
152
+ end
153
+
154
+ def self.parse buf
155
+ buf = Buffer.new(buf) unless buf.is_a? Buffer
156
+ class_id, method_id = buf.read(:short, :short)
157
+ classes[class_id].methods[method_id].new(buf)
158
+ end
159
+ #:stopdoc:
160
+ end
161
+ end
162
+
163
+ if $0 =~ /bacon/ or $0 == __FILE__
164
+ require 'bacon'
165
+ include AMQP
166
+
167
+ describe Protocol do
168
+ should 'instantiate methods with arguments' do
169
+ meth = Protocol::Connection::StartOk.new nil, 'PLAIN', nil, 'en_US'
170
+ meth.locale.should == 'en_US'
171
+ end
172
+
173
+ should 'instantiate methods with named parameters' do
174
+ meth = Protocol::Connection::StartOk.new :locale => 'en_US',
175
+ :mechanism => 'PLAIN'
176
+ meth.locale.should == 'en_US'
177
+ end
178
+
179
+ should 'convert methods to binary' do
180
+ meth = Protocol::Connection::Secure.new :challenge => 'secret'
181
+ meth.to_binary.should.be.kind_of? Buffer
182
+
183
+ meth.to_s.should == [ 10, 20, 6, 'secret' ].pack('nnNa*')
184
+ end
185
+
186
+ should 'convert binary to method' do
187
+ orig = Protocol::Connection::Secure.new :challenge => 'secret'
188
+ copy = Protocol.parse orig.to_binary
189
+ orig.should == copy
190
+ end
191
+
192
+ should 'convert headers to binary' do
193
+ head = Protocol::Header.new Protocol::Basic,
194
+ size = 5,
195
+ weight = 0,
196
+ :content_type => 'text/json',
197
+ :delivery_mode => 1,
198
+ :priority => 1
199
+ head.to_s.should == [ 60, weight, 0, size, 0b1001_1000_0000_0000, 9, 'text/json', 1, 1 ].pack('nnNNnCa*CC')
200
+ end
201
+
202
+ should 'convert binary to header' do
203
+ orig = Protocol::Header.new Protocol::Basic,
204
+ size = 5,
205
+ weight = 0,
206
+ :content_type => 'text/json',
207
+ :delivery_mode => 1,
208
+ :priority => 1
209
+ Protocol::Header.new(orig.to_binary).should == orig
210
+ end
211
+ end
212
+ end