amqp-client 1.0.2 → 1.1.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 +1 -2
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +7 -0
- data/Gemfile +4 -0
- data/README.md +56 -27
- data/Rakefile +3 -1
- data/amqp-client.gemspec +1 -1
- data/lib/amqp/client/channel.rb +11 -10
- data/lib/amqp/client/connection.rb +61 -42
- data/lib/amqp/client/frames.rb +33 -35
- data/lib/amqp/client/message.rb +101 -44
- data/lib/amqp/client/properties.rb +83 -53
- data/lib/amqp/client/table.rb +7 -9
- data/lib/amqp/client/version.rb +1 -1
- data/lib/amqp/client.rb +4 -4
- data/sig/amqp-client.rbs +264 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ec3129a38420f19de4c0225aea1eaefc320463c6e032c54990a998f7654f8347
|
4
|
+
data.tar.gz: 3c8da5fccb818730958f47fdaf78837524381b116a322adaf585ee02d0492b2d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 24b592bb7fc50f29499e32ca6348f2aa25bac9837e694740346fa24e1fe6c45c9f9ab6497f066ca6491c6f09e558b29ac3a1d84f5b31a3b2b68bb8c28cdf76e7
|
7
|
+
data.tar.gz: 6ad3e059d311894f4a023d75df89c72beb4508a2abc83083a47f4f7a07cc6f98edc157e06b5ed42eda55458612d89206de1cce4b0cc94fd598facad4433083e9
|
data/.github/workflows/main.yml
CHANGED
@@ -19,7 +19,7 @@ jobs:
|
|
19
19
|
strategy:
|
20
20
|
fail-fast: false
|
21
21
|
matrix:
|
22
|
-
ruby: ['2.7', '3.0']
|
22
|
+
ruby: ['2.6', '2.7', '3.0']
|
23
23
|
steps:
|
24
24
|
- uses: actions/checkout@v2
|
25
25
|
- name: Set up Ruby
|
@@ -32,4 +32,3 @@ jobs:
|
|
32
32
|
bundle exec rake
|
33
33
|
env:
|
34
34
|
AMQP_PORT: ${{ job.services.rabbitmq.ports[5672] }}
|
35
|
-
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.1.0] - 2021-09-08
|
4
|
+
|
5
|
+
- Fixed: Due to a race condition publishers could get stuck waiting for publish confirms
|
6
|
+
- Change: Message, ReturnMessage and Properties are now classes and not structs (for performance reasons)
|
7
|
+
- Added: Ruby 2.6 support
|
8
|
+
- Added: RBS signatures in sig/amqp-client.rbs
|
9
|
+
|
3
10
|
## [1.0.2] - 2021-09-07
|
4
11
|
|
5
12
|
- Changed: Raise ConnectionClosed and ChannelClosed correctly (previous always ChannelClosed)
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,58 +1,63 @@
|
|
1
1
|
# AMQP::Client
|
2
2
|
|
3
|
-
|
3
|
+
A modern AMQP 0-9-1 Ruby client. Very fast (just as fast as the Java client, and >4x than other Ruby clients), fully thread-safe, blocking operations and straight-forward error handling.
|
4
|
+
|
5
|
+
## Support
|
6
|
+
|
7
|
+
The library is fully supported by [CloudAMQP](https://www.cloudamqp.com), the largest RabbitMQ hosting provider in the world. Open [an issue](https://github.com/cloudamqp/amqp-client.rb/issues) or [email our support](mailto:support@cloudamqp.com) if you have problems or questions.
|
4
8
|
|
5
9
|
## Documentation
|
6
10
|
|
7
11
|
[API reference](https://cloudamqp.github.io/amqp-client.rb/)
|
8
12
|
|
9
|
-
##
|
13
|
+
## Usage
|
10
14
|
|
11
|
-
|
15
|
+
The client has two APIs.
|
12
16
|
|
13
|
-
|
14
|
-
gem 'amqp-client'
|
15
|
-
```
|
17
|
+
### Low level API
|
16
18
|
|
17
|
-
|
19
|
+
This API matches the AMQP protocol very well, it can do everything the protocol allows, but requires some knowledge about the protocol, and doesn't handle reconnects.
|
18
20
|
|
19
|
-
|
21
|
+
```ruby
|
22
|
+
require "amqp-client"
|
20
23
|
|
21
|
-
|
24
|
+
# Opens and establishes a connection
|
25
|
+
conn = AMQP::Client.new("amqp://guest:guest@localhost").connect
|
22
26
|
|
23
|
-
|
27
|
+
# Open a channel
|
28
|
+
ch = conn.channel
|
24
29
|
|
25
|
-
|
30
|
+
# Create a temporary queue
|
31
|
+
q = ch.queue_declare
|
26
32
|
|
27
|
-
|
33
|
+
# Publish a message to said queue
|
34
|
+
ch.basic_publish "Hello World!", "", q.queue_name
|
28
35
|
|
29
|
-
|
30
|
-
|
36
|
+
# Poll the queue for a message
|
37
|
+
msg = ch.basic_get q.queue_name
|
31
38
|
|
32
|
-
|
33
|
-
conn = c.connect
|
34
|
-
ch = conn.channel
|
35
|
-
q = ch.queue_declare
|
36
|
-
ch.basic_publish "Hello World!", "", q[:queue_name]
|
37
|
-
msg = ch.basic_get q[:queue_name]
|
39
|
+
# Print the message's body to STDOUT
|
38
40
|
puts msg.body
|
39
41
|
```
|
40
42
|
|
41
|
-
High level API
|
43
|
+
### High level API
|
44
|
+
|
45
|
+
The library provides a high-level API that is a bit easier to get started with, and also handles reconnection automatically.
|
42
46
|
|
43
47
|
```ruby
|
44
|
-
|
45
|
-
|
48
|
+
# Start the client, it will connect and once connected it will reconnect if that connection is lost
|
49
|
+
# Operation pending when the connection is lost will raise an exception (not timeout)
|
50
|
+
amqp = AMQP::Client.new("amqp://localhost").start
|
46
51
|
|
47
52
|
# Declares a durable queue
|
48
|
-
|
53
|
+
myqueue = amqp.queue("myqueue")
|
49
54
|
|
50
55
|
# Bind the queue to any exchange, with any binding key
|
51
|
-
|
56
|
+
myqueue.bind("amq.topic", "my.events.*")
|
52
57
|
|
53
58
|
# The message will be reprocessed if the client loses connection to the broker
|
54
59
|
# between message arrival and when the message was supposed to be ack'ed.
|
55
|
-
|
60
|
+
myqueue.subscribe(prefetch: 20) do |msg|
|
56
61
|
process(JSON.parse(msg.body))
|
57
62
|
msg.ack
|
58
63
|
rescue
|
@@ -60,13 +65,37 @@ rescue
|
|
60
65
|
end
|
61
66
|
|
62
67
|
# Publish directly to the queue
|
63
|
-
|
68
|
+
myqueue.publish({ foo: "bar" }.to_json, content_type: "application/json")
|
64
69
|
|
65
70
|
# Publish to any exchange
|
66
71
|
amqp.publish("my message", "amq.topic", "topic.foo", headers: { foo: 'bar' })
|
67
72
|
amqp.publish(Zlib.gzip("an event"), "amq.topic", "my.event", content_encoding: 'gzip')
|
68
73
|
```
|
69
74
|
|
75
|
+
## Supported Ruby versions
|
76
|
+
|
77
|
+
All maintained Ruby versions are supported.
|
78
|
+
|
79
|
+
- 3.0
|
80
|
+
- 2.7
|
81
|
+
- 2.6
|
82
|
+
|
83
|
+
## Installation
|
84
|
+
|
85
|
+
Add this line to your application's Gemfile:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
gem 'amqp-client'
|
89
|
+
```
|
90
|
+
|
91
|
+
And then execute:
|
92
|
+
|
93
|
+
$ bundle install
|
94
|
+
|
95
|
+
Or install it yourself as:
|
96
|
+
|
97
|
+
$ gem install amqp-client
|
98
|
+
|
70
99
|
## Development
|
71
100
|
|
72
101
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/Rakefile
CHANGED
data/amqp-client.gemspec
CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.description = "Work in progress"
|
13
13
|
spec.homepage = "https://github.com/cloudamqp/amqp-client.rb"
|
14
14
|
spec.license = "MIT"
|
15
|
-
spec.required_ruby_version = Gem::Requirement.new(">= 2.
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
|
16
16
|
|
17
17
|
spec.metadata["homepage_uri"] = spec.homepage
|
18
18
|
spec.metadata["source_code_uri"] = "#{spec.homepage}.git"
|
data/lib/amqp/client/channel.rb
CHANGED
@@ -215,6 +215,7 @@ module AMQP
|
|
215
215
|
def queue_unbind(name, exchange, binding_key, arguments: {})
|
216
216
|
write_bytes FrameBytes.queue_unbind(@id, name, exchange, binding_key, arguments)
|
217
217
|
expect :queue_unbind_ok
|
218
|
+
nil
|
218
219
|
end
|
219
220
|
|
220
221
|
# @!endgroup
|
@@ -411,11 +412,10 @@ module AMQP
|
|
411
412
|
def wait_for_confirms
|
412
413
|
return true if @unconfirmed.empty?
|
413
414
|
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
end
|
415
|
+
ok = @unconfirmed_empty.pop
|
416
|
+
raise Error::Closed.new(@id, *@closed) if ok.nil?
|
417
|
+
|
418
|
+
ok
|
419
419
|
end
|
420
420
|
|
421
421
|
# Called by Connection when received ack/nack from broker
|
@@ -433,9 +433,8 @@ module AMQP
|
|
433
433
|
end
|
434
434
|
return unless @unconfirmed.empty?
|
435
435
|
|
436
|
-
|
437
|
-
|
438
|
-
end
|
436
|
+
ok = ack_or_nack == :ack
|
437
|
+
@unconfirmed_empty.push(ok) until @unconfirmed_empty.num_waiting.zero?
|
439
438
|
end
|
440
439
|
|
441
440
|
# @!endgroup
|
@@ -474,12 +473,12 @@ module AMQP
|
|
474
473
|
|
475
474
|
# @api private
|
476
475
|
def message_returned(reply_code, reply_text, exchange, routing_key)
|
477
|
-
@next_msg = ReturnMessage.new(reply_code, reply_text, exchange, routing_key
|
476
|
+
@next_msg = ReturnMessage.new(reply_code, reply_text, exchange, routing_key)
|
478
477
|
end
|
479
478
|
|
480
479
|
# @api private
|
481
480
|
def message_delivered(consumer_tag, delivery_tag, redelivered, exchange, routing_key)
|
482
|
-
@next_msg = Message.new(self, delivery_tag, exchange, routing_key,
|
481
|
+
@next_msg = Message.new(self, consumer_tag, delivery_tag, exchange, routing_key, redelivered)
|
483
482
|
end
|
484
483
|
|
485
484
|
# @api private
|
@@ -510,6 +509,7 @@ module AMQP
|
|
510
509
|
# @api private
|
511
510
|
def close_consumer(tag)
|
512
511
|
@consumers.fetch(tag).close
|
512
|
+
nil
|
513
513
|
end
|
514
514
|
|
515
515
|
private
|
@@ -528,6 +528,7 @@ module AMQP
|
|
528
528
|
Thread.pass until (consumer = @consumers[next_msg.consumer_tag])
|
529
529
|
consumer.push next_msg
|
530
530
|
end
|
531
|
+
nil
|
531
532
|
ensure
|
532
533
|
@next_msg = @next_body = @next_body_size = nil
|
533
534
|
end
|
@@ -13,47 +13,31 @@ module AMQP
|
|
13
13
|
class Connection
|
14
14
|
# Establish a connection to an AMQP broker
|
15
15
|
# @param uri [String] URL on the format amqp://username:password@hostname/vhost, use amqps:// for encrypted connection
|
16
|
-
# @param read_loop_thread [Boolean]
|
16
|
+
# @param read_loop_thread [Boolean] If true run {#read_loop} in a background thread,
|
17
|
+
# otherwise the user have to run it explicitly, without {#read_loop} the connection won't function
|
17
18
|
# @option options [Boolean] connection_name (PROGRAM_NAME) Set a name for the connection to be able to identify
|
18
19
|
# the client from the broker
|
19
20
|
# @option options [Boolean] verify_peer (true) Verify broker's TLS certificate, set to false for self-signed certs
|
21
|
+
# @option options [Integer] connect_timeout (30) TCP connection timeout
|
20
22
|
# @option options [Integer] heartbeat (0) Heartbeat timeout, defaults to 0 and relies on TCP keepalive instead
|
21
23
|
# @option options [Integer] frame_max (131_072) Maximum frame size,
|
22
24
|
# the smallest of the client's and the broker's values will be used
|
23
25
|
# @option options [Integer] channel_max (2048) Maxium number of channels the client will be allowed to have open.
|
24
26
|
# Maxium allowed is 65_536. The smallest of the client's and the broker's value will be used.
|
25
27
|
# @return [Connection]
|
26
|
-
def
|
28
|
+
def initialize(uri = "", read_loop_thread: true, **options)
|
27
29
|
uri = URI.parse(uri)
|
28
30
|
tls = uri.scheme == "amqps"
|
29
31
|
port = port_from_env || uri.port || (tls ? 5671 : 5672)
|
30
32
|
host = uri.host || "localhost"
|
31
33
|
user = uri.user || "guest"
|
32
34
|
password = uri.password || "guest"
|
33
|
-
vhost = URI.decode_www_form_component(uri.path[1
|
35
|
+
vhost = URI.decode_www_form_component(uri.path[1..] || "/")
|
34
36
|
options = URI.decode_www_form(uri.query || "").map! { |k, v| [k.to_sym, v] }.to_h.merge(options)
|
35
37
|
|
36
|
-
socket =
|
37
|
-
enable_tcp_keepalive(socket)
|
38
|
-
if tls
|
39
|
-
cert_store = OpenSSL::X509::Store.new
|
40
|
-
cert_store.set_default_paths
|
41
|
-
context = OpenSSL::SSL::SSLContext.new
|
42
|
-
context.cert_store = cert_store
|
43
|
-
context.verify_mode = OpenSSL::SSL::VERIFY_PEER unless [false, "false", "none"].include? options[:verify_peer]
|
44
|
-
socket = OpenSSL::SSL::SSLSocket.new(socket, context)
|
45
|
-
socket.sync_close = true # closing the TLS socket also closes the TCP socket
|
46
|
-
socket.hostname = host # SNI host
|
47
|
-
socket.connect
|
48
|
-
socket.post_connection_check(host) || raise(Error, "TLS certificate hostname doesn't match requested")
|
49
|
-
end
|
38
|
+
socket = open_socket(host, port, tls, options)
|
50
39
|
channel_max, frame_max, heartbeat = establish(socket, user, password, vhost, options)
|
51
|
-
Connection.new(socket, channel_max, frame_max, heartbeat, read_loop_thread: read_loop_thread)
|
52
|
-
end
|
53
40
|
|
54
|
-
# Requires an already established TCP/TLS socket
|
55
|
-
# @api private
|
56
|
-
def initialize(socket, channel_max, frame_max, heartbeat, read_loop_thread: true)
|
57
41
|
@socket = socket
|
58
42
|
@channel_max = channel_max.zero? ? 65_536 : channel_max
|
59
43
|
@frame_max = frame_max
|
@@ -66,6 +50,13 @@ module AMQP
|
|
66
50
|
Thread.new { read_loop } if read_loop_thread
|
67
51
|
end
|
68
52
|
|
53
|
+
# Alias for {#initialize}
|
54
|
+
# @see #initialize
|
55
|
+
# @deprecated
|
56
|
+
def self.connect(uri, read_loop_thread: true, **options)
|
57
|
+
new(uri, read_loop_thread: read_loop_thread, **options)
|
58
|
+
end
|
59
|
+
|
69
60
|
# The max frame size negotiated between the client and the broker
|
70
61
|
# @return [Integer]
|
71
62
|
attr_reader :frame_max
|
@@ -183,7 +174,9 @@ module AMQP
|
|
183
174
|
@closed ||= [400, "unknown"]
|
184
175
|
@replies.close
|
185
176
|
begin
|
186
|
-
@
|
177
|
+
@write_lock.synchronize do
|
178
|
+
@socket.close
|
179
|
+
end
|
187
180
|
rescue IOError, OpenSSL::OpenSSLError, SystemCallError
|
188
181
|
nil
|
189
182
|
end
|
@@ -216,7 +209,7 @@ module AMQP
|
|
216
209
|
@replies.push [:close_ok]
|
217
210
|
return false
|
218
211
|
when 60 # connection#blocked
|
219
|
-
reason_len = buf.
|
212
|
+
reason_len = buf.getbyte(4)
|
220
213
|
reason = buf.byteslice(5, reason_len).force_encoding("utf-8")
|
221
214
|
@blocked = reason
|
222
215
|
@write_lock.lock
|
@@ -256,7 +249,7 @@ module AMQP
|
|
256
249
|
when 50 # queue
|
257
250
|
case method_id
|
258
251
|
when 11 # declare-ok
|
259
|
-
queue_name_len = buf.
|
252
|
+
queue_name_len = buf.getbyte(4)
|
260
253
|
queue_name = buf.byteslice(5, queue_name_len).force_encoding("utf-8")
|
261
254
|
message_count, consumer_count = buf.byteslice(5 + queue_name_len, 8).unpack("L> L>")
|
262
255
|
@channels[channel_id].reply [:queue_declare_ok, queue_name, message_count, consumer_count]
|
@@ -276,17 +269,17 @@ module AMQP
|
|
276
269
|
when 11 # qos-ok
|
277
270
|
@channels[channel_id].reply [:basic_qos_ok]
|
278
271
|
when 21 # consume-ok
|
279
|
-
tag_len = buf.
|
272
|
+
tag_len = buf.getbyte(4)
|
280
273
|
tag = buf.byteslice(5, tag_len).force_encoding("utf-8")
|
281
274
|
@channels[channel_id].reply [:basic_consume_ok, tag]
|
282
275
|
when 30 # cancel
|
283
|
-
tag_len = buf.
|
276
|
+
tag_len = buf.getbyte(4)
|
284
277
|
tag = buf.byteslice(5, tag_len).force_encoding("utf-8")
|
285
|
-
no_wait = buf
|
278
|
+
no_wait = buf.getbyte(5 + tag_len) == 1
|
286
279
|
@channels[channel_id].close_consumer(tag)
|
287
280
|
write_bytes FrameBytes.basic_cancel_ok(@id, tag) unless no_wait
|
288
281
|
when 31 # cancel-ok
|
289
|
-
tag_len = buf.
|
282
|
+
tag_len = buf.getbyte(4)
|
290
283
|
tag = buf.byteslice(5, tag_len).force_encoding("utf-8")
|
291
284
|
@channels[channel_id].reply [:basic_cancel_ok, tag]
|
292
285
|
when 50 # return
|
@@ -294,23 +287,23 @@ module AMQP
|
|
294
287
|
pos = 7
|
295
288
|
reply_text = buf.byteslice(pos, reply_text_len).force_encoding("utf-8")
|
296
289
|
pos += reply_text_len
|
297
|
-
exchange_len = buf
|
290
|
+
exchange_len = buf.getbyte(pos)
|
298
291
|
pos += 1
|
299
292
|
exchange = buf.byteslice(pos, exchange_len).force_encoding("utf-8")
|
300
293
|
pos += exchange_len
|
301
|
-
routing_key_len = buf
|
294
|
+
routing_key_len = buf.getbyte(pos)
|
302
295
|
pos += 1
|
303
296
|
routing_key = buf.byteslice(pos, routing_key_len).force_encoding("utf-8")
|
304
297
|
@channels[channel_id].message_returned(reply_code, reply_text, exchange, routing_key)
|
305
298
|
when 60 # deliver
|
306
|
-
ctag_len = buf
|
299
|
+
ctag_len = buf.getbyte(4)
|
307
300
|
consumer_tag = buf.byteslice(5, ctag_len).force_encoding("utf-8")
|
308
301
|
pos = 5 + ctag_len
|
309
302
|
delivery_tag, redelivered, exchange_len = buf.byteslice(pos, 10).unpack("Q> C C")
|
310
303
|
pos += 8 + 1 + 1
|
311
304
|
exchange = buf.byteslice(pos, exchange_len).force_encoding("utf-8")
|
312
305
|
pos += exchange_len
|
313
|
-
rk_len = buf
|
306
|
+
rk_len = buf.getbyte(pos)
|
314
307
|
pos += 1
|
315
308
|
routing_key = buf.byteslice(pos, rk_len).force_encoding("utf-8")
|
316
309
|
@channels[channel_id].message_delivered(consumer_tag, delivery_tag, redelivered == 1, exchange, routing_key)
|
@@ -319,11 +312,11 @@ module AMQP
|
|
319
312
|
pos = 14
|
320
313
|
exchange = buf.byteslice(pos, exchange_len).force_encoding("utf-8")
|
321
314
|
pos += exchange_len
|
322
|
-
routing_key_len = buf
|
315
|
+
routing_key_len = buf.getbyte(pos)
|
323
316
|
pos += 1
|
324
317
|
routing_key = buf.byteslice(pos, routing_key_len).force_encoding("utf-8")
|
325
|
-
pos += routing_key_len
|
326
|
-
|
318
|
+
# pos += routing_key_len
|
319
|
+
# message_count = buf.byteslice(pos, 4).unpack1("L>")
|
327
320
|
@channels[channel_id].message_delivered(nil, delivery_tag, redelivered == 1, exchange, routing_key)
|
328
321
|
when 72 # get-empty
|
329
322
|
@channels[channel_id].basic_get_empty
|
@@ -377,9 +370,32 @@ module AMQP
|
|
377
370
|
args
|
378
371
|
end
|
379
372
|
|
373
|
+
# Connect to the host/port, optionally establish a TLS connection
|
374
|
+
# @return [Socket]
|
375
|
+
# @return [OpenSSL::SSL::SSLSocket]
|
376
|
+
def open_socket(host, port, tls, options)
|
377
|
+
connect_timeout = options.fetch(:connect_timeout, 30).to_i
|
378
|
+
socket = Socket.tcp host, port, connect_timeout: connect_timeout
|
379
|
+
enable_tcp_keepalive(socket)
|
380
|
+
if tls
|
381
|
+
cert_store = OpenSSL::X509::Store.new
|
382
|
+
cert_store.set_default_paths
|
383
|
+
context = OpenSSL::SSL::SSLContext.new
|
384
|
+
context.cert_store = cert_store
|
385
|
+
verify_peer = [false, "false", "none"].include? options[:verify_peer]
|
386
|
+
context.verify_mode = OpenSSL::SSL::VERIFY_PEER unless verify_peer
|
387
|
+
socket = OpenSSL::SSL::SSLSocket.new(socket, context)
|
388
|
+
socket.sync_close = true # closing the TLS socket also closes the TCP socket
|
389
|
+
socket.hostname = host # SNI host
|
390
|
+
socket.connect
|
391
|
+
socket.post_connection_check(host) || raise(Error, "TLS certificate hostname doesn't match requested")
|
392
|
+
end
|
393
|
+
socket
|
394
|
+
end
|
395
|
+
|
380
396
|
# Negotiate a connection
|
381
397
|
# @return [Array<Integer, Integer, Integer>] channel_max, frame_max, heartbeat
|
382
|
-
def
|
398
|
+
def establish(socket, user, password, vhost, options)
|
383
399
|
channel_max, frame_max, heartbeat = nil
|
384
400
|
socket.write "AMQP\x00\x00\x09\x01"
|
385
401
|
buf = String.new(capacity: 4096)
|
@@ -391,7 +407,7 @@ module AMQP
|
|
391
407
|
end
|
392
408
|
|
393
409
|
type, channel_id, frame_size = buf.unpack("C S> L>")
|
394
|
-
frame_end = buf
|
410
|
+
frame_end = buf.getbyte(frame_size + 7)
|
395
411
|
raise UnexpectedFrameEndError, frame_end if frame_end != 206
|
396
412
|
|
397
413
|
case type
|
@@ -437,7 +453,9 @@ module AMQP
|
|
437
453
|
raise e
|
438
454
|
end
|
439
455
|
|
440
|
-
|
456
|
+
# Enable TCP keepalive, which is prefered to heartbeats
|
457
|
+
# @return [void]
|
458
|
+
def enable_tcp_keepalive(socket)
|
441
459
|
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
|
442
460
|
socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE, 60)
|
443
461
|
socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL, 10)
|
@@ -446,14 +464,15 @@ module AMQP
|
|
446
464
|
warn "AMQP-Client could not enable TCP keepalive on socket. #{e.inspect}"
|
447
465
|
end
|
448
466
|
|
449
|
-
|
467
|
+
# Fetch the AMQP port number from ENV
|
468
|
+
# @return [Integer] A port number
|
469
|
+
# @return [nil] When the environment variable AMQP_PORT isn't set
|
470
|
+
def port_from_env
|
450
471
|
return unless (port = ENV["AMQP_PORT"])
|
451
472
|
|
452
473
|
port.to_i
|
453
474
|
end
|
454
475
|
|
455
|
-
private_class_method :establish, :enable_tcp_keepalive, :port_from_env
|
456
|
-
|
457
476
|
CLIENT_PROPERTIES = {
|
458
477
|
capabilities: {
|
459
478
|
authentication_failure_close: true,
|