amqp-client 0.2.3 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AMQP
4
+ class Client
5
+ # Queue abstraction
6
+ class Queue
7
+ # Should only be initialized from the Client
8
+ # @api private
9
+ def initialize(client, name)
10
+ @client = client
11
+ @name = name
12
+ end
13
+
14
+ # Publish to the queue, wait for confirm
15
+ # @param (see Client#publish)
16
+ # @option (see Client#publish)
17
+ # @raise (see Client#publish)
18
+ # @return [self]
19
+ def publish(body, **properties)
20
+ @client.publish(body, "", @name, **properties)
21
+ self
22
+ end
23
+
24
+ # Subscribe/consume from the queue
25
+ # @param no_ack [Boolean] When false messages have to be manually acknowledged (or rejected)
26
+ # @param prefetch [Integer] Specify how many messages to prefetch for consumers with no_ack is false
27
+ # @param worker_threads [Integer] Number of threads processing messages,
28
+ # 0 means that the thread calling this method will be blocked
29
+ # @param arguments [Hash] Custom arguments to the consumer
30
+ # @yield [Message] Delivered message from the queue
31
+ # @return [self]
32
+ def subscribe(no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
33
+ @client.subscribe(@name, no_ack: no_ack, prefetch: prefetch, worker_threads: worker_threads, arguments: arguments, &blk)
34
+ self
35
+ end
36
+
37
+ # Bind the queue to an exchange
38
+ # @param exchange [String] Name of the exchange to bind to
39
+ # @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
40
+ # @param arguments [Hash] Message headers to match on (only relevant for header exchanges)
41
+ # @return [self]
42
+ def bind(exchange, binding_key, arguments: {})
43
+ @client.bind(@name, exchange, binding_key, arguments: arguments)
44
+ self
45
+ end
46
+
47
+ # Unbind the queue from an exchange
48
+ # @param exchange [String] Name of the exchange to unbind from
49
+ # @param binding_key [String] Binding key which the queue is bound to the exchange with
50
+ # @param arguments [Hash] Arguments matching the binding that's being removed
51
+ # @return [self]
52
+ def unbind(exchange, binding_key, arguments: {})
53
+ @client.unbind(@name, exchange, binding_key, arguments: arguments)
54
+ self
55
+ end
56
+
57
+ # Purge/empty the queue
58
+ # @return [self]
59
+ def purge
60
+ @client.purge(@name)
61
+ self
62
+ end
63
+
64
+ # Delete the queue
65
+ # @return [nil]
66
+ def delete
67
+ @client.delete_queue(@name)
68
+ nil
69
+ end
70
+ end
71
+ end
72
+ end
@@ -1,122 +1,133 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AMQP
4
- # Encode/decode AMQP Tables
5
- module Table
6
- module_function
4
+ class Client
5
+ # Encode and decode an AMQP table to/from hash, only used internally
6
+ # @api private
7
+ module Table
8
+ module_function
7
9
 
8
- def encode(hash)
9
- tbl = ""
10
- hash.each do |k, v|
11
- key = k.to_s
12
- tbl += [key.bytesize, key, encode_field(v)].pack("C a* a*")
10
+ # Encodes a hash into a byte array
11
+ # @return [String] Byte array
12
+ def encode(hash)
13
+ tbl = StringIO.new
14
+ hash.each do |k, v|
15
+ key = k.to_s
16
+ tbl.write [key.bytesize, key].pack("Ca*")
17
+ tbl.write encode_field(v)
18
+ end
19
+ tbl.string
13
20
  end
14
- tbl
15
- end
16
-
17
- def decode(bytes)
18
- h = {}
19
- pos = 0
20
21
 
21
- while pos < bytes.bytesize
22
- key_len = bytes[pos].ord
23
- pos += 1
24
- key = bytes.byteslice(pos, key_len).force_encoding("utf-8")
25
- pos += key_len
26
- rest = bytes.byteslice(pos, bytes.bytesize - pos)
27
- len, value = decode_field(rest)
28
- pos += len + 1
29
- h[key] = value
22
+ # Decodes an AMQP table into a hash
23
+ # @return [Hash<String, Object>]
24
+ def decode(bytes)
25
+ hash = {}
26
+ pos = 0
27
+ while pos < bytes.bytesize
28
+ key_len = bytes[pos].ord
29
+ pos += 1
30
+ key = bytes.byteslice(pos, key_len).force_encoding("utf-8")
31
+ pos += key_len
32
+ rest = bytes.byteslice(pos, bytes.bytesize - pos)
33
+ len, value = decode_field(rest)
34
+ pos += len + 1
35
+ hash[key] = value
36
+ end
37
+ hash
30
38
  end
31
- h
32
- end
33
39
 
34
- def encode_field(value)
35
- case value
36
- when Integer
37
- if value > 2**31
38
- ["l", value].pack("a q>")
39
- else
40
- ["I", value].pack("a l>")
40
+ # Encoding a single value in a table
41
+ # @api private
42
+ def encode_field(value)
43
+ case value
44
+ when Integer
45
+ if value > 2**31
46
+ ["l", value].pack("a q>")
47
+ else
48
+ ["I", value].pack("a l>")
49
+ end
50
+ when Float
51
+ ["d", value].pack("a G")
52
+ when String
53
+ ["S", value.bytesize, value].pack("a L> a*")
54
+ when Time
55
+ ["T", value.to_i].pack("a Q>")
56
+ when Array
57
+ bytes = value.map { |e| encode_field(e) }.join
58
+ ["A", bytes.bytesize, bytes].pack("a L> a*")
59
+ when Hash
60
+ bytes = Table.encode(value)
61
+ ["F", bytes.bytesize, bytes].pack("a L> a*")
62
+ when true
63
+ ["t", 1].pack("a C")
64
+ when false
65
+ ["t", 0].pack("a C")
66
+ when nil
67
+ ["V"].pack("a")
68
+ else raise ArgumentError, "unsupported table field type: #{value.class}"
41
69
  end
42
- when Float
43
- ["d", value].pack("a G")
44
- when String
45
- ["S", value.bytesize, value].pack("a L> a*")
46
- when Time
47
- ["T", value.to_i].pack("a Q>")
48
- when Array
49
- bytes = value.map { |e| encode_field(e) }.join
50
- ["A", bytes.bytesize, bytes].pack("a L> a*")
51
- when Hash
52
- bytes = Table.encode(value)
53
- ["F", bytes.bytesize, bytes].pack("a L> a*")
54
- when true
55
- ["t", 1].pack("a C")
56
- when false
57
- ["t", 0].pack("a C")
58
- when nil
59
- ["V"].pack("a")
60
- else raise "unsupported table field type: #{value.class}"
61
70
  end
62
- end
63
71
 
64
- # returns [length of field including type, value of field]
65
- def decode_field(bytes)
66
- type = bytes[0]
67
- pos = 1
68
- case type
69
- when "S"
70
- len = bytes.byteslice(pos, 4).unpack1("L>")
71
- pos += 4
72
- [4 + len, bytes.byteslice(pos, len).force_encoding("utf-8")]
73
- when "F"
74
- len = bytes.byteslice(pos, 4).unpack1("L>")
75
- pos += 4
76
- [4 + len, decode(bytes.byteslice(pos, len))]
77
- when "A"
78
- len = bytes.byteslice(pos, 4).unpack1("L>")
79
- a = []
80
- while pos < len
81
- length, value = decode_field(bytes.byteslice(pos, -1))
82
- pos += length + 1
83
- a << value
72
+ # Decodes a single value
73
+ # @return [Array<Integer, Object>] Bytes read and the parsed object
74
+ # @api private
75
+ def decode_field(bytes)
76
+ type = bytes[0]
77
+ pos = 1
78
+ case type
79
+ when "S"
80
+ len = bytes.byteslice(pos, 4).unpack1("L>")
81
+ pos += 4
82
+ [4 + len, bytes.byteslice(pos, len).force_encoding("utf-8")]
83
+ when "F"
84
+ len = bytes.byteslice(pos, 4).unpack1("L>")
85
+ pos += 4
86
+ [4 + len, decode(bytes.byteslice(pos, len))]
87
+ when "A"
88
+ len = bytes.byteslice(pos, 4).unpack1("L>")
89
+ a = []
90
+ while pos < len
91
+ length, value = decode_field(bytes.byteslice(pos, -1))
92
+ pos += length + 1
93
+ a << value
94
+ end
95
+ [4 + len, a]
96
+ when "t"
97
+ [1, bytes[pos].ord == 1]
98
+ when "b"
99
+ [1, bytes.byteslice(pos, 1).unpack1("c")]
100
+ when "B"
101
+ [1, bytes.byteslice(pos, 1).unpack1("C")]
102
+ when "s"
103
+ [2, bytes.byteslice(pos, 2).unpack1("s")]
104
+ when "u"
105
+ [2, bytes.byteslice(pos, 2).unpack1("S")]
106
+ when "I"
107
+ [4, bytes.byteslice(pos, 4).unpack1("l>")]
108
+ when "i"
109
+ [4, bytes.byteslice(pos, 4).unpack1("L>")]
110
+ when "l"
111
+ [8, bytes.byteslice(pos, 8).unpack1("q>")]
112
+ when "f"
113
+ [4, bytes.byteslice(pos, 4).unpack1("g")]
114
+ when "d"
115
+ [8, bytes.byteslice(pos, 8).unpack1("G")]
116
+ when "D"
117
+ scale = bytes[pos].ord
118
+ pos += 1
119
+ value = bytes.byteslice(pos, 4).unpack1("L>")
120
+ d = value / 10**scale
121
+ [5, d]
122
+ when "x"
123
+ len = bytes.byteslice(pos, 4).unpack1("L>")
124
+ [4 + len, bytes.byteslice(pos, len)]
125
+ when "T"
126
+ [8, Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))]
127
+ when "V"
128
+ [0, nil]
129
+ else raise ArgumentError, "unsupported table field type: #{type}"
84
130
  end
