bunny 1.0.0.rc2 → 1.0.0.rc3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7a93cb605a6fdd64b33d36cf15c2348e3a63d1f4
4
- data.tar.gz: fb0cf9a5bbfa5d87a01a3e88ac5b4e194912cb6a
3
+ metadata.gz: b96591dc64422653dd1e514413d72e086cd2943b
4
+ data.tar.gz: b7b4930751e88b767acaa8ab7d6db95098cb7b26
5
5
  SHA512:
6
- metadata.gz: 9dddb5e6345033db760b2b12be7c5bd61831d5a68d64b6b471c22557beae60d2a48aec300becea2509bfe66b5acf25ce606e7f8ed6a37c9be279ffb365da326c
7
- data.tar.gz: eaf13dac3eb2d00be80afd360890dc85633054ff2acdb899fb0166958e4ca0a2ec5dd09fa1bc9d71f8e6e1d464773398e5d4d3673283eb081cb5e89baa157f9c
6
+ metadata.gz: e28a7d255892d518d0be18155546ab62702d59605219d736008552fd9e463e66d28ab768065eb81e08062d9ef08e01b8f8ef0ebe5a4873ae08edab5c9a2b0d08
7
+ data.tar.gz: 2a51e2d4c244f06c69b71cf1465c3fdff9a8d1d6b54069e2a62a7c26ce3f447df3aa209b67efd77e5925d46478d4c8f76b219bda6bb42ed6b454572be14b96be
@@ -1,3 +1,4 @@
1
+ language: ruby
1
2
  bundler_args: --without development
2
3
  before_script: "./bin/ci/before_build.sh"
3
4
  script: "bundle exec rspec -cfs spec"
@@ -1,3 +1,56 @@
1
+ ## Changes between Bunny 1.0.0.rc2 and 1.0.0.rc3
2
+
3
+ ### Support [Authentication Failure Notification](http://www.rabbitmq.com/auth-notification.html)
4
+
5
+ `Bunny::AuthenticationFailureError` is a new auth failure exception
6
+ that subclasses `Bunny::PossibleAuthenticationFailureError` for
7
+ backwards compatibility.
8
+
9
+ As such, `Bunny::PossibleAuthenticationFailureError`'s error message
10
+ has changed.
11
+
12
+ This extension is available in RabbitMQ 3.2+.
13
+
14
+
15
+ ### Bunny::Session#exchange_exists?
16
+
17
+ `Bunny::Session#exchange_exists?` is a new predicate that makes it
18
+ easier to check if a exchange exists.
19
+
20
+ It uses a one-off channel and `exchange.declare` with `passive` set to true
21
+ under the hood.
22
+
23
+ ### Bunny::Session#queue_exists?
24
+
25
+ `Bunny::Session#queue_exists?` is a new predicate that makes it
26
+ easier to check if a queue exists.
27
+
28
+ It uses a one-off channel and `queue.declare` with `passive` set to true
29
+ under the hood.
30
+
31
+
32
+ ### Inline TLS Certificates and Keys
33
+
34
+ It is now possible to provide inline client
35
+ certificate and private key (as strings) instead
36
+ of filesystem paths. The options are the same:
37
+
38
+ * `:tls` which, when set to `true`, will set SSL context up and switch to TLS port (5671)
39
+ * `:tls_cert` which now can be a client certificate (public key) in PEM format
40
+ * `:tls_key` which now can be a client key (private key) in PEM format
41
+ * `:tls_ca_certificates` which is an array of string paths to CA certificates in PEM format
42
+
43
+ For example:
44
+
45
+ ``` ruby
46
+ conn = Bunny.new(:tls => true,
47
+ :tls_cert => ENV["TLS_CERTIFICATE"],
48
+ :tls_key => ENV["TLS_PRIVATE_KEY"],
49
+ :tls_ca_certificates => ["./examples/tls/cacert.pem"])
50
+ ```
51
+
52
+
53
+
1
54
  ## Changes between Bunny 1.0.0.rc1 and 1.0.0.rc2
2
55
 
3
56
  ### Ruby 1.8.7 Compatibility Fixes
data/Gemfile CHANGED
@@ -30,7 +30,7 @@ group :development do
30
30
  gem "redcarpet", :platform => :mri
31
31
  gem "ruby-prof", :platform => :mri
32
32
 
33
- gem "json"
33
+ gem "json", :platform => :ruby_18
34
34
  end
35
35
 
36
36
  group :test do
data/README.md CHANGED
@@ -61,11 +61,13 @@ Bunny `0.7.x` and earlier versions support RabbitMQ 1.x and 2.x.
61
61
 
62
62
  ## Project Maturity
63
63
 
64
- Bunny is a pretty old (started circa late 2008) library that, before
65
- version 0.9, **a lot** of missing functionality. Version 0.9 can be
66
- considered to be "second birthday" for Bunny as it was rewritten from
67
- scratch with over a dozen of preview releases over the course of about
68
- a year.
64
+ Bunny is a mature library (started in early 2009) library with
65
+ a stable public API.
66
+
67
+ Before version 0.9, **a lot** of functionality was missing. Version
68
+ 0.9 can be considered to be "second birthday" for Bunny as it was
69
+ rewritten from scratch with over a dozen of preview releases over the
70
+ course of about a year.
69
71
 
70
72
  We (the maintainers) made our best effort to keep the new version as
71
73
  backwards compatible as possible but within reason.
@@ -90,7 +92,7 @@ gem install bunny
90
92
  To use Bunny in a project managed with Bundler:
91
93
 
92
94
  ``` ruby
93
- gem "bunny", ">= 1.0.0.rc1"
95
+ gem "bunny", ">= 1.0.0.rc2"
94
96
  ```
