amqp-client 1.0.0 → 1.0.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/.rubocop.yml +12 -1
- data/.rubocop_todo.yml +14 -58
- data/.yardopts +1 -0
- data/CHANGELOG.md +10 -0
- data/README.md +4 -0
- data/lib/amqp/client/channel.rb +480 -275
- data/lib/amqp/client/connection.rb +401 -356
- 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 -194
- 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 +128 -95
- metadata +6 -2
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,201 +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
|
-
delivery_mode.is_a?(Integer) || raise(ArgumentError, "delivery_mode must be an int")
|
42
|
-
delivery_mode.between?(0, 2) || raise(ArgumentError, "delivery_mode must be be between 0 and 2")
|
43
|
-
|
44
|
-
flags |= (1 << 12)
|
45
|
-
arr << delivery_mode
|
46
|
-
fmt << "C"
|
47
|
-
end
|
48
|
-
|
49
|
-
if priority
|
50
|
-
priority.is_a?(Integer) || raise(ArgumentError, "priority must be an int")
|
51
|
-
flags |= (1 << 11)
|
52
|
-
arr << priority
|
53
|
-
fmt << "C"
|
54
|
-
end
|
55
|
-
|
56
|
-
if correlation_id
|
57
|
-
priority.is_a?(String) || raise(ArgumentError, "correlation_id must be a string")
|
58
|
-
|
59
|
-
flags |= (1 << 10)
|
60
|
-
arr << correlation_id.bytesize << correlation_id
|
61
|
-
fmt << "Ca*"
|
62
|
-
end
|
63
|
-
|
64
|
-
if reply_to
|
65
|
-
reply_to.is_a?(String) || raise(ArgumentError, "reply_to must be a string")
|
66
|
-
|
67
|
-
flags |= (1 << 9)
|
68
|
-
arr << reply_to.bytesize << reply_to
|
69
|
-
fmt << "Ca*"
|
70
|
-
end
|
71
|
-
|
72
|
-
if expiration
|
73
|
-
self.expiration = expiration.to_s if expiration.is_a?(Integer)
|
74
|
-
expiration.is_a?(String) || raise(ArgumentError, "expiration must be a string or integer")
|
75
|
-
|
76
|
-
flags |= (1 << 8)
|
77
|
-
arr << expiration.bytesize << expiration
|
78
|
-
fmt << "Ca*"
|
79
|
-
end
|
80
|
-
|
81
|
-
if message_id
|
82
|
-
message_id.is_a?(String) || raise(ArgumentError, "message_id must be a string")
|
83
|
-
|
84
|
-
flags |= (1 << 7)
|
85
|
-
arr << message_id.bytesize << message_id
|
86
|
-
fmt << "Ca*"
|
87
|
-
end
|
88
|
-
|
89
|
-
if timestamp
|
90
|
-
timestamp.is_a?(Integer) || timestamp.is_a?(Time) || raise(ArgumentError, "timestamp must be an Integer or a Time")
|
91
|
-
|
92
|
-
flags |= (1 << 6)
|
93
|
-
arr << timestamp.to_i
|
94
|
-
fmt << "Q>"
|
95
|
-
end
|
96
|
-
|
97
|
-
if type
|
98
|
-
type.is_a?(String) || raise(ArgumentError, "type must be a string")
|
99
|
-
|
100
|
-
flags |= (1 << 5)
|
101
|
-
arr << type.bytesize << type
|
102
|
-
fmt << "Ca*"
|
103
|
-
end
|
104
|
-
|
105
|
-
if user_id
|
106
|
-
user_id.is_a?(String) || raise(ArgumentError, "user_id must be a string")
|
107
|
-
|
108
|
-
flags |= (1 << 4)
|
109
|
-
arr << user_id.bytesize << user_id
|
110
|
-
fmt << "Ca*"
|
111
|
-
end
|
112
|
-
|
113
|
-
if app_id
|
114
|
-
app_id.is_a?(String) || raise(ArgumentError, "app_id must be a string")
|
115
|
-
|
116
|
-
flags |= (1 << 3)
|
117
|
-
arr << app_id.bytesize << app_id
|
118
|
-
fmt << "Ca*"
|
119
|
-
end
|
120
|
-
|
121
|
-
arr[0] = flags
|
122
|
-
arr.pack(fmt)
|
123
|
-
end
|
124
|
-
|
125
|
-
def self.decode(bytes)
|
126
|
-
h = new
|
127
|
-
flags = bytes.unpack1("S>")
|
128
|
-
pos = 2
|
129
|
-
if (flags & 0x8000).positive?
|
130
|
-
len = bytes[pos].ord
|
131
|
-
pos += 1
|
132
|
-
h[:content_type] = bytes.byteslice(pos, len).force_encoding("utf-8")
|
133
|
-
pos += len
|
134
|
-
end
|
135
|
-
if (flags & 0x4000).positive?
|
136
|
-
len = bytes[pos].ord
|
137
|
-
pos += 1
|
138
|
-
h[:content_encoding] = bytes.byteslice(pos, len).force_encoding("utf-8")
|
139
|
-
pos += len
|
140
|
-
end
|
141
|
-
if (flags & 0x2000).positive?
|
142
|
-
len = bytes.byteslice(pos, 4).unpack1("L>")
|
143
|
-
pos += 4
|
144
|
-
h[:headers] = Table.decode(bytes.byteslice(pos, len))
|
145
|
-
pos += len
|
146
|
-
end
|
147
|
-
if (flags & 0x1000).positive?
|
148
|
-
h[:delivery_mode] = bytes[pos].ord
|
149
|
-
pos += 1
|
150
|
-
end
|
151
|
-
if (flags & 0x0800).positive?
|
152
|
-
h[:priority] = bytes[pos].ord
|
153
|
-
pos += 1
|
154
|
-
end
|
155
|
-
if (flags & 0x0400).positive?
|
156
|
-
len = bytes[pos].ord
|
157
|
-
pos += 1
|
158
|
-
h[:correlation_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
|
159
|
-
pos += len
|
160
|
-
end
|
161
|
-
if (flags & 0x0200).positive?
|
162
|
-
len = bytes[pos].ord
|
163
|
-
pos += 1
|
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
|
194
|
-
end
|
195
|
-
if (flags & 0x0008).positive?
|
196
|
-
len = bytes[pos].ord
|
197
|
-
pos += 1
|
198
|
-
h[:app_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
|
199
|
-
end
|
200
|
-
h
|
201
234
|
end
|
202
235
|
end
|
203
236
|
end
|