bunny 1.7.0 → 2.17.0

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.
Files changed (141) hide show
  1. checksums.yaml +5 -5
  2. data/.github/ISSUE_TEMPLATE.md +18 -0
  3. data/.gitignore +6 -1
  4. data/.rspec +1 -3
  5. data/.travis.yml +21 -14
  6. data/CONTRIBUTING.md +132 -0
  7. data/ChangeLog.md +745 -1
  8. data/Gemfile +13 -13
  9. data/LICENSE +1 -1
  10. data/README.md +41 -75
  11. data/Rakefile +54 -0
  12. data/bunny.gemspec +4 -10
  13. data/docker-compose.yml +28 -0
  14. data/docker/Dockerfile +24 -0
  15. data/docker/apt/preferences.d/erlang +3 -0
  16. data/docker/apt/sources.list.d/bintray.rabbitmq.list +2 -0
  17. data/docker/docker-entrypoint.sh +26 -0
  18. data/docker/rabbitmq.conf +29 -0
  19. data/examples/connection/automatic_recovery_with_basic_get.rb +1 -1
  20. data/examples/connection/automatic_recovery_with_client_named_queues.rb +1 -1
  21. data/examples/connection/automatic_recovery_with_multiple_consumers.rb +1 -1
  22. data/examples/connection/automatic_recovery_with_republishing.rb +1 -1
  23. data/examples/connection/automatic_recovery_with_server_named_queues.rb +1 -1
  24. data/examples/connection/channel_level_exception.rb +1 -9
  25. data/examples/connection/disabled_automatic_recovery.rb +1 -1
  26. data/examples/connection/heartbeat.rb +1 -1
  27. data/examples/consumers/high_and_low_priority.rb +1 -1
  28. data/examples/guides/extensions/alternate_exchange.rb +2 -0
  29. data/examples/guides/getting_started/hello_world.rb +2 -0
  30. data/examples/guides/getting_started/weathr.rb +2 -0
  31. data/examples/guides/queues/one_off_consumer.rb +2 -0
  32. data/examples/guides/queues/redeliveries.rb +2 -0
  33. data/lib/bunny.rb +6 -2
  34. data/lib/bunny/channel.rb +192 -109
  35. data/lib/bunny/channel_id_allocator.rb +6 -4
  36. data/lib/bunny/concurrent/continuation_queue.rb +34 -13
  37. data/lib/bunny/consumer_work_pool.rb +34 -6
  38. data/lib/bunny/cruby/socket.rb +29 -16
  39. data/lib/bunny/cruby/ssl_socket.rb +20 -7
  40. data/lib/bunny/exceptions.rb +7 -1
  41. data/lib/bunny/exchange.rb +11 -7
  42. data/lib/bunny/get_response.rb +1 -1
  43. data/lib/bunny/heartbeat_sender.rb +3 -2
  44. data/lib/bunny/jruby/socket.rb +23 -6
  45. data/lib/bunny/jruby/ssl_socket.rb +5 -0
  46. data/lib/bunny/queue.rb +12 -10
  47. data/lib/bunny/reader_loop.rb +31 -18
  48. data/lib/bunny/session.rb +389 -134
  49. data/lib/bunny/test_kit.rb +14 -0
  50. data/lib/bunny/timeout.rb +1 -12
  51. data/lib/bunny/transport.rb +114 -67
  52. data/lib/bunny/version.rb +1 -1
  53. data/repl +1 -1
  54. data/spec/config/rabbitmq.conf +13 -0
  55. data/spec/higher_level_api/integration/basic_ack_spec.rb +154 -22
  56. data/spec/higher_level_api/integration/basic_cancel_spec.rb +77 -11
  57. data/spec/higher_level_api/integration/basic_consume_spec.rb +60 -55
  58. data/spec/higher_level_api/integration/basic_consume_with_objects_spec.rb +6 -6
  59. data/spec/higher_level_api/integration/basic_get_spec.rb +31 -7
  60. data/spec/higher_level_api/integration/basic_nack_spec.rb +22 -19
  61. data/spec/higher_level_api/integration/basic_publish_spec.rb +11 -100
  62. data/spec/higher_level_api/integration/basic_qos_spec.rb +32 -4
  63. data/spec/higher_level_api/integration/basic_reject_spec.rb +94 -16
  64. data/spec/higher_level_api/integration/basic_return_spec.rb +4 -4
  65. data/spec/higher_level_api/integration/channel_close_spec.rb +51 -10
  66. data/spec/higher_level_api/integration/channel_open_spec.rb +12 -12
  67. data/spec/higher_level_api/integration/connection_recovery_spec.rb +412 -286
  68. data/spec/higher_level_api/integration/connection_spec.rb +284 -134
  69. data/spec/higher_level_api/integration/connection_stop_spec.rb +31 -19
  70. data/spec/higher_level_api/integration/consumer_cancellation_notification_spec.rb +17 -17
  71. data/spec/higher_level_api/integration/dead_lettering_spec.rb +14 -14
  72. data/spec/higher_level_api/integration/exchange_bind_spec.rb +5 -5
  73. data/spec/higher_level_api/integration/exchange_declare_spec.rb +32 -31
  74. data/spec/higher_level_api/integration/exchange_delete_spec.rb +12 -12
  75. data/spec/higher_level_api/integration/exchange_unbind_spec.rb +5 -5
  76. data/spec/higher_level_api/integration/exclusive_queue_spec.rb +5 -5
  77. data/spec/higher_level_api/integration/heartbeat_spec.rb +4 -4
  78. data/spec/higher_level_api/integration/message_properties_access_spec.rb +49 -49
  79. data/spec/higher_level_api/integration/predeclared_exchanges_spec.rb +2 -2
  80. data/spec/higher_level_api/integration/publisher_confirms_spec.rb +92 -27
  81. data/spec/higher_level_api/integration/publishing_edge_cases_spec.rb +19 -19
  82. data/spec/higher_level_api/integration/queue_bind_spec.rb +23 -23
  83. data/spec/higher_level_api/integration/queue_declare_spec.rb +129 -34
  84. data/spec/higher_level_api/integration/queue_delete_spec.rb +2 -2
  85. data/spec/higher_level_api/integration/queue_purge_spec.rb +5 -5
  86. data/spec/higher_level_api/integration/queue_unbind_spec.rb +6 -6
  87. data/spec/higher_level_api/integration/read_only_consumer_spec.rb +9 -9
  88. data/spec/higher_level_api/integration/sender_selected_distribution_spec.rb +10 -10
  89. data/spec/higher_level_api/integration/tls_connection_spec.rb +218 -112
  90. data/spec/higher_level_api/integration/toxiproxy_spec.rb +76 -0
  91. data/spec/higher_level_api/integration/tx_commit_spec.rb +1 -1
  92. data/spec/higher_level_api/integration/tx_rollback_spec.rb +1 -1
  93. data/spec/higher_level_api/integration/with_channel_spec.rb +2 -2
  94. data/spec/issues/issue100_spec.rb +11 -12
  95. data/spec/issues/issue141_spec.rb +13 -14
  96. data/spec/issues/issue202_spec.rb +1 -1
  97. data/spec/issues/issue224_spec.rb +5 -5
  98. data/spec/issues/issue465_spec.rb +32 -0
  99. data/spec/issues/issue549_spec.rb +30 -0
  100. data/spec/issues/issue78_spec.rb +21 -24
  101. data/spec/issues/issue83_spec.rb +5 -6
  102. data/spec/issues/issue97_spec.rb +44 -45
  103. data/spec/lower_level_api/integration/basic_cancel_spec.rb +15 -16
  104. data/spec/lower_level_api/integration/basic_consume_spec.rb +20 -21
  105. data/spec/spec_helper.rb +2 -19
  106. data/spec/stress/channel_close_stress_spec.rb +3 -3
  107. data/spec/stress/channel_open_stress_spec.rb +4 -4
  108. data/spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb +7 -7
  109. data/spec/stress/concurrent_consumers_stress_spec.rb +18 -16
  110. data/spec/stress/concurrent_publishers_stress_spec.rb +16 -19
  111. data/spec/stress/connection_open_close_spec.rb +9 -9
  112. data/spec/stress/merry_go_round_spec.rb +105 -0
  113. data/spec/tls/ca_certificate.pem +27 -16
  114. data/spec/tls/ca_key.pem +52 -27
  115. data/spec/tls/client_certificate.pem +27 -16
  116. data/spec/tls/client_key.pem +49 -25
  117. data/spec/tls/generate-server-cert.sh +8 -0
  118. data/spec/tls/server-openssl.cnf +10 -0
  119. data/spec/tls/server.csr +16 -0
  120. data/spec/tls/server_certificate.pem +27 -16
  121. data/spec/tls/server_key.pem +49 -25
  122. data/spec/toxiproxy_helper.rb +28 -0
  123. data/spec/unit/bunny_spec.rb +5 -5
  124. data/spec/unit/concurrent/atomic_fixnum_spec.rb +6 -6
  125. data/spec/unit/concurrent/condition_spec.rb +8 -8
  126. data/spec/unit/concurrent/linked_continuation_queue_spec.rb +2 -2
  127. data/spec/unit/concurrent/synchronized_sorted_set_spec.rb +16 -16
  128. data/spec/unit/exchange_recovery_spec.rb +39 -0
  129. data/spec/unit/version_delivery_tag_spec.rb +3 -3
  130. metadata +42 -35
  131. data/lib/bunny/system_timer.rb +0 -20
  132. data/spec/config/rabbitmq.config +0 -18
  133. data/spec/higher_level_api/integration/basic_recover_spec.rb +0 -18
  134. data/spec/higher_level_api/integration/confirm_select_spec.rb +0 -19
  135. data/spec/higher_level_api/integration/consistent_hash_exchange_spec.rb +0 -50
  136. data/spec/higher_level_api/integration/merry_go_round_spec.rb +0 -85
  137. data/spec/stress/long_running_consumer_spec.rb +0 -83
  138. data/spec/tls/cacert.pem +0 -18
  139. data/spec/tls/client_cert.pem +0 -18
  140. data/spec/tls/server_cert.pem +0 -18
  141. data/spec/unit/system_timer_spec.rb +0 -10
