amqp-client 1.0.0 → 1.1.1
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 +27 -0
- data/Gemfile +4 -0
- data/README.md +62 -25
- data/Rakefile +3 -1
- data/amqp-client.gemspec +1 -1
- data/lib/amqp/client/channel.rb +487 -275
- data/lib/amqp/client/connection.rb +438 -357
- data/lib/amqp/client/errors.rb +46 -25
- data/lib/amqp/client/exchange.rb +66 -0
- data/lib/amqp/client/frame_bytes.rb +533 -0
- data/lib/amqp/client/message.rb +103 -7
- data/lib/amqp/client/properties.rb +263 -166
- data/lib/amqp/client/queue.rb +72 -0
- data/lib/amqp/client/table.rb +137 -107
- data/lib/amqp/client/version.rb +2 -1
- data/lib/amqp/client.rb +148 -97
- data/sig/amqp-client.rbs +264 -0
- metadata +9 -4
- data/lib/amqp/client/frames.rb +0 -531
@@ -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,152 @@
|
|
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
|
+
# Encodes a hash into a byte array
|
9
|
+
# @param hash [Hash]
|
10
|
+
# @return [String] Byte array
|
11
|
+
def self.encode(hash)
|
12
|
+
return "" if hash.empty?
|
7
13
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
14
|
+
arr = []
|
15
|
+
fmt = String.new
|
16
|
+
hash.each do |k, value|
|
17
|
+
key = k.to_s
|
18
|
+
arr.push(key.bytesize, key)
|
19
|
+
fmt << "Ca*"
|
20
|
+
encode_field(value, arr, fmt)
|
21
|
+
end
|
22
|
+
arr.pack(fmt)
|
13
23
|
end
|
14
|
-
tbl
|
15
|
-
end
|
16
|
-
|
17
|
-
def decode(bytes)
|
18
|
-
h = {}
|
19
|
-
pos = 0
|
20
24
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
pos
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
# Decodes an AMQP table into a hash
|
26
|
+
# @return [Hash<String, Object>]
|
27
|
+
def self.decode(bytes)
|
28
|
+
hash = {}
|
29
|
+
pos = 0
|
30
|
+
while pos < bytes.bytesize
|
31
|
+
key_len = bytes.getbyte(pos)
|
32
|
+
pos += 1
|
33
|
+
key = bytes.byteslice(pos, key_len).force_encoding("utf-8")
|
34
|
+
pos += key_len
|
35
|
+
len, value = decode_field(bytes, pos)
|
36
|
+
pos += len + 1
|
37
|
+
hash[key] = value
|
38
|
+
end
|
39
|
+
hash
|
30
40
|
end
|
31
|
-
h
|
32
|
-
end
|
33
41
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
42
|
+
# Encoding a single value in a table
|
43
|
+
# @return [nil]
|
44
|
+
# @api private
|
45
|
+
def self.encode_field(value, arr, fmt)
|
46
|
+
case value
|
47
|
+
when Integer
|
48
|
+
if value > 2**31
|
49
|
+
arr.push("l", value)
|
50
|
+
fmt << "aq>"
|
51
|
+
else
|
52
|
+
arr.push("I", value)
|
53
|
+
fmt << "al>"
|
54
|
+
end
|
55
|
+
when Float
|
56
|
+
arr.push("d", value)
|
57
|
+
fmt << "aG"
|
58
|
+
when String
|
59
|
+
arr.push("S", value.bytesize, value)
|
60
|
+
fmt << "aL>a*"
|
61
|
+
when Time
|
62
|
+
arr.push("T", value.to_i)
|
63
|
+
fmt << "aQ>"
|
64
|
+
when Array
|
65
|
+
value_arr = []
|
66
|
+
value_fmt = String.new
|
67
|
+
value.each { |e| encode_field(e, value_arr, value_fmt) }
|
68
|
+
bytes = value_arr.pack(value_fmt)
|
69
|
+
arr.push("A", bytes.bytesize, bytes)
|
70
|
+
fmt << "aL>a*"
|
71
|
+
when Hash
|
72
|
+
bytes = Table.encode(value)
|
73
|
+
arr.push("F", bytes.bytesize, bytes)
|
74
|
+
fmt << "aL>a*"
|
75
|
+
when true
|
76
|
+
arr.push("t", 1)
|
77
|
+
fmt << "aC"
|
78
|
+
when false
|
79
|
+
arr.push("t", 0)
|
80
|
+
fmt << "aC"
|
81
|
+
when nil
|
82
|
+
arr << "V"
|
83
|
+
fmt << "a"
|
84
|
+
else raise ArgumentError, "unsupported table field type: #{value.class}"
|
41
85
|
end
|
42
|
-
|
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}"
|
86
|
+
nil
|
61
87
|
end
|
62
|
-
end
|
63
88
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
84
|
-
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
|
89
|
+
# Decodes a single value
|
90
|
+
# @return [Array<Integer, Object>] Bytes read and the parsed value
|
91
|
+
# @api private
|
92
|
+
def self.decode_field(bytes, pos)
|
93
|
+
type = bytes[pos]
|
108
94
|
pos += 1
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
95
|
+
case type
|
96
|
+
when "S"
|
97
|
+
len = bytes.byteslice(pos, 4).unpack1("L>")
|
98
|
+
pos += 4
|
99
|
+
[4 + len, bytes.byteslice(pos, len).force_encoding("utf-8")]
|
100
|
+
when "F"
|
101
|
+
len = bytes.byteslice(pos, 4).unpack1("L>")
|
102
|
+
pos += 4
|
103
|
+
[4 + len, decode(bytes.byteslice(pos, len))]
|
104
|
+
when "A"
|
105
|
+
len = bytes.byteslice(pos, 4).unpack1("L>")
|
106
|
+
pos += 4
|
107
|
+
array_end = pos + len
|
108
|
+
a = []
|
109
|
+
while pos < array_end
|
110
|
+
length, value = decode_field(bytes, pos)
|
111
|
+
pos += length + 1
|
112
|
+
a << value
|
113
|
+
end
|
114
|
+
[4 + len, a]
|
115
|
+
when "t"
|
116
|
+
[1, bytes.getbyte(pos) == 1]
|
117
|
+
when "b"
|
118
|
+
[1, bytes.byteslice(pos, 1).unpack1("c")]
|
119
|
+
when "B"
|
120
|
+
[1, bytes.byteslice(pos, 1).unpack1("C")]
|
121
|
+
when "s"
|
122
|
+
[2, bytes.byteslice(pos, 2).unpack1("s")]
|
123
|
+
when "u"
|
124
|
+
[2, bytes.byteslice(pos, 2).unpack1("S")]
|
125
|
+
when "I"
|
126
|
+
[4, bytes.byteslice(pos, 4).unpack1("l>")]
|
127
|
+
when "i"
|
128
|
+
[4, bytes.byteslice(pos, 4).unpack1("L>")]
|
129
|
+
when "l"
|
130
|
+
[8, bytes.byteslice(pos, 8).unpack1("q>")]
|
131
|
+
when "f"
|
132
|
+
[4, bytes.byteslice(pos, 4).unpack1("g")]
|
133
|
+
when "d"
|
134
|
+
[8, bytes.byteslice(pos, 8).unpack1("G")]
|
135
|
+
when "D"
|
136
|
+
scale = bytes.getbyte(pos)
|
137
|
+
pos += 1
|
138
|
+
value = bytes.byteslice(pos, 4).unpack1("L>")
|
139
|
+
d = value / 10**scale
|
140
|
+
[5, d]
|
141
|
+
when "x"
|
142
|
+
len = bytes.byteslice(pos, 4).unpack1("L>")
|
143
|
+
[4 + len, bytes.byteslice(pos, len)]
|
144
|
+
when "T"
|
145
|
+
[8, Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))]
|
146
|
+
when "V"
|
147
|
+
[0, nil]
|
148
|
+
else raise ArgumentError, "unsupported table field type: #{type}"
|
149
|
+
end
|
120
150
|
end
|
121
151
|
end
|
122
152
|
end
|
data/lib/amqp/client/version.rb
CHANGED
data/lib/amqp/client.rb
CHANGED
@@ -3,11 +3,27 @@
|
|
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
|
|
@@ -17,12 +33,23 @@ module AMQP
|
|
17
33
|
@connq = SizedQueue.new(1)
|
18
34
|
end
|
19
35
|
|
20
|
-
#
|
36
|
+
# @!group Connect and disconnect
|
37
|
+
|
38
|
+
# Establishes and returns a new AMQP connection
|
39
|
+
# @see Connection#initialize
|
40
|
+
# @return [Connection]
|
41
|
+
# @example
|
42
|
+
# connection = AMQP::Client.new("amqps://server.rmq.cloudamqp.com", connection_name: "My connection").connect
|
21
43
|
def connect(read_loop_thread: true)
|
22
|
-
Connection.
|
44
|
+
Connection.new(@uri, read_loop_thread: read_loop_thread, **@options)
|
23
45
|
end
|
24
46
|
|
25
|
-
# Opens an AMQP connection using the high level API, will try to reconnect
|
47
|
+
# Opens an AMQP connection using the high level API, will try to reconnect if successfully connected at first
|
48
|
+
# @return [self]
|
49
|
+
# @example
|
50
|
+
# amqp = AMQP::Client.new("amqps://server.rmq.cloudamqp.com")
|
51
|
+
# amqp.start
|
52
|
+
# amqp.queue("foobar")
|
26
53
|
def start
|
27
54
|
@stopped = false
|
28
55
|
Thread.new(connect(read_loop_thread: false)) do |conn|
|
@@ -42,7 +69,7 @@ module AMQP
|
|
42
69
|
@connq << conn
|
43
70
|
end
|
44
71
|
conn.read_loop # blocks until connection is closed, then reconnect
|
45
|
-
rescue
|
72
|
+
rescue Error => e
|
46
73
|
warn "AMQP-Client reconnect error: #{e.inspect}"
|
47
74
|
sleep @options[:reconnect_interval] || 1
|
48
75
|
ensure
|
@@ -52,76 +79,67 @@ module AMQP
|
|
52
79
|
self
|
53
80
|
end
|
54
81
|
|
82
|
+
# Close the currently open connection
|
83
|
+
# @return [nil]
|
55
84
|
def stop
|
85
|
+
return if @stopped
|
86
|
+
|
56
87
|
@stopped = true
|
57
88
|
conn = @connq.pop
|
58
89
|
conn.close
|
59
90
|
nil
|
60
91
|
end
|
61
92
|
|
62
|
-
|
93
|
+
# @!endgroup
|
94
|
+
# @!group High level objects
|
95
|
+
|
96
|
+
# Declare a queue
|
97
|
+
# @param name [String] Name of the queue
|
98
|
+
# @param durable [Boolean] If true the queue will survive broker restarts,
|
99
|
+
# messages in the queue will only survive if they are published as persistent
|
100
|
+
# @param auto_delete [Boolean] If true the queue will be deleted when the last consumer stops consuming
|
101
|
+
# (it won't be deleted until at least one consumer has consumed from it)
|
102
|
+
# @param arguments [Hash] Custom arguments, such as queue-ttl etc.
|
103
|
+
# @return [Queue]
|
104
|
+
# @example
|
105
|
+
# amqp = AMQP::Client.new.start
|
106
|
+
# q = amqp.queue("foobar")
|
107
|
+
# q.publish("body")
|
108
|
+
def queue(name, durable: true, auto_delete: false, arguments: {})
|
63
109
|
raise ArgumentError, "Currently only supports named, durable queues" if name.empty?
|
64
110
|
|
65
111
|
@queues.fetch(name) do
|
66
112
|
with_connection do |conn|
|
67
|
-
conn.
|
68
|
-
ch.queue_declare(name, durable: durable, exclusive: exclusive, auto_delete: auto_delete, arguments: arguments)
|
69
|
-
end
|
113
|
+
conn.channel(1).queue_declare(name, durable: durable, auto_delete: auto_delete, arguments: arguments)
|
70
114
|
end
|
71
115
|
@queues[name] = Queue.new(self, name)
|
72
116
|
end
|
73
117
|
end
|
74
118
|
|
119
|
+
# Declare an exchange and return a high level Exchange object
|
120
|
+
# @return [Exchange]
|
121
|
+
# @example
|
122
|
+
# amqp = AMQP::Client.new.start
|
123
|
+
# x = amqp.exchange("my.hash.exchange", "x-consistent-hash")
|
124
|
+
# x.publish("body", "routing-key")
|
75
125
|
def exchange(name, type, durable: true, auto_delete: false, internal: false, arguments: {})
|
76
126
|
@exchanges.fetch(name) do
|
77
127
|
with_connection do |conn|
|
78
|
-
conn.
|
79
|
-
|
80
|
-
end
|
128
|
+
conn.channel(1).exchange_declare(name, type, durable: durable, auto_delete: auto_delete,
|
129
|
+
internal: internal, arguments: arguments)
|
81
130
|
end
|
82
131
|
@exchanges[name] = Exchange.new(self, name)
|
83
132
|
end
|
84
133
|
end
|
85
134
|
|
86
|
-
#
|
87
|
-
|
88
|
-
def initialize(client, name)
|
89
|
-
@client = client
|
90
|
-
@name = name
|
91
|
-
end
|
92
|
-
|
93
|
-
def publish(body, routing_key, arguments: {})
|
94
|
-
@client.publish(body, @name, routing_key, arguments: arguments)
|
95
|
-
end
|
96
|
-
|
97
|
-
# Bind to another exchange
|
98
|
-
def bind(exchange, routing_key, arguments: {})
|
99
|
-
@client.exchange_bind(@name, exchange, routing_key, arguments: arguments)
|
100
|
-
end
|
101
|
-
|
102
|
-
# Unbind from another exchange
|
103
|
-
def unbind(exchange, routing_key, arguments: {})
|
104
|
-
@client.exchange_unbind(@name, exchange, routing_key, arguments: arguments)
|
105
|
-
end
|
106
|
-
|
107
|
-
def delete
|
108
|
-
@client.delete_exchange(@name)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
def subscribe(queue_name, no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
|
113
|
-
@subscriptions.add? [queue_name, no_ack, prefetch, worker_threads, arguments, blk]
|
114
|
-
|
115
|
-
with_connection do |conn|
|
116
|
-
ch = conn.channel
|
117
|
-
ch.basic_qos(prefetch)
|
118
|
-
ch.basic_consume(queue_name, no_ack: no_ack, worker_threads: worker_threads, arguments: arguments) do |msg|
|
119
|
-
blk.call(msg)
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
135
|
+
# @!endgroup
|
136
|
+
# @!group Publish
|
123
137
|
|
124
138
|
# Publish a (persistent) message and wait for confirmation
|
139
|
+
# @param (see Connection::Channel#basic_publish_confirm)
|
140
|
+
# @option (see Connection::Channel#basic_publish_confirm)
|
141
|
+
# @return (see Connection::Channel#basic_publish_confirm)
|
142
|
+
# @raise (see Connection::Channel#basic_publish_confirm)
|
125
143
|
def publish(body, exchange, routing_key, **properties)
|
126
144
|
with_connection do |conn|
|
127
145
|
properties = { delivery_mode: 2 }.merge!(properties)
|
@@ -130,6 +148,10 @@ module AMQP
|
|
130
148
|
end
|
131
149
|
|
132
150
|
# Publish a (persistent) message but don't wait for a confirmation
|
151
|
+
# @param (see Connection::Channel#basic_publish)
|
152
|
+
# @option (see Connection::Channel#basic_publish)
|
153
|
+
# @return (see Connection::Channel#basic_publish)
|
154
|
+
# @raise (see Connection::Channel#basic_publish)
|
133
155
|
def publish_and_forget(body, exchange, routing_key, **properties)
|
134
156
|
with_connection do |conn|
|
135
157
|
properties = { delivery_mode: 2 }.merge!(properties)
|
@@ -137,94 +159,123 @@ module AMQP
|
|
137
159
|
end
|
138
160
|
end
|
139
161
|
|
162
|
+
# Wait for unconfirmed publishes
|
163
|
+
# @return [Boolean] True if successful, false if any message negatively acknowledged
|
140
164
|
def wait_for_confirms
|
141
165
|
with_connection do |conn|
|
142
166
|
conn.channel(1).wait_for_confirms
|
143
167
|
end
|
144
168
|
end
|
145
169
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
170
|
+
# @!endgroup
|
171
|
+
# @!group Queue actions
|
172
|
+
|
173
|
+
# Consume messages from a queue
|
174
|
+
# @param queue [String] Name of the queue to subscribe to
|
175
|
+
# @param no_ack [Boolean] When false messages have to be manually acknowledged (or rejected)
|
176
|
+
# @param prefetch [Integer] Specify how many messages to prefetch for consumers with no_ack is false
|
177
|
+
# @param worker_threads [Integer] Number of threads processing messages,
|
178
|
+
# 0 means that the thread calling this method will be blocked
|
179
|
+
# @param arguments [Hash] Custom arguments to the consumer
|
180
|
+
# @yield [Message] Delivered message from the queue
|
181
|
+
# @return [Array<(String, Array<Thread>)>] Returns consumer_tag and an array of worker threads
|
182
|
+
# @return [nil] When `worker_threads` is 0 the method will return when the consumer is cancelled
|
183
|
+
def subscribe(queue, no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
|
184
|
+
@subscriptions.add? [queue, no_ack, prefetch, worker_threads, arguments, blk]
|
151
185
|
|
152
|
-
def unbind(queue, exchange, routing_key, arguments: {})
|
153
186
|
with_connection do |conn|
|
154
|
-
conn.channel
|
187
|
+
ch = conn.channel
|
188
|
+
ch.basic_qos(prefetch)
|
189
|
+
ch.basic_consume(queue, no_ack: no_ack, worker_threads: worker_threads, arguments: arguments, &blk)
|
155
190
|
end
|
156
191
|
end
|
157
192
|
|
158
|
-
|
193
|
+
# Bind a queue to an exchange
|
194
|
+
# @param queue [String] Name of the queue to bind
|
195
|
+
# @param exchange [String] Name of the exchange to bind to
|
196
|
+
# @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
|
197
|
+
# @param arguments [Hash] Message headers to match on (only relevant for header exchanges)
|
198
|
+
# @return [nil]
|
199
|
+
def bind(queue, exchange, binding_key, arguments: {})
|
159
200
|
with_connection do |conn|
|
160
|
-
conn.channel(1).
|
201
|
+
conn.channel(1).queue_bind(queue, exchange, binding_key, arguments: arguments)
|
161
202
|
end
|
162
203
|
end
|
163
204
|
|
164
|
-
|
205
|
+
# Unbind a queue from an exchange
|
206
|
+
# @param queue [String] Name of the queue to unbind
|
207
|
+
# @param exchange [String] Name of the exchange to unbind from
|
208
|
+
# @param binding_key [String] Binding key which the queue is bound to the exchange with
|
209
|
+
# @param arguments [Hash] Arguments matching the binding that's being removed
|
210
|
+
# @return [nil]
|
211
|
+
def unbind(queue, exchange, binding_key, arguments: {})
|
165
212
|
with_connection do |conn|
|
166
|
-
conn.channel(1).
|
213
|
+
conn.channel(1).queue_unbind(queue, exchange, binding_key, arguments: arguments)
|
167
214
|
end
|
168
215
|
end
|
169
216
|
|
217
|
+
# Purge a queue
|
218
|
+
# @param queue [String] Name of the queue
|
219
|
+
# @return [nil]
|
170
220
|
def purge(queue)
|
171
221
|
with_connection do |conn|
|
172
222
|
conn.channel(1).queue_purge(queue)
|
173
223
|
end
|
174
224
|
end
|
175
225
|
|
176
|
-
|
226
|
+
# Delete a queue
|
227
|
+
# @param name [String] Name of the queue
|
228
|
+
# @param if_unused [Boolean] Only delete if the queue doesn't have consumers, raises a ChannelClosed error otherwise
|
229
|
+
# @param if_empty [Boolean] Only delete if the queue is empty, raises a ChannelClosed error otherwise
|
230
|
+
# @return [Integer] Number of messages in the queue when deleted
|
231
|
+
def delete_queue(name, if_unused: false, if_empty: false)
|
177
232
|
with_connection do |conn|
|
178
|
-
conn.channel(1).queue_delete(name)
|
233
|
+
msgs = conn.channel(1).queue_delete(name, if_unused: if_unused, if_empty: if_empty)
|
179
234
|
@queues.delete(name)
|
235
|
+
msgs
|
180
236
|
end
|
181
237
|
end
|
182
238
|
|
183
|
-
|
239
|
+
# @!endgroup
|
240
|
+
# @!group Exchange actions
|
241
|
+
|
242
|
+
# Bind an exchange to an exchange
|
243
|
+
# @param destination [String] Name of the exchange to bind
|
244
|
+
# @param source [String] Name of the exchange to bind to
|
245
|
+
# @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
|
246
|
+
# @param arguments [Hash] Message headers to match on (only relevant for header exchanges)
|
247
|
+
# @return [nil]
|
248
|
+
def exchange_bind(destination, source, binding_key, arguments: {})
|
184
249
|
with_connection do |conn|
|
185
|
-
conn.channel(1).
|
186
|
-
@exchanges.delete(name)
|
250
|
+
conn.channel(1).exchange_bind(destination, source, binding_key, arguments: arguments)
|
187
251
|
end
|
188
252
|
end
|
189
253
|
|
190
|
-
#
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
self
|
200
|
-
end
|
201
|
-
|
202
|
-
def subscribe(no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
|
203
|
-
@client.subscribe(@name, no_ack: no_ack, prefetch: prefetch, worker_threads: worker_threads, arguments: arguments, &blk)
|
204
|
-
self
|
205
|
-
end
|
206
|
-
|
207
|
-
def bind(exchange, routing_key, **headers)
|
208
|
-
@client.bind(@name, exchange, routing_key, **headers)
|
209
|
-
self
|
210
|
-
end
|
211
|
-
|
212
|
-
def unbind(exchange, routing_key, **headers)
|
213
|
-
@client.unbind(@name, exchange, routing_key, **headers)
|
214
|
-
self
|
215
|
-
end
|
216
|
-
|
217
|
-
def purge
|
218
|
-
@client.purge(@name)
|
219
|
-
self
|
254
|
+
# Unbind an exchange from an exchange
|
255
|
+
# @param destination [String] Name of the exchange to unbind
|
256
|
+
# @param source [String] Name of the exchange to unbind from
|
257
|
+
# @param binding_key [String] Binding key which the exchange is bound to the exchange with
|
258
|
+
# @param arguments [Hash] Arguments matching the binding that's being removed
|
259
|
+
# @return [nil]
|
260
|
+
def exchange_unbind(destination, source, binding_key, arguments: {})
|
261
|
+
with_connection do |conn|
|
262
|
+
conn.channel(1).exchange_unbind(destination, source, binding_key, arguments: arguments)
|
220
263
|
end
|
264
|
+
end
|
221
265
|
|
222
|
-
|
223
|
-
|
266
|
+
# Delete an exchange
|
267
|
+
# @param name [String] Name of the exchange
|
268
|
+
# @return [nil]
|
269
|
+
def delete_exchange(name)
|
270
|
+
with_connection do |conn|
|
271
|
+
conn.channel(1).exchange_delete(name)
|
272
|
+
@exchanges.delete(name)
|
224
273
|
nil
|
225
274
|
end
|
226
275
|
end
|
227
276
|
|
277
|
+
# @!endgroup
|
278
|
+
|
228
279
|
private
|
229
280
|
|
230
281
|
def with_connection
|