ivanvanderbyl-amqp 0.6.13.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 (51) hide show
  1. data/.gitignore +4 -0
  2. data/HISTORY +27 -0
  3. data/README.md +169 -0
  4. data/Rakefile +24 -0
  5. data/TODO +32 -0
  6. data/VERSION +1 -0
  7. data/doc/EXAMPLE_01_PINGPONG +2 -0
  8. data/doc/EXAMPLE_02_CLOCK +2 -0
  9. data/doc/EXAMPLE_03_STOCKS +2 -0
  10. data/doc/EXAMPLE_04_MULTICLOCK +2 -0
  11. data/doc/EXAMPLE_05_ACK +2 -0
  12. data/doc/EXAMPLE_05_POP +2 -0
  13. data/doc/EXAMPLE_06_HASHTABLE +2 -0
  14. data/lib/amqp.rb +110 -0
  15. data/lib/amqp/buffer.rb +270 -0
  16. data/lib/amqp/client.rb +225 -0
  17. data/lib/amqp/frame.rb +66 -0
  18. data/lib/amqp/protocol.rb +161 -0
  19. data/lib/amqp/server.rb +99 -0
  20. data/lib/amqp/spec.rb +832 -0
  21. data/lib/amqp/version.rb +6 -0
  22. data/lib/ext/blankslate.rb +7 -0
  23. data/lib/ext/em.rb +8 -0
  24. data/lib/ext/emfork.rb +69 -0
  25. data/lib/mq.rb +875 -0
  26. data/lib/mq/exchange.rb +351 -0
  27. data/lib/mq/header.rb +33 -0
  28. data/lib/mq/logger.rb +89 -0
  29. data/lib/mq/queue.rb +455 -0
  30. data/lib/mq/rpc.rb +100 -0
  31. data/old/README +30 -0
  32. data/old/Rakefile +12 -0
  33. data/old/amqp-0.8.json +606 -0
  34. data/old/amqp_spec.rb +796 -0
  35. data/old/amqpc.rb +695 -0
  36. data/old/codegen.rb +148 -0
  37. data/protocol/amqp-0.8.json +617 -0
  38. data/protocol/amqp-0.8.xml +3908 -0
  39. data/protocol/codegen.rb +173 -0
  40. data/protocol/doc.txt +281 -0
  41. data/research/api.rb +88 -0
  42. data/research/primes-forked.rb +63 -0
  43. data/research/primes-processes.rb +135 -0
  44. data/research/primes-threaded.rb +49 -0
  45. data/tasks/common.rake +18 -0
  46. data/tasks/doc.rake +14 -0
  47. data/tasks/gem.rake +40 -0
  48. data/tasks/git.rake +34 -0
  49. data/tasks/spec.rake +15 -0
  50. data/tasks/version.rake +71 -0
  51. metadata +158 -0