@@ -1,9 +1,23 @@
1
1
  # -*- coding: utf-8 -*-
2
+
3
+ require "timeout"
4
+
2
5
  module Bunny
3
6
  # Unit, integration and stress testing toolkit
4
7
  class TestKit
5
8
  class << self
6
9
 
10
+ def poll_while(timeout = 60, &probe)
11
+ Timeout.timeout(timeout) {
12
+ sleep 0.1 while probe.call
13
+ }
14
+ end
15
+ def poll_until(timeout = 60, &probe)
16
+ Timeout.timeout(timeout) {
17
+ sleep 0.1 until probe.call
18
+ }
19
+ end
20
+
7
21
  # @return [Integer] Random integer in the range of [a, b]
8
22
  # @api private
9
23
  def random_in_range(a, b)
@@ -1,16 +1,5 @@
1
1
  module Bunny
2
- # Unifies Ruby standard library's Timeout (which is not accurate on
3
- # Ruby 1.8) and SystemTimer (the gem)
4
- Timeout = if RUBY_VERSION < "1.9"
5
- begin
6
- require "bunny/system_timer"
7
- Bunny::SystemTimer
8
- rescue LoadError
9
- Timeout
10
- end
11
- else
12
- Timeout
13
- end
2
+ Timeout = ::Timeout
14
3
 
