amqp-client 0.2.2 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/docs.yml +25 -0
- data/.rubocop.yml +12 -1
- data/.rubocop_todo.yml +14 -58
- data/.yardopts +1 -0
- data/CHANGELOG.md +39 -0
- data/README.md +8 -4
- data/lib/amqp/client/channel.rb +484 -247
- data/lib/amqp/client/connection.rb +405 -341
- data/lib/amqp/client/errors.rb +30 -26
- data/lib/amqp/client/exchange.rb +66 -0
- data/lib/amqp/client/frames.rb +526 -522
- data/lib/amqp/client/message.rb +48 -9
- data/lib/amqp/client/properties.rb +227 -192
- data/lib/amqp/client/queue.rb +78 -0
- data/lib/amqp/client/table.rb +118 -107
- data/lib/amqp/client/version.rb +2 -1
- data/lib/amqp/client.rb +164 -55
- metadata +7 -3
@@ -0,0 +1,78 @@
|
|
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
|
15
|
+
# @param body [String] The message body
|
16
|
+
# @param properties [Properties]
|
17
|
+
# @option properties [String] content_type Content type of the message body
|
18
|
+
# @option properties [String] content_encoding Content encoding of the body
|
19
|
+
# @option properties [Hash<String, Object>] headers Custom headers
|
20
|
+
# @option properties [Integer] delivery_mode 2 for persisted message, transient messages for all other values
|
21
|
+
# @option properties [Integer] priority A priority of the message (between 0 and 255)
|
22
|
+
# @option properties [Integer] correlation_id A correlation id, most often used used for RPC communication
|
23
|
+
# @option properties [String] reply_to Queue to reply RPC responses to
|
24
|
+
# @option properties [Integer, String] expiration Number of seconds the message will stay in the queue
|
25
|
+
# @option properties [String] message_id Can be used to uniquely identify the message, e.g. for deduplication
|
26
|
+
# @option properties [Date] timestamp Often used for the time the message was originally generated
|
27
|
+
# @option properties [String] type Can indicate what kind of message this is
|
28
|
+
# @option properties [String] user_id Can be used to verify that this is the user that published the message
|
29
|
+
# @option properties [String] app_id Can be used to indicates which app that generated the message
|
30
|
+
# @return [Queue] self
|
31
|
+
def publish(body, **properties)
|
32
|
+
@client.publish(body, "", @name, **properties)
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
# Subscribe/consume from the queue
|
37
|
+
# @return [Queue] self
|
38
|
+
def subscribe(no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
|
39
|
+
@client.subscribe(@name, no_ack: no_ack, prefetch: prefetch, worker_threads: worker_threads, arguments: arguments, &blk)
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
# Bind the queue to an exchange
|
44
|
+
# @param exchange [String] Name of the exchange to bind to
|
45
|
+
# @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
|
46
|
+
# @param arguments [Hash] Message headers to match on (only relevant for header exchanges)
|
47
|
+
# @return [Queue] self
|
48
|
+
def bind(exchange, binding_key, arguments: {})
|
49
|
+
@client.bind(@name, exchange, binding_key, arguments: arguments)
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
# Unbind the queue from an exchange
|
54
|
+
# @param exchange [String] Name of the exchange to unbind from
|
55
|
+
# @param binding_key [String] Binding key which the queue is bound to the exchange with
|
56
|
+
# @param arguments [Hash] Arguments matching the binding that's being removed
|
57
|
+
# @return [Queue] self
|
58
|
+
def unbind(exchange, binding_key, arguments: {})
|
59
|
+
@client.unbind(@name, exchange, binding_key, arguments: arguments)
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
# Purge/empty the queue
|
64
|
+
# @return [Queue] self
|
65
|
+
def purge
|
66
|
+
@client.purge(@name)
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
# Delete the queue
|
71
|
+
# @return [nil]
|
72
|
+
def delete
|
73
|
+
@client.delete_queue(@name)
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/amqp/client/table.rb
CHANGED
@@ -1,122 +1,133 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module AMQP
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
-
|
9
|
-
|
10
|
-
hash
|
11
|
-
|
12
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
pos
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
pos
|
83
|
-
|
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
|
data/lib/amqp/client/version.rb
CHANGED
data/lib/amqp/client.rb
CHANGED
@@ -3,152 +3,261 @@
|
|
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,
|
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
|
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
|
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
|
-
|
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.
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
+
# @return [nil]
|
77
126
|
def publish(body, exchange, routing_key, **properties)
|
78
127
|
with_connection do |conn|
|
79
|
-
|
128
|
+
properties = { delivery_mode: 2 }.merge!(properties)
|
80
129
|
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
130
|
end
|
85
|
-
rescue => e
|
86
|
-
warn "AMQP-Client error publishing, retrying (#{e.inspect})"
|
87
|
-
retry
|
88
131
|
end
|
89
132
|
|
90
|
-
|
133
|
+
# Publish a (persistent) message but don't wait for a confirmation
|
134
|
+
# @return [nil]
|
135
|
+
def publish_and_forget(body, exchange, routing_key, **properties)
|
91
136
|
with_connection do |conn|
|
92
|
-
|
137
|
+
properties = { delivery_mode: 2 }.merge!(properties)
|
138
|
+
conn.channel(1).basic_publish(body, exchange, routing_key, **properties)
|
93
139
|
end
|
94
140
|
end
|
95
141
|
|
96
|
-
|
142
|
+
# Wait for unconfirmed publishes
|
143
|
+
# @return [Boolean] True if successful, false if any message negatively acknowledged
|
144
|
+
def wait_for_confirms
|
97
145
|
with_connection do |conn|
|
98
|
-
conn.channel(1).
|
146
|
+
conn.channel(1).wait_for_confirms
|
99
147
|
end
|
100
148
|
end
|
101
149
|
|
102
|
-
|
150
|
+
# @!endgroup
|
151
|
+
# @!group Queue actions
|
152
|
+
|
153
|
+
# Consume messages from a queue
|
154
|
+
# @param queue [String] Name of the queue to subscribe to
|
155
|
+
# @param no_ack [Boolean] When false messages have to be manually acknowledged (or rejected)
|
156
|
+
# @param prefetch [Integer] Specify how many messages to prefetch for consumers with no_ack is false
|
157
|
+
# @param worker_threads [Integer] Number of threads processing messages,
|
158
|
+
# 0 means that the thread calling this method will be blocked
|
159
|
+
# @param arguments [Hash] Custom arguments to the consumer
|
160
|
+
# @yield [Message] Delivered message from the queue
|
161
|
+
# @return [Array<(String, Array<Thread>)>] Returns consumer_tag and an array of worker threads
|
162
|
+
# @return [nil] When `worker_threads` is 0 the method will return when the consumer is cancelled
|
163
|
+
def subscribe(queue, no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
|
164
|
+
@subscriptions.add? [queue, no_ack, prefetch, worker_threads, arguments, blk]
|
165
|
+
|
103
166
|
with_connection do |conn|
|
104
|
-
conn.channel
|
167
|
+
ch = conn.channel
|
168
|
+
ch.basic_qos(prefetch)
|
169
|
+
ch.basic_consume(queue, no_ack: no_ack, worker_threads: worker_threads, arguments: arguments) do |msg|
|
170
|
+
blk.call(msg)
|
171
|
+
end
|
105
172
|
end
|
106
173
|
end
|
107
174
|
|
108
|
-
|
175
|
+
# Bind a queue to an exchange
|
176
|
+
# @param queue [String] Name of the queue to bind
|
177
|
+
# @param exchange [String] Name of the exchange to bind to
|
178
|
+
# @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
|
179
|
+
# @param arguments [Hash] Message headers to match on (only relevant for header exchanges)
|
180
|
+
# @return [nil]
|
181
|
+
def bind(queue, exchange, binding_key, arguments: {})
|
109
182
|
with_connection do |conn|
|
110
|
-
conn.channel(1).
|
183
|
+
conn.channel(1).queue_bind(queue, exchange, binding_key, arguments: arguments)
|
111
184
|
end
|
112
185
|
end
|
113
186
|
|
114
|
-
#
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
187
|
+
# Unbind a queue from an exchange
|
188
|
+
# @param queue [String] Name of the queue to unbind
|
189
|
+
# @param exchange [String] Name of the exchange to unbind from
|
190
|
+
# @param binding_key [String] Binding key which the queue is bound to the exchange with
|
191
|
+
# @param arguments [Hash] Arguments matching the binding that's being removed
|
192
|
+
# @return [nil]
|
193
|
+
def unbind(queue, exchange, binding_key, arguments: {})
|
194
|
+
with_connection do |conn|
|
195
|
+
conn.channel(1).queue_unbind(queue, exchange, binding_key, arguments: arguments)
|
119
196
|
end
|
197
|
+
end
|
120
198
|
|
121
|
-
|
122
|
-
|
123
|
-
|
199
|
+
# Purge a queue
|
200
|
+
# @param queue [String] Name of the queue
|
201
|
+
# @return [nil]
|
202
|
+
def purge(queue)
|
203
|
+
with_connection do |conn|
|
204
|
+
conn.channel(1).queue_purge(queue)
|
124
205
|
end
|
206
|
+
end
|
125
207
|
|
126
|
-
|
127
|
-
|
128
|
-
|
208
|
+
# Delete a queue
|
209
|
+
# @param name [String] Name of the queue
|
210
|
+
# @param if_unused [Boolean] Only delete if the queue doesn't have consumers, raises a ChannelClosed error otherwise
|
211
|
+
# @param if_empty [Boolean] Only delete if the queue is empty, raises a ChannelClosed error otherwise
|
212
|
+
# @return [Integer] Number of messages in the queue when deleted
|
213
|
+
def delete_queue(name, if_unused: false, if_empty: false)
|
214
|
+
with_connection do |conn|
|
215
|
+
msgs = conn.channel(1).queue_delete(name, if_unused: if_unused, if_empty: if_empty)
|
216
|
+
@queues.delete(name)
|
217
|
+
msgs
|
129
218
|
end
|
219
|
+
end
|
130
220
|
|
131
|
-
|
132
|
-
|
133
|
-
self
|
134
|
-
end
|
221
|
+
# @!endgroup
|
222
|
+
# @!group Exchange actions
|
135
223
|
|
136
|
-
|
137
|
-
|
138
|
-
|
224
|
+
# Bind an exchange to an exchange
|
225
|
+
# @param destination [String] Name of the exchange to bind
|
226
|
+
# @param source [String] Name of the exchange to bind to
|
227
|
+
# @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
|
228
|
+
# @param arguments [Hash] Message headers to match on (only relevant for header exchanges)
|
229
|
+
# @return [nil]
|
230
|
+
def exchange_bind(destination, source, binding_key, arguments: {})
|
231
|
+
with_connection do |conn|
|
232
|
+
conn.channel(1).exchange_bind(destination, source, binding_key, arguments: arguments)
|
139
233
|
end
|
234
|
+
end
|
140
235
|
|
141
|
-
|
142
|
-
|
143
|
-
|
236
|
+
# Unbind an exchange from an exchange
|
237
|
+
# @param destination [String] Name of the exchange to unbind
|
238
|
+
# @param source [String] Name of the exchange to unbind from
|
239
|
+
# @param binding_key [String] Binding key which the exchange is bound to the exchange with
|
240
|
+
# @param arguments [Hash] Arguments matching the binding that's being removed
|
241
|
+
# @return [nil]
|
242
|
+
def exchange_unbind(destination, source, binding_key, arguments: {})
|
243
|
+
with_connection do |conn|
|
244
|
+
conn.channel(1).exchange_unbind(destination, source, binding_key, arguments: arguments)
|
144
245
|
end
|
246
|
+
end
|
145
247
|
|
146
|
-
|
147
|
-
|
248
|
+
# Delete an exchange
|
249
|
+
# @param name [String] Name of the exchange
|
250
|
+
# @return [nil]
|
251
|
+
def delete_exchange(name)
|
252
|
+
with_connection do |conn|
|
253
|
+
conn.channel(1).exchange_delete(name)
|
254
|
+
@exchanges.delete(name)
|
148
255
|
nil
|
149
256
|
end
|
150
257
|
end
|
151
258
|
|
259
|
+
# @!endgroup
|
260
|
+
|
152
261
|
private
|
153
262
|
|
154
263
|
def with_connection
|