fotonauts-amqp 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
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