15
4
  # Backwards compatibility
16
5
  # @private
@@ -4,7 +4,7 @@ require "monitor"
4
4
 
5
5
  begin
6
6
  require "openssl"
7
- rescue LoadError => le
7
+ rescue LoadError => _le
8
8
  $stderr.puts "Could not load OpenSSL"
9
9
  end
10
10
 
@@ -20,19 +20,22 @@ module Bunny
20
20
  #
21
21
 
22
22
  # Default TCP connection timeout
23
- DEFAULT_CONNECTION_TIMEOUT = 25.0
23
+ DEFAULT_CONNECTION_TIMEOUT = 30.0
24
24
 
25
- DEFAULT_READ_TIMEOUT = 5.0
26
- DEFAULT_WRITE_TIMEOUT = 5.0
25
+ DEFAULT_READ_TIMEOUT = 30.0
26
+ DEFAULT_WRITE_TIMEOUT = 30.0
27
27
 
28
28
  attr_reader :session, :host, :port, :socket, :connect_timeout, :read_timeout, :write_timeout, :disconnect_timeout
29
- attr_reader :tls_context
29
+ attr_reader :tls_context, :verify_peer, :tls_ca_certificates, :tls_certificate_path, :tls_key_path
30
30
 
31
- attr_writer :read_timeout
31
+ def read_timeout=(v)
32
+ @read_timeout = v
33
+ @read_timeout = nil if @read_timeout == 0
34
+ end
32
35
 
33
36
  def initialize(session, host, port, opts)
34
37
  @session = session
35
- @session_thread = opts[:session_thread]
38
+ @session_error_handler = opts[:session_error_handler]
36
39
  @host = host
37
40
  @port = port
38
41
  @opts = opts
@@ -54,6 +57,8 @@ module Bunny
54
57
 
55
58
  @writes_mutex = @session.mutex_impl.new
56
59
 
60
+ @socket = nil
61
+
57
62
  prepare_tls_context(opts) if @tls_enabled
58
63
  end
59
64
 
@@ -77,10 +82,29 @@ module Bunny
77
82
 
78
83
 
79
84
  def connect
80
- if uses_ssl?
81
- @socket.connect
82
- if uses_tls? && @verify_peer
83
- @socket.post_connection_check(host)
85
+ if uses_tls?
86
+ begin
87
+ @socket.connect
88
+ rescue OpenSSL::SSL::SSLError => e
89
+ @logger.error { "TLS connection failed: #{e.message}" }
90
+ raise e
91
+ end
92
+
93
+ log_peer_certificate_info(Logger::DEBUG, @socket.peer_cert)
94
+ log_peer_certificate_chain_info(Logger::DEBUG, @socket.peer_cert_chain)
95
+
96
+ begin
97
+ @socket.post_connection_check(host) if @verify_peer
98
+ rescue OpenSSL::SSL::SSLError => e
99
+ @logger.error do
100
+ msg = "Peer verification of target server failed: #{e.message}. "
101
+ msg += "Target hostname: #{hostname}, see peer certificate chain details below."
102
+ msg
103
+ end
104
+ log_peer_certificate_info(Logger::ERROR, @socket.peer_cert)
105
+ log_peer_certificate_chain_info(Logger::ERROR, @socket.peer_cert_chain)
106
+
107
+ raise e
84
108
  end
