Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Extract AMQ::Settings from amq-client

  • Loading branch information...
commit 551d0927e8f487cb13f5ed503ee59110232156db 1 parent 74287ff
@michaelklishin michaelklishin authored
Showing with 265 additions and 0 deletions.
  1. +17 −0 ChangeLog.md
  2. +132 −0 lib/amq/settings.rb
  3. +116 −0 spec/amq/settings_spec.rb
View
17 ChangeLog.md
@@ -1,5 +1,22 @@
## Changes between 1.0.0.pre6 and 1.0.0.pre7
+### AMQ::Settings
+
+`AMQ::Settings` extracts settings merging logic and AMQP/AMQPS URI parsing from `amq-client`.
+Parsing follows the same convention amqp gem and RabbitMQ Java client follow.
+
+Examples:
+
+``` ruby
+AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com") # => vhost is nil, so default (/) will be used
+AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/") # => vhost is an empty string
+AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/%2Fvault") # => vhost is /vault
+AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/production") # => vhost is production
+AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/a.b.c") # => vhost is a.b.c
+AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/foo/bar") # => ArgumentError
+```
+
+
### AMQ::Protocol::TLS_PORT
`AMQ::Protocol::TLS_PORT` is a new constant that contains default AMQPS 0.9.1 port,
View
132 lib/amq/settings.rb
@@ -0,0 +1,132 @@
+# encoding: utf-8
+
+require "amq/protocol/client"
+require "uri"
+
+module AMQ
+ module Settings
+ # @private
+ AMQP_PORTS = {"amqp" => 5672, "amqps" => 5671}.freeze
+
+ # @private
+ AMQPS = "amqps".freeze
+
+ # Default connection settings used by AMQ clients
+ #
+ # @see AMQ::Client::Settings.configure
+ def self.default
+ @default ||= {
+ # server
+ :host => "127.0.0.1",
+ :port => AMQ::Protocol::DEFAULT_PORT,
+
+ # login
+ :user => "guest",
+ :pass => "guest",
+ :vhost => "/",
+
+ # ssl
+ :ssl => false,
+
+ :frame_max => (128 * 1024),
+ :heartbeat => 0
+ }
+ end
+
+
+ # Merges given configuration parameters with defaults and returns
+ # the result.
+ #
+ # @param [Hash] Configuration parameters to use.
+ #
+ # @option settings [String] :host ("127.0.0.1") Hostname AMQ broker runs on.
+ # @option settings [String] :port (5672) Port AMQ broker listens on.
+ # @option settings [String] :vhost ("/") Virtual host to use.
+ # @option settings [String] :user ("guest") Username to use for authentication.
+ # @option settings [String] :pass ("guest") Password to use for authentication.
+ # @option settings [String] :ssl (false) Should be use TLS (SSL) for connection?
+ # @option settings [String] :timeout (nil) Connection timeout.
+ # @option settings [String] :broker (nil) Broker name (use if you intend to use broker-specific features).
+ # @option settings [Fixnum] :frame_max (131072) Maximum frame size to use. If broker cannot support frames this large, broker's maximum value will be used instead.
+ #
+ # @return [Hash] Merged configuration parameters.
+ def self.configure(settings = nil)
+ case settings
+ when Hash then
+ if username = (settings.delete(:username) || settings.delete(:user))
+ settings[:user] ||= username
+ end
+
+ if password = (settings.delete(:password) || settings.delete(:pass))
+ settings[:pass] ||= password
+ end
+
+
+ self.default.merge(settings)
+ when String then
+ settings = self.parse_amqp_url(settings)
+ self.default.merge(settings)
+ when NilClass then
+ self.default
+ end
+ end
+
+ # Parses AMQP connection URI and returns its components as a hash.
+ #
+ # h2. vhost naming schemes
+ #
+ # It is convenient to be able to specify the AMQP connection
+ # parameters as a URI string, and various "amqp" URI schemes
+ # exist. Unfortunately, there is no standard for these URIs, so
+ # while the schemes share the basic idea, they differ in some
+ # details. This implementation aims to encourage URIs that work
+ # as widely as possible.
+ #
+ # The URI scheme should be "amqp", or "amqps" if SSL is required.
+ #
+ # The host, port, username and password are represented in the
+ # authority component of the URI in the same way as in http URIs.
+ #
+ # The vhost is obtained from the first segment of the path, with the
+ # leading slash removed. The path should contain only a single
+ # segment (i.e, the only slash in it should be the leading one).
+ # If the vhost is to include slashes or other reserved URI
+ # characters, these should be percent-escaped.
+ #
+ # @example How vhost is parsed
+ #
+ # AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com") # => vhost is nil, so default (/) will be used
+ # AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/") # => vhost is an empty string
+ # AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/%2Fvault") # => vhost is /vault
+ # AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/production") # => vhost is production
+ # AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/a.b.c") # => vhost is a.b.c
+ # AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/foo/bar") # => ArgumentError
+ #
+ #
+ # @param [String] connection_string AMQP connection URI, à la JDBC connection string. For example: amqp://bus.megacorp.internal:5877.
+ # @return [Hash] Connection parameters (:username, :password, :vhost, :host, :port, :ssl)
+ #
+ # @raise [ArgumentError] When connection URI schema is not amqp or amqps, or the path contains multiple segments
+ #
+ # @api public
+ def self.parse_amqp_url(connection_string)
+ uri = URI.parse(connection_string)
+ 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)
+
+ opts = {}
+
+ opts[:scheme] = uri.scheme
+ opts[:user] = URI.unescape(uri.user) if uri.user
+ opts[:pass] = URI.unescape(uri.password) if uri.password
+ opts[:host] = uri.host if uri.host
+ opts[:port] = uri.port || AMQP_PORTS[uri.scheme]
+ opts[:ssl] = uri.scheme.to_s.downcase =~ /amqps/i
+ if uri.path =~ %r{^/(.*)}
+ 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('/')
+ opts[:vhost] = URI.unescape($1)
+ end
+
+ opts
+ end
+ end
+end
View
116 spec/amq/settings_spec.rb
@@ -0,0 +1,116 @@
+# encoding: utf-8
+
+require "spec_helper"
+require "amq/settings"
+
+describe AMQ::Settings do
+ describe ".default" do
+ it "should provide some default values" do
+ AMQ::Settings.default.should_not be_nil
+ AMQ::Settings.default[:host].should_not be_nil
+ end
+ end
+
+ describe ".configure(&block)" do
+ it "should merge custom settings with default settings" do
+ settings = AMQ::Settings.configure(:host => "tagadab")
+ settings[:host].should eql("tagadab")
+ end
+
+ it "should merge custom settings from AMQP URL with default settings" do
+ settings = AMQ::Settings.configure("amqp://tagadab")
+ settings[:host].should eql("tagadab")
+ end
+ end
+
+ describe ".parse_amqp_url(connection_string)" do
+ context "when schema is not one of [amqp, amqps]" do
+ it "raises ArgumentError" do
+ expect {
+ described_class.parse_amqp_url("http://dev.rabbitmq.com")
+ }.to raise_error(ArgumentError, /amqp or amqps schema/)
+ end
+ end
+
+
+ it "handles amqp:// URIs w/o path part" do
+ val = described_class.parse_amqp_url("amqp://dev.rabbitmq.com")
+
+ val[:vhost].should be_nil # in this case, default / will be used
+ val[:host].should == "dev.rabbitmq.com"
+ val[:port].should == 5672
+ val[:scheme].should == "amqp"
+ val[:ssl].should be_false
+ end
+
+ it "handles amqps:// URIs w/o path part" do
+ val = described_class.parse_amqp_url("amqps://dev.rabbitmq.com")
+
+ val[:vhost].should be_nil
+ val[:host].should == "dev.rabbitmq.com"
+ val[:port].should == 5671
+ val[:scheme].should == "amqps"
+ val[:ssl].should be_true
+ end
+
+
+ context "when URI ends in a slash" do
+ it "parses vhost as an empty string" do
+ val = described_class.parse_amqp_url("amqp://dev.rabbitmq.com/")
+
+ val[:host].should == "dev.rabbitmq.com"
+ val[:port].should == 5672
+ val[:scheme].should == "amqp"
+ val[:ssl].should be_false
+ val[:vhost].should == ""
+ end
+ end
+
+
+ context "when URI ends in /%2Fvault" do
+ it "parses vhost as /vault" do
+ val = described_class.parse_amqp_url("amqp://dev.rabbitmq.com/%2Fvault")
+
+ val[:host].should == "dev.rabbitmq.com"
+ val[:port].should == 5672
+ val[:scheme].should == "amqp"
+ val[:ssl].should be_false
+ val[:vhost].should == "/vault"
+ end
+ end
+
+
+ context "when URI is amqp://dev.rabbitmq.com/a.path.without.slashes" do
+ it "parses vhost as a.path.without.slashes" do
+ val = described_class.parse_amqp_url("amqp://dev.rabbitmq.com/a.path.without.slashes")
+
+ val[:host].should == "dev.rabbitmq.com"
+ val[:port].should == 5672
+ val[:scheme].should == "amqp"
+ val[:ssl].should be_false
+ val[:vhost].should == "a.path.without.slashes"
+ end
+ end
+
+ context "when URI is amqp://dev.rabbitmq.com/a/path/with/slashes" do
+ it "raises an ArgumentError" do
+ lambda { described_class.parse_amqp_url("amqp://dev.rabbitmq.com/a/path/with/slashes") }.should raise_error(ArgumentError)
+ end
+ end
+
+
+ context "when URI has username:password, for instance, amqp://hedgehog:t0ps3kr3t@hub.megacorp.internal" do
+ it "parses them out" do
+ val = described_class.parse_amqp_url("amqp://hedgehog:t0ps3kr3t@hub.megacorp.internal")
+
+ val[:host].should == "hub.megacorp.internal"
+ val[:port].should == 5672
+ val[:scheme].should == "amqp"
+ val[:ssl].should be_false
+ val[:user].should == "hedgehog"
+ val[:pass].should == "t0ps3kr3t"
+ val[:vhost].should be_nil # in this case, default / will be used
+ end
+ end
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.