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.
@@ -1,83 +1,158 @@
1
- module API
2
- class Exchange
3
-
4
- attr_reader :client, :type, :name, :opts, :key
1
+ module Bunny
2
+
3
+ =begin rdoc
5
4
 
6
- def initialize(client, name, opts = {})
7
- # check connection to server
8
- raise API::ConnectionError, 'Not connected to server' if client.status == NOT_CONNECTED
9
-
10
- @client, @name, @opts = client, name, opts
11
-
12
- # set up the exchange type catering for default names
13
- if name.match(/^amq\./)
14
- new_type = name.sub(/amq\./, '')
15
- # handle 'amq.match' default
16
- new_type = 'headers' if new_type == 'match'
17
- @type = new_type.to_sym
18
- else
19
- @type = opts[:type] || :direct
20
- end
21
-
22
- @key = opts[:key]
23
- @client.exchanges[@name] ||= self
24
-
25
- # ignore the :nowait option if passed, otherwise program will hang waiting for a
26
- # response that will not be sent by the server
27
- opts.delete(:nowait)
28
-
29
- unless name == "amq.#{type}" or name == ''
30
- client.send_frame(
31
- Protocol::Exchange::Declare.new(
32
- { :exchange => name, :type => type, :nowait => false }.merge(opts)
33
- )
34
- )
35
-
36
- raise API::ProtocolError,
37
- "Error declaring exchange #{name}: type = #{type}" unless
38
- client.next_method.is_a?(Protocol::Exchange::DeclareOk)
39
- end
40
- end
41
-
42
- def publish(data, opts = {})
43
- out = []
44
-
45
- out << Protocol::Basic::Publish.new(
46
- { :exchange => name, :routing_key => opts.delete(:key) || key }.merge(opts)
47
- )
48
- data = data.to_s
49
- out << Protocol::Header.new(
50
- Protocol::Basic,
51
- data.length, {
52
- :content_type => 'application/octet-stream',
53
- :delivery_mode => (opts.delete(:persistent) ? 2 : 1),
54
- :priority => 0
55
- }.merge(opts)
56
- )
57
- out << Transport::Frame::Body.new(data)
58
-
59
- client.send_frame(*out)
60
- end
61
-
62
- def delete(opts = {})
63
- # ignore the :nowait option if passed, otherwise program will hang waiting for a
64
- # response that will not be sent by the server
65
- opts.delete(:nowait)
66
-
67
- client.send_frame(
68
- Protocol::Exchange::Delete.new({ :exchange => name, :nowait => false }.merge(opts))
69
- )
70
-
71
- raise API::ProtocolError,
72
- "Error deleting exchange #{name}" unless
73
- client.next_method.is_a?(Protocol::Exchange::DeleteOk)
74
-
75
- client.exchanges.delete(name)
76
-
77
- # return confirmation
78
- EXCHANGE_DELETED
79
- end
80
-
81
- end
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