85
- [4 + len, a]
86
- when "t"
87
- [1, bytes[pos].ord == 1]
88
- when "b"
89
- [1, bytes.byteslice(pos, 1).unpack1("c")]
90
- when "B"
91
- [1, bytes.byteslice(pos, 1).unpack1("C")]
92
- when "s"
93
- [2, bytes.byteslice(pos, 2).unpack1("s")]
94
- when "u"
95
- [2, bytes.byteslice(pos, 2).unpack1("S")]
96
- when "I"
97
- [4, bytes.byteslice(pos, 4).unpack1("l>")]
98
- when "i"
99
- [4, bytes.byteslice(pos, 4).unpack1("L>")]
100
- when "l"
101
- [8, bytes.byteslice(pos, 8).unpack1("q>")]
102
- when "f"
103
- [4, bytes.byteslice(pos, 4).unpack1("g")]
104
- when "d"
105
- [8, bytes.byteslice(pos, 8).unpack1("G")]
106
- when "D"
107
- scale = bytes[pos].ord
108
- pos += 1
109
- value = bytes.byteslice(pos, 4).unpack1("L>")
110
- d = value / 10**scale
111
- [5, d]
112
- when "x"
113
- len = bytes.byteslice(pos, 4).unpack1("L>")
114
- [4 + len, bytes.byteslice(pos, len)]
115
- when "T"
116
- [8, Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))]
117
- when "V"
118
- [0, nil]
119
- else raise "unsupported table field type: #{type}"
120
131
  end
