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.
@@ -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