bunny 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,62 @@
1
+ module Bunny
2
+ module Transport #:nodoc: all
3
+ class Frame
4
+ def initialize payload = nil, channel = 0
5
+ @channel, @payload = channel, payload
6
+ end
7
+ attr_accessor :channel, :payload
8
+
9
+ def id
10
+ self.class::ID
11
+ end
12
+
13
+ def to_binary
14
+ buf = Transport::Buffer.new
15
+ buf.write :octet, id
16
+ buf.write :short, channel
17
+ buf.write :longstr, payload
18
+ buf.write :octet, Transport::Frame::FOOTER
19
+ buf.rewind
20
+ buf
21
+ end
22
+
23
+ def to_s
24
+ to_binary.to_s
25
+ end
26
+
27
+ def == frame
28
+ [ :id, :channel, :payload ].inject(true) do |eql, field|
29
+ eql and __send__(field) == frame.__send__(field)
30
+ end
31
+ end
32
+
33
+ class Method
34
+ def initialize payload = nil, channel = 0
35
+ super
36
+ unless @payload.is_a? Protocol::Class::Method or @payload.nil?
37
+ @payload = Protocol.parse(@payload)
38
+ end
39
+ end
40
+ end
41
+
42
+ class Header
43
+ def initialize payload = nil, channel = 0
44
+ super
45
+ unless @payload.is_a? Protocol::Header or @payload.nil?
46
+ @payload = Protocol::Header.new(@payload)
47
+ end
48
+ end
49
+ end
50
+
51
+ class Body; end
52
+
53
+ def self.parse buf
54
+ buf = Transport::Buffer.new(buf) unless buf.is_a? Transport::Buffer
55
+ buf.extract do
56
+ id, channel, payload, footer = buf.read(:octet, :short, :longstr, :octet)
57
+ Transport::Frame.types[id].new(payload, channel) if footer == Transport::Frame::FOOTER
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -11,12 +11,12 @@ require File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib bunny]))
11
11
  describe Bunny do
12
12
 
13
13
  before(:each) do
14
- @b = Bunny.new
14
+ @b = Bunny::Client.new
15
15
  @b.start
16
16
  end
17
17
 
18
18
  it "should connect to an AMQP server" do
19
- @b.status.should == 'CONNECTED'
19
+ @b.status.should == :connected
20
20
  end
21
21
 
22
22
  it "should be able to create an exchange" do
@@ -11,12 +11,12 @@ require File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib bunny]))
11
11
  describe Bunny::Exchange do
12
12
 
13
13
  before(:each) do
14
- @b = Bunny.new
14
+ @b = Bunny::Client.new
15
15
  @b.start
16
16
  end
17
17
 
18
18
  it "should raise an error if instantiated as non-existent type" do
19
- lambda { @b.exchange('bogus_ex', :type => :bogus) }.should raise_error(API::ProtocolError)
19
+ lambda { @b.exchange('bogus_ex', :type => :bogus) }.should raise_error(Bunny::ProtocolError)
20
20
  end
21
21
 
22
22
  it "should allow a default direct exchange to be instantiated by specifying :type" do
@@ -105,7 +105,7 @@ describe Bunny::Exchange do
105
105
  it "should be able to be deleted" do
106
106
  exch = @b.exchange('direct_exchange')
107
107
  res = exch.delete
108
- res.should == 'EXCHANGE DELETED'
108
+ res.should == :delete_ok
109
109
  @b.exchanges.has_key?('direct_exchange').should be false
110
110
  end
111
111
 
@@ -11,7 +11,7 @@ require File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib bunny]))
11
11
  describe Bunny::Queue do
12
12
 
13
13
  before(:each) do
14
- @b = Bunny.new
14
+ @b = Bunny::Client.new
15
15
  @b.start
16
16
  end
17
17
 
@@ -22,25 +22,25 @@ describe Bunny::Queue do
22
22
  it "should ignore the :nowait option when binding to an exchange" do
23
23
  exch = @b.exchange('direct_exch')
