bunny 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,12 @@
1
+ task :codegen do
2
+ sh 'ruby protocol/codegen.rb > lib/amqp/spec.rb'
3
+ sh 'ruby lib/amqp/spec.rb'
4
+ end
5
+
6
+ task :spec do
7
+ require 'spec/rake/spectask'
8
+ Spec::Rake::SpecTask.new do |t|
9
+ t.spec_opts = ['--color']
10
+ end
11
+ end
12
+
@@ -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
@@ -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
@@ -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