celldee-bunny 0.2.0 → 0.3.0
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/LICENSE +20 -0
- data/README +49 -0
- data/Rakefile +3 -4
- data/bunny.gemspec +37 -0
- data/examples/simple.rb +1 -1
- data/examples/simple_ack.rb +1 -1
- data/examples/simple_consumer.rb +1 -1
- data/examples/simple_fanout.rb +1 -1
- data/examples/simple_publisher.rb +1 -1
- data/examples/simple_topic.rb +3 -3
- data/ext/codegen.rb +177 -0
- data/lib/bunny/client.rb +161 -14
- data/lib/bunny/exchange.rb +155 -80
- data/lib/bunny/protocol/protocol.rb +135 -0
- data/lib/bunny/protocol/spec.rb +836 -0
- data/lib/bunny/queue.rb +223 -23
- data/lib/bunny/transport/buffer.rb +266 -0
- data/lib/bunny/transport/frame.rb +62 -0
- data/lib/bunny.rb +20 -62
- data/spec/bunny_spec.rb +2 -2
- data/spec/exchange_spec.rb +3 -3
- data/spec/queue_spec.rb +10 -9
- metadata +22 -22
- data/README.markdown +0 -126
- data/lib/api_messages.rb +0 -18
- data/lib/bunny/header.rb +0 -30
- data/lib/engineroom/buffer.rb +0 -274
- data/lib/engineroom/frame.rb +0 -61
- data/lib/engineroom/protocol.rb +0 -156
- data/lib/engineroom/spec.rb +0 -830
- data/protocol/codegen.rb +0 -171
- /data/{protocol → ext}/amqp-0.8.json +0 -0
data/lib/bunny/exchange.rb
CHANGED
|
@@ -1,83 +1,158 @@
|
|
|
1
|
-
module
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
attr_reader :client, :type, :name, :opts, :key
|
|
1
|
+
module Bunny
|
|
2
|
+
|
|
3
|
+
=begin rdoc
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
5
|
+
=== DESCRIPTION:
|
|
6
|
+
|
|
7
|
+
*Exchanges* are the routing and distribution hub of AMQP. All messages that Bunny sends
|
|
8
|
+
to an AMQP broker/server _have_ to pass through an exchange in order to be routed to a
|
|
9
|
+
destination queue. The AMQP specification defines the types of exchange that you can create.
|
|
10
|
+
|
|
11
|
+
At the time of writing there are four (4) types of exchange defined -
|
|
12
|
+
|
|
13
|
+
* <tt>:direct</tt>
|
|
14
|
+
* <tt>:fanout</tt>
|
|
15
|
+
* <tt>:topic</tt>
|
|
16
|
+
* <tt>:headers</tt>
|
|
17
|
+
|
|
18
|
+
AMQP-compliant brokers/servers are required to provide default exchanges for the _direct_ and
|
|
19
|
+
_fanout_ exchange types. All default exchanges are prefixed with <tt>'amq.'</tt>, for example -
|
|
20
|
+
|
|
21
|
+
* <tt>amq.direct</tt>
|
|
22
|
+
* <tt>amq.fanout</tt>
|
|
23
|
+
* <tt>amq.topic</tt>
|
|
24
|
+
* <tt>amq.match</tt> or <tt>amq.headers</tt>
|
|
25
|
+
|
|
26
|
+
If you want more information about exchanges, please consult the documentation for your
|
|
27
|
+
target broker/server or visit the {AMQP website}[http://www.amqp.org] to find the version of the
|
|
28
|
+
specification that applies to your target broker/server.
|
|
29
|
+
|
|
30
|
+
=end
|
|
31
|
+
|
|
32
|
+
class Exchange
|
|
33
|
+
|
|
34
|
+
attr_reader :client, :type, :name, :opts, :key
|
|
35
|
+
|
|
36
|
+
def initialize(client, name, opts = {})
|
|
37
|
+
# check connection to server
|
|
38
|
+
raise Bunny::ConnectionError, 'Not connected to server' if client.status == :not_connected
|
|
39
|
+
|
|
40
|
+
@client, @name, @opts = client, name, opts
|
|
41
|
+
|
|
42
|
+
# set up the exchange type catering for default names
|
|
43
|
+
if name.match(/^amq\./)
|
|
44
|
+
new_type = name.sub(/amq\./, '')
|
|
45
|
+
# handle 'amq.match' default
|
|
46
|
+
new_type = 'headers' if new_type == 'match'
|
|
47
|
+
@type = new_type.to_sym
|
|
48
|
+
else
|
|
49
|
+
@type = opts[:type] || :direct
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
@key = opts[:key]
|
|
53
|
+
@client.exchanges[@name] ||= self
|
|
54
|
+
|
|
55
|
+
# ignore the :nowait option if passed, otherwise program will hang waiting for a
|
|
56
|
+
# response that will not be sent by the server
|
|
57
|
+
opts.delete(:nowait)
|
|
58
|
+
|
|
59
|
+
unless name == "amq.#{type}" or name == ''
|
|
60
|
+
client.send_frame(
|
|
61
|
+
Protocol::Exchange::Declare.new(
|
|
62
|
+
{ :exchange => name, :type => type, :nowait => false }.merge(opts)
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
raise Bunny::ProtocolError,
|
|
67
|
+
"Error declaring exchange #{name}: type = #{type}" unless
|
|
68
|
+
client.next_method.is_a?(Protocol::Exchange::DeclareOk)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
=begin rdoc
|
|
73
|
+
|
|
74
|
+
=== DESCRIPTION:
|
|
75
|
+
|
|
76
|
+
Publishes a message to a specific exchange. The message will be routed to queues as defined
|
|
77
|
+
by the exchange configuration and distributed to any active consumers when the transaction,
|
|
78
|
+
if any, is committed.
|
|
79
|
+
|
|
80
|
+
==== OPTIONS:
|
|
81
|
+
|
|
82
|
+
* <tt>:key => 'routing_key'</tt> - Specifies the routing key for the message. The routing key is
|
|
83
|
+
used for routing messages depending on the exchange configuration.
|
|
84
|
+
* <tt>:mandatory => true or false (_default_)</tt> - Tells the server how to react if the message
|
|
85
|
+
cannot be routed to a queue. If set to _true_, the server will return an unroutable message
|
|
86
|
+
with a Return method. If this flag is zero, the server silently drops the message.
|
|
87
|
+
* <tt>:immediate => true or false (_default_)</tt> - Tells the server how to react if the message
|
|
88
|
+
cannot be routed to a queue consumer immediately. If set to _true_, the server will return an
|
|
89
|
+
undeliverable message with a Return method. If set to _false_, the server will queue the message,
|
|
90
|
+
but with no guarantee that it will ever be consumed.
|
|
91
|
+
|
|
92
|
+
==== RETURNS:
|
|
93
|
+
|
|
94
|
+
nil
|
|
95
|
+
|
|
96
|
+
=end
|
|
97
|
+
|
|
98
|
+
def publish(data, opts = {})
|
|
99
|
+
out = []
|
|
100
|
+
|
|
101
|
+
out << Protocol::Basic::Publish.new(
|
|
102
|
+
{ :exchange => name, :routing_key => opts.delete(:key) || key }.merge(opts)
|
|
103
|
+
)
|
|
104
|
+
data = data.to_s
|
|
105
|
+
out << Protocol::Header.new(
|
|
106
|
+
Protocol::Basic,
|
|
107
|
+
data.length, {
|
|
108
|
+
:content_type => 'application/octet-stream',
|
|
109
|
+
:delivery_mode => (opts.delete(:persistent) ? 2 : 1),
|
|
110
|
+
:priority => 0
|
|
111
|
+
}.merge(opts)
|
|
112
|
+
)
|
|
113
|
+
out << Transport::Frame::Body.new(data)
|
|
114
|
+
|
|
115
|
+
client.send_frame(*out)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
=begin rdoc
|
|
119
|
+
|
|
120
|
+
=== DESCRIPTION:
|
|
121
|
+
|
|
122
|
+
Requests that an exchange is deleted from broker/server. Removes reference from exchanges
|
|
123
|
+
if successful. If an error occurs raises _Bunny_::_ProtocolError_.
|
|
82
124
|
|
|
125
|
+
==== Options:
|
|
126
|
+
|
|
127
|
+
* <tt>:if_unused => true or false (_default_)</tt> - If set to _true_, the server will only
|
|
128
|
+
delete the exchange if it has no queue bindings. If the exchange has queue bindings the
|
|
129
|
+
server does not delete it but raises a channel exception instead.
|
|
130
|
+
* <tt>:nowait => true or false (_default_)</tt> - Ignored by Bunny, always _false_.
|
|
131
|
+
|
|
132
|
+
==== Returns:
|
|
133
|
+
|
|
134
|
+
<tt>:delete_ok</tt> if successful
|
|
135
|
+
=end
|
|
136
|
+
|
|
137
|
+
def delete(opts = {})
|
|
138
|
+
# ignore the :nowait option if passed, otherwise program will hang waiting for a
|
|
139
|
+
# response that will not be sent by the server
|
|
140
|
+
opts.delete(:nowait)
|
|
141
|
+
|
|
142
|
+
client.send_frame(
|
|
143
|
+
Protocol::Exchange::Delete.new({ :exchange => name, :nowait => false }.merge(opts))
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
raise Bunny::ProtocolError,
|
|
147
|
+
"Error deleting exchange #{name}" unless
|
|
148
|
+
client.next_method.is_a?(Protocol::Exchange::DeleteOk)
|
|
149
|
+
|
|
150
|
+
client.exchanges.delete(name)
|
|
151
|
+
|
|
152
|
+
# return confirmation
|
|
153
|
+
:delete_ok
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
end
|
|
157
|
+
|
|
83
158
|
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
module Bunny
|
|
2
|
+
module Protocol
|
|
3
|
+
#:stopdoc:
|
|
4
|
+
class Class::Method
|
|
5
|
+
def initialize *args
|
|
6
|
+
opts = args.pop if args.last.is_a? Hash
|
|
7
|
+
opts ||= {}
|
|
8
|
+
|
|
9
|
+
@debug = 1 # XXX hack, p(obj) == '' if no instance vars are set
|
|
10
|
+
|
|
11
|
+
if args.size == 1 and args.first.is_a? Transport::Buffer
|
|
12
|
+
buf = args.shift
|
|
13
|
+
else
|
|
14
|
+
buf = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
self.class.arguments.each do |type, name|
|
|
18
|
+
val = buf ? buf.read(type) :
|
|
19
|
+
args.shift || opts[name] || opts[name.to_s]
|
|
20
|
+
instance_variable_set("@#{name}", val)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def arguments
|
|
25
|
+
self.class.arguments.inject({}) do |hash, (type, name)|
|
|
26
|
+
hash.update name => instance_variable_get("@#{name}")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def to_binary
|
|
31
|
+
buf = Transport::Buffer.new
|
|
32
|
+
buf.write :short, self.class.parent.id
|
|
33
|
+
buf.write :short, self.class.id
|
|
34
|
+
|
|
35
|
+
bits = []
|
|
36
|
+
|
|
37
|
+
self.class.arguments.each do |type, name|
|
|
38
|
+
val = instance_variable_get("@#{name}")
|
|
39
|
+
if type == :bit
|
|
40
|
+
bits << (val || false)
|
|
41
|
+
else
|
|
42
|
+
unless bits.empty?
|
|
43
|
+
buf.write :bit, bits
|
|
44
|
+
bits = []
|
|
45
|
+
end
|
|
46
|
+
buf.write type, val
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
buf.write :bit, bits unless bits.empty?
|
|
51
|
+
buf.rewind
|
|
52
|
+
|
|
53
|
+
buf
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def to_s
|
|
57
|
+
to_binary.to_s
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def to_frame channel = 0
|
|
61
|
+
Transport::Frame::Method.new(self, channel)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
class Header
|
|
66
|
+
def initialize *args
|
|
67
|
+
opts = args.pop if args.last.is_a? Hash
|
|
68
|
+
opts ||= {}
|
|
69
|
+
|
|
70
|
+
first = args.shift
|
|
71
|
+
|
|
72
|
+
if first.is_a? ::Class and first.ancestors.include? Protocol::Class
|
|
73
|
+
@klass = first
|
|
74
|
+
@size = args.shift || 0
|
|
75
|
+
@weight = args.shift || 0
|
|
76
|
+
@properties = opts
|
|
77
|
+
|
|
78
|
+
elsif first.is_a? Transport::Buffer or first.is_a? String
|
|
79
|
+
buf = first
|
|
80
|
+
buf = Transport::Buffer.new(buf) unless buf.is_a? Transport::Buffer
|
|
81
|
+
|
|
82
|
+
@klass = Protocol.classes[buf.read(:short)]
|
|
83
|
+
@weight = buf.read(:short)
|
|
84
|
+
@size = buf.read(:longlong)
|
|
85
|
+
|
|
86
|
+
props = buf.read(:properties, *klass.properties.map{|type,_| type })
|
|
87
|
+
@properties = Hash[*klass.properties.map{|_,name| name }.zip(props).reject{|k,v| v.nil? }.flatten]
|
|
88
|
+
|
|
89
|
+
else
|
|
90
|
+
raise ArgumentError, 'Invalid argument'
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
end
|
|
94
|
+
attr_accessor :klass, :size, :weight, :properties
|
|
95
|
+
|
|
96
|
+
def to_binary
|
|
97
|
+
buf = Transport::Buffer.new
|
|
98
|
+
buf.write :short, klass.id
|
|
99
|
+
buf.write :short, weight # XXX rabbitmq only supports weight == 0
|
|
100
|
+
buf.write :longlong, size
|
|
101
|
+
buf.write :properties, (klass.properties.map do |type, name|
|
|
102
|
+
[ type, properties[name] || properties[name.to_s] ]
|
|
103
|
+
end)
|
|
104
|
+
buf.rewind
|
|
105
|
+
buf
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def to_s
|
|
109
|
+
to_binary.to_s
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def to_frame channel = 0
|
|
113
|
+
Transport::Frame::Header.new(self, channel)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def == header
|
|
117
|
+
[ :klass, :size, :weight, :properties ].inject(true) do |eql, field|
|
|
118
|
+
eql and __send__(field) == header.__send__(field)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def method_missing meth, *args, &blk
|
|
123
|
+
@properties.has_key?(meth) || @klass.properties.find{|_,name| name == meth } ? @properties[meth] :
|
|
124
|
+
super
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def self.parse buf
|
|
129
|
+
buf = Transport::Buffer.new(buf) unless buf.is_a? Transport::Buffer
|
|
130
|
+
class_id, method_id = buf.read(:short, :short)
|
|
131
|
+
classes[class_id].methods[method_id].new(buf)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
end
|
|
135
|
+
end
|