amqp-client 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +19 -2
- data/.gitignore +1 -0
- data/.rubocop_todo.yml +39 -33
- data/CHANGELOG.md +5 -0
- data/README.md +31 -0
- data/lib/amqp/client.rb +145 -77
- data/lib/amqp/client/channel.rb +239 -32
- data/lib/amqp/client/connection.rb +293 -42
- data/lib/amqp/client/errors.rb +7 -2
- data/lib/amqp/client/frames.rb +361 -16
- data/lib/amqp/client/message.rb +11 -1
- data/lib/amqp/client/properties.rb +201 -0
- data/lib/amqp/client/table.rb +123 -0
- data/lib/amqp/client/version.rb +1 -1
- metadata +4 -3
- data/Gemfile.lock +0 -42
data/lib/amqp/client/message.rb
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module AMQP
|
4
|
-
Message = Struct.new(:exchange_name, :routing_key, :properties, :body, :redelivered)
|
4
|
+
Message = Struct.new(:channel, :delivery_tag, :exchange_name, :routing_key, :properties, :body, :redelivered, :consumer_tag) do
|
5
|
+
def ack
|
6
|
+
channel.basic_ack(delivery_tag)
|
7
|
+
end
|
8
|
+
|
9
|
+
def reject(requeue: false)
|
10
|
+
channel.basic_reject(delivery_tag, requeue)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
ReturnMessage = Struct.new(:reply_code, :reply_text, :exchange, :routing_key, :properties, :body)
|
5
15
|
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./table"
|
4
|
+
|
5
|
+
module AMQP
|
6
|
+
# Encode/decode AMQP Properties
|
7
|
+
Properties = Struct.new(:content_type, :content_encoding, :headers, :delivery_mode, :priority, :correlation_id,
|
8
|
+
:reply_to, :expiration, :message_id, :timestamp, :type, :user_id, :app_id,
|
9
|
+
keyword_init: true) do
|
10
|
+
def encode
|
11
|
+
flags = 0
|
12
|
+
arr = [flags]
|
13
|
+
fmt = String.new("S>")
|
14
|
+
|
15
|
+
if content_type
|
16
|
+
content_type.is_a?(String) || raise(ArgumentError, "content_type must be a string")
|
17
|
+
|
18
|
+
flags |= (1 << 15)
|
19
|
+
arr << content_type.bytesize << content_type
|
20
|
+
fmt << "Ca*"
|
21
|
+
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
|
+
end
|
200
|
+
end
|
201
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AMQP
|
4
|
+
# Encode/decode AMQP Tables
|
5
|
+
module Table
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def encode(hash)
|
9
|
+
tbl = ""
|
10
|
+
hash.each do |k, v|
|
11
|
+
key = k.to_s
|
12
|
+
tbl += [key.bytesize, key, encode_field(v)].pack("C a* a*")
|
13
|
+
end
|
14
|
+
tbl
|
15
|
+
end
|
16
|
+
|
17
|
+
def decode(bytes)
|
18
|
+
h = {}
|
19
|
+
pos = 0
|
20
|
+
|
21
|
+
while pos < bytes.bytesize
|
22
|
+
key_len = bytes[pos].ord
|
23
|
+
pos += 1
|
24
|
+
key = bytes.byteslice(pos, key_len).force_encoding("utf-8")
|
25
|
+
pos += key_len
|
26
|
+
rest = bytes.byteslice(pos, bytes.bytesize - pos)
|
27
|
+
len, value = decode_field(rest)
|
28
|
+
pos += len + 1
|
29
|
+
h[key] = value
|
30
|
+
end
|
31
|
+
h
|
32
|
+
end
|
33
|
+
|
34
|
+
def encode_field(value)
|
35
|
+
case value
|
36
|
+
when Integer
|
37
|
+
if value > 2**31
|
38
|
+
["l", value].pack("a q>")
|
39
|
+
else
|
40
|
+
["I", value].pack("a l>")
|
41
|
+
end
|
42
|
+
when Float
|
43
|
+
["d", value].pack("a G")
|
44
|
+
when String
|
45
|
+
["S", value.bytesize, value].pack("a L> a*")
|
46
|
+
when Time
|
47
|
+
["T", value.to_i].pack("a Q>")
|
48
|
+
when Array
|
49
|
+
bytes = value.map { |e| encode_field(e) }.join
|
50
|
+
["A", bytes.bytesize, bytes].pack("a L> a*")
|
51
|
+
when Hash
|
52
|
+
bytes = Table.encode(value)
|
53
|
+
["F", bytes.bytesize, bytes].pack("a L> a*")
|
54
|
+
when true
|
55
|
+
["t", 1].pack("a C")
|
56
|
+
when false
|
57
|
+
["t", 0].pack("a C")
|
58
|
+
when nil
|
59
|
+
["V"].pack("a")
|
60
|
+
else raise "unsupported table field type: #{value.class}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# returns [length of field including type, value of field]
|
65
|
+
def decode_field(bytes)
|
66
|
+
type = bytes[0]
|
67
|
+
pos = 1
|
68
|
+
case type
|
69
|
+
when "S"
|
70
|
+
len = bytes.byteslice(pos, 4).unpack1("L>")
|
71
|
+
pos += 4
|
72
|
+
[4 + len, bytes.byteslice(pos, len).force_encoding("utf-8")]
|
73
|
+
when "F"
|
74
|
+
len = bytes.byteslice(pos, 4).unpack1("L>")
|
75
|
+
pos += 4
|
76
|
+
[4 + len, decode(bytes.byteslice(pos, len))]
|
77
|
+
when "A"
|
78
|
+
len = bytes.byteslice(pos, 4).unpack1("L>")
|
79
|
+
a = []
|
80
|
+
while pos < len
|
81
|
+
length, value = decode_field(bytes.byteslice(pos, -1))
|
82
|
+
pos += length + 1
|
83
|
+
a << value
|
84
|
+
end
|
85
|
+
[4 + len, a]
|
86
|
+
when "t"
|
87
|
+
[1, bytes[pos].ord == 1]
|
88
|
+
when "b"
|
89
|
+
[1, bytes.byteslice(pos, 1).unpack1("c")]
|
90
|
+
when "B"
|
91
|
+
[1, bytes.byteslice(pos, 1).unpack1("C")]
|
92
|
+
when "s"
|
93
|
+
[2, bytes.byteslice(pos, 2).unpack1("s")]
|
94
|
+
when "u"
|
95
|
+
[2, bytes.byteslice(pos, 2).unpack1("S")]
|
96
|
+
when "I"
|
97
|
+
[4, bytes.byteslice(pos, 4).unpack1("l>")]
|
98
|
+
when "i"
|
99
|
+
[4, bytes.byteslice(pos, 4).unpack1("L>")]
|
100
|
+
when "l"
|
101
|
+
[8, bytes.byteslice(pos, 8).unpack1("q>")]
|
102
|
+
when "f"
|
103
|
+
[4, bytes.byteslice(pos, 4).unpack1("g")]
|
104
|
+
when "d"
|
105
|
+
[8, bytes.byteslice(pos, 8).unpack1("G")]
|
106
|
+
when "D"
|
107
|
+
scale = bytes[pos].ord
|
108
|
+
pos += 1
|
109
|
+
value = bytes.byteslice(pos, 4).unpack1("L>")
|
110
|
+
d = value / 10**scale
|
111
|
+
[5, d]
|
112
|
+
when "x"
|
113
|
+
len = bytes.byteslice(pos, 4).unpack1("L>")
|
114
|
+
[4 + len, bytes.byteslice(pos, len)]
|
115
|
+
when "T"
|
116
|
+
[8, Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))]
|
117
|
+
when "V"
|
118
|
+
[0, nil]
|
119
|
+
else raise "unsupported table field type: #{type}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/amqp/client/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: amqp-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carl Hörberg
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-08-19 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Work in progress
|
14
14
|
email:
|
@@ -23,7 +23,6 @@ files:
|
|
23
23
|
- ".rubocop_todo.yml"
|
24
24
|
- CHANGELOG.md
|
25
25
|
- Gemfile
|
26
|
-
- Gemfile.lock
|
27
26
|
- LICENSE.txt
|
28
27
|
- README.md
|
29
28
|
- Rakefile
|
@@ -36,6 +35,8 @@ files:
|
|
36
35
|
- lib/amqp/client/errors.rb
|
37
36
|
- lib/amqp/client/frames.rb
|
38
37
|
- lib/amqp/client/message.rb
|
38
|
+
- lib/amqp/client/properties.rb
|
39
|
+
- lib/amqp/client/table.rb
|
39
40
|
- lib/amqp/client/version.rb
|
40
41
|
homepage: https://github.com/cloudamqp/amqp-client.rb
|
41
42
|
licenses:
|
data/Gemfile.lock
DELETED
@@ -1,42 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
amqp-client (0.1.0)
|
5
|
-
|
6
|
-
GEM
|
7
|
-
remote: https://rubygems.org/
|
8
|
-
specs:
|
9
|
-
ast (2.4.2)
|
10
|
-
minitest (5.14.2)
|
11
|
-
parallel (1.20.1)
|
12
|
-
parser (3.0.1.0)
|
13
|
-
ast (~> 2.4.1)
|
14
|
-
rainbow (3.0.0)
|
15
|
-
rake (13.0.3)
|
16
|
-
regexp_parser (2.1.1)
|
17
|
-
rexml (3.2.4)
|
18
|
-
rubocop (1.12.1)
|
19
|
-
parallel (~> 1.10)
|
20
|
-
parser (>= 3.0.0.0)
|
21
|
-
rainbow (>= 2.2.2, < 4.0)
|
22
|
-
regexp_parser (>= 1.8, < 3.0)
|
23
|
-
rexml
|
24
|
-
rubocop-ast (>= 1.2.0, < 2.0)
|
25
|
-
ruby-progressbar (~> 1.7)
|
26
|
-
unicode-display_width (>= 1.4.0, < 3.0)
|
27
|
-
rubocop-ast (1.4.1)
|
28
|
-
parser (>= 2.7.1.5)
|
29
|
-
ruby-progressbar (1.11.0)
|
30
|
-
unicode-display_width (2.0.0)
|
31
|
-
|
32
|
-
PLATFORMS
|
33
|
-
x86_64-linux
|
34
|
-
|
35
|
-
DEPENDENCIES
|
36
|
-
amqp-client!
|
37
|
-
minitest (~> 5.0)
|
38
|
-
rake (~> 13.0)
|
39
|
-
rubocop (~> 1.7)
|
40
|
-
|
41
|
-
BUNDLED WITH
|
42
|
-
2.2.15
|