@@ -0,0 +1,225 @@
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 => @settings[: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
+
46
+ when Frame::Heartbeat
47
+ @last_server_heartbeat = Time.now
48
+ end
49
+ end
50
+ end
51
+
52
+ def self.client
53
+ @client ||= BasicClient
54
+ end
55
+
56
+ def self.client= mod
57
+ mod.__send__ :include, AMQP
58
+ @client = mod
59
+ end
60
+
61
+ module Client
62
+ include EM::Deferrable
63
+
64
+ def initialize opts = {}
65
+ @settings = opts
66
+ extend AMQP.client
67
+
68
+ @on_disconnect ||= proc{ raise Error, "Could not connect to server #{opts[:host]}:#{opts[:port]}" }
69
+
70
+ timeout @settings[:timeout] if @settings[:timeout]
71
+ errback{ @on_disconnect.call } unless @reconnecting
72
+
73
+ @connected = false
74
+ end
75
+
76
+ def connection_completed
77
+ start_tls if @settings[:ssl]
78
+ log 'connected'
79
+ # @on_disconnect = proc{ raise Error, 'Disconnected from server' }
80
+ unless @closing
81
+ @on_disconnect = method(:disconnected)
82
+ @reconnecting = false
83
+ end
84
+
85
+ @connected = true
86
+ @connection_status.call(:connected) if @connection_status
87
+
88
+ @buf = Buffer.new
89
+ send_data HEADER
90
+ send_data [1, 1, VERSION_MAJOR, VERSION_MINOR].pack('C4')
91
+
92
+ if heartbeat = @settings[:heartbeat]
93
+ init_heartbeat if (@settings[:heartbeat] = heartbeat.to_i) > 0
94
+ end
95
+ end
96
+
97
+ def init_heartbeat
98
+ @last_server_heartbeat = Time.now
99
+
100
+ @timer ||= EM::PeriodicTimer.new(@settings[:heartbeat]) do
101
+ if connected?
102
+ if @last_server_heartbeat < (Time.now - (@settings[:heartbeat] * 2))
103
+ log "Reconnecting due to missing server heartbeats"
104
+ reconnect(true)
105
+ else
106
+ send AMQP::Frame::Heartbeat.new
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ def connected?
113
+ @connected
114
+ end
115
+
116
+ def unbind
117
+ log 'disconnected'
118
+ @connected = false
119
+ EM.next_tick{ @on_disconnect.call }
120
+ end
121
+
122
+ def add_channel mq
123
+ (@_channel_mutex ||= Mutex.new).synchronize do
124
+ channels[ key = (channels.keys.max || 0) + 1 ] = mq
125
+ key
126
+ end
127
+ end
128
+
129
+ def channels
130
+ @channels ||= {}
131
+ end
132
+
133
+ def receive_data data
134
+ # log 'receive_data', data
135
+ @buf << data
136
+
137
+ while frame = Frame.parse(@buf)
138
+ log 'receive', frame
139
+ process_frame frame
140
+ end
141
+ end
142
+
143
+ def process_frame frame
144
+ # this is a stub meant to be
145
+ # replaced by the module passed into initialize
146
+ end
147
+
148
+ def send data, opts = {}
149
+ channel = opts[:channel] ||= 0
150
+ data = data.to_frame(channel) unless data.is_a? Frame
151
+ data.channel = channel
152
+
153
+ log 'send', data
154
+ send_data data.to_s
155
+ end
156
+
157
+ def close &on_disconnect
158
+ if on_disconnect
159
+ @closing = true
160
+ @on_disconnect = proc{
161
+ on_disconnect.call
162
+ @closing = false
163
+ }
164
+ end
165
+
166
+ callback{ |c|
167
+ if c.channels.any?
168
+ c.channels.each do |ch, mq|
169
+ mq.close
170
+ end
171
+ else
172
+ send Protocol::Connection::Close.new(:reply_code => 200,
173
+ :reply_text => 'Goodbye',
174
+ :class_id => 0,
175
+ :method_id => 0)
176
+ end
177
+ }
178
+ end
179
+
180
+ def reconnect force = false
181
+ if @reconnecting and not force
182
+ # wait 1 second after first reconnect attempt, in between each subsequent attempt
183
+ EM.add_timer(1){ reconnect(true) }
184
+ return
185
+ end
186
+
187
+ unless @reconnecting
188
+ @reconnecting = true
189
+
190
+ @deferred_status = nil
191
+ initialize(@settings)
192
+
193
+ mqs = @channels
194
+ @channels = {}
195
+ mqs.each{ |_,mq| mq.reset } if mqs
196
+ end
197
+
198
+ log 'reconnecting'
199
+ EM.reconnect @settings[:host], @settings[:port], self
200
+ end
201
+
202
+ def self.connect opts = {}
203
+ opts = AMQP.settings.merge(opts)
204
+ EM.connect opts[:host], opts[:port], self, opts
205
+ end
206
+
207
+ def connection_status &blk
208
+ @connection_status = blk
209
+ end
210
+
211
+ private
212
+
213
+ def disconnected
214
+ @connection_status.call(:disconnected) if @connection_status
215
+ reconnect
216
+ end
217
+
218
+ def log *args
219
+ return unless @settings[:logging] or AMQP.logging
220
+ require 'pp'
221
+ pp args
222
+ puts
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,66 @@
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
@@ -0,0 +1,161 @@
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