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.
- data/.gitignore +4 -0
- data/HISTORY +27 -0
- data/README.md +169 -0
- data/Rakefile +24 -0
- data/TODO +32 -0
- data/VERSION +1 -0
- data/doc/EXAMPLE_01_PINGPONG +2 -0
- data/doc/EXAMPLE_02_CLOCK +2 -0
- data/doc/EXAMPLE_03_STOCKS +2 -0
- data/doc/EXAMPLE_04_MULTICLOCK +2 -0
- data/doc/EXAMPLE_05_ACK +2 -0
- data/doc/EXAMPLE_05_POP +2 -0
- data/doc/EXAMPLE_06_HASHTABLE +2 -0
- data/lib/amqp.rb +110 -0
- data/lib/amqp/buffer.rb +270 -0
- data/lib/amqp/client.rb +225 -0
- data/lib/amqp/frame.rb +66 -0
- data/lib/amqp/protocol.rb +161 -0
- data/lib/amqp/server.rb +99 -0
- data/lib/amqp/spec.rb +832 -0
- data/lib/amqp/version.rb +6 -0
- data/lib/ext/blankslate.rb +7 -0
- data/lib/ext/em.rb +8 -0
- data/lib/ext/emfork.rb +69 -0
- data/lib/mq.rb +875 -0
- data/lib/mq/exchange.rb +351 -0
- data/lib/mq/header.rb +33 -0
- data/lib/mq/logger.rb +89 -0
- data/lib/mq/queue.rb +455 -0
- data/lib/mq/rpc.rb +100 -0
- data/old/README +30 -0
- data/old/Rakefile +12 -0
- data/old/amqp-0.8.json +606 -0
- data/old/amqp_spec.rb +796 -0
- data/old/amqpc.rb +695 -0
- data/old/codegen.rb +148 -0
- data/protocol/amqp-0.8.json +617 -0
- data/protocol/amqp-0.8.xml +3908 -0
- data/protocol/codegen.rb +173 -0
- data/protocol/doc.txt +281 -0
- data/research/api.rb +88 -0
- data/research/primes-forked.rb +63 -0
- data/research/primes-processes.rb +135 -0
- data/research/primes-threaded.rb +49 -0
- data/tasks/common.rake +18 -0
- data/tasks/doc.rake +14 -0
- data/tasks/gem.rake +40 -0
- data/tasks/git.rake +34 -0
- data/tasks/spec.rake +15 -0
- data/tasks/version.rake +71 -0
- metadata +158 -0
data/lib/amqp/client.rb
ADDED
@@ -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
|
data/lib/amqp/frame.rb
ADDED
@@ -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
|