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
data/lib/amqp/client/message.rb
CHANGED
@@ -1,15 +1,111 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module AMQP
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
class Client
|
5
|
+
# A message delivered from the broker
|
6
|
+
class Message
|
7
|
+
# @api private
|
8
|
+
def initialize(channel, consumer_tag, delivery_tag, exchange, routing_key, redelivered)
|
9
|
+
@channel = channel
|
10
|
+
@consumer_tag = consumer_tag
|
11
|
+
@delivery_tag = delivery_tag
|
12
|
+
@exchange = exchange
|
13
|
+
@routing_key = routing_key
|
14
|
+
@redelivered = redelivered
|
15
|
+
@properties = nil
|
16
|
+
@body = ""
|
17
|
+
end
|
18
|
+
|
19
|
+
# The channel the message was deliviered to
|
20
|
+
# @return [Connection::Channel]
|
21
|
+
attr_reader :channel
|
22
|
+
|
23
|
+
# The tag of the consumer the message was deliviered to
|
24
|
+
# @return [String]
|
25
|
+
# @return [nil] If the message was polled and not deliviered to a consumer
|
26
|
+
attr_reader :consumer_tag
|
27
|
+
|
28
|
+
# The delivery tag of the message, used for acknowledge or reject the message
|
29
|
+
# @return [Integer]
|
30
|
+
attr_reader :delivery_tag
|
31
|
+
|
32
|
+
# Name of the exchange the message was published to
|
33
|
+
# @return [String]
|
34
|
+
attr_reader :exchange
|
35
|
+
|
36
|
+
# The routing key the message was published with
|
37
|
+
# @return [String]
|
38
|
+
attr_reader :routing_key
|
39
|
+
|
40
|
+
# True if the message have been delivered before
|
41
|
+
# @return [Boolean]
|
42
|
+
attr_reader :redelivered
|
43
|
+
|
44
|
+
# Message properties
|
45
|
+
# @return [Properties]
|
46
|
+
attr_accessor :properties
|
47
|
+
|
48
|
+
# The message body
|
49
|
+
# @return [String]
|
50
|
+
attr_accessor :body
|
51
|
+
|
52
|
+
# Acknowledge the message
|
53
|
+
# @return [nil]
|
54
|
+
def ack
|
55
|
+
@channel.basic_ack(@delivery_tag)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Reject the message
|
59
|
+
# @param requeue [Boolean] If true the message will be put back into the queue again, ready to be redelivered
|
60
|
+
# @return [nil]
|
61
|
+
def reject(requeue: false)
|
62
|
+
@channel.basic_reject(@delivery_tag, requeue: requeue)
|
63
|
+
end
|
64
|
+
|
65
|
+
# @see #exchange
|
66
|
+
# @deprecated
|
67
|
+
# @!attribute [r] exchange_name
|
68
|
+
# @return [String]
|
69
|
+
def exchange_name
|
70
|
+
@exchange
|
71
|
+
end
|
7
72
|
end
|
8
73
|
|
9
|
-
|
10
|
-
|
74
|
+
# A published message returned by the broker due to some error
|
75
|
+
class ReturnMessage
|
76
|
+
# @api private
|
77
|
+
def initialize(reply_code, reply_text, exchange, routing_key)
|
78
|
+
@reply_code = reply_code
|
79
|
+
@reply_text = reply_text
|
80
|
+
@exchange = exchange
|
81
|
+
@routing_key = routing_key
|
82
|
+
@properties = nil
|
83
|
+
@body = ""
|
84
|
+
end
|
85
|
+
|
86
|
+
# Error code
|
87
|
+
# @return [Integer]
|
88
|
+
attr_reader :reply_code
|
89
|
+
|
90
|
+
# Description on why the message was returned
|
91
|
+
# @return [String]
|
92
|
+
attr_reader :reply_text
|
93
|
+
|
94
|
+
# Name of the exchange the message was published to
|
95
|
+
# @return [String]
|
96
|
+
attr_reader :exchange
|
97
|
+
|
98
|
+
# The routing key the message was published with
|
99
|
+
# @return [String]
|
100
|
+
attr_reader :routing_key
|
101
|
+
|
102
|
+
# Message properties
|
103
|
+
# @return [Properties]
|
104
|
+
attr_accessor :properties
|
105
|
+
|
106
|
+
# The message body
|
107
|
+
# @return [String]
|
108
|
+
attr_accessor :body
|
11
109
|
end
|
12
110
|
end
|
13
|
-
|
14
|
-
ReturnMessage = Struct.new(:reply_code, :reply_text, :exchange, :routing_key, :properties, :body)
|
15
111
|
end
|
@@ -3,200 +3,264 @@
|
|
3
3
|
require_relative "./table"
|
4
4
|
|
5
5
|
module AMQP
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
6
|
+
class Client
|
7
|
+
# Encode/decode AMQP Properties
|
8
|
+
class Properties
|
9
|
+
def initialize(content_type: nil, content_encoding: nil, headers: nil, delivery_mode: nil, priority: nil, correlation_id: nil,
|
10
|
+
reply_to: nil, expiration: nil, message_id: nil, timestamp: nil, type: nil, user_id: nil, app_id: nil)
|
11
|
+
@content_type = content_type
|
12
|
+
@content_encoding = content_encoding
|
13
|
+
@headers = headers
|
14
|
+
@delivery_mode = delivery_mode
|
15
|
+
@priority = priority
|
16
|
+
@correlation_id = correlation_id
|
17
|
+
@reply_to = reply_to
|
18
|
+
@expiration = expiration
|
19
|
+
@message_id = message_id
|
20
|
+
@timestamp = timestamp
|
21
|
+
@type = type
|
22
|
+
@user_id = user_id
|
23
|
+
@app_id = app_id
|
21
24
|
end
|
22
25
|
|
23
|
-
|
24
|
-
|
26
|
+
# Content type of the message body
|
27
|
+
# @return [String, nil]
|
28
|
+
attr_accessor :content_type
|
29
|
+
# Content encoding of the body
|
30
|
+
# @return [String, nil]
|
31
|
+
attr_accessor :content_encoding
|
32
|
+
# Custom headers
|
33
|
+
# @return [Hash<String, Object>, nil]
|
34
|
+
attr_accessor :headers
|
35
|
+
# 2 for persisted message, transient messages for all other values
|
36
|
+
# @return [Integer, nil]
|
37
|
+
attr_accessor :delivery_mode
|
38
|
+
# A priority of the message (between 0 and 255)
|
39
|
+
# @return [Integer, nil]
|
40
|
+
attr_accessor :priority
|
41
|
+
# A correlation id, most often used used for RPC communication
|
42
|
+
# @return [Integer, nil]
|
43
|
+
attr_accessor :correlation_id
|
44
|
+
# Queue to reply RPC responses to
|
45
|
+
# @return [String, nil]
|
46
|
+
attr_accessor :reply_to
|
47
|
+
# Number of seconds the message will stay in the queue
|
48
|
+
# @return [Integer]
|
49
|
+
# @return [String]
|
50
|
+
# @return [nil]
|
51
|
+
attr_accessor :expiration
|
52
|
+
# @return [String, nil]
|
53
|
+
attr_accessor :message_id
|
54
|
+
# User-definable, but often used for the time the message was originally generated
|
55
|
+
# @return [Date, nil]
|
56
|
+
attr_accessor :timestamp
|
57
|
+
# User-definable, but can can indicate what kind of message this is
|
58
|
+
# @return [String, nil]
|
59
|
+
attr_accessor :type
|
60
|
+
# User-definable, but can be used to verify that this is the user that published the message
|
61
|
+
# @return [String, nil]
|
62
|
+
attr_accessor :user_id
|
63
|
+
# User-definable, but often indicates which app that generated the message
|
64
|
+
# @return [String, nil]
|
65
|
+
attr_accessor :app_id
|
25
66
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
67
|
+
# Encode properties into a byte array
|
68
|
+
# @return [String] byte array
|
69
|
+
def encode
|
70
|
+
flags = 0
|
71
|
+
arr = [flags]
|
72
|
+
fmt = StringIO.new(String.new("S>", capacity: 35))
|
73
|
+
fmt.pos = 2
|
30
74
|
|
31
|
-
|
32
|
-
|
75
|
+
if content_type
|
76
|
+
content_type.is_a?(String) || raise(ArgumentError, "content_type must be a string")
|
33
77
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
78
|
+
flags |= (1 << 15)
|
79
|
+
arr << content_type.bytesize << content_type
|
80
|
+
fmt << "Ca*"
|
81
|
+
end
|
39
82
|
|
40
|
-
|
41
|
-
|
83
|
+
if content_encoding
|
84
|
+
content_encoding.is_a?(String) || raise(ArgumentError, "content_encoding must be a string")
|
42
85
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
86
|
+
flags |= (1 << 14)
|
87
|
+
arr << content_encoding.bytesize << content_encoding
|
88
|
+
fmt << "Ca*"
|
89
|
+
end
|
47
90
|
|
48
|
-
|
49
|
-
|
50
|
-
flags |= (1 << 11)
|
51
|
-
arr << priority
|
52
|
-
fmt << "C"
|
53
|
-
end
|
91
|
+
if headers
|
92
|
+
headers.is_a?(Hash) || raise(ArgumentError, "headers must be a hash")
|
54
93
|
|
55
|
-
|
56
|
-
|
94
|
+
flags |= (1 << 13)
|
95
|
+
tbl = Table.encode(headers)
|
96
|
+
arr << tbl.bytesize << tbl
|
97
|
+
fmt << "L>a*"
|
98
|
+
end
|
57
99
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
end
|
100
|
+
if delivery_mode
|
101
|
+
delivery_mode.is_a?(Integer) || raise(ArgumentError, "delivery_mode must be an int")
|
102
|
+
delivery_mode.between?(0, 2) || raise(ArgumentError, "delivery_mode must be be between 0 and 2")
|
62
103
|
|
63
|
-
|
64
|
-
|
104
|
+
flags |= (1 << 12)
|
105
|
+
arr << delivery_mode
|
106
|
+
fmt << "C"
|
107
|
+
end
|
65
108
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
109
|
+
if priority
|
110
|
+
priority.is_a?(Integer) || raise(ArgumentError, "priority must be an int")
|
111
|
+
flags |= (1 << 11)
|
112
|
+
arr << priority
|
113
|
+
fmt << "C"
|
114
|
+
end
|
70
115
|
|
71
|
-
|
72
|
-
|
73
|
-
expiration.is_a?(String) || raise(ArgumentError, "expiration must be a string or integer")
|
116
|
+
if correlation_id
|
117
|
+
priority.is_a?(String) || raise(ArgumentError, "correlation_id must be a string")
|
74
118
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
119
|
+
flags |= (1 << 10)
|
120
|
+
arr << correlation_id.bytesize << correlation_id
|
121
|
+
fmt << "Ca*"
|
122
|
+
end
|
79
123
|
|
80
|
-
|
81
|
-
|
124
|
+
if reply_to
|
125
|
+
reply_to.is_a?(String) || raise(ArgumentError, "reply_to must be a string")
|
82
126
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
127
|
+
flags |= (1 << 9)
|
128
|
+
arr << reply_to.bytesize << reply_to
|
129
|
+
fmt << "Ca*"
|
130
|
+
end
|
87
131
|
|
88
|
-
|
89
|
-
|
132
|
+
if expiration
|
133
|
+
self.expiration = expiration.to_s if expiration.is_a?(Integer)
|
134
|
+
expiration.is_a?(String) || raise(ArgumentError, "expiration must be a string or integer")
|
90
135
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
136
|
+
flags |= (1 << 8)
|
137
|
+
arr << expiration.bytesize << expiration
|
138
|
+
fmt << "Ca*"
|
139
|
+
end
|
95
140
|
|
96
|
-
|
97
|
-
|
141
|
+
if message_id
|
142
|
+
message_id.is_a?(String) || raise(ArgumentError, "message_id must be a string")
|
98
143
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
144
|
+
flags |= (1 << 7)
|
145
|
+
arr << message_id.bytesize << message_id
|
146
|
+
fmt << "Ca*"
|
147
|
+
end
|
103
148
|
|
104
|
-
|
105
|
-
|
149
|
+
if timestamp
|
150
|
+
timestamp.is_a?(Integer) || timestamp.is_a?(Time) || raise(ArgumentError, "timestamp must be an Integer or a Time")
|
106
151
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
152
|
+
flags |= (1 << 6)
|
153
|
+
arr << timestamp.to_i
|
154
|
+
fmt << "Q>"
|
155
|
+
end
|
111
156
|
|
112
|
-
|
113
|
-
|
157
|
+
if type
|
158
|
+
type.is_a?(String) || raise(ArgumentError, "type must be a string")
|
114
159
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
160
|
+
flags |= (1 << 5)
|
161
|
+
arr << type.bytesize << type
|
162
|
+
fmt << "Ca*"
|
163
|
+
end
|
119
164
|
|
120
|
-
|
121
|
-
|
122
|
-
end
|
165
|
+
if user_id
|
166
|
+
user_id.is_a?(String) || raise(ArgumentError, "user_id must be a string")
|
123
167
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
end
|
140
|
-
if (flags & 0x2000).positive?
|
141
|
-
len = bytes.byteslice(pos, 4).unpack1("L>")
|
142
|
-
pos += 4
|
143
|
-
h[:headers] = Table.decode(bytes.byteslice(pos, len))
|
144
|
-
pos += len
|
145
|
-
end
|
146
|
-
if (flags & 0x1000).positive?
|
147
|
-
h[:delivery_mode] = bytes[pos].ord
|
148
|
-
pos += 1
|
149
|
-
end
|
150
|
-
if (flags & 0x0800).positive?
|
151
|
-
h[:priority] = bytes[pos].ord
|
152
|
-
pos += 1
|
153
|
-
end
|
154
|
-
if (flags & 0x0400).positive?
|
155
|
-
len = bytes[pos].ord
|
156
|
-
pos += 1
|
157
|
-
h[:correlation_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
|
158
|
-
pos += len
|
159
|
-
end
|
160
|
-
if (flags & 0x0200).positive?
|
161
|
-
len = bytes[pos].ord
|
162
|
-
pos += 1
|
163
|
-
h[:reply_to] = bytes.byteslice(pos, len).force_encoding("utf-8")
|
164
|
-
pos += len
|
165
|
-
end
|
166
|
-
if (flags & 0x0100).positive?
|
167
|
-
len = bytes[pos].ord
|
168
|
-
pos += 1
|
169
|
-
h[:expiration] = bytes.byteslice(pos, len).force_encoding("utf-8")
|
170
|
-
pos += len
|
171
|
-
end
|
172
|
-
if (flags & 0x0080).positive?
|
173
|
-
len = bytes[pos].ord
|
174
|
-
pos += 1
|
175
|
-
h[:message_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
|
176
|
-
pos += len
|
177
|
-
end
|
178
|
-
if (flags & 0x0040).positive?
|
179
|
-
h[:timestamp] = Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))
|
180
|
-
pos += 8
|
181
|
-
end
|
182
|
-
if (flags & 0x0020).positive?
|
183
|
-
len = bytes[pos].ord
|
184
|
-
pos += 1
|
185
|
-
h[:type] = bytes.byteslice(pos, len).force_encoding("utf-8")
|
186
|
-
pos += len
|
187
|
-
end
|
188
|
-
if (flags & 0x0010).positive?
|
189
|
-
len = bytes[pos].ord
|
190
|
-
pos += 1
|
191
|
-
h[:user_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
|
192
|
-
pos += len
|
168
|
+
flags |= (1 << 4)
|
169
|
+
arr << user_id.bytesize << user_id
|
170
|
+
fmt << "Ca*"
|
171
|
+
end
|
172
|
+
|
173
|
+
if app_id
|
174
|
+
app_id.is_a?(String) || raise(ArgumentError, "app_id must be a string")
|
175
|
+
|
176
|
+
flags |= (1 << 3)
|
177
|
+
arr << app_id.bytesize << app_id
|
178
|
+
fmt << "Ca*"
|
179
|
+
end
|
180
|
+
|
181
|
+
arr[0] = flags
|
182
|
+
arr.pack(fmt.string)
|
193
183
|
end
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
184
|
+
|
185
|
+
# Decode a byte array
|
186
|
+
# @return [Properties]
|
187
|
+
def self.decode(bytes)
|
188
|
+
p = new
|
189
|
+
flags = bytes.unpack1("S>")
|
190
|
+
pos = 2
|
191
|
+
if (flags & 0x8000).positive?
|
192
|
+
len = bytes.getbyte(pos)
|
193
|
+
pos += 1
|
194
|
+
p.content_type = bytes.byteslice(pos, len).force_encoding("utf-8")
|
195
|
+
pos += len
|
196
|
+
end
|
197
|
+
if (flags & 0x4000).positive?
|
198
|
+
len = bytes.getbyte(pos)
|
199
|
+
pos += 1
|
200
|
+
p.content_encoding = bytes.byteslice(pos, len).force_encoding("utf-8")
|
201
|
+
pos += len
|
202
|
+
end
|
203
|
+
if (flags & 0x2000).positive?
|
204
|
+
len = bytes.byteslice(pos, 4).unpack1("L>")
|
205
|
+
pos += 4
|
206
|
+
p.headers = Table.decode(bytes.byteslice(pos, len))
|
207
|
+
pos += len
|
208
|
+
end
|
209
|
+
if (flags & 0x1000).positive?
|
210
|
+
p.delivery_mode = bytes.getbyte(pos)
|
211
|
+
pos += 1
|
212
|
+
end
|
213
|
+
if (flags & 0x0800).positive?
|
214
|
+
p.priority = bytes.getbyte(pos)
|
215
|
+
pos += 1
|
216
|
+
end
|
217
|
+
if (flags & 0x0400).positive?
|
218
|
+
len = bytes.getbyte(pos)
|
219
|
+
pos += 1
|
220
|
+
p.correlation_id = bytes.byteslice(pos, len).force_encoding("utf-8")
|
221
|
+
pos += len
|
222
|
+
end
|
223
|
+
if (flags & 0x0200).positive?
|
224
|
+
len = bytes.getbyte(pos)
|
225
|
+
pos += 1
|
226
|
+
p.reply_to = bytes.byteslice(pos, len).force_encoding("utf-8")
|
227
|
+
pos += len
|
228
|
+
end
|
229
|
+
if (flags & 0x0100).positive?
|
230
|
+
len = bytes.getbyte(pos)
|
231
|
+
pos += 1
|
232
|
+
p.expiration = bytes.byteslice(pos, len).force_encoding("utf-8")
|
233
|
+
pos += len
|
234
|
+
end
|
235
|
+
if (flags & 0x0080).positive?
|
236
|
+
len = bytes.getbyte(pos)
|
237
|
+
pos += 1
|
238
|
+
p.message_id = bytes.byteslice(pos, len).force_encoding("utf-8")
|
239
|
+
pos += len
|
240
|
+
end
|
241
|
+
if (flags & 0x0040).positive?
|
242
|
+
p.timestamp = Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))
|
243
|
+
pos += 8
|
244
|
+
end
|
245
|
+
if (flags & 0x0020).positive?
|
246
|
+
len = bytes.getbyte(pos)
|
247
|
+
pos += 1
|
248
|
+
p.type = bytes.byteslice(pos, len).force_encoding("utf-8")
|
249
|
+
pos += len
|
250
|
+
end
|
251
|
+
if (flags & 0x0010).positive?
|
252
|
+
len = bytes.getbyte(pos)
|
253
|
+
pos += 1
|
254
|
+
p.user_id = bytes.byteslice(pos, len).force_encoding("utf-8")
|
255
|
+
pos += len
|
256
|
+
end
|
257
|
+
if (flags & 0x0008).positive?
|
258
|
+
len = bytes.getbyte(pos)
|
259
|
+
pos += 1
|
260
|
+
p.app_id = bytes.byteslice(pos, len).force_encoding("utf-8")
|
261
|
+
end
|
262
|
+
p
|
198
263
|
end
|
199
|
-
h
|
200
264
|
end
|
201
265
|
end
|
202
266
|
end
|