85
109
 
86
110
  @status = :connected
@@ -92,7 +116,7 @@ module Bunny
92
116
  end
93
117
 
94
118
  def connected?
95
- :not_connected == @status && open?
119
+ :connected == @status && open?
96
120
  end
97
121
 
98
122
  def configure_socket(&block)
@@ -122,7 +146,7 @@ module Bunny
122
146
  if @session.automatically_recover?
123
147
  @session.handle_network_failure(e)
124
148
  else
125
- @session_thread.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
149
+ @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
126
150
  end
127
151
  end
128
152
  end
@@ -146,24 +170,25 @@ module Bunny
146
170
  if @session.automatically_recover?
147
171
  @session.handle_network_failure(e)
148
172
  else
149
- @session_thread.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
173
+ @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
150
174
  end
151
175
  end
152
176
  end
153
177
  end
154
178
 
155
179
  # Writes data to the socket without timeout checks
156
- def write_without_timeout(data)
180
+ def write_without_timeout(data, raise_exceptions = false)
157
181
  begin
158
182
  @writes_mutex.synchronize { @socket.write(data) }
159
183
  @socket.flush
160
184
  rescue SystemCallError, Bunny::ConnectionError, IOError => e
161
185
  close
186
+ raise e if raise_exceptions
162
187
 
163
188
  if @session.automatically_recover?
164
189
  @session.handle_network_failure(e)
165
190
  else
166
- @session_thread.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
191
+ @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
167
192
  end
168
193
  end
169
194
  end
@@ -218,9 +243,9 @@ module Bunny
218
243
  @status = :not_connected
219
244
 
220
245
  if @session.automatically_recover?
221
- @session.handle_network_failure(e)
246
+ raise
222
247
  else
223
- @session_thread.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
248
+ @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
224
249
  end
225
250
  end
226
251
  end
@@ -233,17 +258,13 @@ module Bunny
233
258
  # Exposed primarily for Bunny::Channel
234
259
  # @private
235
260
  def read_next_frame(opts = {})
236
- header = read_fully(7)
237
- # TODO: network issues here will sometimes cause
238
- # the socket method return an empty string. We need to log
239
- # and handle this better.
240
- # type, channel, size = begin
241
- # AMQ::Protocol::Frame.decode_header(header)
242
- # rescue AMQ::Protocol::EmptyResponseError => e
243
- # puts "Got AMQ::Protocol::EmptyResponseError, header is #{header.inspect}"
244
- # end
261
+ header = read_fully(7)
245
262
  type, channel, size = AMQ::Protocol::Frame.decode_header(header)
246
- payload = read_fully(size)
263
+ payload = if size > 0
264
+ read_fully(size)
265
+ else
266
+ ''
267
+ end
247
268
  frame_end = read_fully(1)
248
269
 
249
270
  # 1) the size is miscalculated
@@ -263,7 +284,7 @@ module Bunny
263
284
  :connect_timeout => timeout)
264
285
 
265
286
  true
266
- rescue SocketError, Timeout::Error => e
287
+ rescue SocketError, Timeout::Error => _e
267
288
  false
268
289
  ensure
269
290
  s.close if s
@@ -292,7 +313,7 @@ module Bunny
292
313
  end
293
314
 
294
315
  def post_initialize_socket
295
- @socket = if uses_tls?
316
+ @socket = if uses_tls? and !@socket.is_a?(Bunny::SSLSocketImpl)
296
317
  wrap_in_tls_socket(@socket)
297
318
  else
298
319
  @socket
@@ -302,23 +323,27 @@ module Bunny
302
323
  protected
303
324
 
304
325
  def tls_enabled?(opts)
305
- return opts[:tls] unless opts[:tls].nil?
306
- return opts[:ssl] unless opts[:ssl].nil?
326
+ return !!opts[:tls] unless opts[:tls].nil?
327
+ return !!opts[:ssl] unless opts[:ssl].nil?
307
328
  (opts[:port] == AMQ::Protocol::TLS_PORT) || false
308
329
  end
309
330
 
331
+ def tls_ca_certificates_paths_from(opts)
332
+ Array(opts[:cacertfile] || opts[:tls_ca_certificates] || opts[:ssl_ca_certificates])
333
+ end
334
+
310
335
  def tls_certificate_path_from(opts)
311
- opts[:tls_cert] || opts[:ssl_cert] || opts[:tls_cert_path] || opts[:ssl_cert_path] || opts[:tls_certificate_path] || opts[:ssl_certificate_path]
336
+ opts[:certfile] || opts[:tls_cert] || opts[:ssl_cert] || opts[:tls_cert_path] || opts[:ssl_cert_path] || opts[:tls_certificate_path] || opts[:ssl_certificate_path]
312
337
  end
