bunny 0.0.8
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/README.markdown +127 -0
- data/Rakefile +12 -0
- data/examples/simple.rb +35 -0
- data/lib/amqp.rb +15 -0
- data/lib/amqp/buffer.rb +274 -0
- data/lib/amqp/client.rb +171 -0
- data/lib/amqp/frame.rb +60 -0
- data/lib/amqp/protocol.rb +158 -0
- data/lib/amqp/spec.rb +832 -0
- data/lib/bunny.rb +67 -0
- data/lib/bunny/exchange.rb +54 -0
- data/lib/bunny/header.rb +30 -0
- data/lib/bunny/queue.rb +111 -0
- data/protocol/amqp-0.8.json +617 -0
- data/protocol/amqp-0.8.xml +3908 -0
- data/protocol/codegen.rb +173 -0
- data/spec/bunny_spec.rb +40 -0
- data/spec/exchange_spec.rb +65 -0
- data/spec/queue_spec.rb +51 -0
- metadata +73 -0
data/README.markdown
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Bunny: A synchronous Ruby AMQP client
|
|
2
|
+
|
|
3
|
+
Google Group: [bunny-amqp](http://groups.google.com/group/bunny-amqp)
|
|
4
|
+
|
|
5
|
+
## Announcements
|
|
6
|
+
|
|
7
|
+
**IMPORTANT**
|
|
8
|
+
|
|
9
|
+
The Exchange#initialize method arguments have changed as of version 0.0.7
|
|
10
|
+
|
|
11
|
+
You now create an exchange like this -
|
|
12
|
+
|
|
13
|
+
b = Bunny.new
|
|
14
|
+
exch = b.exchange('my_exchange', :type => :fanout)
|
|
15
|
+
|
|
16
|
+
If you do not specify a :type option then a default of :direct is used.
|
|
17
|
+
|
|
18
|
+
The old way was -
|
|
19
|
+
|
|
20
|
+
b = Bunny.new
|
|
21
|
+
exch = b.exchange(:fanout, 'my_exchange')
|
|
22
|
+
|
|
23
|
+
## About
|
|
24
|
+
|
|
25
|
+
*Bunny* is an [AMQP](http://www.amqp.org) (Advanced Message Queuing Protocol) client, written in Ruby, that is intended to allow you to interact with AMQP-compliant message brokers/servers such as [RabbitMQ](http://www.rabbitmq.com) in a synchronous fashion.
|
|
26
|
+
|
|
27
|
+
You can use *Bunny* to -
|
|
28
|
+
|
|
29
|
+
* Create and delete exchanges
|
|
30
|
+
* Create and delete queues
|
|
31
|
+
* Publish and consume messages
|
|
32
|
+
|
|
33
|
+
*Bunny* is known to work with RabbitMQ version 1.5.4 and version 0-8 of the AMQP specification. If you want to try to use it with other AMQP message brokers/servers please let me know how you get on.
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
require 'bunny'
|
|
38
|
+
|
|
39
|
+
b = Bunny.new(:logging => true)
|
|
40
|
+
|
|
41
|
+
# start a communication session with the amqp server
|
|
42
|
+
begin
|
|
43
|
+
b.start
|
|
44
|
+
rescue Exception => e
|
|
45
|
+
puts 'ERROR - Could not start a session: ' + e
|
|
46
|
+
exit
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# declare a queue
|
|
50
|
+
q = b.queue('test1')
|
|
51
|
+
|
|
52
|
+
# publish a message to the queue
|
|
53
|
+
q.publish('Hello everybody!')
|
|
54
|
+
|
|
55
|
+
# get message from the queue
|
|
56
|
+
msg = q.pop
|
|
57
|
+
|
|
58
|
+
puts 'This is the message: ' + msg + "\n\n"
|
|
59
|
+
|
|
60
|
+
# close the connection
|
|
61
|
+
b.close
|
|
62
|
+
|
|
63
|
+
## Bunny methods
|
|
64
|
+
|
|
65
|
+
These are the Bunny methods that you will probably want to use -
|
|
66
|
+
|
|
67
|
+
### Create a Bunny instance
|
|
68
|
+
Bunny#new({_options_})
|
|
69
|
+
|
|
70
|
+
### Start a communication session with the target server
|
|
71
|
+
Bunny#start
|
|
72
|
+
|
|
73
|
+
### Stop a communication session with the target server
|
|
74
|
+
Bunny#stop
|
|
75
|
+
|
|
76
|
+
### Create a Queue
|
|
77
|
+
Bunny#queue(_**name**_, {_options_})
|
|
78
|
+
|
|
79
|
+
### Create an Exchange
|
|
80
|
+
Bunny#exchange(_**name**_, {_options_})
|
|
81
|
+
|
|
82
|
+
### Return connection status ('CONNECTED' or 'NOT CONNECTED')
|
|
83
|
+
Bunny#status
|
|
84
|
+
|
|
85
|
+
### Publish a message to an exchange
|
|
86
|
+
Exchange#publish(_**data**_, {_options_})
|
|
87
|
+
|
|
88
|
+
### Delete an exchange from the target server
|
|
89
|
+
Exchange#delete({_options_})
|
|
90
|
+
|
|
91
|
+
### Bind a queue to an exchange
|
|
92
|
+
Queue#bind(_**exchange**_, {_options_})
|
|
93
|
+
|
|
94
|
+
### Unbind a queue from an exchange
|
|
95
|
+
Queue#unbind(_**exchange**_, {_options_})
|
|
96
|
+
|
|
97
|
+
### Publish a message to a queue
|
|
98
|
+
Queue#publish(_**data**_, {_options_})
|
|
99
|
+
|
|
100
|
+
### Pop a message off of a queue
|
|
101
|
+
Queue#pop({_options_})
|
|
102
|
+
|
|
103
|
+
### Return queue message count
|
|
104
|
+
Queue#message_count
|
|
105
|
+
|
|
106
|
+
### Return queue consumer count
|
|
107
|
+
Queue#consumer_count
|
|
108
|
+
|
|
109
|
+
### Return queue status (array of message count and consumer_count)
|
|
110
|
+
Queue#status
|
|
111
|
+
|
|
112
|
+
### Send an acknowledge message to the server
|
|
113
|
+
Queue#ack
|
|
114
|
+
|
|
115
|
+
### Delete a queue from the target server
|
|
116
|
+
Queue#delete({_options_})
|
|
117
|
+
|
|
118
|
+
## Acknowledgements
|
|
119
|
+
|
|
120
|
+
This project has borrowed heavily from the following two projects and owes their respective creators and collaborators a whole lot of gratitude:
|
|
121
|
+
|
|
122
|
+
1. **amqp** by *tmm1* [http://github.com/tmm1/amqp/tree/master](http://github.com/tmm1/amqp/tree/master)
|
|
123
|
+
2. **carrot** by *famoseagle* [http://github.com/famoseagle/carrot/tree/master](http://github.com/famoseagle/carrot/tree/master)
|
|
124
|
+
|
|
125
|
+
## LICENSE
|
|
126
|
+
|
|
127
|
+
Copyright (c) 2009 Chris Duncan; Published under The MIT License, see License
|
data/Rakefile
ADDED
data/examples/simple.rb
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# simple.rb
|
|
2
|
+
|
|
3
|
+
# Assumes that target message broker/server has a user called 'guest' with a password 'guest'
|
|
4
|
+
# and that it is running on 'localhost'.
|
|
5
|
+
|
|
6
|
+
# If this is not the case, please change the 'Bunny.new' call below to include
|
|
7
|
+
# the relevant arguments e.g. b = Bunny.new(:user => 'john', :pass => 'doe', :host => 'foobar')
|
|
8
|
+
|
|
9
|
+
$:.unshift File.dirname(__FILE__) + '/../lib'
|
|
10
|
+
|
|
11
|
+
require 'bunny'
|
|
12
|
+
|
|
13
|
+
b = Bunny.new(:logging => true)
|
|
14
|
+
|
|
15
|
+
# start a communication session with the amqp server
|
|
16
|
+
begin
|
|
17
|
+
b.start
|
|
18
|
+
rescue Exception => e
|
|
19
|
+
puts 'ERROR - Could not start a session: ' + e
|
|
20
|
+
exit
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# declare a queue
|
|
24
|
+
q = b.queue('test1')
|
|
25
|
+
|
|
26
|
+
# publish a message to the queue
|
|
27
|
+
q.publish('Hello everybody!')
|
|
28
|
+
|
|
29
|
+
# get message from the queue
|
|
30
|
+
msg = q.pop
|
|
31
|
+
|
|
32
|
+
puts 'This is the message: ' + msg + "\n\n"
|
|
33
|
+
|
|
34
|
+
# close the client connection
|
|
35
|
+
b.stop
|
data/lib/amqp.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module AMQP
|
|
2
|
+
%w[ spec buffer protocol frame client ].each do |file|
|
|
3
|
+
require "amqp/#{file}"
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
# constants
|
|
7
|
+
CONNECTED = 'CONNECTED'
|
|
8
|
+
NOT_CONNECTED = 'NOT CONNECTED'
|
|
9
|
+
|
|
10
|
+
# specific error definitions
|
|
11
|
+
class ProtocolError < StandardError; end
|
|
12
|
+
class ServerDown < StandardError; end
|
|
13
|
+
class Overflow < StandardError; end
|
|
14
|
+
class InvalidType < StandardError; end
|
|
15
|
+
end
|
data/lib/amqp/buffer.rb
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
if [].map.respond_to? :with_index
|
|
2
|
+
class Array #:nodoc:
|
|
3
|
+
def enum_with_index
|
|
4
|
+
each.with_index
|
|
5
|
+
end
|
|
6
|
+
end
|
|
7
|
+
else
|
|
8
|
+
require 'enumerator'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module AMQP
|
|
12
|
+
class Buffer #:nodoc: all
|
|
13
|
+
|
|
14
|
+
def initialize data = ''
|
|
15
|
+
@data = data
|
|
16
|
+
@pos = 0
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attr_reader :pos
|
|
20
|
+
|
|
21
|
+
def data
|
|
22
|
+
@data.clone
|
|
23
|
+
end
|
|
24
|
+
alias :contents :data
|
|
25
|
+
alias :to_s :data
|
|
26
|
+
|
|
27
|
+
def << data
|
|
28
|
+
@data << data.to_s
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def length
|
|
33
|
+
@data.length
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def empty?
|
|
37
|
+
pos == length
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def rewind
|
|
41
|
+
@pos = 0
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def read_properties *types
|
|
45
|
+
types.shift if types.first == :properties
|
|
46
|
+
|
|
47
|
+
i = 0
|
|
48
|
+
values = []
|
|
49
|
+
|
|
50
|
+
while props = read(:short)
|
|
51
|
+
(0..14).each do |n|
|
|
52
|
+
# no more property types
|
|
53
|
+
break unless types[i]
|
|
54
|
+
|
|
55
|
+
# if flag is set
|
|
56
|
+
if props & (1<<(15-n)) != 0
|
|
57
|
+
if types[i] == :bit
|
|
58
|
+
# bit values exist in flags only
|
|
59
|
+
values << true
|
|
60
|
+
else
|
|
61
|
+
# save type name for later reading
|
|
62
|
+
values << types[i]
|
|
63
|
+
end
|
|
64
|
+
else
|
|
65
|
+
# property not set or is false bit
|
|
66
|
+
values << (types[i] == :bit ? false : nil)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
i+=1
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# bit(0) == 0 means no more property flags
|
|
73
|
+
break unless props & 1 == 1
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
values.map do |value|
|
|
77
|
+
value.is_a?(Symbol) ? read(value) : value
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def read *types
|
|
82
|
+
if types.first == :properties
|
|
83
|
+
return read_properties(*types)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
values = types.map do |type|
|
|
87
|
+
case type
|
|
88
|
+
when :octet
|
|
89
|
+
_read(1, 'C')
|
|
90
|
+
when :short
|
|
91
|
+
_read(2, 'n')
|
|
92
|
+
when :long
|
|
93
|
+
_read(4, 'N')
|
|
94
|
+
when :longlong
|
|
95
|
+
upper, lower = _read(8, 'NN')
|
|
96
|
+
upper << 32 | lower
|
|
97
|
+
when :shortstr
|
|
98
|
+
_read read(:octet)
|
|
99
|
+
when :longstr
|
|
100
|
+
_read read(:long)
|
|
101
|
+
when :timestamp
|
|
102
|
+
Time.at read(:longlong)
|
|
103
|
+
when :table
|
|
104
|
+
t = Hash.new
|
|
105
|
+
|
|
106
|
+
table = Buffer.new(read(:longstr))
|
|
107
|
+
until table.empty?
|
|
108
|
+
key, type = table.read(:shortstr, :octet)
|
|
109
|
+
key = key.intern
|
|
110
|
+
t[key] ||= case type
|
|
111
|
+
when 83 # 'S'
|
|
112
|
+
table.read(:longstr)
|
|
113
|
+
when 73 # 'I'
|
|
114
|
+
table.read(:long)
|
|
115
|
+
when 68 # 'D'
|
|
116
|
+
exp = table.read(:octet)
|
|
117
|
+
num = table.read(:long)
|
|
118
|
+
num / 10.0**exp
|
|
119
|
+
when 84 # 'T'
|
|
120
|
+
table.read(:timestamp)
|
|
121
|
+
when 70 # 'F'
|
|
122
|
+
table.read(:table)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
t
|
|
127
|
+
when :bit
|
|
128
|
+
if (@bits ||= []).empty?
|
|
129
|
+
val = read(:octet)
|
|
130
|
+
@bits = (0..7).map{|i| (val & 1<<i) != 0 }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
@bits.shift
|
|
134
|
+
else
|
|
135
|
+
raise InvalidType, "Cannot read data of type #{type}"
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
types.size == 1 ? values.first : values
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def write type, data
|
|
143
|
+
case type
|
|
144
|
+
when :octet
|
|
145
|
+
_write(data, 'C')
|
|
146
|
+
when :short
|
|
147
|
+
_write(data, 'n')
|
|
148
|
+
when :long
|
|
149
|
+
_write(data, 'N')
|
|
150
|
+
when :longlong
|
|
151
|
+
lower = data & 0xffffffff
|
|
152
|
+
upper = (data & ~0xffffffff) >> 32
|
|
153
|
+
_write([upper, lower], 'NN')
|
|
154
|
+
when :shortstr
|
|
155
|
+
data = (data || '').to_s
|
|
156
|
+
_write([data.length, data], 'Ca*')
|
|
157
|
+
when :longstr
|
|
158
|
+
if data.is_a? Hash
|
|
159
|
+
write(:table, data)
|
|
160
|
+
else
|
|
161
|
+
data = (data || '').to_s
|
|
162
|
+
_write([data.length, data], 'Na*')
|
|
163
|
+
end
|
|
164
|
+
when :timestamp
|
|
165
|
+
write(:longlong, data.to_i)
|
|
166
|
+
when :table
|
|
167
|
+
data ||= {}
|
|
168
|
+
write :longstr, (data.inject(Buffer.new) do |table, (key, value)|
|
|
169
|
+
table.write(:shortstr, key.to_s)
|
|
170
|
+
|
|
171
|
+
case value
|
|
172
|
+
when String
|
|
173
|
+
table.write(:octet, 83) # 'S'
|
|
174
|
+
table.write(:longstr, value.to_s)
|
|
175
|
+
when Fixnum
|
|
176
|
+
table.write(:octet, 73) # 'I'
|
|
177
|
+
table.write(:long, value)
|
|
178
|
+
when Float
|
|
179
|
+
table.write(:octet, 68) # 'D'
|
|
180
|
+
# XXX there's gotta be a better way to do this..
|
|
181
|
+
exp = value.to_s.split('.').last.length
|
|
182
|
+
num = value * 10**exp
|
|
183
|
+
table.write(:octet, exp)
|
|
184
|
+
table.write(:long, num)
|
|
185
|
+
when Time
|
|
186
|
+
table.write(:octet, 84) # 'T'
|
|
187
|
+
table.write(:timestamp, value)
|
|
188
|
+
when Hash
|
|
189
|
+
table.write(:octet, 70) # 'F'
|
|
190
|
+
table.write(:table, value)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
table
|
|
194
|
+
end)
|
|
195
|
+
when :bit
|
|
196
|
+
[*data].to_enum(:each_slice, 8).each{|bits|
|
|
197
|
+
write(:octet, bits.enum_with_index.inject(0){ |byte, (bit, i)|
|
|
198
|
+
byte |= 1<<i if bit
|
|
199
|
+
byte
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
when :properties
|
|
203
|
+
values = []
|
|
204
|
+
data.enum_with_index.inject(0) do |short, ((type, value), i)|
|
|
205
|
+
n = i % 15
|
|
206
|
+
last = i+1 == data.size
|
|
207
|
+
|
|
208
|
+
if (n == 0 and i != 0) or last
|
|
209
|
+
if data.size > i+1
|
|
210
|
+
short |= 1<<0
|
|
211
|
+
elsif last and value
|
|
212
|
+
values << [type,value]
|
|
213
|
+
short |= 1<<(15-n)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
write(:short, short)
|
|
217
|
+
short = 0
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
if value and !last
|
|
221
|
+
values << [type,value]
|
|
222
|
+
short |= 1<<(15-n)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
short
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
values.each do |type, value|
|
|
229
|
+
write(type, value) unless type == :bit
|
|
230
|
+
end
|
|
231
|
+
else
|
|
232
|
+
raise InvalidType, "Cannot write data of type #{type}"
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
self
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def extract
|
|
239
|
+
begin
|
|
240
|
+
cur_data, cur_pos = @data.clone, @pos
|
|
241
|
+
yield self
|
|
242
|
+
rescue Overflow
|
|
243
|
+
@data, @pos = cur_data, cur_pos
|
|
244
|
+
nil
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def _read(size, pack = nil)
|
|
249
|
+
if @data.is_a?(Client)
|
|
250
|
+
raw = @data.read(size)
|
|
251
|
+
return raw if raw.nil? or pack.nil?
|
|
252
|
+
return raw.unpack(pack).first
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
if @pos + size > length
|
|
256
|
+
raise Overflow
|
|
257
|
+
else
|
|
258
|
+
data = @data[@pos,size]
|
|
259
|
+
@data[@pos,size] = ''
|
|
260
|
+
if pack
|
|
261
|
+
data = data.unpack(pack)
|
|
262
|
+
data = data.pop if data.size == 1
|
|
263
|
+
end
|
|
264
|
+
data
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def _write data, pack = nil
|
|
269
|
+
data = [*data].pack(pack) if pack
|
|
270
|
+
@data[@pos,0] = data
|
|
271
|
+
@pos += data.length
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|