totty-amqp 0.6.7.1.totty

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/README +143 -0
  2. data/Rakefile +20 -0
  3. data/amqp.todo +32 -0
  4. data/doc/EXAMPLE_01_PINGPONG +2 -0
  5. data/doc/EXAMPLE_02_CLOCK +2 -0
  6. data/doc/EXAMPLE_03_STOCKS +2 -0
  7. data/doc/EXAMPLE_04_MULTICLOCK +2 -0
  8. data/doc/EXAMPLE_05_ACK +2 -0
  9. data/doc/EXAMPLE_05_POP +2 -0
  10. data/doc/EXAMPLE_06_HASHTABLE +2 -0
  11. data/examples/amqp/simple.rb +79 -0
  12. data/examples/mq/ack.rb +45 -0
  13. data/examples/mq/clock.rb +56 -0
  14. data/examples/mq/hashtable.rb +52 -0
  15. data/examples/mq/internal.rb +49 -0
  16. data/examples/mq/logger.rb +88 -0
  17. data/examples/mq/multiclock.rb +49 -0
  18. data/examples/mq/pingpong.rb +45 -0
  19. data/examples/mq/pop.rb +43 -0
  20. data/examples/mq/primes-simple.rb +19 -0
  21. data/examples/mq/primes.rb +99 -0
  22. data/examples/mq/stocks.rb +58 -0
  23. data/lib/amqp/buffer.rb +395 -0
  24. data/lib/amqp/client.rb +210 -0
  25. data/lib/amqp/frame.rb +124 -0
  26. data/lib/amqp/protocol.rb +212 -0
  27. data/lib/amqp/server.rb +99 -0
  28. data/lib/amqp/spec.rb +832 -0
  29. data/lib/amqp/version.rb +3 -0
  30. data/lib/amqp.rb +152 -0
  31. data/lib/ext/blankslate.rb +7 -0
  32. data/lib/ext/em.rb +8 -0
  33. data/lib/ext/emfork.rb +69 -0
  34. data/lib/mq/exchange.rb +314 -0
  35. data/lib/mq/header.rb +33 -0
  36. data/lib/mq/logger.rb +89 -0
  37. data/lib/mq/queue.rb +455 -0
  38. data/lib/mq/rpc.rb +100 -0
  39. data/lib/mq.rb +877 -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. data/totty-amqp.gemspec +87 -0
  55. metadata +142 -0
@@ -0,0 +1,210 @@
1
+ require File.expand_path('../frame', __FILE__)
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
data/lib/amqp/frame.rb ADDED
@@ -0,0 +1,124 @@
1
+ require File.expand_path('../spec', __FILE__)
2
+ require File.expand_path('../buffer', __FILE__)
3
+ require File.expand_path('../protocol', __FILE__)
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 File.expand_path('../spec', __FILE__)
2
+ require File.expand_path('../buffer', __FILE__)
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.section.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
@@ -0,0 +1,99 @@
1
+ require File.expand_path('../frame', __FILE__)
2
+
3
+ module AMQP
4
+ module Server
5
+ def post_init
6
+ @buf = ''
7
+ @channels = {}
8
+ @started = false
9
+ end
10
+
11
+ def receive_data data
12
+ @buf << data
13
+
14
+ unless @started
15
+ if @buf.size >= 8
16
+ if @buf.slice!(0,8) == "AMQP\001\001\b\000"
17
+ send Protocol::Connection::Start.new(
18
+ 8,
19
+ 0,
20
+ {
21
+ :information => 'Licensed under the Ruby license. See http://github.com/tmm1/amqp',
22
+ :copyright => 'Copyright (c) 2008-2009 Aman Gupta',
23
+ :platform => 'Ruby/EventMachine',
24
+ :version => '0.6.1',
25
+ :product => 'SquirrelMQ'
26
+ },
27
+ 'PLAIN AMQPLAIN',
28
+ 'en_US'
29
+ )
30
+ else
31
+ close_connection
32
+ return
33
+ end
34
+ @started = true
35
+ else
36
+ return
37
+ end
38
+ end
39
+
40
+ while frame = Frame.parse(@buf)
41
+ process_frame frame
42
+ end
43
+ end
44
+
45
+ def process_frame frame
46
+ channel = frame.channel
47
+
48
+ case method = frame.payload
49
+ when Protocol::Connection::StartOk
50
+ @user = method.response[:LOGIN]
51
+ @pass = method.response[:PASSWORD]
52
+ send Protocol::Connection::Tune.new(0, 131072, 0)
53
+
54
+ when Protocol::Connection::TuneOk
55
+ # mnnk
56
+
57
+ when Protocol::Connection::Open
58
+ @vhost = method.virtual_host
59
+ send Protocol::Connection::OpenOk.new('localhost')
60
+
61
+ when Protocol::Channel::Open
62
+ @channels[channel] = []
63
+ send Protocol::Channel::OpenOk.new, :channel => channel
64
+
65
+ when Protocol::Access::Request
66
+ send Protocol::Access::RequestOk.new(101)
67
+ end
68
+ end
69
+
70
+ def send data, opts = {}
71
+ channel = opts[:channel] ||= 0
72
+ data = data.to_frame(channel) unless data.is_a? Frame
73
+ data.channel = channel
74
+
75
+ log 'send', data
76
+ send_data data.to_s
77
+ end
78
+
79
+ def self.start host = 'localhost', port = 5672
80
+ EM.start_server host, port, self
81
+ end
82
+
83
+ private
84
+
85
+ def log *args
86
+ require 'pp'
87
+ pp args
88
+ puts
89
+ end
90
+ end
91
+ end
92
+
93
+ if __FILE__ == $0
94
+ require 'rubygems'
95
+ require 'eventmachine'
96
+ EM.run{
97
+ AMQP::Server.start
98
+ }
99
+ end