amq-client 0.7.0.alpha35 → 0.8.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 (69) hide show
  1. data/.rspec +0 -1
  2. data/.travis.yml +9 -3
  3. data/Gemfile +22 -12
  4. data/amq-client.gemspec +1 -1
  5. data/examples/coolio_adapter/example_helper.rb +2 -0
  6. data/examples/eventmachine_adapter/basic_consume_with_acknowledgements.rb +3 -3
  7. data/examples/eventmachine_adapter/{connection_failure_callback.rb → error_handling/connection_failure_callback.rb} +4 -8
  8. data/examples/eventmachine_adapter/{connection_failure_exception.rb → error_handling/connection_failure_exception.rb} +5 -9
  9. data/examples/eventmachine_adapter/{connection_loss_handler.rb → error_handling/connection_loss_handler_that_fails_over.rb} +12 -12
  10. data/examples/eventmachine_adapter/error_handling/connection_loss_handler_with_automatic_recovery.rb +85 -0
  11. data/examples/eventmachine_adapter/error_handling/connection_loss_handler_with_manual_recovery.rb +85 -0
  12. data/examples/eventmachine_adapter/error_handling/handling_a_channel_level_exception.rb +2 -5
  13. data/examples/eventmachine_adapter/example_helper.rb +2 -0
  14. data/examples/eventmachine_adapter/server_capabilities.rb +12 -0
  15. data/examples/eventmachine_adapter/tls/tls_without_peer_verification.rb +2 -2
  16. data/lib/amq/client/async/adapter.rb +170 -31
  17. data/lib/amq/client/async/adapters/coolio.rb +18 -1
  18. data/lib/amq/client/async/adapters/event_machine.rb +48 -32
  19. data/lib/amq/client/async/adapters/eventmachine.rb +3 -1
  20. data/lib/amq/client/async/callbacks.rb +9 -7
  21. data/lib/amq/client/async/channel.rb +113 -20
  22. data/lib/amq/client/async/consumer.rb +270 -0
  23. data/lib/amq/client/async/exchange.rb +137 -16
  24. data/lib/amq/client/async/extensions/rabbitmq/confirm.rb +4 -4
  25. data/lib/amq/client/async/queue.rb +217 -113
  26. data/lib/amq/client/callbacks.rb +2 -0
  27. data/lib/amq/client/consumer_tag_generator.rb +24 -0
  28. data/lib/amq/client/exceptions.rb +10 -6
  29. data/lib/amq/client/handlers_registry.rb +2 -0
  30. data/lib/amq/client/queue.rb +2 -0
  31. data/lib/amq/client/server_named_entity.rb +1 -8
  32. data/lib/amq/client/settings.rb +64 -2
  33. data/lib/amq/client/version.rb +3 -1
  34. data/spec/benchmarks/adapters.rb +2 -0
  35. data/spec/client/framing/io_frame_spec.rb +9 -6
  36. data/spec/integration/coolio/basic_ack_spec.rb +2 -0
  37. data/spec/integration/coolio/basic_cancel_spec.rb +2 -0
  38. data/spec/integration/coolio/basic_consume_spec.rb +58 -0
  39. data/spec/integration/coolio/basic_get_spec.rb +2 -0
  40. data/spec/integration/coolio/basic_return_spec.rb +2 -0
  41. data/spec/integration/coolio/channel_close_spec.rb +2 -0
  42. data/spec/integration/coolio/channel_flow_spec.rb +2 -0
  43. data/spec/integration/coolio/connection_close_spec.rb +2 -0
  44. data/spec/integration/coolio/connection_start_spec.rb +2 -0
  45. data/spec/integration/coolio/exchange_declare_spec.rb +8 -6
  46. data/spec/integration/coolio/spec_helper.rb +2 -0
  47. data/spec/integration/coolio/tx_commit_spec.rb +2 -1
  48. data/spec/integration/coolio/tx_rollback_spec.rb +1 -1
  49. data/spec/integration/eventmachine/basic_ack_spec.rb +3 -1
  50. data/spec/integration/eventmachine/basic_cancel_spec.rb +2 -0
  51. data/spec/integration/eventmachine/basic_consume_spec.rb +90 -6
  52. data/spec/integration/eventmachine/basic_get_spec.rb +2 -0
  53. data/spec/integration/eventmachine/basic_return_spec.rb +2 -0
  54. data/spec/integration/eventmachine/channel_close_spec.rb +2 -0
  55. data/spec/integration/eventmachine/channel_flow_spec.rb +4 -2
  56. data/spec/integration/eventmachine/concurrent_basic_publish_spec.rb +79 -0
  57. data/spec/integration/eventmachine/connection_close_spec.rb +2 -0
  58. data/spec/integration/eventmachine/connection_start_spec.rb +2 -0
  59. data/spec/integration/eventmachine/exchange_declare_spec.rb +4 -2
  60. data/spec/integration/eventmachine/queue_declare_spec.rb +2 -0
  61. data/spec/integration/eventmachine/regressions/amqp_gem_issue66_spec.rb +2 -0
  62. data/spec/integration/eventmachine/spec_helper.rb +2 -0
  63. data/spec/integration/eventmachine/tx_commit_spec.rb +2 -1
  64. data/spec/integration/eventmachine/tx_rollback_spec.rb +1 -1
  65. data/spec/regression/bad_frame_slicing_in_adapters_spec.rb +2 -0
  66. data/spec/spec_helper.rb +10 -0
  67. data/spec/unit/client/settings_spec.rb +92 -3
  68. metadata +24 -23
  69. data/CONTRIBUTORS +0 -3
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require "amq/client/async/callbacks"
2
4
 