121
132
  end
122
133
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module AMQP
4
4
  class Client
5
- VERSION = "0.2.3"
5
+ # Version of the client library
6
+ VERSION = "1.0.2"
6
7
  end
7
8
  end
data/lib/amqp/client.rb CHANGED
@@ -3,152 +3,267 @@
3
3
  require "set"
4
4
  require_relative "client/version"
5
5
  require_relative "client/connection"
6
+ require_relative "client/exchange"
7
+ require_relative "client/queue"
6
8
 
9
+ # AMQP 0-9-1 Protocol, this library only implements the Client
10
+ # @see Client
7
11
  module AMQP
8
12
  # AMQP 0-9-1 Client
13
+ # @see Connection
9
14
  class Client
15
+ # Create a new Client object, this won't establish a connection yet, use {#connect} or {#start} for that
16
+ # @param uri [String] URL on the format amqp://username:password@hostname/vhost,
17
+ # use amqps:// for encrypted connection
18
+ # @option options [Boolean] connection_name (PROGRAM_NAME) Set a name for the connection to be able to identify
19
+ # the client from the broker
20
+ # @option options [Boolean] verify_peer (true) Verify broker's TLS certificate, set to false for self-signed certs
21
+ # @option options [Integer] heartbeat (0) Heartbeat timeout, defaults to 0 and relies on TCP keepalive instead
22
+ # @option options [Integer] frame_max (131_072) Maximum frame size,
23
+ # the smallest of the client's and the broker's values will be used
24
+ # @option options [Integer] channel_max (2048) Maxium number of channels the client will be allowed to have open.
25
+ # Maxium allowed is 65_536. The smallest of the client's and the broker's value will be used.
10
26
  def initialize(uri, **options)
