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
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
|