24
24
  q = @b.queue('test0')
25
- q.bind(exch, :nowait => true).should == 'BIND SUCCEEDED'
25
+ q.bind(exch, :nowait => true).should == :bind_ok
26
26
  end
27
27
 
28
28
  it "should be able to bind to an exchange" do
29
29
  exch = @b.exchange('direct_exch')
30
30
  q = @b.queue('test1')
31
- q.bind(exch).should == 'BIND SUCCEEDED'
31
+ q.bind(exch).should == :bind_ok
32
32
  end
33
33
 
34
34
  it "should ignore the :nowait option when unbinding from an exchange" do
35
35
  exch = @b.exchange('direct_exch')
36
36
  q = @b.queue('test0')
37
- q.unbind(exch, :nowait => true).should == 'UNBIND SUCCEEDED'
37
+ q.unbind(exch, :nowait => true).should == :unbind_ok
38
38
  end
39
39
 
40
40
  it "should be able to unbind from an exchange" do
41
41
  exch = @b.exchange('direct_exch')
42
42
  q = @b.queue('test1')
43
- q.unbind(exch).should == 'UNBIND SUCCEEDED'
43
+ q.unbind(exch).should == :unbind_ok
44
44
  end
45
45
 
46
46
  it "should be able to publish a message" do
@@ -49,12 +49,13 @@ describe Bunny::Queue do
49
49
  q.message_count.should == 1
50
50
  end
51
51
 
52
- it "should be able to pop a message complete with header" do
52
+ it "should be able to pop a message complete with header and delivery details" do
53
53
  q = @b.queue('test1')
54
54
  msg = q.pop(:header => true)
55
55
  msg.should be_an_instance_of Hash
56
- msg[:header].should be_an_instance_of Protocol::Header
56
+ msg[:header].should be_an_instance_of Bunny::Protocol::Header
57
57
  msg[:payload].should == 'This is a test message'
58
+ msg[:delivery_details].should be_an_instance_of Hash
58
59
  q.message_count.should == 0
59
60
  end
60
61
 
@@ -71,13 +72,13 @@ describe Bunny::Queue do
71
72
  q.publish('This is another test message')
72
73
  q.pop
73
74
  msg = q.pop
74
- msg.should == 'QUEUE EMPTY'
75
+ msg.should == :queue_empty
75
76
  end
76
77
 
77
78
  it "should be able to be deleted" do
78
79
  q = @b.queue('test1')
79
80
  res = q.delete
80
- res.should == 'QUEUE DELETED'
81
+ res.should == :delete_ok
81
82
  @b.queues.has_key?('test1').should be false
82
83
  end
83
84
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bunny
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Duncan
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-05-01 00:00:00 +01:00
12
+ date: 2009-05-10 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -19,38 +19,38 @@ executables: []
19
19
 
20
20
  extensions: []
21
21
 
22
- extra_rdoc_files: []
23
-
22
+ extra_rdoc_files:
23
+ - README
24
24
  files:
25
+ - LICENSE
26
+ - README
25
27
  - Rakefile
26
- - README.markdown
27
- - lib/api_messages.rb
28
- - lib/bunny.rb
29
- - lib/engineroom/buffer.rb
30
- - lib/engineroom/frame.rb
31
- - lib/engineroom/protocol.rb
32
- - lib/engineroom/spec.rb
33
- - lib/bunny/exchange.rb
34
- - lib/bunny/header.rb
35
- - lib/bunny/queue.rb
36
- - lib/bunny/client.rb
28
+ - bunny.gemspec
37
29
  - examples/simple.rb
38
- - examples/simple_fanout.rb
30
+ - examples/simple_ack.rb
39
31
  - examples/simple_consumer.rb
32
+ - examples/simple_fanout.rb
40
33
  - examples/simple_publisher.rb
41
- - examples/simple_ack.rb
42
34
  - examples/simple_topic.rb