313
338
 
314
339
  def tls_key_path_from(opts)
315
- opts[:tls_key] || opts[:ssl_key] || opts[:tls_key_path] || opts[:ssl_key_path]
340
+ opts[:keyfile] || opts[:tls_key] || opts[:ssl_key] || opts[:tls_key_path] || opts[:ssl_key_path]
316
341
  end
317
342
 
318
343
  def tls_certificate_from(opts)
319
344
  begin
320
345
  read_client_certificate!
321
- rescue MissingTLSCertificateFile => e
346
+ rescue MissingTLSCertificateFile => _e
322
347
  inline_client_certificate_from(opts)
323
348
  end
324
349
  end
@@ -326,11 +351,34 @@ module Bunny
326
351
  def tls_key_from(opts)
327
352
  begin
328
353
  read_client_key!
329
- rescue MissingTLSKeyFile => e
354
+ rescue MissingTLSKeyFile => _e
330
355
  inline_client_key_from(opts)
331
356
  end
332
357
  end
333
358
 
359
+ def peer_certificate_info(peer_cert, prefix = "Peer's leaf certificate")
360
+ exts = peer_cert.extensions.map { |x| x.value }
361
+ # Subject Alternative Names
362
+ sans = exts.select { |s| s =~ /^DNS/ }.map { |s| s.gsub(/^DNS:/, "") }
363
+
364
+ msg = "#{prefix} subject: #{peer_cert.subject}, "
365
+ msg += "subject alternative names: #{sans.join(', ')}, "
366
+ msg += "issuer: #{peer_cert.issuer}, "
367
+ msg += "not valid after: #{peer_cert.not_after}, "
368
+ msg += "X.509 usage extensions: #{exts.join(', ')}"
369
+
370
+ msg
371
+ end
372
+
373
+ def log_peer_certificate_info(severity, peer_cert, prefix = "Peer's leaf certificate")
374
+ @logger.add(severity) { peer_certificate_info(peer_cert, prefix) }
375
+ end
376
+
377
+ def log_peer_certificate_chain_info(severity, chain)
378
+ chain.each do |cert|
379
+ self.log_peer_certificate_info(severity, cert, "Peer's certificate chain entry")
380
+ end
381
+ end
334
382
 
335
383
  def inline_client_certificate_from(opts)
336
384
  opts[:tls_certificate] || opts[:ssl_cert_string] || opts[:tls_cert]
@@ -341,7 +389,7 @@ module Bunny
341
389
  end
342
390
 
343
391
  def prepare_tls_context(opts)
344
- if (opts[:verify_ssl] || opts[:verify_peer]).nil?
392
+ if opts.values_at(:verify_ssl, :verify_peer, :verify).all?(&:nil?)
345
393
  opts[:verify_peer] = true
346
394
  end
347
395
 
@@ -353,17 +401,32 @@ module Bunny
353
401
  @tls_key = tls_key_from(opts)
354
402
  @tls_certificate_store = opts[:tls_certificate_store]
355
403
 
356
- @tls_ca_certificates = opts.fetch(:tls_ca_certificates, default_tls_certificates)
357
- @verify_peer = (opts[:verify_ssl] || opts[:verify_peer])
404
+ @verify_peer = as_boolean(opts[:verify_ssl] || opts[:verify_peer] || opts[:verify])
358
405
 
359
406
  @tls_context = initialize_tls_context(OpenSSL::SSL::SSLContext.new, opts)
360
407
  end
361
408
 
409
+ def as_boolean(val)
410
+ case val
411
+ when true then true
412
+ when false then false
413
+ when "true" then true
414
+ when "false" then false
415
+ else
416
+ !!val
417
+ end
418
+ end
419
+
362
420
  def wrap_in_tls_socket(socket)
363
421
  raise ArgumentError, "cannot wrap nil into TLS socket, @tls_context is nil. This is a Bunny bug." unless socket
364
422
  raise "cannot wrap a socket into TLS socket, @tls_context is nil. This is a Bunny bug." unless @tls_context
365
423
 
366
424
  s = Bunny::SSLSocketImpl.new(socket, @tls_context)
425
+
426
+ # always set the SNI server name if possible since RFC 3546 and RFC 6066 both state
427
+ # that TLS clients supporting the extensions can talk to TLS servers that do not
428
+ s.hostname = @host if s.respond_to?(:hostname)
429
+
367
430
  s.sync_close = true
368
431
  s
369
432
  end
@@ -396,13 +459,15 @@ module Bunny
396
459
  ctx.cert_store = if @tls_certificate_store
397
460
  @tls_certificate_store
398
461
  else