3
5
  module AMQ
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ module AMQ
4
+ module Client
5
+ class ConsumerTagGenerator
6
+
7
+ #
8
+ # API
9
+ #
10
+
11
+ # @return [String] Generated consumer tag
12
+ def generate
13
+ "#{Kernel.rand}-#{Time.now.to_i * 1000}-#{Kernel.rand(999_999_999_999)}"
14
+ end # generate
15
+
16
+ # @return [String] Generated consumer tag
17
+ def generate_for(queue)
18
+ raise ArgumentError, "argument must respond to :name" unless queue.respond_to?(:name)
19
+
20
+ "#{queue.name}-#{Time.now.to_i * 1000}-#{Kernel.rand(999_999_999_999)}"
21
+ end # generate_for(queue)
22
+ end # ConsumerTagGenerator
23
+ end # Client
24
+ end # AMQ
@@ -51,12 +51,6 @@ module AMQ
51
51
  # Client
52
52
  #
53
53
 
54
- class MissingInterfaceMethodError < NotImplementedError
55
- def initialize(method_name)
56
- super("Method #{method_name} is supposed to be overriden by adapter")
57
- end
58
- end
59
-
60
54
  class MissingHandlerError < StandardError
61
55
  def initialize(frame)
62
56
  super("No callback registered for #{frame.method_class}")
@@ -97,5 +91,15 @@ module AMQ
97
91
  end # initialize(settings)
98
92
  end # PossibleAuthenticationFailureError
99
93
 
94
+
95
+
96
+ class UnknownExchangeTypeError < StandardError
97
+ BUILTIN_TYPES = [:fanout, :direct, :topic, :headers].freeze
98
+
99
+ def initialize(types, given)
100
+ super("#{given.inspect} exchange type is unknown. Standard types are #{BUILTIN_TYPES.inspect}, custom exchange types must begin with x-, for example: x-recent-history")
101
+ end
102
+ end
103
+
100
104
  end # Client
101
105
  end # AMQ
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module AMQ
2
4
  module Client
3
5
  class HandlersRegistry
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require "amq/client/async/queue"
2
4
 
3
5
  module AMQ
@@ -7,17 +7,10 @@ module AMQ
7
7
 
8
8
  # @return [Boolean] true if this entity is anonymous (server-named)
9
9
  def server_named?
10
- @name.nil? or @name.empty?
10
+ @server_named || @name.nil? || @name.empty?
11
11
  end
12
12
  # backwards compabitility. MK.
13
13
  alias anonymous? server_named?
14
-
15
- def dup
16
- if server_named?
17
- raise RuntimeError.new("You can't clone anonymous queue until it receives back the name in Queue.Declare-Ok response. Move the code with #dup to the callback for the #declare method.") # TODO: that's not true in all cases, imagine the user didn't call #declare yet.
18
- end
19
- super
20
- end
21
14
  end # ServerNamedEntity
22
15
  end # Client
23
16
  end # AMQ