11
27
  @uri = uri
12
28
  @options = options
13
29
 
14
30
  @queues = {}
31
+ @exchanges = {}
15
32
  @subscriptions = Set.new
16
33
  @connq = SizedQueue.new(1)
17
34
  end
18
35
 
36
+ # @!group Connect and disconnect
37
+
38
+ # Establishes a new AMQP connection, does not try to reconnect
39
+ # @see Connection.connect
40
+ # @return [Connection]
19
41
  def connect(read_loop_thread: true)
20
- Connection.connect(@uri, **@options.merge(read_loop_thread: read_loop_thread))
42
+ Connection.connect(@uri, read_loop_thread: read_loop_thread, **@options)
21
43
  end
22
44
 
45
+ # Opens an AMQP connection using the high level API, will try to reconnect if successfully connected at first
46
+ # @return [self]
23
47
  def start
24
48
  @stopped = false
25
- Thread.new do
49
+ Thread.new(connect(read_loop_thread: false)) do |conn|
50
+ Thread.abort_on_exception = true # Raising an unhandled exception is a bug
26
51
  loop do
27
52
  break if @stopped
28
53
 
29
- conn = connect(read_loop_thread: false)
54
+ conn ||= connect(read_loop_thread: false)
30
55
  Thread.new do
31
56
  # restore connection in another thread, read_loop have to run
32
57
  conn.channel(1) # reserve channel 1 for publishes
33
- @subscriptions.each { |args| subscribe(*args) }
58
+ @subscriptions.each do |queue_name, no_ack, prefetch, wt, args, blk|
59
+ ch = conn.channel
60
+ ch.basic_qos(prefetch)
61
+ ch.basic_consume(queue_name, no_ack: no_ack, worker_threads: wt, arguments: args, &blk)
62
+ end
34
63
  @connq << conn
35
64
  end
36
65
  conn.read_loop # blocks until connection is closed, then reconnect
37
- rescue => e
66
+ rescue Error => e
38
67
  warn "AMQP-Client reconnect error: #{e.inspect}"
39
68
  sleep @options[:reconnect_interval] || 1
69
+ ensure
70
+ conn = nil
40
71
  end
41
72
  end
42
73
  self
43
74
  end
44
75
 
76
+ # Close the currently open connection
77
+ # @return [nil]
45
78
  def stop
79
+ return if @stopped
80
+
46
81
  @stopped = true
47
82
  conn = @connq.pop
48
83
  conn.close
49
84
  nil
50
85
  end
51
86
 
52
- def queue(name, arguments: {})
87
+ # @!endgroup
88
+ # @!group High level objects
89
+
90
+ # Declare a queue
91
+ # @param name [String] Name of the queue
92
+ # @param durable [Boolean] If true the queue will survive broker restarts,
93
+ # messages in the queue will only survive if they are published as persistent
94
+ # @param auto_delete [Boolean] If true the queue will be deleted when the last consumer stops consuming
95
+ # (it won't be deleted until at least one consumer has consumed from it)
96
+ # @param arguments [Hash] Custom arguments, such as queue-ttl etc.
97
+ # @return [Queue]
98
+ def queue(name, durable: true, auto_delete: false, arguments: {})
53
99
  raise ArgumentError, "Currently only supports named, durable queues" if name.empty?
54
100
 
55
101
  @queues.fetch(name) do
56
102
  with_connection do |conn|
57
- conn.with_channel do |ch| # use a temp channel in case the declaration fails
58
- ch.queue_declare(name, arguments: arguments)
59
- end
103
+ conn.channel(1).queue_declare(name, durable: durable, auto_delete: auto_delete, arguments: arguments)
60
104
  end
61
105
  @queues[name] = Queue.new(self, name)
62
106
  end
63
107
  end
64
108
 
