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,210 @@
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
+
70
+ @connected = false
71
+ end
72
+
73
+ def connection_completed
74
+ start_tls if @settings[:ssl]
75
+ log 'connected'
76
+ # @on_disconnect = proc{ raise Error, 'Disconnected from server' }
77
+ unless @closing
78
+ @on_disconnect = method(:disconnected)
79
+ @reconnecting = false
80
+ end
81
+
82
+ @connected = true
83
+ @connection_status.call(:connected) if @connection_status
84
+
85
+ @buf = Buffer.new
86
+ send_data HEADER
87
+ send_data [1, 1, VERSION_MAJOR, VERSION_MINOR].pack('C4')
88
+ end
89
+
90
+ def connected?
91
+ @connected
92
+ end
93
+
94
+ def unbind
95
+ log 'disconnected'
96
+ @connected = false
97
+ EM.next_tick{ @on_disconnect.call }
98
+ end
99
+
100
+ def add_channel mq
101
+ (@_channel_mutex ||= Mutex.new).synchronize do
102
+ channels[ key = (channels.keys.max || 0) + 1 ] = mq
103
+ key
104
+ end
105
+ end
106
+
107
+ def channels
108
+ @channels ||= {}
109
+ end
110
+
111
+ def receive_data data
112
+ # log 'receive_data', data
113
+ @buf << data
114
+
115
+ while frame = Frame.parse(@buf)
116
+ log 'receive', frame
117
+ process_frame frame
118
+ end
119
+ end
120
+
121
+ def process_frame frame
122
+ # this is a stub meant to be
123
+ # replaced by the module passed into initialize
124
+ end
125
+
126
+ def send data, opts = {}
127
+ channel = opts[:channel] ||= 0
128
+ data = data.to_frame(channel) unless data.is_a? Frame
129
+ data.channel = channel
130
+
131
+ log 'send', data
132
+ send_data data.to_s
133
+ end
134
+
135
+ #:stopdoc:
136
+ # def send_data data
137
+ # log 'send_data', data
138
+ # super
139
+ # end
140
+ #:startdoc:
141
+
142
+ def close &on_disconnect
143
+ if on_disconnect
144
+ @closing = true
145
+ @on_disconnect = proc{
146
+ on_disconnect.call
147
+ @closing = false
148
+ }
149
+ end
150
+
151
+ callback{ |c|
152
+ if c.channels.any?
153
+ c.channels.each do |ch, mq|
154
+ mq.close
155
+ end
156
+ else
157
+ send Protocol::Connection::Close.new(:reply_code => 200,
158
+ :reply_text => 'Goodbye',
159
+ :class_id => 0,
160
+ :method_id => 0)
161
+ end
162
+ }
163
+ end
164
+
165
+ def reconnect force = false
166
+ if @reconnecting and not force
167
+ # wait 1 second after first reconnect attempt, in between each subsequent attempt
168
+ EM.add_timer(1){ reconnect(true) }
169
+ return
170
+ end
171
+
172
+ unless @reconnecting
173
+ @reconnecting = true
174
+
175
+ @deferred_status = nil
176
+ initialize(@settings)
177
+
178
+ mqs = @channels
179
+ @channels = {}
180
+ mqs.each{ |_,mq| mq.reset } if mqs
181
+ end
182
+
183
+ log 'reconnecting'
184
+ EM.reconnect @settings[:host], @settings[:port], self
185
+ end
186
+
187
+ def self.connect opts = {}
188
+ opts = AMQP.settings.merge(opts)
189
+ EM.connect opts[:host], opts[:port], self, opts
190
+ end
191
+
192
+ def connection_status &blk
193
+ @connection_status = blk
194
+ end
195
+
196
+ private
197
+
198
+ def disconnected
199
+ @connection_status.call(:disconnected) if @connection_status
200
+ reconnect
201
+ end
202
+
203
+ def log *args
204
+ return unless @settings[:logging] or AMQP.logging
205
+ require 'pp'
206
+ pp args
207
+ puts
208
+ end
209
+ end
210
+ 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.amqp_parent.amqp_id
36
+ buf.write :short, self.class.amqp_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.amqp_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