@@ -1,11 +1,18 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  require "amq/protocol/client" # TODO: "amq/protocol/constants"
4
+ require "uri"
4
5
 
5
6
  module AMQ
6
7
  module Client
7
8
  # @see AMQ::Client::Settings.configure
8
9
  module Settings
10
+ # @private
11
+ AMQP_PORTS = {"amqp" => 5672, "amqps" => 5671}.freeze
12
+
13
+ # @private
14
+ AMQPS = "amqps".freeze
15
+
9
16
  # Default connection settings used by AMQ clients
10
17
  #
11
18
  # @see AMQ::Client::Settings.configure
@@ -86,8 +93,63 @@ module AMQ
86
93
  end
87
94
  end
88
95
 
89
- def self.parse_amqp_url(string)
90
- raise NotImplementedError.new
96
+ # Parses AMQP connection URI and returns its components as a hash.
97
+ #
98
+ # h2. vhost naming schemes
99
+ #
100
+ # It is convenient to be able to specify the AMQP connection
101
+ # parameters as a URI string, and various "amqp" URI schemes
102
+ # exist. Unfortunately, there is no standard for these URIs, so
103
+ # while the schemes share the basic idea, they differ in some
104
+ # details. This implementation aims to encourage URIs that work
105
+ # as widely as possible.
106
+ #
107
+ # The URI scheme should be "amqp", or "amqps" if SSL is required.
108
+ #
109
+ # The host, port, username and password are represented in the
110
+ # authority component of the URI in the same way as in http URIs.
111
+ #
112
+ # The vhost is obtained from the first segment of the path, with the
113
+ # leading slash removed. The path should contain only a single
114
+ # segment (i.e, the only slash in it should be the leading one).
115
+ # If the vhost is to include slashes or other reserved URI
116
+ # characters, these should be percent-escaped.
117
+ #
118
+ # @example How vhost is parsed
119
+ #
120
+ # AMQ::Client::Settings.parse_amqp_url("amqp://dev.rabbitmq.com") # => vhost is nil, so default (/) will be used
121
+ # AMQ::Client::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/") # => vhost is an empty string
122
+ # AMQ::Client::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/%2Fvault") # => vhost is /vault
123
+ # AMQ::Client::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/production") # => vhost is production
124
+ # AMQ::Client::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/a.b.c") # => vhost is a.b.c
125
+ # AMQ::Client::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/foo/bar") # => ArgumentError
126
+ #
127
+ #
128
+ # @param [String] connection_string AMQP connection URI, à la JDBC connection string. For example: amqp://bus.megacorp.internal:5877.
129
+ # @return [Hash] Connection parameters (:username, :password, :vhost, :host, :port, :ssl)
130
+ #
131
+ # @raise [ArgumentError] When connection URI schema is not amqp or amqps, or the path contains multiple segments
132
+ #
133
+ # @see http://bit.ly/ks8MXK Connecting to The Broker documentation guide
134
+ # @api public
135
+ def self.parse_amqp_url(connection_string)
136
+ uri = URI.parse(connection_string)
137
+ raise ArgumentError.new("Connection URI must use amqp or amqps schema (example: amqp://bus.megacorp.internal:5766), learn more at http://bit.ly/ks8MXK") unless %w{amqp amqps}.include?(uri.scheme)
138
+
139
+ opts = {}
140
+
141
+ opts[:scheme] = uri.scheme
142
+ opts[:user] = URI.unescape(uri.user) if uri.user
143
+ opts[:pass] = URI.unescape(uri.password) if uri.password
144
+ opts[:host] = uri.host if uri.host
145
+ opts[:port] = uri.port || AMQ::Client::Settings::AMQP_PORTS[uri.scheme]
146
+ opts[:ssl] = uri.scheme == AMQ::Client::Settings::AMQPS
147
+ if uri.path =~ %r{^/(.*)}
148
+ raise ArgumentError.new("#{uri} has multiple-segment path; please percent-encode any slashes in the vhost name (e.g. /production => %2Fproduction). Learn more at http://bit.ly/amqp-gem-and-connection-uris") if $1.index('/')
149
+ opts[:vhost] = URI.unescape($1)
150
+ end
151
+
152
+ opts
91
153
  end
92
154
  end
93
155
  end
@@ -1,5 +1,7 @@
1
+ # encoding: utf-8
2
+
1
3
  module AMQ
