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
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,201 +3,298 @@
|
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
# Properties as a Hash
|
27
|
+
# @return [Hash] Properties
|
28
|
+
def to_h
|
29
|
+
{
|
30
|
+
content_type: content_type,
|
31
|
+
content_encoding: content_encoding,
|
32
|
+
headers: headers,
|
33
|
+
delivery_mode: delivery_mode,
|
34
|
+
priority: priority,
|
35
|
+
correlation_id: correlation_id,
|
36
|
+
reply_to: reply_to,
|
37
|
+
expiration: expiration,
|
38
|
+
message_id: message_id,
|
39
|
+
timestamp: timestamp,
|
40
|
+
type: type,
|
41
|
+
user_id: user_id,
|
42
|
+
app_id: app_id
|
43
|
+
}
|
29
44
|
end
|
30
45
|
|
31
|
-
|
32
|
-
|
46
|
+
alias to_hash to_h
|
47
|
+
|
48
|
+
# Returns true if two Property objects holds the same information
|
49
|
+
# @return [Boolean]
|
50
|
+
def ==(other)
|
51
|
+
return false unless other.is_a? self.class
|
33
52
|
|
34
|
-
|
35
|
-
tbl = Table.encode(headers)
|
36
|
-
arr << tbl.bytesize << tbl
|
37
|
-
fmt << "L>a*"
|
53
|
+
instance_variables.all? { |v| instance_variable_get(v) == other.instance_variable_get(v) }
|
38
54
|
end
|
39
55
|
|
40
|
-
|
41
|
-
|
42
|
-
|
56
|
+
# Content type of the message body
|
57
|
+
# @return [String, nil]
|
58
|
+
attr_accessor :content_type
|
59
|
+
# Content encoding of the body
|
60
|
+
# @return [String, nil]
|
61
|
+
attr_accessor :content_encoding
|
62
|
+
# Headers, for applications and header exchange routing
|
63
|
+
# @return [Hash<String, Object>, nil]
|
64
|
+
attr_accessor :headers
|
65
|
+
# Message persistent level
|
66
|
+
# @note The exchange and queue have to durable as well for the message to be persistent
|
67
|
+
# @return [1] Transient message
|
68
|
+
# @return [2] Persistent message
|
69
|
+
# @return [nil] Not specified (implicitly transient)
|
70
|
+
attr_accessor :delivery_mode
|
71
|
+
# A priority of the message (between 0 and 255)
|
72
|
+
# @return [Integer, nil]
|
73
|
+
attr_accessor :priority
|
74
|
+
# Message correlation id, commonly used to correlate RPC requests and responses
|
75
|
+
# @return [String, nil]
|
76
|
+
attr_accessor :correlation_id
|
77
|
+
# Queue to reply RPC responses to
|
78
|
+
# @return [String, nil]
|
79
|
+
attr_accessor :reply_to
|
80
|
+
# Number of seconds the message will stay in the queue
|
81
|
+
# @return [String, nil]
|
82
|
+
attr_accessor :expiration
|
83
|
+
# Application message identifier
|
84
|
+
# @return [String, nil]
|
85
|
+
attr_accessor :message_id
|
86
|
+
# Message timestamp, often indicates when the message was originally generated
|
87
|
+
# @return [Date, nil]
|
88
|
+
attr_accessor :timestamp
|
89
|
+
# Message type name
|
90
|
+
# @return [String, nil]
|
91
|
+
attr_accessor :type
|
92
|
+
# The user that published the message
|
93
|
+
# @return [String, nil]
|
94
|
+
attr_accessor :user_id
|
95
|
+
# Name of application that generated the message
|
96
|
+
# @return [String, nil]
|
97
|
+
attr_accessor :app_id
|
43
98
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
99
|
+
# Encode properties into a byte array
|
100
|
+
# @param properties [Hash]
|
101
|
+
# @return [String] byte array
|
102
|
+
def self.encode(properties)
|
103
|
+
return "\x00\x00" if properties.empty?
|
48
104
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
arr << priority
|
53
|
-
fmt << "C"
|
54
|
-
end
|
105
|
+
flags = 0
|
106
|
+
arr = [flags]
|
107
|
+
fmt = String.new("S>", capacity: 37)
|
55
108
|
|
56
|
-
|
57
|
-
|
109
|
+
if (content_type = properties[:content_type])
|
110
|
+
content_type.is_a?(String) || raise(ArgumentError, "content_type must be a string")
|
58
111
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
112
|
+
flags |= (1 << 15)
|
113
|
+
arr << content_type.bytesize << content_type
|
114
|
+
fmt << "Ca*"
|
115
|
+
end
|
63
116
|
|
64
|
-
|
65
|
-
|
117
|
+
if (content_encoding = properties[:content_encoding])
|
118
|
+
content_encoding.is_a?(String) || raise(ArgumentError, "content_encoding must be a string")
|
66
119
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
120
|
+
flags |= (1 << 14)
|
121
|
+
arr << content_encoding.bytesize << content_encoding
|
122
|
+
fmt << "Ca*"
|
123
|
+
end
|
71
124
|
|
72
|
-
|
73
|
-
|
74
|
-
expiration.is_a?(String) || raise(ArgumentError, "expiration must be a string or integer")
|
125
|
+
if (headers = properties[:headers])
|
126
|
+
headers.is_a?(Hash) || raise(ArgumentError, "headers must be a hash")
|
75
127
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
128
|
+
flags |= (1 << 13)
|
129
|
+
tbl = Table.encode(headers)
|
130
|
+
arr << tbl.bytesize << tbl
|
131
|
+
fmt << "L>a*"
|
132
|
+
end
|
80
133
|
|
81
|
-
|
82
|
-
|
134
|
+
if (delivery_mode = properties[:delivery_mode])
|
135
|
+
delivery_mode.is_a?(Integer) || raise(ArgumentError, "delivery_mode must be an int")
|
83
136
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
137
|
+
flags |= (1 << 12)
|
138
|
+
arr << delivery_mode
|
139
|
+
fmt << "C"
|
140
|
+
end
|
88
141
|
|
89
|
-
|
90
|
-
|
142
|
+
if (priority = properties[:priority])
|
143
|
+
priority.is_a?(Integer) || raise(ArgumentError, "priority must be an int")
|
91
144
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
145
|
+
flags |= (1 << 11)
|
146
|
+
arr << priority
|
147
|
+
fmt << "C"
|
148
|
+
end
|
96
149
|
|
97
|
-
|
98
|
-
|
150
|
+
if (correlation_id = properties[:correlation_id])
|
151
|
+
correlation_id.is_a?(String) || raise(ArgumentError, "correlation_id must be a string")
|
99
152
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
153
|
+
flags |= (1 << 10)
|
154
|
+
arr << correlation_id.bytesize << correlation_id
|
155
|
+
fmt << "Ca*"
|
156
|
+
end
|
104
157
|
|
105
|
-
|
106
|
-
|
158
|
+
if (reply_to = properties[:reply_to])
|
159
|
+
reply_to.is_a?(String) || raise(ArgumentError, "reply_to must be a string")
|
107
160
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
161
|
+
flags |= (1 << 9)
|
162
|
+
arr << reply_to.bytesize << reply_to
|
163
|
+
fmt << "Ca*"
|
164
|
+
end
|
112
165
|
|
113
|
-
|
114
|
-
|
166
|
+
if (expiration = properties[:expiration])
|
167
|
+
expiration = expiration.to_s if expiration.is_a?(Integer)
|
168
|
+
expiration.is_a?(String) || raise(ArgumentError, "expiration must be a string or integer")
|
115
169
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
170
|
+
flags |= (1 << 8)
|
171
|
+
arr << expiration.bytesize << expiration
|
172
|
+
fmt << "Ca*"
|
173
|
+
end
|
120
174
|
|
121
|
-
|
122
|
-
|
123
|
-
end
|
175
|
+
if (message_id = properties[:message_id])
|
176
|
+
message_id.is_a?(String) || raise(ArgumentError, "message_id must be a string")
|
124
177
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
h[:reply_to] = bytes.byteslice(pos, len).force_encoding("utf-8")
|
165
|
-
pos += len
|
166
|
-
end
|
167
|
-
if (flags & 0x0100).positive?
|
168
|
-
len = bytes[pos].ord
|
169
|
-
pos += 1
|
170
|
-
h[:expiration] = bytes.byteslice(pos, len).force_encoding("utf-8")
|
171
|
-
pos += len
|
172
|
-
end
|
173
|
-
if (flags & 0x0080).positive?
|
174
|
-
len = bytes[pos].ord
|
175
|
-
pos += 1
|
176
|
-
h[:message_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
|
177
|
-
pos += len
|
178
|
-
end
|
179
|
-
if (flags & 0x0040).positive?
|
180
|
-
h[:timestamp] = Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))
|
181
|
-
pos += 8
|
182
|
-
end
|
183
|
-
if (flags & 0x0020).positive?
|
184
|
-
len = bytes[pos].ord
|
185
|
-
pos += 1
|
186
|
-
h[:type] = bytes.byteslice(pos, len).force_encoding("utf-8")
|
187
|
-
pos += len
|
188
|
-
end
|
189
|
-
if (flags & 0x0010).positive?
|
190
|
-
len = bytes[pos].ord
|
191
|
-
pos += 1
|
192
|
-
h[:user_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
|
193
|
-
pos += len
|
178
|
+
flags |= (1 << 7)
|
179
|
+
arr << message_id.bytesize << message_id
|
180
|
+
fmt << "Ca*"
|
181
|
+
end
|
182
|
+
|
183
|
+
if (timestamp = properties[:timestamp])
|
184
|
+
timestamp.is_a?(Integer) || timestamp.is_a?(Time) || raise(ArgumentError, "timestamp must be an Integer or a Time")
|
185
|
+
|
186
|
+
flags |= (1 << 6)
|
187
|
+
arr << timestamp.to_i
|
188
|
+
fmt << "Q>"
|
189
|
+
end
|
190
|
+
|
191
|
+
if (type = properties[:type])
|
192
|
+
type.is_a?(String) || raise(ArgumentError, "type must be a string")
|
193
|
+
|
194
|
+
flags |= (1 << 5)
|
195
|
+
arr << type.bytesize << type
|
196
|
+
fmt << "Ca*"
|
197
|
+
end
|
198
|
+
|
199
|
+
if (user_id = properties[:user_id])
|
200
|
+
user_id.is_a?(String) || raise(ArgumentError, "user_id must be a string")
|
201
|
+
|
202
|
+
flags |= (1 << 4)
|
203
|
+
arr << user_id.bytesize << user_id
|
204
|
+
fmt << "Ca*"
|
205
|
+
end
|
206
|
+
|
207
|
+
if (app_id = properties[:app_id])
|
208
|
+
app_id.is_a?(String) || raise(ArgumentError, "app_id must be a string")
|
209
|
+
|
210
|
+
flags |= (1 << 3)
|
211
|
+
arr << app_id.bytesize << app_id
|
212
|
+
fmt << "Ca*"
|
213
|
+
end
|
214
|
+
|
215
|
+
arr[0] = flags
|
216
|
+
arr.pack(fmt)
|
194
217
|
end
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
218
|
+
|
219
|
+
# Decode a byte array
|
220
|
+
# @return [Properties]
|
221
|
+
def self.decode(bytes, pos = 0)
|
222
|
+
p = new
|
223
|
+
flags = bytes.byteslice(pos, 2).unpack1("S>")
|
224
|
+
pos += 2
|
225
|
+
if (flags & 0x8000).positive?
|
226
|
+
len = bytes.getbyte(pos)
|
227
|
+
pos += 1
|
228
|
+
p.content_type = bytes.byteslice(pos, len).force_encoding("utf-8")
|
229
|
+
pos += len
|
230
|
+
end
|
231
|
+
if (flags & 0x4000).positive?
|
232
|
+
len = bytes.getbyte(pos)
|
233
|
+
pos += 1
|
234
|
+
p.content_encoding = bytes.byteslice(pos, len).force_encoding("utf-8")
|
235
|
+
pos += len
|
236
|
+
end
|
237
|
+
if (flags & 0x2000).positive?
|
238
|
+
len = bytes.byteslice(pos, 4).unpack1("L>")
|
239
|
+
pos += 4
|
240
|
+
p.headers = Table.decode(bytes.byteslice(pos, len))
|
241
|
+
pos += len
|
242
|
+
end
|
243
|
+
if (flags & 0x1000).positive?
|
244
|
+
p.delivery_mode = bytes.getbyte(pos)
|
245
|
+
pos += 1
|
246
|
+
end
|
247
|
+
if (flags & 0x0800).positive?
|
248
|
+
p.priority = bytes.getbyte(pos)
|
249
|
+
pos += 1
|
250
|
+
end
|
251
|
+
if (flags & 0x0400).positive?
|
252
|
+
len = bytes.getbyte(pos)
|
253
|
+
pos += 1
|
254
|
+
p.correlation_id = bytes.byteslice(pos, len).force_encoding("utf-8")
|
255
|
+
pos += len
|
256
|
+
end
|
257
|
+
if (flags & 0x0200).positive?
|
258
|
+
len = bytes.getbyte(pos)
|
259
|
+
pos += 1
|
260
|
+
p.reply_to = bytes.byteslice(pos, len).force_encoding("utf-8")
|
261
|
+
pos += len
|
262
|
+
end
|
263
|
+
if (flags & 0x0100).positive?
|
264
|
+
len = bytes.getbyte(pos)
|
265
|
+
pos += 1
|
266
|
+
p.expiration = bytes.byteslice(pos, len).force_encoding("utf-8")
|
267
|
+
pos += len
|
268
|
+
end
|
269
|
+
if (flags & 0x0080).positive?
|
270
|
+
len = bytes.getbyte(pos)
|
271
|
+
pos += 1
|
272
|
+
p.message_id = bytes.byteslice(pos, len).force_encoding("utf-8")
|
273
|
+
pos += len
|
274
|
+
end
|
275
|
+
if (flags & 0x0040).positive?
|
276
|
+
p.timestamp = Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))
|
277
|
+
pos += 8
|
278
|
+
end
|
279
|
+
if (flags & 0x0020).positive?
|
280
|
+
len = bytes.getbyte(pos)
|
281
|
+
pos += 1
|
282
|
+
p.type = bytes.byteslice(pos, len).force_encoding("utf-8")
|
283
|
+
pos += len
|
284
|
+
end
|
285
|
+
if (flags & 0x0010).positive?
|
286
|
+
len = bytes.getbyte(pos)
|
287
|
+
pos += 1
|
288
|
+
p.user_id = bytes.byteslice(pos, len).force_encoding("utf-8")
|
289
|
+
pos += len
|
290
|
+
end
|
291
|
+
if (flags & 0x0008).positive?
|
292
|
+
len = bytes.getbyte(pos)
|
293
|
+
pos += 1
|
294
|
+
p.app_id = bytes.byteslice(pos, len).force_encoding("utf-8")
|
295
|
+
end
|
296
|
+
p
|
199
297
|
end
|
200
|
-
h
|
201
298
|
end
|
202
299
|
end
|
203
300
|
end
|