amq-client 0.7.0.alpha35 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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