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