2
4
  module Client
3
- VERSION = "0.7.0.alpha35"
5
+ VERSION = "0.8.0"
4
6
  end
5
7
  end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  Bundler.setup
2
4
  Bundler.require(:default)
3
5
  $LOAD_PATH.unshift(File.expand_path("../../../lib", __FILE__))
@@ -45,13 +45,16 @@ describe AMQ::Client::Framing::IO do
45
45
  subject.decode(@io).payload.should eql("\x00\n\x00(\x01/\x00\x00")
46
46
  end
47
47
 
48
- it "should raise an error if the frame length is miscalculated" do
49
- pending "How to do that with socket?"
48
+ context "if the frame length is miscalculated" do
49
+ it "should raise an error"
50
50
  end
51
51
 
52
- it "should raise an error if the frame doesn't end with FINAL_OCTET" do
53
- data = @io.read[0..-2] + "too long" + "\xCE"
54
- io = StringIO.new(data)
55
- lambda { subject.decode(io) }.should raise_error(AMQ::Client::NoFinalOctetError)
52
+
53
+ context "if frame doesn't end with FINAL_OCTET" do
54
+ it "should raise an error" do
55
+ data = @io.read[0..-2] + "too long" + "\xCE"
56
+ io = StringIO.new(data)
57
+ lambda { subject.decode(io) }.should raise_error(AMQ::Client::NoFinalOctetError)
58
+ end
56
59
  end
57
60
  end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'spec_helper'
2
4
  require 'integration/coolio/spec_helper'
3
5
 
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'spec_helper'
2
4
  require 'integration/coolio/spec_helper'
3
5
 
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'spec_helper'
2
4
  require 'integration/coolio/spec_helper'
3
5
 
@@ -87,3 +89,59 @@ describe "AMQ::Client::CoolioClient", "Basic.Consume", :nojruby => true do
87
89
  end # it "should not leave messages in the queues with noack=true"
88
90
  end # context "sending 100 messages"
89
91
  end # describe AMQ::Client::CoolioClient, "Basic.Consume"
92
+
93
+
94
+ describe "Multiple", AMQ::Client::Async::Consumer, :nojruby => true do
95
+ include EventedSpec::SpecHelper
96
+ default_timeout 4
97
+
98
+ context "sharing the same queue with equal prefetch levels" do
99
+ let(:messages) { (0..99).map {|i| "Message #{i}" } }
100
+
101
+ it "have messages distributed to them in the round-robin manner" do
102
+ @consumer1_mailbox = []
103
+ @consumer2_mailbox = []
104
+ @consumer3_mailbox = []
105
+
106
+ coolio_amqp_connect do |client|
107
+ channel = AMQ::Client::Channel.new(client, 1)
108
+ channel.open do
109
+ queue = AMQ::Client::Queue.new(client, channel).declare(false, false, false, true)
110
+ queue.bind("amq.fanout")
111
+
112
+ consumer1 = AMQ::Client::Async::Consumer.new(channel, queue, "#{queue.name}-consumer-#{Time.now}")
113
+ consumer2 = AMQ::Client::Async::Consumer.new(channel, queue)
114
+ consumer3 = AMQ::Client::Async::Consumer.new(channel, queue, "#{queue.name}-consumer-#{rand}-#{Time.now}", false, true)
115
+
116
+
117
+ consumer1.consume.on_delivery do |method, header, payload|
118
+ @consumer1_mailbox << payload
119
+ end
120
+
121
+ consumer2.consume(true).on_delivery do |method, header, payload|
122
+ @consumer2_mailbox << payload
123
+ end
124
+
125
+ consumer3.consume(false) do
126
+ puts "Consumer 3 is ready"
127
+ end
128
+ consumer3.on_delivery do |method, header, payload|
129
+ @consumer3_mailbox << payload
130
+ end
131
+
132
+
133
+ exchange = AMQ::Client::Exchange.new(client, channel, "amq.fanout", :fanout)
134
+ messages.each do |message|
135
+ exchange.publish(message)
136
+ end
137
+ end
138
+
139
+ done(1.5) {
140
+ @consumer1_mailbox.size.should == 34
141
+ @consumer2_mailbox.size.should == 33
142
+ @consumer3_mailbox.size.should == 33
143
+ }
144
+ end
145
+ end # it
146
+ end # context
147
+ end # describe
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'spec_helper'
2
4
  require 'integration/coolio/spec_helper'