462
+ # this ivar exists so that this value can be exposed in the API
463
+ @tls_ca_certificates = tls_ca_certificates_paths_from(opts)
399
464
  initialize_tls_certificate_store(@tls_ca_certificates)
400
465
  end
401
466
 
402
467
  if !@tls_certificate
403
468
  @logger.warn <<-MSG
404
- Using TLS but no client certificate is provided! If RabbitMQ is configured to verify peer
405
- certificate, connection upgrade will fail!
469
+ Using TLS but no client certificate is provided. If RabbitMQ is configured to require & verify peer
470
+ certificate, connection will be rejected. Learn more at https://www.rabbitmq.com/ssl.html
406
471
  MSG
407
472
  end
408
473
  if @tls_certificate && !@tls_key
@@ -414,12 +479,13 @@ certificate, connection upgrade will fail!
414
479
  else
415
480
  OpenSSL::SSL::VERIFY_NONE
416
481
  end
482
+ @logger.debug { "Will use peer verification mode #{verify_mode}" }
417
483
  ctx.verify_mode = verify_mode
418
484
 
419
485
  if !@verify_peer
420
486
  @logger.warn <<-MSG
421
487
  Using TLS but peer hostname verification is disabled. This is convenient for local development
422
- but prone man-in-the-middle attacks. Please set :verify_peer => true in production!
488
+ but prone to man-in-the-middle attacks. Please set verify_peer: true in production. Learn more at https://www.rabbitmq.com/ssl.html
423
489
  MSG
424
490
  end
425
491
 
@@ -429,41 +495,22 @@ but prone man-in-the-middle attacks. Please set :verify_peer => true in producti
429
495
  ctx
430
496
  end
431
497
 
432
- def default_tls_certificates
433
- if defined?(JRUBY_VERSION)
434
- # see https://github.com/jruby/jruby/issues/1055. MK.
435
- []
436
- else
437
- default_ca_file = ENV[OpenSSL::X509::DEFAULT_CERT_FILE_ENV] || OpenSSL::X509::DEFAULT_CERT_FILE
438
- default_ca_path = ENV[OpenSSL::X509::DEFAULT_CERT_DIR_ENV] || OpenSSL::X509::DEFAULT_CERT_DIR
439
-
440
- [
441
- default_ca_file,
442
- File.join(default_ca_path, 'ca-certificates.crt'), # Ubuntu/Debian
443
- File.join(default_ca_path, 'ca-bundle.crt'), # Amazon Linux & Fedora/RHEL
444
- File.join(default_ca_path, 'ca-bundle.pem') # OpenSUSE
445
- ].uniq
446
- end
447
- end
448
-
449
498
  def initialize_tls_certificate_store(certs)
450
499
  cert_files = []
451
500
  cert_inlines = []
452
501
  certs.each do |cert|
453
- # if it starts with / then it's a file path that may or may not
454
- # exists (e.g. a default OpenSSL path). MK.
455
- if File.readable?(cert) || cert =~ /^\//
502
+ # if it starts with / or C:/ then it's a file path that may or may not
503
+ # exist (e.g. a default OpenSSL path). MK.
504
+ if File.readable?(cert) || cert =~ /^([a-z]:?)?\//i
456
505
  cert_files.push(cert)
457
506
  else
458
507
  cert_inlines.push(cert)
459
508
  end
460
509
  end
461
- @logger.debug "Using CA certificates at #{cert_files.join(', ')}"
462
- @logger.debug "Using #{cert_inlines.count} inline CA certificates"
463
- if certs.empty?
464
- @logger.error "No CA certificates found, add one with :tls_ca_certificates"
465
- end
510
+ @logger.debug { "Using CA certificates at #{cert_files.join(', ')}" }
511
+ @logger.debug { "Using #{cert_inlines.count} inline CA certificates" }
466
512
  OpenSSL::X509::Store.new.tap do |store|
513
+ store.set_default_paths
467
514
  cert_files.select { |path| File.readable?(path) }.
468
515
  each { |path| store.add_file(path) }
469
516
  cert_inlines.
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Bunny
4
4
  # @return [String] Version of the library
5
- VERSION = "1.7.0"
5
+ VERSION = "2.17.0"
6
6
  end
data/repl CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/bin/sh
2
2
 
3
- bundle exec irb -Ilib -r'bunny'
3
+ bundle exec ruby `which ripl` -Ilib -r'bunny' -r'ripl/multi_line' -r'ripl/irb'
@@ -0,0 +1,13 @@
1
+ listeners.tcp.1 = 0.0.0.0:5672
2
+
3
+ listeners.ssl.default = 5671
4
+
5
+ # mounted by docker-compose
6
+ ssl_options.cacertfile = /spec/tls/ca_certificate.pem
7
+ ssl_options.certfile = /spec/tls/server_certificate.pem
8
+ ssl_options.keyfile = /spec/tls/server_key.pem
9
+
10
+ ssl_options.verify = verify_none
11
+ ssl_options.fail_if_no_peer_cert = false
12
+
13
+ loopback_users = none
@@ -2,7 +2,7 @@ require "spec_helper"
2
2
 
