bunny 1.0.0.rc2 → 1.0.0.rc3

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
  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