amqp-client 0.3.0 → 1.1.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.
- checksums.yaml +4 -4
- data/.github/workflows/docs.yml +25 -0
- data/.github/workflows/main.yml +1 -2
- data/.rubocop.yml +13 -2
- data/.rubocop_todo.yml +14 -58
- data/.yardopts +1 -0
- data/CHANGELOG.md +43 -0
- data/Gemfile +4 -0
- data/README.md +59 -26
- data/Rakefile +3 -1
- data/amqp-client.gemspec +1 -1
- data/lib/amqp/client/channel.rb +490 -258
- data/lib/amqp/client/connection.rb +439 -343
- data/lib/amqp/client/errors.rb +46 -25
- data/lib/amqp/client/exchange.rb +66 -0
- data/lib/amqp/client/frames.rb +524 -522
- data/lib/amqp/client/message.rb +103 -7
- data/lib/amqp/client/properties.rb +230 -166
- data/lib/amqp/client/queue.rb +72 -0
- data/lib/amqp/client/table.rb +117 -108
- data/lib/amqp/client/version.rb +2 -1
- data/lib/amqp/client.rb +171 -56
- data/sig/amqp-client.rbs +264 -0
- metadata +9 -4
@@ -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
|
data/lib/amqp/client/table.rb
CHANGED
@@ -1,122 +1,131 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module AMQP
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
hash
|
11
|
-
|
12
|
-
|
4
|
+
class Client
|
5
|
+
# Encode and decode an AMQP table to/from hash, only used internally
|
6
|
+
# @api private
|
7
|
+
module Table
|
8
|
+
# Encodes a hash into a byte array
|
9
|
+
# @return [String] Byte array
|
10
|
+
def self.encode(hash)
|
11
|
+
tbl = StringIO.new
|
12
|
+
hash.each do |k, v|
|
13
|
+
key = k.to_s
|
14
|
+
tbl.write [key.bytesize, key].pack("Ca*")
|
15
|
+
tbl.write encode_field(v)
|
16
|
+
end
|
17
|
+
tbl.string
|
13
18
|
end
|
14
|
-
tbl
|
15
|
-
end
|
16
|
-
|
17
|
-
def decode(bytes)
|
18
|
-
h = {}
|
19
|
-
pos = 0
|
20
19
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
pos
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
20
|
+
# Decodes an AMQP table into a hash
|
21
|
+
# @return [Hash<String, Object>]
|
22
|
+
def self.decode(bytes)
|
23
|
+
hash = {}
|
24
|
+
pos = 0
|
25
|
+
while pos < bytes.bytesize
|
26
|
+
key_len = bytes.getbyte(pos)
|
27
|
+
pos += 1
|
28
|
+
key = bytes.byteslice(pos, key_len).force_encoding("utf-8")
|
29
|
+
pos += key_len
|
30
|
+
rest = bytes.byteslice(pos, bytes.bytesize - pos)
|
31
|
+
len, value = decode_field(rest)
|
32
|
+
pos += len + 1
|
33
|
+
hash[key] = value
|
34
|
+
end
|
35
|
+
hash
|
30
36
|
end
|
31
|
-
h
|
32
|
-
end
|
33
37
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
# Encoding a single value in a table
|
39
|
+
# @api private
|
40
|
+
def self.encode_field(value)
|
41
|
+
case value
|
42
|
+
when Integer
|
43
|
+
if value > 2**31
|
44
|
+
["l", value].pack("a q>")
|
45
|
+
else
|
46
|
+
["I", value].pack("a l>")
|
47
|
+
end
|
48
|
+
when Float
|
49
|
+
["d", value].pack("a G")
|
50
|
+
when String
|
51
|
+
["S", value.bytesize, value].pack("a L> a*")
|
52
|
+
when Time
|
53
|
+
["T", value.to_i].pack("a Q>")
|
54
|
+
when Array
|
55
|
+
bytes = value.map { |e| encode_field(e) }.join
|
56
|
+
["A", bytes.bytesize, bytes].pack("a L> a*")
|
57
|
+
when Hash
|
58
|
+
bytes = Table.encode(value)
|
59
|
+
["F", bytes.bytesize, bytes].pack("a L> a*")
|
60
|
+
when true
|
61
|
+
["t", 1].pack("a C")
|
62
|
+
when false
|
63
|
+
["t", 0].pack("a C")
|
64
|
+
when nil
|
65
|
+
["V"].pack("a")
|
66
|
+
else raise ArgumentError, "unsupported table field type: #{value.class}"
|
41
67
|
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
68
|
end
|
62
|
-
end
|
63
69
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
pos
|
83
|
-
|
70
|
+
# Decodes a single value
|
71
|
+
# @return [Array<Integer, Object>] Bytes read and the parsed object
|
72
|
+
# @api private
|
73
|
+
def self.decode_field(bytes)
|
74
|
+
type = bytes[0]
|
75
|
+
pos = 1
|
76
|
+
case type
|
77
|
+
when "S"
|
78
|
+
len = bytes.byteslice(pos, 4).unpack1("L>")
|
79
|
+
pos += 4
|
80
|
+
[4 + len, bytes.byteslice(pos, len).force_encoding("utf-8")]
|
81
|
+
when "F"
|
82
|
+
len = bytes.byteslice(pos, 4).unpack1("L>")
|
83
|
+
pos += 4
|
84
|
+
[4 + len, decode(bytes.byteslice(pos, len))]
|
85
|
+
when "A"
|
86
|
+
len = bytes.byteslice(pos, 4).unpack1("L>")
|
87
|
+
a = []
|
88
|
+
while pos < len
|
89
|
+
length, value = decode_field(bytes.byteslice(pos, -1))
|
90
|
+
pos += length + 1
|
91
|
+
a << value
|
92
|
+
end
|
93
|
+
[4 + len, a]
|
94
|
+
when "t"
|
95
|
+
[1, bytes.getbyte(pos) == 1]
|
96
|
+
when "b"
|
97
|
+
[1, bytes.byteslice(pos, 1).unpack1("c")]
|
98
|
+
when "B"
|
99
|
+
[1, bytes.byteslice(pos, 1).unpack1("C")]
|
100
|
+
when "s"
|
101
|
+
[2, bytes.byteslice(pos, 2).unpack1("s")]
|
102
|
+
when "u"
|
103
|
+
[2, bytes.byteslice(pos, 2).unpack1("S")]
|
104
|
+
when "I"
|
105
|
+
[4, bytes.byteslice(pos, 4).unpack1("l>")]
|
106
|
+
when "i"
|
107
|
+
[4, bytes.byteslice(pos, 4).unpack1("L>")]
|
108
|
+
when "l"
|
109
|
+
[8, bytes.byteslice(pos, 8).unpack1("q>")]
|
110
|
+
when "f"
|
111
|
+
[4, bytes.byteslice(pos, 4).unpack1("g")]
|
112
|
+
when "d"
|
113
|
+
[8, bytes.byteslice(pos, 8).unpack1("G")]
|
114
|
+
when "D"
|
115
|
+
scale = bytes.getbyte(pos)
|
116
|
+
pos += 1
|
117
|
+
value = bytes.byteslice(pos, 4).unpack1("L>")
|
118
|
+
d = value / 10**scale
|
119
|
+
[5, d]
|
120
|
+
when "x"
|
121
|
+
len = bytes.byteslice(pos, 4).unpack1("L>")
|
122
|
+
[4 + len, bytes.byteslice(pos, len)]
|
123
|
+
when "T"
|
124
|
+
[8, Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))]
|
125
|
+
when "V"
|
126
|
+
[0, nil]
|
127
|
+
else raise ArgumentError, "unsupported table field type: #{type}"
|
84
128
|
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
129
|
end
|
121
130
|
end
|
122
131
|
end
|
data/lib/amqp/client/version.rb
CHANGED
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
|
10
|
-
|
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.
|
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 and returns a new AMQP connection
|
39
|
+
# @see Connection#initialize
|
40
|
+
# @return [Connection]
|
19
41
|
def connect(read_loop_thread: true)
|
20
|
-
Connection.
|
42
|
+
Connection.new(@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
|
+
# @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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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).
|
152
|
+
conn.channel(1).wait_for_confirms
|
99
153
|
end
|
100
154
|
end
|
101
155
|
|
102
|
-
|
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
|
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
|
-
|
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).
|
189
|
+
conn.channel(1).queue_bind(queue, exchange, binding_key, arguments: arguments)
|
111
190
|
end
|
112
191
|
end
|
113
192
|
|
114
|
-
#
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
127
|
-
|
128
|
-
|
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
|
-
|
132
|
-
|
133
|
-
self
|
134
|
-
end
|
227
|
+
# @!endgroup
|
228
|
+
# @!group Exchange actions
|
135
229
|
|
136
|
-
|
137
|
-
|
138
|
-
|
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
|
-
|
142
|
-
|
143
|
-
|
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
|
-
|
147
|
-
|
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
|