amqp-client 1.0.0 → 1.1.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/.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
|