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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 59f619f7aec324221f4c5dd66a10e5e30b1904a224d34702f94f9f75ac19f1c3
4
- data.tar.gz: 4d3e3fc1234f44ab741b15b61156617a7f8f181ce5fdc2aacbbc4c07a787129e
3
+ metadata.gz: ec3129a38420f19de4c0225aea1eaefc320463c6e032c54990a998f7654f8347
4
+ data.tar.gz: 3c8da5fccb818730958f47fdaf78837524381b116a322adaf585ee02d0492b2d
5
5
  SHA512:
6
- metadata.gz: 6ade93665b42b3c64ebed869be2af64cde53b259a6f376483c8c4d7e9de6432e2453be88caddf58655c249ae0d531a924348eb97c92b0a6a21b13a3276e3019b
7
- data.tar.gz: '0479fe0b0c6f120e4bf0c5b72b905d7946037f8b5e1d83f462c18c2df790c8ed67ff5e32be693deac570d7d82dc9411875f252384364feea3b6e8b85c6abd27d'
6
+ metadata.gz: 24b592bb7fc50f29499e32ca6348f2aa25bac9837e694740346fa24e1fe6c45c9f9ab6497f066ca6491c6f09e558b29ac3a1d84f5b31a3b2b68bb8c28cdf76e7
7
+ data.tar.gz: 6ad3e059d311894f4a023d75df89c72beb4508a2abc83083a47f4f7a07cc6f98edc157e06b5ed42eda55458612d89206de1cce4b0cc94fd598facad4433083e9
@@ -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
@@ -1,7 +1,7 @@
1
1
  inherit_from: .rubocop_todo.yml
2
2
 
3
3
  AllCops:
4
- TargetRubyVersion: 2.5
4
+ TargetRubyVersion: 2.6
5
5
 
6
6
  Style/StringLiterals:
7
7
  Enabled: true
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
@@ -10,3 +10,7 @@ gem "rake", "~> 13.0"
10
10
  gem "minitest", "~> 5.0"
11
11
 
12
12
  gem "rubocop", "~> 1.7"
13
+
14
+ gem "yard", require: false
15
+
16
+ gem "rubocop-minitest", require: false
data/README.md CHANGED
@@ -1,58 +1,63 @@
1
1
  # AMQP::Client
2
2
 
3
- An AMQP 0-9-1 Ruby client, trying to keep things as simple as possible.
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
- ## Installation
13
+ ## Usage
10
14
 
11
- Add this line to your application's Gemfile:
15
+ The client has two APIs.
12
16
 
13
- ```ruby
14
- gem 'amqp-client'
15
- ```
17
+ ### Low level API
16
18
 
17
- And then execute:
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
- $ bundle install
21
+ ```ruby
22
+ require "amqp-client"
20
23
 
21
- Or install it yourself as:
24
+ # Opens and establishes a connection
25
+ conn = AMQP::Client.new("amqp://guest:guest@localhost").connect
22
26
 
23
- $ gem install amqp-client
27
+ # Open a channel
28
+ ch = conn.channel
24
29
 
25
- ## Usage
30
+ # Create a temporary queue
31
+ q = ch.queue_declare
26
32
 
27
- Low level API
33
+ # Publish a message to said queue
34
+ ch.basic_publish "Hello World!", "", q.queue_name
28
35
 
29
- ```ruby
30
- require "amqp-client"
36
+ # Poll the queue for a message
37
+ msg = ch.basic_get q.queue_name
31
38
 
32
- c = AMQP::Client.new("amqp://guest:guest@localhost")
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, is an easier and safer API, that only deal with durable queues and persisted messages. All methods are blocking in the case of connection loss etc. It's also fully thread-safe. Don't expect it to have extreme throughput, but expect 100% delivery guarantees (messages might be delivered twice, in the unlikely event of connection loss between message publish and message confirmation by the broker).
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
- amqp = AMQP::Client.new("amqp://localhost")
45
- amqp.start
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
- q = amqp.queue("myqueue")
53
+ myqueue = amqp.queue("myqueue")
49
54
 
50
55
  # Bind the queue to any exchange, with any binding key
51
- q.bind("amq.topic", "my.events.*")
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
- q.subscribe(prefetch: 20) do |msg|
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
- q.publish { foo: "bar" }.to_json, content_type: "application/json"
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
@@ -11,6 +11,8 @@ end
11
11
 
12
12
  require "rubocop/rake_task"
13
13
 
14
- RuboCop::RakeTask.new
14
+ RuboCop::RakeTask.new do |task|
15
+ task.requires << "rubocop-minitest"
16
+ end
15
17
 
16
18
  task default: %i[test rubocop]
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.5.0")
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"
@@ -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
- case @unconfirmed_empty.pop
415
- when true then true
416
- when false then false
417
- else raise Error::Closed.new(@id, *@closed)
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
- @unconfirmed_empty.num_waiting.times do
437
- @unconfirmed_empty << (ack_or_nack == :ack)
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, nil, "")
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, nil, "", redelivered, consumer_tag)
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] Set to false if you manually want to run the {#read_loop}
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 self.connect(uri, read_loop_thread: true, **options)
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..-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 = Socket.tcp host, port, connect_timeout: 20, resolv_timeout: 5
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
- @socket.close
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.unpack1("@4 C")
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.unpack1("@4 C")
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.unpack1("@4 C")
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.unpack1("@4 C")
276
+ tag_len = buf.getbyte(4)
284
277
  tag = buf.byteslice(5, tag_len).force_encoding("utf-8")
285
- no_wait = buf[5 + tag_len].ord == 1
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.unpack1("@4 C")
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[pos].ord
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[pos].ord
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[4].ord
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[pos].ord
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[pos].ord
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
- _message_count = buf.byteslice(pos, 4).unpack1("L>")
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 self.establish(socket, user, password, vhost, options)
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[frame_size + 7].ord
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
- def self.enable_tcp_keepalive(socket)
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
- def self.port_from_env
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,