35
+ - ext/amqp-0.8.json
36
+ - ext/codegen.rb
37
+ - lib/bunny.rb
38
+ - lib/bunny/client.rb
39
+ - lib/bunny/exchange.rb
40
+ - lib/bunny/protocol/protocol.rb
41
+ - lib/bunny/protocol/spec.rb
42
+ - lib/bunny/queue.rb
43
+ - lib/bunny/transport/buffer.rb
44
+ - lib/bunny/transport/frame.rb
43
45
  - spec/bunny_spec.rb
44
46
  - spec/exchange_spec.rb
45
47
  - spec/queue_spec.rb
46
- - protocol/amqp-0.8.json
47
- - protocol/codegen.rb
48
48
  has_rdoc: true
49
49
  homepage: http://github.com/celldee/bunny
50
50
  post_install_message:
51
51
  rdoc_options:
52
- - --inline-source
53
- - --charset=UTF-8
52
+ - --main
53
+ - README
54
54
  require_paths:
55
55
  - lib
56
56
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -71,6 +71,6 @@ rubyforge_project: bunny-amqp
71
71
  rubygems_version: 1.3.1
72
72
  signing_key:
73
73
  specification_version: 2
74
- summary: Another synchronous Ruby AMQP client
74
+ summary: A synchronous Ruby AMQP client that enables interaction with AMQP-compliant brokers/servers.
75
75
  test_files: []
76
76
 