65
- def subscribe(queue_name, no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
66
- @subscriptions.add? [queue_name, no_ack, prefetch, arguments, blk]
67
-
68
- with_connection do |conn|
69
- ch = conn.channel
70
- ch.basic_qos(prefetch)
71
- ch.basic_consume(queue_name, no_ack: no_ack, worker_threads: worker_threads, arguments: arguments) do |msg|
72
- blk.call(msg)
109
+ # Declare an exchange and return a high level Exchange object
110
+ # @return [Exchange]
111
+ def exchange(name, type, durable: true, auto_delete: false, internal: false, arguments: {})
112
+ @exchanges.fetch(name) do
113
+ with_connection do |conn|
114
+ conn.channel(1).exchange_declare(name, type, durable: durable, auto_delete: auto_delete,
115
+ internal: internal, arguments: arguments)
73
116
  end
117
+ @exchanges[name] = Exchange.new(self, name)
74
118
  end
75
119
  end
76
120
 
121
+ # @!endgroup
122
+ # @!group Publish
123
+
124
+ # Publish a (persistent) message and wait for confirmation
125
+ # @param (see Connection::Channel#basic_publish_confirm)
126
+ # @option (see Connection::Channel#basic_publish_confirm)
127
+ # @return (see Connection::Channel#basic_publish_confirm)
128
+ # @raise (see Connection::Channel#basic_publish_confirm)
77
129
  def publish(body, exchange, routing_key, **properties)
78
130
  with_connection do |conn|
79
- # Use channel 1 for publishes
131
+ properties = { delivery_mode: 2 }.merge!(properties)
80
132
  conn.channel(1).basic_publish_confirm(body, exchange, routing_key, **properties)
81
- rescue
82
- conn.channel(1) # reopen channel 1 if it raised
83
- raise
84
133
  end
85
- rescue => e
86
- warn "AMQP-Client error publishing, retrying (#{e.inspect})"
87
- retry
88
134
  end
89
135
 
90
- def bind(queue, exchange, routing_key, **headers)
136
+ # Publish a (persistent) message but don't wait for a confirmation
137
+ # @param (see Connection::Channel#basic_publish)
138
+ # @option (see Connection::Channel#basic_publish)
139
+ # @return (see Connection::Channel#basic_publish)
140
+ # @raise (see Connection::Channel#basic_publish)
141
+ def publish_and_forget(body, exchange, routing_key, **properties)
91
142
  with_connection do |conn|
92
- conn.channel(1).queue_bind(queue, exchange, routing_key, **headers)
143
+ properties = { delivery_mode: 2 }.merge!(properties)
144
+ conn.channel(1).basic_publish(body, exchange, routing_key, **properties)
93
145
  end
94
146
  end
95
147
 
96
- def unbind(queue, exchange, routing_key, **headers)
148
+ # Wait for unconfirmed publishes
149
+ # @return [Boolean] True if successful, false if any message negatively acknowledged
150
+ def wait_for_confirms
97
151
  with_connection do |conn|
98
- conn.channel(1).queue_unbind(queue, exchange, routing_key, **headers)
152
+ conn.channel(1).wait_for_confirms
99
153
  end
100
154
  end
101
155
 
102
- def purge(queue)
156
+ # @!endgroup
157
+ # @!group Queue actions
158
+
159
+ # Consume messages from a queue
160
+ # @param queue [String] Name of the queue to subscribe to
161
+ # @param no_ack [Boolean] When false messages have to be manually acknowledged (or rejected)
162
+ # @param prefetch [Integer] Specify how many messages to prefetch for consumers with no_ack is false
163
+ # @param worker_threads [Integer] Number of threads processing messages,
164
+ # 0 means that the thread calling this method will be blocked
165
+ # @param arguments [Hash] Custom arguments to the consumer
166
+ # @yield [Message] Delivered message from the queue
167
+ # @return [Array<(String, Array<Thread>)>] Returns consumer_tag and an array of worker threads
168
+ # @return [nil] When `worker_threads` is 0 the method will return when the consumer is cancelled
169
+ def subscribe(queue, no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
170
+ @subscriptions.add? [queue, no_ack, prefetch, worker_threads, arguments, blk]
171
+
103
172
  with_connection do |conn|
104
- conn.channel(1).queue_purge(queue)
173
+ ch = conn.channel
174
+ ch.basic_qos(prefetch)
175
+ ch.basic_consume(queue, no_ack: no_ack, worker_threads: worker_threads, arguments: arguments) do |msg|
176
+ blk.call(msg)
177
+ end
105
178
  end
106
179
  end
107
180
 
108
- def delete_queue(queue)
181
+ # Bind a queue to an exchange
182
+ # @param queue [String] Name of the queue to bind
183
+ # @param exchange [String] Name of the exchange to bind to
184
+ # @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
185
+ # @param arguments [Hash] Message headers to match on (only relevant for header exchanges)
186
+ # @return [nil]
187
+ def bind(queue, exchange, binding_key, arguments: {})
109
188
  with_connection do |conn|
110
- conn.channel(1).queue_delete(queue)
189
+ conn.channel(1).queue_bind(queue, exchange, binding_key, arguments: arguments)
111
190
  end
112
191
  end
113
192
 
114
- # Queue abstraction
115
- class Queue
116
- def initialize(client, name)
117
- @client = client
118
- @name = name
193
+ # Unbind a queue from an exchange
194
+ # @param queue [String] Name of the queue to unbind
195
+ # @param exchange [String] Name of the exchange to unbind from
196
+ # @param binding_key [String] Binding key which the queue is bound to the exchange with
197
+ # @param arguments [Hash] Arguments matching the binding that's being removed
198
+ # @return [nil]
199
+ def unbind(queue, exchange, binding_key, arguments: {})
200
+ with_connection do |conn|
201
+ conn.channel(1).queue_unbind(queue, exchange, binding_key, arguments: arguments)
119
202
  end
203
+ end
120
204
 
121
- def publish(body, **properties)
122
- @client.publish(body, "", @name, **properties)
123
- self
205
+ # Purge a queue
206
+ # @param queue [String] Name of the queue
207
+ # @return [nil]
208
+ def purge(queue)
209
+ with_connection do |conn|
210
+ conn.channel(1).queue_purge(queue)
124
211
  end
212
+ end
125
213
 
126
- def subscribe(no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
127
- @client.subscribe(@name, no_ack: no_ack, prefetch: prefetch, worker_threads: worker_threads, arguments: arguments, &blk)
128
- self
214
+ # Delete a queue
215
+ # @param name [String] Name of the queue
216
+ # @param if_unused [Boolean] Only delete if the queue doesn't have consumers, raises a ChannelClosed error otherwise
217
+ # @param if_empty [Boolean] Only delete if the queue is empty, raises a ChannelClosed error otherwise
218
+ # @return [Integer] Number of messages in the queue when deleted
219
+ def delete_queue(name, if_unused: false, if_empty: false)
220
+ with_connection do |conn|
221
+ msgs = conn.channel(1).queue_delete(name, if_unused: if_unused, if_empty: if_empty)
222
+ @queues.delete(name)
223
+ msgs
129
224
  end
225
+ end
130
226
 
131
- def bind(exchange, routing_key, **headers)
132
- @client.bind(@name, exchange, routing_key, **headers)
133
- self
134
- end
227
+ # @!endgroup
228
+ # @!group Exchange actions
135
229
 
136
- def unbind(exchange, routing_key, **headers)
137
- @client.unbind(@name, exchange, routing_key, **headers)
138
- self
230
+ # Bind an exchange to an exchange
231
+ # @param destination [String] Name of the exchange to bind
232
+ # @param source [String] Name of the exchange to bind to
233
+ # @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
234
+ # @param arguments [Hash] Message headers to match on (only relevant for header exchanges)
235
+ # @return [nil]
236
+ def exchange_bind(destination, source, binding_key, arguments: {})
237
+ with_connection do |conn|
238
+ conn.channel(1).exchange_bind(destination, source, binding_key, arguments: arguments)
139
239
  end
240
+ end
140
241
 
141
- def purge
142
- @client.purge(@name)
143
- self
242
+ # Unbind an exchange from an exchange
243
+ # @param destination [String] Name of the exchange to unbind
244
+ # @param source [String] Name of the exchange to unbind from
245
+ # @param binding_key [String] Binding key which the exchange is bound to the exchange with
246
+ # @param arguments [Hash] Arguments matching the binding that's being removed
247
+ # @return [nil]
248
+ def exchange_unbind(destination, source, binding_key, arguments: {})
249
+ with_connection do |conn|
250
+ conn.channel(1).exchange_unbind(destination, source, binding_key, arguments: arguments)
144
251
  end
252
+ end
145
253
 
146
- def delete
147
- @client.delete_queue(@name)
254
+ # Delete an exchange
255
+ # @param name [String] Name of the exchange
256
+ # @return [nil]
257
+ def delete_exchange(name)
258
+ with_connection do |conn|
259
+ conn.channel(1).exchange_delete(name)
260
+ @exchanges.delete(name)
148
261
  nil
149
262
  end
150
263
  end
151
264
 
265
+ # @!endgroup
266
+
152
267
  private
153
268
 
154
269
  def with_connection