95
97
 
96
98
 
@@ -177,15 +179,21 @@ More detailed announcements can be found in the blogs
177
179
 
178
180
  ### Reporting Issues
179
181
 
180
- If you find a bug, poor default, missing feature or find any part of the API inconvenient, please [file an issue](http://github.com/ruby-amqp/bunny/issues) on GitHub.
181
- When filing an issue, please specify which Bunny and RabbitMQ versions you are using, provide recent RabbitMQ log file contents if possible,
182
- and try to explain what behavior you expected and why. Bonus points for contributing failing test cases.
182
+ If you find a bug, poor default, missing feature or find any part of
183
+ the API inconvenient, please [file an
184
+ issue](http://github.com/ruby-amqp/bunny/issues) on GitHub. When
185
+ filing an issue, please specify which Bunny and RabbitMQ versions you
186
+ are using, provide recent RabbitMQ log file contents if possible, and
187
+ try to explain what behavior you expected and why. Bonus points for
188
+ contributing failing test cases.
183
189
 
184
190
 
185
191
  ## Other Ruby RabbitMQ Clients
186
192
 
187
- Other widely used Ruby RabbitMQ clients are [Hot Bunnies](http://github.com/ruby-amqp/hot_bunnies) (JRuby-only) and [amqp gem](http://rubyamqp.info).
188
- Both are mature libraries and require RabbitMQ 2.x or 3.x.
193
+ Other widely used Ruby RabbitMQ clients are [March
194
+ Hare](http://rubymarchhare.info) (JRuby-only) and [amqp
195
+ gem](http://rubyamqp.info). Both are mature libraries and require
196
+ RabbitMQ 2.x or 3.x.
189
197
 
190
198
 
191
199
  ## Contributing
@@ -202,7 +210,7 @@ then set up RabbitMQ vhosts with
202
210
 
203
211
  and then run tests with
204
212
 
205
- ./bin/rspec -cfs spec
213
+ CI=true ./bin/rspec -cfs spec
206
214
 
207
215
  After that create a branch and make your changes on it. Once you are done with your changes and all tests pass, submit a pull request
208
216
  on GitHub.
@@ -157,6 +157,7 @@ module Bunny
157
157
  # @return [Hash<String, Bunny::Consumer>] Consumer instances declared on this channel
158
158
  attr_reader :consumers
159
159
 
160
+ DEFAULT_CONTENT_TYPE = "application/octet-stream".freeze
160
161
 
161
162
  # @param [Bunny::Session] connection AMQP 0.9.1 connection
162
163
  # @param [Integer] id Channel id, pass nil to make Bunny automatically allocate it
@@ -524,23 +525,25 @@ module Bunny
524
525
  else
525
526
  1
526
527
  end
527
- meta = { :priority => 0, :delivery_mode => mode, :content_type => "application/octet-stream" }.
528
- merge(opts)
528
+
529
+ opts[:delivery_mode] ||= mode
530
+ opts[:content_type] ||= DEFAULT_CONTENT_TYPE
531
+ opts[:priority] ||= 0
529
532
 
530
533
  if @next_publish_seq_no > 0
531
534
  @unconfirmed_set.add(@next_publish_seq_no)
532
535
  @next_publish_seq_no += 1
533
536
  end
534
537
 
535
- m = AMQ::Protocol::Basic::Publish.encode(@id,
538
+ frames = AMQ::Protocol::Basic::Publish.encode(@id,
536
539
  payload,
537
- meta,
540
+ opts,
538
541
  exchange_name,
539
542
  routing_key,
540
- meta[:mandatory],
543
+ opts[:mandatory],
541
544
  false,
542
545
  @connection.frame_max)
543
- @connection.send_frameset_without_timeout(m, self)
546
+ @connection.send_frameset_without_timeout(frames, self)
544
547
 
545
548
  self
546
549
  end
@@ -789,8 +792,8 @@ module Bunny
789
792
  #
790
793
  # @param [String, Bunny::Queue] queue Queue to consume from
791
794
  # @param [String] consumer_tag Consumer tag (unique identifier), generated by Bunny by default
792
- # @param [Boolean] no_ack (false) If false, delivered messages will be automatically acknowledged.
793
- # If true, manual acknowledgements will be necessary.
795
+ # @param [Boolean] no_ack (false) If true, delivered messages will be automatically acknowledged.
796
+ # If false, manual acknowledgements will be necessary.
794
797
  # @param [Boolean] exclusive (false) Should this consumer be exclusive?
795
798
  # @param [Hash] arguments (nil) Optional arguments that may be used by RabbitMQ extensions, etc
796
799
  #
@@ -9,7 +9,6 @@ module Bunny
9
9
  # API
10
10
  #
11
11
 
12
- # @api public
13
12
  # @private
14
13
  def channel_from(channel_or_connection)
15
14
  # Bunny 0.8.x and earlier completely hide channels from the API. So, queues and exchanges are
@@ -26,8 +26,8 @@ module Bunny
26
26
  # @param [Bunny::Queue,String] queue Queue messages will be consumed from
27
27
  # @param [String] consumer_tag Consumer tag (unique identifier). Generally it is better to let Bunny generate one.
28
28
  # Empty string means RabbitMQ will generate consumer tag.
29
- # @param [Boolean] no_ack (false) If false, delivered messages will be automatically acknowledged.
30
- # If true, manual acknowledgements will be necessary.
29
+ # @param [Boolean] no_ack (true) If true, delivered messages will be automatically acknowledged.
30
+ # If false, manual acknowledgements will be necessary.
31
31
  # @param [Boolean] exclusive (false) Should this consumer be exclusive?
32
32
  # @param [Hash] arguments (nil) Optional arguments that may be used by RabbitMQ extensions, etc
33
33
  # @api public
@@ -93,13 +93,13 @@ module Bunny
93
93
  # @return [Boolean] true if this consumer uses automatic acknowledgement mode
94
94
  # @api public
95
95
  def automatic_acknowledgement?
96
- @no_ack == false
96
+ @no_ack == true
97
97
  end
98
98
 
99
99
  # @return [Boolean] true if this consumer uses manual (explicit) acknowledgement mode
100
100
  # @api public
101
101
  def manual_acknowledgement?
102
- @no_ack == true
102
+ @no_ack == false
103
103
  end
104
104
 
105
105
  #
@@ -87,11 +87,31 @@ module Bunny
87
87
  @username = username
88
88
  @vhost = vhost
89
89
 
90
- super("RabbitMQ closed TCP connection before authentication succeeded: this usually means authentication failure due to misconfiguration or that RabbitMQ version does not support AMQP 0.9.1. Please check your configuration. Username: #{username}, vhost: #{vhost}, password length: #{password_length}")
90
+ super("Authentication with RabbitMQ failed. Please check your connection settings. Username: #{username}, vhost: #{vhost}, password length: #{password_length}")
91
91
  end # initialize(settings)
92
92
  end # PossibleAuthenticationFailureError
93
93
 
94
94
 
95
+ # Raised when RabbitMQ closes TCP connection due to an authentication failure.
96
+ # Relies on RabbitMQ 3.2 Authentication Failure Notifications extension:
97
+ # http://www.rabbitmq.com/auth-notification.html
98
+ class AuthenticationFailureError < PossibleAuthenticationFailureError
99
+
100
+ #
101
+ # API
102
+ #
103
+
104
+ attr_reader :username, :vhost
105
+
106
+ def initialize(username, vhost, password_length)
107
+ @username = username
108
+ @vhost = vhost
109
+
110
+ super(username, vhost, password_length)
111
+ end # initialize(settings)
112
+ end # AuthenticationFailureError
113
+
114
+
95
115
  # backwards compatibility
96
116
  # @private
97
117
  ConnectionError = TCPConnectionFailed
@@ -206,4 +226,11 @@ module Bunny
206
226
  class ConnectionForced < ConnectionLevelException
207
227
  end
208
228
 
229
+ # @private
230
+ class MissingTLSCertificateFile < Exception
231
+ end
232
+
233
+ # @private
234
+ class MissingTLSKeyFile < Exception
235
+ end
209
236
  end
@@ -92,7 +92,6 @@ module Bunny
92
92
  @arguments
93
93
  end
94
94
 
95
-
96
95
  # Binds queue to an exchange
97
96
  #
98
97
  # @param [Bunny::Exchange,String] exchange Exchange to bind to
@@ -20,7 +20,7 @@ require "amq/protocol/client"
20
20
  require "amq/settings"
21
21
 
22
22
  module Bunny
23
- # Represents AMQP 0.9.1 connection (connection to RabbitMQ).
23
+ # Represents AMQP 0.9.1 connection (to a RabbitMQ node).
24
24
  # @see http://rubybunny.info/articles/connecting.html Connecting to RabbitMQ guide
25
25
  class Session
26
26
 
@@ -51,11 +51,13 @@ module Bunny
51
51
  # RabbitMQ client metadata
52
52
  DEFAULT_CLIENT_PROPERTIES = {
53
53
  :capabilities => {
54
- :publisher_confirms => true,
55
- :consumer_cancel_notify => true,
56
- :exchange_exchange_bindings => true,
57
- :"basic.nack" => true,
58
- :"connection.blocked" => true
54
+ :publisher_confirms => true,
55
+ :consumer_cancel_notify => true,
56
+ :exchange_exchange_bindings => true,
57
+ :"basic.nack" => true,
58
+ :"connection.blocked" => true,
59
+ # See http://www.rabbitmq.com/auth-notification.html
60
+ :authentication_failure_close => true
59
61
  },
60
62
  :product => "Bunny",
61
63
  :platform => ::RUBY_DESCRIPTION,
@@ -98,10 +100,12 @@ module Bunny
98
100
  # @option connection_string_or_opts [String] :password ("guest") Password
99
101
  # @option connection_string_or_opts [String] :vhost ("/") Virtual host to use
100
102
  # @option connection_string_or_opts [Integer] :heartbeat (600) Heartbeat interval. 0 means no heartbeat.
103
+ # @option connection_string_or_opts [Integer] :network_recovery_interval (4) Recovery interval periodic network recovery will use. This includes initial pause after network failure.
101
104
  # @option connection_string_or_opts [Boolean] :tls (false) Should TLS/SSL be used?
102
105
  # @option connection_string_or_opts [String] :tls_cert (nil) Path to client TLS/SSL certificate file (.pem)
103
106
  # @option connection_string_or_opts [String] :tls_key (nil) Path to client TLS/SSL private key file (.pem)
104
107
  # @option connection_string_or_opts [Array<String>] :tls_ca_certificates Array of paths to TLS/SSL CA files (.pem), by default detected from OpenSSL configuration
108
+ # @option connection_string_or_opts [Integer] :continuation_timeout (4000) Timeout for client operations that expect a response (e.g. {Bunny::Queue#get}), in milliseconds.
105
109
  #
106
110
  # @option optz [String] :auth_mechanism ("PLAIN") Authentication mechanism, PLAIN or EXTERNAL
107
111
  # @option optz [String] :locale ("PLAIN") Locale RabbitMQ should use
@@ -296,7 +300,7 @@ module Bunny
296
300
  def with_channel(n = nil)
297
301
  ch = create_channel(n)
298
302
  yield ch
299
- ch.close
303
+ ch.close if ch.open?
300
304
 
301
305
  self
302
306
  end
@@ -306,10 +310,12 @@ module Bunny
306
310
  status == :connecting
307
311
  end
308
312
 
313
+ # @return [Boolean] true if this AMQP 0.9.1 connection is closed
309
314
  def closed?
310
315
  status == :closed
311
316
  end
312
317
 
318
+ # @return [Boolean] true if this AMQP 0.9.1 connection is open
313
319
  def open?
314
320
  (status == :open || status == :connected || status == :connecting) && @transport.open?
315
321
  end
@@ -390,6 +396,46 @@ module Bunny
390
396
  AMQ::Settings.parse_amqp_url(uri)
391
397
  end
392
398
 
399
+ # Checks if a queue with given name exists.
400
+ #
401
+ # Implemented using queue.declare
402
+ # with passive set to true and a one-off (short lived) channel
403
+ # under the hood.
404
+ #
405
+ # @param [String] name Queue name
406
+ # @return [Boolean] true if queue exists
407
+ def queue_exists?(name)
408
+ ch = create_channel
409
+ begin
410
+ ch.queue(name, :passive => true)
411
+ true
412
+ rescue Bunny::NotFound => _
413
+ false
414
+ ensure
415
+ ch.close if ch.open?
416
+ end
417
+ end
418
+
419
+ # Checks if a exchange with given name exists.
420
+ #
421
+ # Implemented using exchange.declare
422
+ # with passive set to true and a one-off (short lived) channel
423
+ # under the hood.
424
+ #
425
+ # @param [String] name Exchange name
426
+ # @return [Boolean] true if exchange exists
427
+ def exchange_exists?(name)
428
+ ch = create_channel
429
+ begin
430
+ ch.exchange(name, :passive => true)
431
+ true
432
+ rescue Bunny::NotFound => _
433
+ false
434
+ ensure
435
+ ch.close if ch.open?
436
+ end
437
+ end
438
+
393
439
 
394
440
  #
395
441
  # Implementation
@@ -442,6 +488,13 @@ module Bunny
442
488
  end
443
489
  end
444
490
 
491
+ # Handles incoming frames and dispatches them.
492
+ #
493
+ # Channel methods (`channel.open-ok`, `channel.close-ok`) are
494
+ # handled by the session itself.
495
+ # Connection level errors result in exceptions being raised.
496
+ # Deliveries and other methods are passed on to channels to dispatch.
497
+ #
445
498
  # @private
446
499
  def handle_frame(ch_number, method)
447
500
  @logger.debug "Session#handle_frame on #{ch_number}: #{method.inspect}"
@@ -782,6 +835,19 @@ module Bunny
782
835
  end
783
836
  end # send_frameset_without_timeout(frames)
784
837
 
838
+ # @private
839
+ def send_raw_without_timeout(data, channel)
840
+ # some developers end up sharing channels between threads and when multiple
841
+ # threads publish on the same channel aggressively, at some point frames will be
842
+ # delivered out of order and broker will raise 505 UNEXPECTED_FRAME exception.
843
+ # If we synchronize on the channel, however, this is both thread safe and pretty fine-grained
844
+ # locking. Note that "single frame" methods do not need this kind of synchronization. MK.
845
+ channel.synchronize do
846
+ @transport.write(data)
847
+ signal_activity!
848
+ end
849
+ end # send_frameset_without_timeout(frames)
850
+
785
851
  # @return [String]
786
852
  # @api public
787
853
  def to_s
@@ -823,7 +889,16 @@ module Bunny
823
889
  raise Bunny::PossibleAuthenticationFailureError.new(self.user, self.vhost, self.password.size)
824
890
  end
825
891
 
826
- connection_tune = frame.decode_payload
892
+ response = frame.decode_payload
893
+ if response.is_a?(AMQ::Protocol::Connection::Close)
894
+ @state = :closed
895
+ @logger.error "Authentication with RabbitMQ failed: #{response.reply_code} #{response.reply_text}"
896
+ raise Bunny::AuthenticationFailureError.new(self.user, self.vhost, self.password.size)
897
+ end
898
+
899
+
900
+
901
+ connection_tune = response
827
902
 
828
903
  @frame_max = negotiate_value(@client_frame_max, connection_tune.frame_max)
829
904
  @channel_max = negotiate_value(@client_channel_max, connection_tune.channel_max)
@@ -7,7 +7,7 @@ module Bunny
7
7
  class SystemTimer
8
8
  # Executes a block of code, raising if the execution does not finish
9
9
  # in the alloted period of time, in seconds.
10
- def self.timeout(seconds, exception)
10
+ def self.timeout(seconds, exception = nil)
11
11
  if seconds
12
12
  ::SystemTimer.timeout_after(seconds, exception) do
13
13
  yield
@@ -5,7 +5,7 @@ require "monitor"
5
5
  begin
6
6
  require "openssl"
7
7
  rescue LoadError => le
8
- puts "Could not load OpenSSL"
8
+ $stderr.puts "Could not load OpenSSL"
9
9
  end
10
10
 
11
11
  require "bunny/exceptions"
@@ -19,8 +19,10 @@ module Bunny
19
19
  # API
20
20
  #
21
21
 
22
+ # Default TCP connection timeout
22
23
  DEFAULT_CONNECTION_TIMEOUT = 5.0
23
- # same as in RabbitMQ Java client
24
+ # Default TLS protocol version to use.
25
+ # Currently SSLv3, same as in RabbitMQ Java client
24
26
  DEFAULT_TLS_PROTOCOL = "SSLv3"
25
27
 
26
28
 
@@ -36,21 +38,6 @@ module Bunny
36
38
 
37
39
  @logger = session.logger
38
40
  @tls_enabled = tls_enabled?(opts)
39
- @tls_certificate_path = tls_certificate_path_from(opts)
40
- @tls_key_path = tls_key_path_from(opts)
41
- @tls_certificate = opts[:tls_certificate] || opts[:ssl_cert_string]
42
- @tls_key = opts[:tls_key] || opts[:ssl_key_string]
43
- @tls_certificate_store = opts[:tls_certificate_store]
44
-
45
- default_ca_file = ENV[OpenSSL::X509::DEFAULT_CERT_FILE_ENV] || OpenSSL::X509::DEFAULT_CERT_FILE
46
- default_ca_path = ENV[OpenSSL::X509::DEFAULT_CERT_DIR_ENV] || OpenSSL::X509::DEFAULT_CERT_DIR
47
- @tls_ca_certificates = opts.fetch(:tls_ca_certificates, [
48
- default_ca_file,
49
- File.join(default_ca_path, 'ca-certificates.crt'), # Ubuntu/Debian
50
- File.join(default_ca_path, 'ca-bundle.crt'), # Amazon Linux & Fedora/RHEL
51
- File.join(default_ca_path, 'ca-bundle.pem') # OpenSUSE
52
- ])
53
- @verify_peer = opts[:verify_ssl] || opts[:verify_peer]
54
41
 
55
42
  @read_write_timeout = opts[:socket_timeout] || 3
56
43
  @read_write_timeout = nil if @read_write_timeout == 0
@@ -61,10 +48,9 @@ module Bunny
61
48
  @writes_mutex = @session.mutex_impl.new
62
49
 
63
50
  maybe_initialize_socket
64
- prepare_tls_context if @tls_enabled
51
+ prepare_tls_context(opts) if @tls_enabled
65
52
  end
66
53
 
67
-
68
54
  def hostname
69
55
  @host
70
56
  end
@@ -234,7 +220,7 @@ module Bunny
234
220
  def self.reacheable?(host, port, timeout)
235
221
  begin
236
222
  s = Bunny::Socket.open(host, port,
237
- :socket_timeout => timeout)
223
+ :socket_timeout => timeout)
238
224
 
239
225
  true
240
226
  rescue SocketError, Timeout::Error => e
@@ -252,8 +238,8 @@ module Bunny
252
238
  begin
253
239
  @socket = Bunny::Timeout.timeout(@connect_timeout, ClientTimeout) do
254
240
  Bunny::Socket.open(@host, @port,
255
- :keepalive => @opts[:keepalive],
256
- :socket_timeout => @connect_timeout)
241
+ :keepalive => @opts[:keepalive],
242
+ :socket_timeout => @connect_timeout)
257
243
  end
258
244
  rescue StandardError, ClientTimeout => e
259
245
  @status = :not_connected
@@ -289,8 +275,49 @@ module Bunny
289
275
  opts[:tls_key] || opts[:ssl_key] || opts[:tls_key_path] || opts[:ssl_key_path]
290
276
  end
291
277
 
292
- def prepare_tls_context
293
- read_tls_keys!
278
+ def tls_certificate_from(opts)
279
+ begin
280
+ read_client_certificate!
281
+ rescue MissingTLSCertificateFile => e
282
+ inline_client_certificate_from(opts)
283
+ end
284
+ end
285
+
286
+ def tls_key_from(opts)
287
+ begin
288
+ read_client_key!
289
+ rescue MissingTLSKeyFile => e
290
+ inline_client_key_from(opts)
291
+ end
292
+ end
293
+
294
+
295
+ def inline_client_certificate_from(opts)
296
+ opts[:tls_certificate] || opts[:ssl_cert_string]
297
+ end
298
+
299
+ def inline_client_key_from(opts)
300
+ opts[:tls_key] || opts[:ssl_key_string]
301
+ end
302
+
303
+ def prepare_tls_context(opts)
304
+ # client cert/key paths
305
+ @tls_certificate_path = tls_certificate_path_from(opts)
306
+ @tls_key_path = tls_key_path_from(opts)
307
+ # client cert/key
308
+ @tls_certificate = tls_certificate_from(opts)
309
+ @tls_key = tls_key_from(opts)
310
+ @tls_certificate_store = opts[:tls_certificate_store]
311
+
312
+ default_ca_file = ENV[OpenSSL::X509::DEFAULT_CERT_FILE_ENV] || OpenSSL::X509::DEFAULT_CERT_FILE
313
+ default_ca_path = ENV[OpenSSL::X509::DEFAULT_CERT_DIR_ENV] || OpenSSL::X509::DEFAULT_CERT_DIR
314
+ @tls_ca_certificates = opts.fetch(:tls_ca_certificates, [
315
+ default_ca_file,
316
+ File.join(default_ca_path, 'ca-certificates.crt'), # Ubuntu/Debian
317
+ File.join(default_ca_path, 'ca-bundle.crt'), # Amazon Linux & Fedora/RHEL
318
+ File.join(default_ca_path, 'ca-bundle.pem') # OpenSUSE
319
+ ])
320
+ @verify_peer = opts[:verify_ssl] || opts[:verify_peer]
294
321
 
295
322
  @tls_context = initialize_tls_context(OpenSSL::SSL::SSLContext.new)
296
323
  end
@@ -304,17 +331,24 @@ module Bunny
304
331
  s
305
332
  end
306
333
 
307
- def check_local_path!(s)
308
- raise ArgumentError, "cannot read TLS certificate or key from #{s}" unless File.file?(s) && File.readable?(s)
334
+ def check_local_certificate_path!(s)
335
+ raise MissingTLSCertificateFile, "cannot read client TLS certificate from #{s}" unless File.file?(s) && File.readable?(s)
309
336
  end
310
337
 
311
- def read_tls_keys!
338
+ def check_local_key_path!(s)
339
+ raise MissingTLSKeyFile, "cannot read client TLS private key from #{s}" unless File.file?(s) && File.readable?(s)
340
+ end
341
+
342
+ def read_client_certificate!
312
343
  if @tls_certificate_path
313
- check_local_path!(@tls_certificate_path)
344
+ check_local_certificate_path!(@tls_certificate_path)
314
345
  @tls_certificate = File.read(@tls_certificate_path)
315
346
  end
347
+ end
348
+
349
+ def read_client_key!
316
350
  if @tls_key_path
317
- check_local_path!(@tls_key_path)
351
+ check_local_key_path!(@tls_key_path)
318
352
  @tls_key = File.read(@tls_key_path)
319
353
  end
320
354
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Bunny
4
4
  # @return [String] Version of the library
5
- VERSION = "1.0.0.rc2"
5
+ VERSION = "1.0.0.rc3"
6
6
  end
data/repl ADDED
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ bundle exec irb -Ilib -r'bunny'
@@ -14,18 +14,18 @@ describe Bunny::Session do
14
14
  context "when schema is not one of [amqp, amqps]" do
15
15
  it "raises ArgumentError" do
16
16
  expect {
17
- described_class.new("http://dev.rabbitmq.com")
17
+ described_class.new("http://127.0.0.1")
18
18
  }.to raise_error(ArgumentError, /amqp or amqps schema/)
19
19
  end
20
20
  end
21
21
 
22
22
 
23
23
  it "handles amqp:// URIs w/o path part" do
24
- session = described_class.new("amqp://dev.rabbitmq.com")
24
+ session = described_class.new("amqp://127.0.0.1")
25
25
  session.start
26
26
 
27
27
  session.vhost.should == "/"
28
- session.host.should == "dev.rabbitmq.com"
28
+ session.host.should == "127.0.0.1"
29
29
  session.port.should == 5672
30
30
  session.ssl?.should be_false
31
31
 
@@ -35,9 +35,9 @@ describe Bunny::Session do
35
35
 
36
36
  context "when URI ends in a slash" do
37
37
  it "parses vhost as an empty string" do
38
- session = described_class.new("amqp://dev.rabbitmq.com/")
38
+ session = described_class.new("amqp://127.0.0.1/")
39
39
 
40
- session.hostname.should == "dev.rabbitmq.com"
40
+ session.hostname.should == "127.0.0.1"
41
41
  session.port.should == 5672
42
42
  session.vhost.should == ""
43
43
  end
@@ -18,67 +18,88 @@ describe Bunny::Exchange, "#delete" do
18
18
  x = ch.fanout("bunny.tests.exchanges.fanout#{rand}")
19
19
 
20
20
  x.delete
21
- expect {
22
- x.delete
23
- }.to raise_error(Bunny::NotFound)
24
-
21
+ # no exception as of RabbitMQ 3.2. MK.
22
+ x.delete
23
+
25
24
  ch.exchanges.size.should == 0
26
25
  end
27
26
  end
28
27
 
29
28
 
30
29
  context "with a name of a non-existent exchange" do
31
- it "raises an exception" do
30
+ it "DOES NOT rais an exception" do
32
31
  ch = connection.create_channel
33
32
 
34
- expect {
35
- ch.exchange_delete("sdkhflsdjflskdjflsd#{rand}")
36
- }.to raise_error(Bunny::NotFound)
33
+ # no exception as of RabbitMQ 3.2. MK.
34
+ ch.exchange_delete("sdkhflsdjflskdjflsd#{rand}")
35
+ ch.exchange_delete("sdkhflsdjflskdjflsd#{rand}")
36
+ ch.exchange_delete("sdkhflsdjflskdjflsd#{rand}")
37
+ ch.exchange_delete("sdkhflsdjflskdjflsd#{rand}")
37
38
  end
38
39
  end
39
-
40
+
40
41
  context "with a name of 'amq.direct'" do
41
42
  it "does not delete the exchange" do
42
43
  ch = connection.create_channel
43
44
  x = ch.direct('amq.direct')
44
-
45
+
45
46
  x.delete.should == nil
46
47
  end
47
48
  end
48
-
49
+
49
50
  context "with a name of 'amq.fanout'" do
50
51
  it "does not delete the exchange" do
51
52
  ch = connection.create_channel
52
53
  x = ch.fanout('amq.fanout')
53
-
54
+
54
55
  x.delete.should == nil
55
56
  end
56
57
  end
57
-
58
+
58
59
  context "with a name of 'amq.topic'" do
59
60
  it "does not delete the exchange" do
60
61
  ch = connection.create_channel
61
62
  x = ch.topic('amq.topic')
62
-
63
+
63
64
  x.delete.should == nil
64
65
  end
65
66
  end
66
-
67
+
67
68
  context "with a name of 'amq.headers'" do
68
69
  it "does not delete the exchange" do
69
70
  ch = connection.create_channel
70
71
  x = ch.headers('amq.headers')
71
-
72
+
72
73
  x.delete.should == nil
73
74
  end
74
75
  end
75
-
76
+
76
77
  context "with a name of 'amq.match'" do
77
78
  it "does not delete the exchange" do
78
79
  ch = connection.create_channel
79
80
  x = ch.headers('amq.match')
80
-
81
+
81
82
  x.delete.should == nil
82
83
  end
83
84
  end
85
+
86
+
87
+ describe "#exchange_exists?" do
88
+ context "when a exchange exists" do
89
+ it "returns true" do
90
+ ch = connection.create_channel
91
+
92
+ connection.exchange_exists?("amq.fanout").should be_true
93
+ connection.exchange_exists?("amq.direct").should be_true
94
+ connection.exchange_exists?("amq.topic").should be_true
95
+ connection.exchange_exists?("amq.match").should be_true
96
+ end
97
+ end
98
+
99
+ context "when a exchange DOES NOT exist" do
100
+ it "returns false" do
101
+ connection.exchange_exists?("suf89u9a4jo3ndnakls##{Time.now.to_i}").should be_false
102
+ end
103
+ end
104
+ end
84
105
  end
@@ -136,6 +136,25 @@ describe Bunny::Queue do
136
136
  end
137
137
 
138
138
 
139
+ describe "#queue_exists?" do
140
+ context "when a queue exists" do
141
+ it "returns true" do
142
+ ch = connection.create_channel
143
+ q = ch.queue("", :exlusive => true)
144
+
145
+ connection.queue_exists?(q.name).should be_true
146
+ end
147
+ end
148
+
149
+ context "when a queue DOES NOT exist" do
150
+ it "returns false" do
151
+ connection.queue_exists?("suf89u9a4jo3ndnakls##{Time.now.to_i}").should be_false
152
+ end
153
+ end
154
+ end
155
+
156
+
157
+
139
158
  unless ENV["CI"]
140
159
  # requires RabbitMQ 3.1+
141
160
  context "when queue is declared with bounded length" do
@@ -19,9 +19,8 @@ describe Bunny::Queue, "#delete" do
19
19
  q = ch.queue("")
20
20
 
21
21
  q.delete
22
- expect {
23
- q.delete
24
- }.to raise_error(Bunny::NotFound)
22
+ # no exception as of RabbitMQ 3.2. MK.
23
+ q.delete
25
24
 
26
25
  ch.queues.size.should == 0
27
26
  end
@@ -29,12 +28,14 @@ describe Bunny::Queue, "#delete" do
29
28
 
30
29
 
31
30
  context "with a name of an existing queue" do
32
- it "raises an exception" do
31
+ it "DOES NOT raise an exception" do
33
32
  ch = connection.create_channel
34
33
 
35
- expect {
36
- ch.queue_delete("sdkhflsdjflskdjflsd#{rand}")
37
- }.to raise_error(Bunny::NotFound)
34
+ # no exception as of RabbitMQ 3.2. MK.
35
+ ch.queue_delete("sdkhflsdjflskdjflsd#{rand}")
36
+ ch.queue_delete("sdkhflsdjflskdjflsd#{rand}")
37
+ ch.queue_delete("sdkhflsdjflskdjflsd#{rand}")
38
+ ch.queue_delete("sdkhflsdjflskdjflsd#{rand}")
38
39
  end
39
40
  end
40
41
  end
@@ -47,8 +47,8 @@ describe Bunny::Queue, "NOT bound to an exchange" do
47
47
  x = ch.fanout("amq.fanout")
48
48
  q = ch.queue("", :exclusive => true)
49
49
 
50
- lambda {
51
- q.unbind(x)
52
- }.should raise_error(Bunny::NotFound, /no binding/)
50
+ # No exception as of RabbitMQ 3.2. MK.
51
+ q.unbind(x)
52
+ q.unbind(x)
53
53
  end
54
54
  end
@@ -2,24 +2,9 @@
2
2
  require "spec_helper"
3
3
 
4
4
  unless ENV["CI"]
5
- describe "TLS connection to RabbitMQ with client certificates" do
6
- let(:connection) do
7
- c = Bunny.new(:user => "bunny_gem",
8
- :password => "bunny_password",
9
- :vhost => "bunny_testbed",
10
- :tls => true,
11
- :tls_cert => "spec/tls/client_cert.pem",
12
- :tls_key => "spec/tls/client_key.pem",
13
- :tls_ca_certificates => ["./spec/tls/cacert.pem"])
14
- c.start
15
- c
16
- end
17
-
18
- after :each do
19
- connection.close
20
- end
21
-
22
- it "provides the same API as a regular connection" do
5
+ shared_examples_for "successful TLS connection" do
6
+ it "succeeds" do
7
+ connection.should be_tls
23
8
  ch = connection.create_channel
24
9
 
25
10
  q = ch.queue("", :exclusive => true)
@@ -46,13 +31,15 @@ unless ENV["CI"]
46
31
  end
47
32
 
48
33
 
49
- describe "TLS connection to RabbitMQ without client certificates" do
34
+ describe "TLS connection to RabbitMQ with client certificates" do
50
35
  let(:connection) do
51
36
  c = Bunny.new(:user => "bunny_gem",
52
- :password => "bunny_password",
53
- :vhost => "bunny_testbed",
54
- :tls => true,
55
- :tls_ca_certificates => ["./spec/tls/cacert.pem"])
37
+ :password => "bunny_password",
38
+ :vhost => "bunny_testbed",
39
+ :tls => true,
40
+ :tls_cert => "spec/tls/client_cert.pem",
41
+ :tls_key => "spec/tls/client_key.pem",
42
+ :tls_ca_certificates => ["./spec/tls/cacert.pem"])
56
43
  c.start
57
44
  c
58
45
  end
@@ -61,30 +48,26 @@ unless ENV["CI"]
61
48
  connection.close
62
49
  end
63
50
 
64
- it "provides the same API as a regular connection" do
65
- ch = connection.create_channel
66
-
67
- q = ch.queue("", :exclusive => true)
68
- x = ch.default_exchange
69
-
70
- x.publish("xyzzy", :routing_key => q.name).
71
- publish("xyzzy", :routing_key => q.name).
72
- publish("xyzzy", :routing_key => q.name).
73
- publish("xyzzy", :routing_key => q.name)
51
+ include_examples "successful TLS connection"
52
+ end
74
53
 
75
- sleep 0.5
76
- q.message_count.should == 4
77
54
 
78
- i = 0
79
- q.subscribe do |delivery_info, _, payload|
80
- i += 1
81
- end
82
- sleep 1.0
83
- i.should == 4
84
- q.message_count.should == 0
55
+ describe "TLS connection to RabbitMQ without client certificates" do
56
+ let(:connection) do
57
+ c = Bunny.new(:user => "bunny_gem",
58
+ :password => "bunny_password",
59
+ :vhost => "bunny_testbed",
60
+ :tls => true,
61
+ :tls_ca_certificates => ["./spec/tls/cacert.pem"])
62
+ c.start
63
+ c
64
+ end
85
65
 
86
- ch.close
66
+ after :each do
67
+ connection.close
87
68
  end
69
+
70
+ include_examples "successful TLS connection"
88
71
  end
89
72
 
90
73
 
@@ -102,30 +85,43 @@ unless ENV["CI"]
102
85
  connection.close
103
86
  end
104
87
 
105
- it "provides the same API as a regular connection" do
106
- connection.should be_tls
107
- ch = connection.create_channel
88
+ include_examples "successful TLS connection"
89
+ end
108
90
 
109
- q = ch.queue("", :exclusive => true)
110
- x = ch.default_exchange
111
91
 
112
- x.publish("xyzzy", :routing_key => q.name).
113
- publish("xyzzy", :routing_key => q.name).
114
- publish("xyzzy", :routing_key => q.name).
115
- publish("xyzzy", :routing_key => q.name)
92
+ describe "TLS connection to RabbitMQ with a connection string and w/o client certificate and key" do
93
+ let(:connection) do
94
+ c = Bunny.new("amqps://bunny_gem:bunny_password@127.0.0.1/bunny_testbed",
95
+ :tls_ca_certificates => ["./spec/tls/cacert.pem"])
96
+ c.start
97
+ c
98
+ end
116
99
 
117
- sleep 0.5
118
- q.message_count.should == 4
100
+ after :each do
101
+ connection.close
102
+ end
119
103
 
120
- i = 0
121
- q.subscribe do |delivery_info, _, payload|
122
- i += 1
123
- end
124
- sleep 1.0
125
- i.should == 4
126
- q.message_count.should == 0
104
+ include_examples "successful TLS connection"
105
+ end
127
106
 
128
- ch.close
107
+
108
+ describe "TLS connection to RabbitMQ with client certificates provided inline" do
109
+ let(:connection) do
110
+ c = Bunny.new(:user => "bunny_gem",
111
+ :password => "bunny_password",
112
+ :vhost => "bunny_testbed",
113
+ :tls => true,
114
+ :tls_cert => File.read("./spec/tls/client_cert.pem"),
115
+ :tls_key => File.read("./spec/tls/client_key.pem"),
116
+ :tls_ca_certificates => ["./spec/tls/cacert.pem"])
117
+ c.start
118
+ c
129
119
  end
120
+
121
+ after :each do
122
+ connection.close
123
+ end
124
+
125
+ include_examples "successful TLS connection"
130
126
  end
131
127
  end
@@ -0,0 +1,10 @@
1
+ require "spec_helper"
2
+
3
+ if RUBY_VERSION.to_f < 1.9
4
+ describe Bunny::SystemTimer do
5
+ it 'supports being called with a single argument' do
6
+ lambda {Bunny::SystemTimer::timeout(1) {}}.should_not raise_error
7
+ lambda {Bunny::SystemTimer::timeout(1, nil) {}}.should_not raise_error
8
+ end
9
+ end
10
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bunny
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.rc2
4
+ version: 1.0.0.rc3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Duncan
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2013-10-03 00:00:00.000000000 Z
15
+ date: 2013-10-24 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: amq-protocol
@@ -129,6 +129,7 @@ files:
129
129
  - lib/bunny/version.rb
130
130
  - lib/bunny/versioned_delivery_tag.rb
131
131
  - profiling/basic_publish/with_4K_messages.rb
132
+ - repl
132
133
  - spec/compatibility/queue_declare_spec.rb
133
134
  - spec/compatibility/queue_declare_with_default_channel_spec.rb
134
135
  - spec/higher_level_api/integration/basic_ack_spec.rb
@@ -197,6 +198,7 @@ files:
197
198
  - spec/unit/concurrent/condition_spec.rb
198
199
  - spec/unit/concurrent/linked_continuation_queue_spec.rb
199
200
  - spec/unit/concurrent/synchronized_sorted_set_spec.rb
201
+ - spec/unit/system_timer_spec.rb
200
202
  homepage: http://rubybunny.info
201
203
  licenses:
202
204
  - MIT
@@ -217,7 +219,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
217
219
  version: 1.3.1
218
220
  requirements: []
219
221
  rubyforge_project:
220
- rubygems_version: 2.0.5
222
+ rubygems_version: 2.1.9
221
223
  signing_key:
222
224
  specification_version: 4
223
225
  summary: Popular easy to use Ruby client for RabbitMQ
@@ -290,4 +292,5 @@ test_files:
290
292
  - spec/unit/concurrent/condition_spec.rb
291
293
  - spec/unit/concurrent/linked_continuation_queue_spec.rb
292
294
  - spec/unit/concurrent/synchronized_sorted_set_spec.rb
295
+ - spec/unit/system_timer_spec.rb
293
296
  has_rdoc: true