3
3
  describe Bunny::Channel, "#ack" do
4
4
  let(:connection) do
5
- c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed")
5
+ c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed")
6
6
  c.start
7
7
  c
8
8
  end
@@ -14,50 +14,98 @@ describe Bunny::Channel, "#ack" do
14
14
  context "with a valid (known) delivery tag" do
15
15
  it "acknowledges a message" do
16
16
  ch = connection.create_channel
17
- q = ch.queue("bunny.basic.ack.manual-acks", :exclusive => true)
17
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
18
18
  x = ch.default_exchange
19
19
 
20
- x.publish("bunneth", :routing_key => q.name)
20
+ x.publish("bunneth", routing_key: q.name)
21
21
  sleep 0.5
22
- q.message_count.should == 1
23
- delivery_details, properties, content = q.pop(:manual_ack => true)
22
+ expect(q.message_count).to eq 1
23
+ delivery_details, properties, content = q.pop(manual_ack: true)
24
24
 
25
25
  ch.ack(delivery_details.delivery_tag, true)
26
- q.message_count.should == 0
26
+ ch.close
27
+
28
+ ch = connection.create_channel
29
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
30
+ expect(q.message_count).to eq 0
31
+ ch.close
32
+ end
33
+ end
34
+
35
+ context "with a valid (known) delivery tag (multiple = true)" do
36
+ it "acknowledges a message" do
37
+ ch = connection.create_channel
38
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
39
+ x = ch.default_exchange
27
40
 
41
+ x.publish("bunneth", routing_key: q.name)
42
+ x.publish("bunneth", routing_key: q.name)
43
+ sleep 0.5
44
+ expect(q.message_count).to eq 2
45
+ delivery_details_1, _properties, _content = q.pop(manual_ack: true)
46
+ delivery_details_2, _properties, _content = q.pop(manual_ack: true)
47
+
48
+ ch.ack(delivery_details_2.delivery_tag, true)
49
+ ch.close
50
+
51
+ ch = connection.create_channel
52
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
53
+ expect(q.message_count).to eq 0
28
54
  ch.close
29
55
  end
30
56
  end
31
57
 
58
+ context "with a valid (known) delivery tag (multiple = false)" do
59
+ it "acknowledges a message" do
60
+ ch = connection.create_channel
61
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
62
+ x = ch.default_exchange
63
+
64
+ x.publish("bunneth", routing_key: q.name)
65
+ x.publish("bunneth", routing_key: q.name)
66
+ sleep 0.5
67
+ expect(q.message_count).to eq 2
68
+ delivery_details_1, _properties, _content = q.pop(manual_ack: true)
69
+ delivery_details_2, _properties, _content = q.pop(manual_ack: true)
70
+
71
+ ch.ack(delivery_details_2.delivery_tag, false)
72
+ ch.close
73
+
74
+ ch = connection.create_channel
75
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
76
+ expect(q.message_count).to eq 1
77
+ ch.close
78
+ end
79
+ end
32
80
 
33
81
  context "with a valid (known) delivery tag and automatic ack mode" do
34
82
  it "results in a channel exception" do
35
83
  ch = connection.create_channel
36
- q = ch.queue("bunny.basic.ack.manual-acks", :exclusive => true)
84
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
37
85
  x = ch.default_exchange
38
86
 
39
- q.subscribe(:manual_ack => false) do |delivery_info, properties, payload|
87
+ q.subscribe(manual_ack: false) do |delivery_info, properties, payload|
40
88
  ch.ack(delivery_info.delivery_tag, false)
41
89
  end
42
90
 
43
- x.publish("bunneth", :routing_key => q.name)
91
+ x.publish("bunneth", routing_key: q.name)
44
92
  sleep 0.5
45
- lambda do
93
+ expect do
46
94
  q.message_count
47
- end.should raise_error(Bunny::ChannelAlreadyClosed)
95
+ end.to raise_error(Bunny::ChannelAlreadyClosed)
48
96
  end
49
97
  end
50
98
 
51
99
  context "with an invalid (random) delivery tag" do
52
100
  it "causes a channel-level error" do
53
101
  ch = connection.create_channel
54
- q = ch.queue("bunny.basic.ack.unknown-delivery-tag", :exclusive => true)
102
+ q = ch.queue("bunny.basic.ack.unknown-delivery-tag", exclusive: true)
55
103
  x = ch.default_exchange
56
104
 
57
- x.publish("bunneth", :routing_key => q.name)
105
+ x.publish("bunneth", routing_key: q.name)
58
106
  sleep 0.5
59
- q.message_count.should == 1
60
- _, _, content = q.pop(:manual_ack => true)
107
+ expect(q.message_count).to eq 1
108
+ _, _, content = q.pop(manual_ack: true)
61
109
 