@@ -1,126 +0,0 @@
1
- # Bunny: A synchronous Ruby AMQP client
2
-
3
- Google Group: [http://groups.google.com/group/bunny-amqp](http://groups.google.com/group/bunny-amqp)
4
-
5
- Mailing List: [http://rubyforge.org/mailman/listinfo/bunny-amqp-devel](http://rubyforge.org/mailman/listinfo/bunny-amqp-devel)
6
-
7
- Rubyforge: [http://rubyforge.org/projects/bunny-amqp](http://rubyforge.org/projects/bunny-amqp)
8
-
9
- Twitter: [http://twitter.com/bunny_amqp](https://twitter.com/bunny_amqp)
10
-
11
- ## Announcements
12
-
13
- Bunny v0.2.0 is now available. The highlights are -
14
-
15
- * Code has been re-organised enabling Bunny to play nicely with [amqp](http://github.com/tmm1/amqp) (thanks [Dan](http://github.com/danielsdeleo))
16
- * When instantiating a default exchange (one beginning with ‘amq.’) the type will be inferred from the name.
17
- * Fixed Queue#subscribe and included Queue#unsubscribe method. See examples/simple_consumer.rb for details
18
-
19
- ## About
20
-
21
- *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.
22
-
23
- It is based on a great deal of fabulous code from [amqp](http://github.com/tmm1/amqp) by Aman Gupta and [Carrot](http://github.com/famoseagle/carrot) by Amos Elliston.
24
-
25
- You can use *Bunny* to -
26
-
27
- * Create and delete exchanges
28
- * Create and delete queues
29
- * Publish and consume messages
30
-
31
- *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.
32
-
33
- ## Quick Start
34
-
35
- require 'bunny'
36
-
37
- b = Bunny.new(:logging => true)
38
-
39
- # start a communication session with the amqp server
40
- b.start
41
-
42
- # declare a queue
43
- q = b.queue('test1')
44
-
45
- # publish a message to the queue
46
- q.publish('Hello everybody!')
47
-
48
- # get message from the queue
49
- msg = q.pop
50
-
51
- puts 'This is the message: ' + msg + "\n\n"
52
-
53
- # close the connection
54
- b.close
55
-
56
- ## Bunny methods
57
-
58
- These are the Bunny methods that you will probably want to use -
59
-
60
- ### Create a Bunny instance
61
- Bunny#new({_options_})
62
-
63
- ### Start a communication session with the target server
64
- Bunny#start
65
-
66
- ### Stop a communication session with the target server
67
- Bunny#stop
68
-
69
- ### Create a Queue
70
- Bunny#queue(_**name**_, {_options_})
71
-
72
- ### Create an Exchange
73
- Bunny#exchange(_**name**_, {_options_})
74
-
75
- ### Return connection status ('CONNECTED' or 'NOT CONNECTED')
76
- Bunny#status
77
-
78
- ### Publish a message to an exchange
79
- Exchange#publish(_**data**_, {_options_})
80
-
81
- ### Delete an exchange from the target server
82
- Exchange#delete({_options_})
83
-
84
- ### Bind a queue to an exchange
85
- Queue#bind(_**exchange**_, {_options_})
86
-
87
- ### Unbind a queue from an exchange
88
- Queue#unbind(_**exchange**_, {_options_})
89
-
90
- ### Publish a message to a queue
91
- Queue#publish(_**data**_, {_options_})
92
-
93
- ### Pop a message off of a queue
94
- Queue#pop({_options_})
95
-
96
- ### Subscribe to a queue
97
- Queue#subscribe({_options_}, &blk)
98
-
99
- ### Unsubscribe from a queue
100
- Queue#unsubscribe({_options_})
101
-
102
- ### Return queue message count
103
- Queue#message_count
104
-
105
- ### Return queue consumer count
106
- Queue#consumer_count
107
-
108
- ### Return queue status (hash {:message count, :consumer_count})
109
- Queue#status
110
-
111
- ### Delete a queue from the target server
112
- Queue#delete({_options_})
113
-
114
- ### Acknowledge receipt of a message
115
- Queue#ack
116
-
117
- ## Acknowledgements
118
-
119
- This project has borrowed heavily from the following two projects and owes their respective creators and collaborators a whole lot of gratitude:
120
-
121
- 1. **amqp** by *tmm1* [http://github.com/tmm1/amqp/tree/master](http://github.com/tmm1/amqp/tree/master)
122
- 2. **carrot** by *famoseagle* [http://github.com/famoseagle/carrot/tree/master](http://github.com/famoseagle/carrot/tree/master)
123
-
124
- ## LICENSE
125
-
126
- Copyright (c) 2009 Chris Duncan; Published under The MIT License, see License
@@ -1,18 +0,0 @@
1
- module API
2
- # return messages
3
- CONNECTED = 'CONNECTED'
4
- NOT_CONNECTED = 'NOT CONNECTED'
5
- QUEUE_EMPTY = 'QUEUE EMPTY'
6
- QUEUE_DELETED = 'QUEUE DELETED'
7
- EXCHANGE_DELETED = 'EXCHANGE DELETED'
8
- BIND_SUCCEEDED = 'BIND SUCCEEDED'
9
- UNBIND_SUCCEEDED = 'UNBIND SUCCEEDED'
10
-
11
- # specific error definitions
12
- class ProtocolError < StandardError; end
13
- class ServerDownError < StandardError; end
14
- class BufferOverflowError < StandardError; end
15
- class InvalidTypeError < StandardError; end
16
- class ConnectionError < StandardError; end
17
- class MessageError < StandardError; end
18
- end
@@ -1,30 +0,0 @@
1
- module API
2
- class Header
3
-
4
- attr_reader :client
5
-
6
- def initialize(client, header_obj)
7
- @client = client
8
- @header = header_obj
9
- end
10
-
11
- # Acknowledges the receipt of this message with the server.
12
- def ack
13
- client.send(Protocol::Basic::Ack.new(:delivery_tag => properties[:delivery_tag]))
14
- end
15
-
16
- # Reject this message (XXX currently unimplemented in rabbitmq)
17
- # * :requeue => true | false (default false)
18
- def reject(opts = {})
19
- client.send(Protocol::Basic::Reject.new(opts.merge(:delivery_tag => properties[:delivery_tag])))
20
- end
21
-
22
- def method_missing(meth, *args, &blk)
23
- @header.send(meth, *args, &blk)
24
- end
25
-
26
- def inspect
27
- @header.inspect
28
- end
29
- end
30
- end
@@ -1,274 +0,0 @@
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 Transport
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 API::InvalidTypeError, "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 API::InvalidTypeError, "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 API::BufferOverflowError
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?(API::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 API::BufferOverflowError
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