3
5
 
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'spec_helper'
2
4
  require 'integration/coolio/spec_helper'
3
5
 
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'spec_helper'
2
4
  require 'integration/coolio/spec_helper'
3
5
 
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'spec_helper'
2
4
  require 'integration/coolio/spec_helper'
3
5
 
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'spec_helper'
2
4
  require 'integration/coolio/spec_helper'
3
5
 
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'spec_helper'
2
4
  require 'integration/coolio/spec_helper'
3
5
 
@@ -1,9 +1,11 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'spec_helper'
2
4
  require 'integration/coolio/spec_helper'
3
5
 
4
6
  describe "AMQ::Client::CoolioClient", "Exchange.Declare", :nojruby => true do
5
7
  include EventedSpec::SpecHelper
6
- default_timeout 1
8
+ default_timeout 2
7
9
  let(:exchange_name) { "amq-client.testexchange.#{Time.now.to_i}" }
8
10
  it "should create an exchange and trigger a callback" do
9
11
  coolio_amqp_connect do |client|
@@ -12,7 +14,7 @@ describe "AMQ::Client::CoolioClient", "Exchange.Declare", :nojruby => true do
12
14
  exchange = AMQ::Client::Exchange.new(client, channel, exchange_name, :fanout)
13
15
  exchange.declare do
14
16
  exchange.delete
15
- done(0.2)
17
+ done(0.5)
16
18
  end
17
19
  end
18
20
  end
@@ -33,7 +35,7 @@ describe "AMQ::Client::CoolioClient", "Exchange.Declare", :nojruby => true do
33
35
  @callback_fired = true
34
36
  end
35
37
  delayed(0.1) { exchange.delete }
36
- done(0.3)
38
+ done(0.5)
37
39
  end
38
40
  end
39
41
 
@@ -55,7 +57,7 @@ describe "AMQ::Client::CoolioClient", "Exchange.Declare", :nojruby => true do
55
57
  end
56
58
  end
57
59
  delayed(0.1) { exchange.delete }
58
- done(0.3)
60
+ done(0.5)
59
61
  end
60
62
  end
61
63
 
@@ -77,7 +79,7 @@ describe "AMQ::Client::CoolioClient", "Exchange.Declare", :nojruby => true do
77
79
  @callback_fired = true
78
80
  end
79
81
  delayed(0.1) { exchange.delete }
80
- done(0.3)
82
+ done(0.5)
81
83
  end
82
84
  end
83
85
 
@@ -99,7 +101,7 @@ describe "AMQ::Client::CoolioClient", "Exchange.Declare", :nojruby => true do
99
101
  end
100
102
  end
101
103
  delayed(0.1) { exchange.delete }
102
- done(0.3)
104
+ done(0.5)
103
105
  end
104
106
  end
105
107
 
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  begin
2
4
  require "amq/client/adapters/coolio"
3
5
  rescue LoadError => e
@@ -1,10 +1,11 @@
1
1
  # encoding: utf-8
2
+
2
3
  require 'spec_helper'
3
4
  require 'integration/coolio/spec_helper'
4
5
 
5
6
  describe "AMQ::Client::CoolioClient", "Tx.Commit", :nojruby => true do
6
7
  include EventedSpec::SpecHelper
7
- default_timeout 2
8
+ default_timeout 4
8
9
  let(:message) { "Hello, world!" }
9
10
 
10
11
 
@@ -4,7 +4,7 @@ require 'integration/coolio/spec_helper'
4
4
 
5
5
  describe "AMQ::Client::CoolioClient", "Tx.Rollback", :nojruby => true do
6
6
  include EventedSpec::SpecHelper
7
- default_timeout 2
7
+ default_timeout 4
8
8
 
9
9
  let(:message) { "Hello, world!" }
10
10
 
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'spec_helper'
2
4
  require 'integration/eventmachine/spec_helper'
3
5
 
@@ -28,7 +30,7 @@ describe AMQ::Client::EventMachineClient, "Basic.Ack" do
28
30
  end
29
31
  end
30
32
 
31
- done(2.5) {
33
+ done(3.5) {
32
34
  @received_messages =~ messages
33
35
  }
34
36
  end