62
110
  ch.on_error do |ch, channel_close|
63
111
  @channel_close = channel_close
@@ -65,33 +113,117 @@ describe Bunny::Channel, "#ack" do
65
113
  ch.ack(82, true)
66
114
  sleep 0.25
67
115
 
68
- @channel_close.reply_code.should == AMQ::Protocol::PreconditionFailed::VALUE
116
+ expect(@channel_close.reply_code).to eq AMQ::Protocol::PreconditionFailed::VALUE
69
117
  end
70
118
  end
71
119
 
72
120
  context "with a valid (known) delivery tag" do
73
121
  it "gets a depricated message warning for using :ack" do
74
122
  ch = connection.create_channel
75
- q = ch.queue("bunny.basic.ack.manual-acks", :exclusive => true)
123
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
76
124
  x = ch.default_exchange
77
125
 
78
- x.publish("bunneth", :routing_key => q.name)
126
+ x.publish("bunneth", routing_key: q.name)
79
127
  sleep 0.5
80
- q.message_count.should == 1
128
+ expect(q.message_count).to eq 1
81
129
 
82
130
  orig_stderr = $stderr
83
131
  $stderr = StringIO.new
84
132
 
85
- delivery_details, properties, content = q.pop(:ack => true)
133
+ delivery_details, properties, content = q.pop(ack: true)
86
134
 
87
135
  $stderr.rewind
88
- $stderr.string.chomp.should eq("[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead.\n[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead.")
136
+ expect($stderr.string.chomp).to eq("[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead.\n[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead.")
89
137
 
90
138
  $stderr = orig_stderr
91
139
 
92
140
  ch.ack(delivery_details.delivery_tag, true)
93
- q.message_count.should == 0
141
+ ch.close
142
+
143
+ ch = connection.create_channel
144
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
145
+ expect(q.message_count).to eq 0
146
+ ch.close
147
+ end
148
+ end
149
+ end
150
+
151
+ describe Bunny::Channel, "#basic_ack" do
152
+ let(:connection) do
153
+ c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed")
154
+ c.start
155
+ c
156
+ end
94
157
 
158
+ after :each do
159
+ connection.close if connection.open?
160
+ end
161
+
162
+ context "with a valid (known) delivery tag (multiple = true)" do
163
+ it "acknowledges a message" do
164
+ ch = connection.create_channel
165
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
166
+ x = ch.default_exchange
167
+
168
+ x.publish("bunneth", routing_key: q.name)
169
+ x.publish("bunneth", routing_key: q.name)
170
+ sleep 0.5
171
+ expect(q.message_count).to eq 2
172
+ delivery_details_1, _properties, _content = q.pop(manual_ack: true)
173
+ delivery_details_2, _properties, _content = q.pop(manual_ack: true)
174
+
175
+ ch.basic_ack(delivery_details_2.delivery_tag.to_i, true)
176
+ ch.close
177
+
178
+ ch = connection.create_channel
179
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
180
+ expect(q.message_count).to eq 0
181
+ ch.close
182
+ end
183
+ end
184
+
185
+ context "with a valid (known) delivery tag (multiple = false)" do
186
+ it "acknowledges a message" do
187
+ ch = connection.create_channel
188
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
189
+ x = ch.default_exchange
190
+
191
+ x.publish("bunneth", routing_key: q.name)
192
+ x.publish("bunneth", routing_key: q.name)
193
+ sleep 0.5
194
+ expect(q.message_count).to eq 2
195
+ delivery_details_1, _properties, _content = q.pop(manual_ack: true)
196
+ delivery_details_2, _properties, _content = q.pop(manual_ack: true)
197
+
198
+ ch.basic_ack(delivery_details_2.delivery_tag.to_i, false)
199
+ ch.close
200
+
201
+ ch = connection.create_channel
202
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
203
+ expect(q.message_count).to eq 1
204
+ ch.close
205
+ end
206
+ end
207
+
208
+ context "with a valid (known) delivery tag (multiple = default)" do
209
+ it "acknowledges a message" do
210
+ ch = connection.create_channel
211
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
212
+ x = ch.default_exchange
213
+
214
+ x.publish("bunneth", routing_key: q.name)
215
+ x.publish("bunneth", routing_key: q.name)
216
+ sleep 0.5
217
+ expect(q.message_count).to eq 2
218
+ delivery_details_1, _properties, _content = q.pop(manual_ack: true)
219
+ delivery_details_2, _properties, _content = q.pop(manual_ack: true)
220
+
221
+ ch.basic_ack(delivery_details_2.delivery_tag.to_i)
222
+ ch.close
223
+
224
+ ch = connection.create_channel
225
+ q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true)
226
+ expect(q.message_count).to eq 1
95
227
  ch.close
96
228
  end
97
229
  end